diff --git a/.github/workflows/on-pull-request.yaml b/.github/workflows/on-pull-request.yaml index 44f2e58..b583394 100644 --- a/.github/workflows/on-pull-request.yaml +++ b/.github/workflows/on-pull-request.yaml @@ -14,6 +14,7 @@ jobs: go-version: - 1.22.x - 1.23.x + - 1.24.x os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: diff --git a/groupcache.go b/groupcache.go index 787f1dc..8e0a96b 100644 --- a/groupcache.go +++ b/groupcache.go @@ -106,8 +106,31 @@ func GetGroups() []*Group { // completes. // // The group name must be unique for each getter. -func NewGroup(name string, cacheBytes int64, getter Getter) *Group { - return newGroup(name, cacheBytes, getter, nil) +func NewGroup(name string, cacheBytes int64, getter Getter, opts ...Option) *Group { + group := newGroup(name, cacheBytes, getter, nil) + for _, opt := range opts { + opt.apply(group) + } + return group +} + +// Option for NewGroup(). +type Option interface { + apply(*Group) +} + +type withPruneIntervalOption struct { + pruneInterval int +} + +func (o *withPruneIntervalOption) apply(group *Group) { + group.mainCache.pruneInterval = o.pruneInterval + group.hotCache.pruneInterval = o.pruneInterval +} + +// Pass prune interval option for NewGroup(). +func WithPruneInterval(pruneInterval int) Option { + return &withPruneIntervalOption{pruneInterval: pruneInterval} } // DeregisterGroup removes group from group pool @@ -608,11 +631,12 @@ var NowFunc lru.NowFunc = time.Now // makes values always be ByteView, and counts the size of all keys and // values. type cache struct { - mu sync.RWMutex - nbytes int64 // of all keys and values - lru *lru.Cache - nhit, nget int64 - nevict int64 // number of evictions + mu sync.RWMutex + nbytes int64 // of all keys and values + lru *lru.Cache + nhit, nget int64 + nevict int64 // number of evictions + pruneInterval int } func (c *cache) stats() CacheStats { @@ -638,6 +662,7 @@ func (c *cache) add(key string, value ByteView) { c.nbytes -= int64(len(key.(string))) + int64(val.Len()) c.nevict++ }, + PruneInterval: c.pruneInterval, } } c.lru.Add(key, value, value.Expire()) diff --git a/lru/lru.go b/lru/lru.go index e278234..da0d8b9 100644 --- a/lru/lru.go +++ b/lru/lru.go @@ -39,8 +39,13 @@ type Cache struct { // Defaults to time.Now() Now NowFunc - ll *list.List - cache map[interface{}]*list.Element + // PruneInterval sets number of cache removals before internal map is pruned. + // If set to zero, no pruning is performed. + PruneInterval int + + ll *list.List + cache map[interface{}]*list.Element + removeCounter int } // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators @@ -69,6 +74,9 @@ func (c *Cache) Add(key Key, value interface{}, expire time.Time) { if c.cache == nil { c.cache = make(map[interface{}]*list.Element) c.ll = list.New() + } else if c.PruneInterval > 0 && c.removeCounter >= c.PruneInterval { + c.removeCounter = 0 + c.Prune() } if ee, ok := c.cache[key]; ok { eee := ee.Value.(*entry) @@ -131,6 +139,7 @@ func (c *Cache) removeElement(e *list.Element) { c.ll.Remove(e) kv := e.Value.(*entry) delete(c.cache, kv.key) + c.removeCounter++ if c.OnEvicted != nil { c.OnEvicted(kv.key, kv.value) } @@ -155,3 +164,16 @@ func (c *Cache) Clear() { c.ll = nil c.cache = nil } + +// Prune cache state to flush map storage from previously deleted keys. +func (c *Cache) Prune() { + if c.cache == nil { + return + } + newCache := make(map[any]*list.Element) + for k, v := range c.cache { + newCache[k] = v + delete(c.cache, k) + } + c.cache = newCache +}