autobrr/pkg/ttlcache/expiration.go

79 lines
1.6 KiB
Go

// Copyright (c) 2021-2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
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)
}
}