mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-11-10 14:44:04 +08:00
Share daily limits through the usage table, drop the midnight reset and the stats queue
The server builds the usage store on the user database (the shared pool on Postgres, the auth-file on SQLite) and starts the limiter before serving. A visitor without a user writes its message count from a tenth of the cap on, everyone else from the first message; anonymous emails get the daily cap the replenish interval implies. The account API reports reset_at. The user stats columns are no longer read or written. Claude-Session: https://claude.ai/code/session_01DeXuhSjq5Gta5qPJgSceFe
This commit is contained in:
1 parent
dad9cfa03b
commit
c113e20677
17 files changed
+176
-394
No files matched your search
+19
-32
@@ -301,7 +301,6 @@ func New(conf *Config) (*Server, error) {
|
||||
messages: messages,
|
||||
messagesHistory: []int64{messages},
|
||||
messagesFlushed: messages, // The loaded total is already persisted; only new publishes are deltas
|
||||
limiter: limit.New(&limit.Config{}),
|
||||
stripe: stripe,
|
||||
streams: newStreamRegistry(),
|
||||
}
|
||||
@@ -348,6 +347,9 @@ func New(conf *Config) (*Server, error) {
|
||||
firebaseClient = newFirebaseClient(sender, auther)
|
||||
}
|
||||
s.userManager = userManager
|
||||
s.limiter = limit.New(&limit.Config{
|
||||
Store: createUsageStore(pool, userManager),
|
||||
})
|
||||
s.visitors = newVisitorEnv(conf, s.limiter, messageCache, userManager)
|
||||
s.firebaseClient = firebaseClient
|
||||
if pool != nil {
|
||||
@@ -376,6 +378,9 @@ func New(conf *Config) (*Server, error) {
|
||||
s.tail = tail
|
||||
go s.runTail()
|
||||
}
|
||||
if err := s.limiter.Start(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -392,6 +397,18 @@ func (s *Server) dbPoolStats() []*metrics.DBPool {
|
||||
return pools
|
||||
}
|
||||
|
||||
// createUsageStore returns the store of the usage table, which is part of the user database: the
|
||||
// shared pool on Postgres, the auth-file on SQLite. Without a user database, the daily counts are
|
||||
// kept in memory only.
|
||||
func createUsageStore(pool *db.DB, userManager *user.Manager) *limit.Store {
|
||||
if pool != nil {
|
||||
return limit.NewPostgresStore(pool)
|
||||
} else if userManager != nil {
|
||||
return limit.NewSQLiteStore(userManager.DB())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createMessageCache(conf *Config, pool *db.DB) (*message.Cache, error) {
|
||||
if conf.CacheDuration == 0 {
|
||||
return message.NewNopStore()
|
||||
@@ -532,7 +549,6 @@ func (s *Server) Run() error {
|
||||
}
|
||||
s.mu.Unlock()
|
||||
go s.runManager()
|
||||
go s.runStatsResetter()
|
||||
go s.runDelayedSender()
|
||||
go s.runFirebaseKeepaliver()
|
||||
|
||||
@@ -585,6 +601,7 @@ func (s *Server) stop() {
|
||||
if s.tail != nil {
|
||||
s.tail.Close() // Before the databases close under it
|
||||
}
|
||||
s.limiter.Close() // Writes the unflushed daily counts, before the databases close under it
|
||||
s.closeDatabases()
|
||||
if s.ban != nil {
|
||||
s.ban.Close()
|
||||
@@ -2136,36 +2153,6 @@ func (s *Server) runManager() {
|
||||
}
|
||||
}
|
||||
|
||||
// runStatsResetter runs once a day (usually midnight UTC) to reset all the visitor's message and
|
||||
// email counters. The stats are used to display the counters in the web app, as well as for rate limiting.
|
||||
func (s *Server) runStatsResetter() {
|
||||
for {
|
||||
runAt := util.NextOccurrenceUTC(s.config.VisitorStatsResetTime, time.Now())
|
||||
timer := time.NewTimer(time.Until(runAt))
|
||||
log.Tag(tagResetter).Debug("Waiting until %v to reset visitor stats", runAt)
|
||||
select {
|
||||
case <-timer.C:
|
||||
log.Tag(tagResetter).Debug("Running stats resetter")
|
||||
s.resetStats()
|
||||
case <-s.closeChan:
|
||||
log.Tag(tagResetter).Debug("Stopping stats resetter")
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) resetStats() {
|
||||
log.Info("Resetting all visitor stats (daily task)")
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock() // Includes the database query to avoid races with other processes
|
||||
if s.userManager != nil {
|
||||
if err := s.userManager.ResetStats(); err != nil {
|
||||
log.Tag(tagResetter).Warn("Failed to write to database: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) runFirebaseKeepaliver() {
|
||||
if s.firebaseClient == nil {
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user