package ttlcache import ( "time" ) func (c *Cache[K, V]) startExpirations() { timer := time.NewTimer(1 * time.Second) stopTimer(timer) // wasteful, but makes the loop cleaner because this is initialized. defer stopTimer(timer) var timeSleep time.Time for { select { case t, ok := <-c.ch: if !ok { return } else if t.IsZero() { continue } if timeSleep.IsZero() || timeSleep.After(t) { timeSleep = t restartTimer(timer, timeSleep.Sub(c.tc.Now())) } case <-timer.C: stopTimer(timer) c.expire() timeSleep = time.Time{} } } } func restartTimer(t *time.Timer, d time.Duration) { stopTimer(t) t.Reset(d) } func stopTimer(t *time.Timer) { t.Stop() // go < 1.23 returns stale values on expired timers. if len(t.C) != 0 { <-t.C } } func (c *Cache[K, V]) expire() { t := c.tc.Now() var soon time.Time c.l.Lock() defer c.l.Unlock() for k, v := range c.m { if v.t.IsZero() { continue } else if v.t.After(t) { if soon.IsZero() || soon.After(v.t) { soon = v.t } continue } c.deleteUnsafe(k, v, ReasonTimedOut) } if !soon.IsZero() { // wake-up feedback loop go func(s time.Time) { // we need to release the lock, if the input pipeline has exceeded the wakeup budget. defer func() { _ = recover() // if the channel is closed, this doesn't matter on shutdown because this is expected. }() c.ch <- s }(soon) } }