1
0
mirror of https://github.com/regclient/regclient.git synced 2025-04-18 22:44:00 +03:00
Brandon Mitchell eea06e2a5c
Refactoring the type package
I feel like I need to explain, this is all to move the descriptor package.
The platform package could not use the predefined errors in types because of a circular dependency from descriptor.
The most appropriate way to reorg this is to move descriptor out of the type package since it was more complex than a self contained type.
When doing that, type aliases were needed to avoid breaking changes to existing users.
Those aliases themselves caused circular dependency loops because of the media types and errors, so those were also pulled out to separate packages.
All of the old values were aliased and deprecated, and to fix the linter, those deprecations were fixed by updating the imports... everywhere.

Signed-off-by: Brandon Mitchell <git@bmitch.net>
2024-03-04 15:43:18 -05:00

183 lines
3.4 KiB
Go

//go:build go1.18
// +build go1.18
// Package cache is used to store values with limits.
// Items are automatically pruned when too many entries are stored, or values become stale.
package cache
import (
"sort"
"sync"
"time"
"github.com/regclient/regclient/types/errs"
)
type Cache[k comparable, v any] struct {
mu sync.Mutex
minAge time.Duration
maxAge time.Duration
minCount int
maxCount int
timer *time.Timer
entries map[k]*Entry[v]
}
type Entry[v any] struct {
used time.Time
value v
}
type sortKeys[k comparable] struct {
keys []k
lessFn func(a, b k) bool
}
type conf struct {
minAge time.Duration
maxCount int
}
type cacheOpts func(*conf)
func WithAge(age time.Duration) cacheOpts {
return func(c *conf) {
c.minAge = age
}
}
func WithCount(count int) cacheOpts {
return func(c *conf) {
c.maxCount = count
}
}
func New[k comparable, v any](opts ...cacheOpts) Cache[k, v] {
c := conf{}
for _, opt := range opts {
opt(&c)
}
maxAge := c.minAge + (c.minAge / 10)
minCount := 0
if c.maxCount > 0 {
minCount = int(float64(c.maxCount) * 0.9)
}
return Cache[k, v]{
minAge: c.minAge,
maxAge: maxAge,
minCount: minCount,
maxCount: c.maxCount,
entries: map[k]*Entry[v]{},
}
}
func (c *Cache[k, v]) Delete(key k) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
delete(c.entries, key)
if len(c.entries) == 0 && c.timer != nil {
c.timer.Stop()
c.timer = nil
}
}
func (c *Cache[k, v]) Set(key k, val v) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.entries[key] = &Entry[v]{
used: time.Now(),
value: val,
}
if len(c.entries) > c.maxCount {
c.pruneLocked()
} else if c.timer == nil {
// prune resets the timer, so this is only needed if the prune wasn't triggered
c.timer = time.AfterFunc(c.maxAge, c.prune)
}
}
func (c *Cache[k, v]) Get(key k) (v, error) {
if c == nil {
var val v
return val, errs.ErrNotFound
}
c.mu.Lock()
defer c.mu.Unlock()
if e, ok := c.entries[key]; ok {
if e.used.Add(c.minAge).Before(time.Now()) {
// entry expired
go c.prune()
} else {
c.entries[key].used = time.Now()
return e.value, nil
}
}
var val v
return val, errs.ErrNotFound
}
func (c *Cache[k, v]) prune() {
c.mu.Lock()
defer c.mu.Unlock()
c.pruneLocked()
}
func (c *Cache[k, v]) pruneLocked() {
// sort key list by last used date
keyList := make([]k, 0, len(c.entries))
for key := range c.entries {
keyList = append(keyList, key)
}
sk := sortKeys[k]{
keys: keyList,
lessFn: func(a, b k) bool {
return c.entries[a].used.Before(c.entries[b].used)
},
}
sort.Sort(&sk)
// prune entries
now := time.Now()
cutoff := now.Add(c.minAge * -1)
nextTime := now
delCount := len(keyList) - c.minCount
for i, key := range keyList {
if i < delCount || c.entries[key].used.Before(cutoff) {
delete(c.entries, key)
} else {
nextTime = c.entries[key].used
break
}
}
// set next timer
if len(c.entries) > 0 {
dur := nextTime.Sub(now) + c.maxAge
if c.timer == nil {
// this shouldn't be possible
c.timer = time.AfterFunc(dur, c.prune)
} else {
c.timer.Reset(dur)
}
} else if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
}
func (sk *sortKeys[k]) Len() int {
return len(sk.keys)
}
func (sk *sortKeys[k]) Less(i, j int) bool {
return sk.lessFn(sk.keys[i], sk.keys[j])
}
func (sk *sortKeys[k]) Swap(i, j int) {
sk.keys[i], sk.keys[j] = sk.keys[j], sk.keys[i]
}