From a7002e854f16c4d968b166240a02e8247cebf252 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:51:44 +0000 Subject: [PATCH] build(deps): bump github.com/jellydator/ttlcache/v3 from 3.3.0 to 3.4.0 Bumps [github.com/jellydator/ttlcache/v3](https://github.com/jellydator/ttlcache) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/jellydator/ttlcache/releases) - [Commits](https://github.com/jellydator/ttlcache/compare/v3.3.0...v3.4.0) --- updated-dependencies: - dependency-name: github.com/jellydator/ttlcache/v3 dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../jellydator/ttlcache/v3/README.md | 47 +++++- .../jellydator/ttlcache/v3/cache.go | 157 +++++++++++++++--- .../github.com/jellydator/ttlcache/v3/item.go | 80 ++++++--- .../jellydator/ttlcache/v3/metrics.go | 3 + .../jellydator/ttlcache/v3/options.go | 105 ++++++++++-- vendor/modules.txt | 4 +- 8 files changed, 329 insertions(+), 73 deletions(-) diff --git a/go.mod b/go.mod index 85880b31a..b8f51d631 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/invopop/validation v0.8.0 github.com/jellydator/ttlcache/v2 v2.11.1 - github.com/jellydator/ttlcache/v3 v3.3.0 + github.com/jellydator/ttlcache/v3 v3.4.0 github.com/jinzhu/now v1.1.5 github.com/justinas/alice v1.2.0 github.com/kovidgoyal/imaging v1.6.4 diff --git a/go.sum b/go.sum index e2bb27501..1f5e19077 100644 --- a/go.sum +++ b/go.sum @@ -647,8 +647,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jellydator/ttlcache/v2 v2.11.1 h1:AZGME43Eh2Vv3giG6GeqeLeFXxwxn1/qHItqWZl6U64= github.com/jellydator/ttlcache/v2 v2.11.1/go.mod h1:RtE5Snf0/57e+2cLWFYWCCsLas2Hy3c5Z4n14XmSvTI= -github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= -github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= +github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= +github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= diff --git a/vendor/github.com/jellydator/ttlcache/v3/README.md b/vendor/github.com/jellydator/ttlcache/v3/README.md index a17cb2437..d941495fa 100644 --- a/vendor/github.com/jellydator/ttlcache/v3/README.md +++ b/vendor/github.com/jellydator/ttlcache/v3/README.md @@ -10,10 +10,9 @@ - Type parameters - Item expiration and automatic deletion - Automatic expiration time extension on each `Get` call -- `Loader` interface that may be used to load/lazily initialize missing cache -- Thread Safe -items -- Event handlers (insertion and eviction) +- `Loader` interface that may be used to load/lazily initialize missing cache items +- Thread safety +- Event handlers (insertion, update, and eviction) - Metrics ## Installation @@ -21,6 +20,10 @@ items go get github.com/jellydator/ttlcache/v3 ``` +## Status +The `ttlcache` package is stable and used by [Jellydator](https://jellydator.com/), +as well as thousands of other projects and organizations in production. + ## Usage The main type of `ttlcache` is `Cache`. It represents a single in-memory data store. @@ -100,7 +103,7 @@ func main() { } ``` -To subscribe to insertion and eviction events, `cache.OnInsertion()` and +To subscribe to insertion, update and eviction events, `cache.OnInsertion()`, `cache.OnUpdate()` and `cache.OnEviction()` methods should be used: ```go func main() { @@ -112,6 +115,9 @@ func main() { cache.OnInsertion(func(ctx context.Context, item *ttlcache.Item[string, string]) { fmt.Println(item.Value(), item.ExpiresAt()) }) + cache.OnUpdate(func(ctx context.Context, item *ttlcache.Item[string, string]) { + fmt.Println(item.Value(), item.ExpiresAt()) + }) cache.OnEviction(func(ctx context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[string, string]) { if reason == ttlcache.EvictionReasonCapacityReached { fmt.Println(item.Key(), item.Value()) @@ -141,3 +147,34 @@ func main() { item := cache.Get("key from file") } ``` + +To restrict the cache's capacity based on criteria beyond the number +of items it can hold, the `ttlcache.WithMaxCost` option allows for +implementing custom strategies. The following example shows how to limit +memory usage for cached entries to ~5KiB. +```go +import ( + "github.com/jellydator/ttlcache" +) + +func main() { + cache := ttlcache.New[string, string]( + ttlcache.WithMaxCost[string, string](5120, func(item ttlcache.CostItem[string, string]) uint64 { + // Note: The below line doesn't include memory used by internal + // structures or string metadata for the key and the value. + return len(item.Key) + len(item.Value) + }), + ) + + cache.Set("first", "value1", ttlcache.DefaultTTL) +} +``` + +## Examples & Tutorials + +See the [example](https://github.com/jellydator/ttlcache/tree/v3/examples) +directory for applications demonstrating how to use `ttlcache`. + +If you want to learn and follow along as these example applications are +built, check out the tutorials below: +- [Speeding Up HTTP Endpoints with Response Caching in Go](https://jellydator.com/blog/speeding-up-http-endpoints-with-response-caching-in-go/) diff --git a/vendor/github.com/jellydator/ttlcache/v3/cache.go b/vendor/github.com/jellydator/ttlcache/v3/cache.go index 1b9e72ef0..1cfcaf914 100644 --- a/vendor/github.com/jellydator/ttlcache/v3/cache.go +++ b/vendor/github.com/jellydator/ttlcache/v3/cache.go @@ -15,6 +15,7 @@ const ( EvictionReasonDeleted EvictionReason = iota + 1 EvictionReasonCapacityReached EvictionReasonExpired + EvictionReasonMaxCostExceeded ) // EvictionReason is used to specify why a certain item was @@ -36,6 +37,7 @@ type Cache[K comparable, V any] struct { timerCh chan time.Duration } + cost uint64 metricsMu sync.RWMutex metrics Metrics @@ -46,6 +48,11 @@ type Cache[K comparable, V any] struct { nextID uint64 fns map[uint64]func(*Item[K, V]) } + update struct { + mu sync.RWMutex + nextID uint64 + fns map[uint64]func(*Item[K, V]) + } eviction struct { mu sync.RWMutex nextID uint64 @@ -53,23 +60,28 @@ type Cache[K comparable, V any] struct { } } + stopMu sync.Mutex stopCh chan struct{} + stopped bool + options options[K, V] } // New creates a new instance of cache. func New[K comparable, V any](opts ...Option[K, V]) *Cache[K, V] { c := &Cache[K, V]{ - stopCh: make(chan struct{}), + stopCh: make(chan struct{}), + stopped: true, // cache cleanup process is stopped by default } c.items.values = make(map[K]*list.Element) c.items.lru = list.New() c.items.expQueue = newExpirationQueue[K, V]() c.items.timerCh = make(chan time.Duration, 1) // buffer is important c.events.insertion.fns = make(map[uint64]func(*Item[K, V])) + c.events.update.fns = make(map[uint64]func(*Item[K, V])) c.events.eviction.fns = make(map[uint64]func(EvictionReason, *Item[K, V])) - applyOptions(&c.options, opts...) + c.options = applyOptions(c.options, opts...) return c } @@ -137,9 +149,30 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] { if elem != nil { // update/overwrite an existing item item := elem.Value.(*Item[K, V]) + oldItemCost := item.cost + item.update(value, ttl) + c.updateExpirations(false, elem) + if c.options.maxCost != 0 { + c.cost = c.cost - oldItemCost + item.cost + + for c.cost > c.options.maxCost { + c.evict(EvictionReasonMaxCostExceeded, c.items.lru.Back()) + } + } + + c.metricsMu.Lock() + c.metrics.Updates++ + c.metricsMu.Unlock() + + c.events.update.mu.RLock() + for _, fn := range c.events.update.fns { + fn(item) + } + c.events.update.mu.RUnlock() + return item } @@ -153,11 +186,19 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] { } // create a new item - item := newItem(key, value, ttl, c.options.enableVersionTracking) + item := NewItemWithOpts(key, value, ttl, c.options.itemOpts...) elem = c.items.lru.PushFront(item) c.items.values[key] = elem c.updateExpirations(true, elem) + if c.options.maxCost != 0 { + c.cost += item.cost + + for c.cost > c.options.maxCost { + c.evict(EvictionReasonMaxCostExceeded, c.items.lru.Back()) + } + } + c.metricsMu.Lock() c.metrics.Insertions++ c.metricsMu.Unlock() @@ -212,7 +253,7 @@ func (c *Cache[K, V]) getWithOpts(key K, lockAndLoad bool, opts ...Option[K, V]) disableTouchOnHit: c.options.disableTouchOnHit, } - applyOptions(&getOpts, opts...) + getOpts = applyOptions(getOpts, opts...) if lockAndLoad { c.items.mu.Lock() @@ -258,6 +299,11 @@ func (c *Cache[K, V]) evict(reason EvictionReason, elems ...*list.Element) { for i := range elems { item := elems[i].Value.(*Item[K, V]) delete(c.items.values, item.key) + + if c.options.maxCost != 0 { + c.cost -= item.cost + } + c.items.lru.Remove(elems[i]) c.items.expQueue.remove(elems[i]) @@ -351,6 +397,23 @@ func (c *Cache[K, V]) Has(key K) bool { // If the loader is non-nil (i.e., used as an option or specified when // creating the cache instance), its execution is skipped. func (c *Cache[K, V]) GetOrSet(key K, value V, opts ...Option[K, V]) (*Item[K, V], bool) { + return c.GetOrSetFunc( + key, + func() V { + return value + }, + opts..., + ) +} + +// GetOrSetFunc retrieves an item from the cache by the provided key. +// If the element is not found, it is created by executing the fn function +// with the provided options and then returned. +// The bool return value is true if the item was found, false if created +// during the execution of the method. +// If the loader is non-nil (i.e., used as an option or specified when +// creating the cache instance), its execution is skipped. +func (c *Cache[K, V]) GetOrSetFunc(key K, fn func() V, opts ...Option[K, V]) (*Item[K, V], bool) { c.items.mu.Lock() defer c.items.mu.Unlock() @@ -362,9 +425,9 @@ func (c *Cache[K, V]) GetOrSet(key K, value V, opts ...Option[K, V]) (*Item[K, V setOpts := options[K, V]{ ttl: c.options.ttl, } - applyOptions(&setOpts, opts...) // used only to update the TTL + setOpts = applyOptions(setOpts, opts...) // used only to update the TTL - item := c.set(key, value, setOpts.ttl) + item := c.set(key, fn(), setOpts.ttl) return item, false } @@ -386,7 +449,7 @@ func (c *Cache[K, V]) GetAndDelete(key K, opts ...Option[K, V]) (*Item[K, V], bo getOpts := options[K, V]{ loader: c.options.loader, } - applyOptions(&getOpts, opts...) // used only to update the loader + getOpts = applyOptions(getOpts, opts...) // used only to update the loader if getOpts.loader != nil { item := getOpts.loader.Load(c, key) @@ -529,19 +592,17 @@ func (c *Cache[K, V]) Range(fn func(item *Item[K, V]) bool) { return } - for item := c.items.lru.Front(); item != c.items.lru.Back().Next(); item = item.Next() { + for item := c.items.lru.Front(); c.items.lru.Len() != 0 && item != c.items.lru.Back().Next(); item = item.Next() { i := item.Value.(*Item[K, V]) expired := i.isExpiredUnsafe() - c.items.mu.RUnlock() - + c.items.mu.RUnlock() // unlock mutex so fn func can access it (if it needs to) if !expired && !fn(i) { return } - - if item.Next() != nil { - c.items.mu.RLock() - } + c.items.mu.RLock() } + + c.items.mu.RUnlock() } // RangeBackwards calls fn for each unexpired item in the cache in reverse order. @@ -555,19 +616,17 @@ func (c *Cache[K, V]) RangeBackwards(fn func(item *Item[K, V]) bool) { return } - for item := c.items.lru.Back(); item != c.items.lru.Front().Prev(); item = item.Prev() { + for item := c.items.lru.Back(); c.items.lru.Len() != 0 && item != c.items.lru.Front().Prev(); item = item.Prev() { i := item.Value.(*Item[K, V]) expired := i.isExpiredUnsafe() - c.items.mu.RUnlock() - + c.items.mu.RUnlock() // unlock mutex so fn func can access it (if it needs to) if !expired && !fn(i) { return } - - if item.Prev() != nil { - c.items.mu.RLock() - } + c.items.mu.RLock() } + + c.items.mu.RUnlock() } // Metrics returns the metrics of the cache. @@ -582,6 +641,15 @@ func (c *Cache[K, V]) Metrics() Metrics { // expired items. // It blocks until Stop is called. func (c *Cache[K, V]) Start() { + c.stopMu.Lock() + if !c.stopped { + c.stopMu.Unlock() + return + } + + c.stopped = false + c.stopMu.Unlock() + waitDur := func() time.Duration { c.items.mu.RLock() defer c.items.mu.RUnlock() @@ -635,7 +703,16 @@ func (c *Cache[K, V]) Start() { // Stop stops the automatic cleanup process. // It blocks until the cleanup process exits. func (c *Cache[K, V]) Stop() { + c.stopMu.Lock() + defer c.stopMu.Unlock() + + if c.stopped { + return + } + c.stopCh <- struct{}{} + c.stopped = true + } // OnInsertion adds the provided function to be executed when @@ -676,6 +753,44 @@ func (c *Cache[K, V]) OnInsertion(fn func(context.Context, *Item[K, V])) func() } } +// OnUpdate adds the provided function to be executed when +// an item is updated in the cache. The function is executed +// on a separate goroutine and does not block the flow of the cache +// manager. +// The returned function may be called to delete the subscription function +// from the list of update subscribers. +// When the returned function is called, it blocks until all instances of +// the same subscription function return. A context is used to notify the +// subscription function when the returned/deletion function is called. +func (c *Cache[K, V]) OnUpdate(fn func(context.Context, *Item[K, V])) func() { + var ( + wg sync.WaitGroup + ctx, cancel = context.WithCancel(context.Background()) + ) + + c.events.update.mu.Lock() + id := c.events.update.nextID + c.events.update.fns[id] = func(item *Item[K, V]) { + wg.Add(1) + go func() { + fn(ctx, item) + wg.Done() + }() + } + c.events.update.nextID++ + c.events.update.mu.Unlock() + + return func() { + cancel() + + c.events.update.mu.Lock() + delete(c.events.update.fns, id) + c.events.update.mu.Unlock() + + wg.Wait() + } +} + // OnEviction adds the provided function to be executed when // an item is evicted/deleted from the cache. The function is executed // on a separate goroutine and does not block the flow of the cache diff --git a/vendor/github.com/jellydator/ttlcache/v3/item.go b/vendor/github.com/jellydator/ttlcache/v3/item.go index c3c26cf6b..5321d4635 100644 --- a/vendor/github.com/jellydator/ttlcache/v3/item.go +++ b/vendor/github.com/jellydator/ttlcache/v3/item.go @@ -18,6 +18,13 @@ const ( DefaultTTL time.Duration = 0 ) +// CostItem holds the key and the value of the Item object for +// Item cost calculation purposes. +type CostItem[K comparable, V any] struct { + Key K + Value V +} + // Item holds all the information that is associated with a single // cache value. type Item[K comparable, V any] struct { @@ -30,28 +37,42 @@ type Item[K comparable, V any] struct { // well, so locking this mutex would be redundant. // In other words, this mutex is only useful when these fields // are being read from the outside (e.g. in event functions). - mu sync.RWMutex - key K - value V - ttl time.Duration - expiresAt time.Time - queueIndex int - version int64 + mu sync.RWMutex + key K + value V + ttl time.Duration + expiresAt time.Time + queueIndex int + version int64 + calculateCost CostFunc[K, V] + cost uint64 } -// newItem creates a new cache item. -func newItem[K comparable, V any](key K, value V, ttl time.Duration, enableVersionTracking bool) *Item[K, V] { +// NewItem creates a new cache item. +// +// Deprecated: Use NewItemWithOpts instead. This function will be removed +// in a future release. +func NewItem[K comparable, V any](key K, value V, ttl time.Duration, enableVersionTracking bool) *Item[K, V] { + return NewItemWithOpts(key, value, ttl, WithItemVersion[K, V](enableVersionTracking)) +} + +// NewItemWithOpts creates a new cache item and applies the provided item +// options. +func NewItemWithOpts[K comparable, V any](key K, value V, ttl time.Duration, opts ...ItemOption[K, V]) *Item[K, V] { item := &Item[K, V]{ - key: key, - value: value, - ttl: ttl, - } - - if !enableVersionTracking { - item.version = -1 + key: key, + value: value, + ttl: ttl, + version: -1, + calculateCost: func(item CostItem[K, V]) uint64 { return 0 }, } + applyItemOptions(item, opts...) item.touch() + item.cost = item.calculateCost(CostItem[K, V]{ + Key: key, + Value: value, + }) return item } @@ -69,16 +90,19 @@ func (item *Item[K, V]) update(value V, ttl time.Duration) { } // no need to update ttl or expiry in this case - if ttl == PreviousOrDefaultTTL { - return + if ttl != PreviousOrDefaultTTL { + item.ttl = ttl + // reset expiration timestamp because the new TTL may be + // 0 or below + item.expiresAt = time.Time{} + item.touchUnsafe() } - item.ttl = ttl - - // reset expiration timestamp because the new TTL may be - // 0 or below - item.expiresAt = time.Time{} - item.touchUnsafe() + // calculating the costs + item.cost = item.calculateCost(CostItem[K, V]{ + Key: item.key, + Value: item.value, + }) } // touch updates the item's expiration timestamp. @@ -142,6 +166,14 @@ func (item *Item[K, V]) TTL() time.Duration { return item.ttl } +// Cost returns the cost of the item. +func (item *Item[K, V]) Cost() uint64 { + item.mu.RLock() + defer item.mu.RUnlock() + + return item.cost +} + // ExpiresAt returns the expiration timestamp of the item. func (item *Item[K, V]) ExpiresAt() time.Time { item.mu.RLock() diff --git a/vendor/github.com/jellydator/ttlcache/v3/metrics.go b/vendor/github.com/jellydator/ttlcache/v3/metrics.go index 8e7595a0b..19ee2e014 100644 --- a/vendor/github.com/jellydator/ttlcache/v3/metrics.go +++ b/vendor/github.com/jellydator/ttlcache/v3/metrics.go @@ -6,6 +6,9 @@ type Metrics struct { // Insertions specifies how many items were inserted. Insertions uint64 + // Updates specifies how many items were updated. + Updates uint64 + // Hits specifies how many items were successfully retrieved // from the cache. // Retrievals made with a loader function are not tracked. diff --git a/vendor/github.com/jellydator/ttlcache/v3/options.go b/vendor/github.com/jellydator/ttlcache/v3/options.go index 8a6088c05..a248b5b67 100644 --- a/vendor/github.com/jellydator/ttlcache/v3/options.go +++ b/vendor/github.com/jellydator/ttlcache/v3/options.go @@ -4,46 +4,56 @@ import "time" // Option sets a specific cache option. type Option[K comparable, V any] interface { - apply(opts *options[K, V]) + apply(opts options[K, V]) options[K, V] } // optionFunc wraps a function and implements the Option interface. -type optionFunc[K comparable, V any] func(*options[K, V]) +type optionFunc[K comparable, V any] func(options[K, V]) options[K, V] // apply calls the wrapped function. -func (fn optionFunc[K, V]) apply(opts *options[K, V]) { - fn(opts) +func (fn optionFunc[K, V]) apply(opts options[K, V]) options[K, V] { + return fn(opts) } +// CostFunc is used to calculate the cost of the key and the item to be +// inserted into the cache. +type CostFunc[K comparable, V any] func(item CostItem[K, V]) uint64 + // options holds all available cache configuration options. type options[K comparable, V any] struct { - capacity uint64 - ttl time.Duration - loader Loader[K, V] - disableTouchOnHit bool - enableVersionTracking bool + capacity uint64 + maxCost uint64 + ttl time.Duration + loader Loader[K, V] + disableTouchOnHit bool + itemOpts []ItemOption[K, V] } -// applyOptions applies the provided option values to the option struct. -func applyOptions[K comparable, V any](v *options[K, V], opts ...Option[K, V]) { +// applyOptions applies the provided option values to the option struct +// and returns the modified option struct. +func applyOptions[K comparable, V any](v options[K, V], opts ...Option[K, V]) options[K, V] { for i := range opts { - opts[i].apply(v) + v = opts[i].apply(v) } + + return v } // WithCapacity sets the maximum capacity of the cache. // It has no effect when used with Get(). func WithCapacity[K comparable, V any](c uint64) Option[K, V] { - return optionFunc[K, V](func(opts *options[K, V]) { + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { opts.capacity = c + return opts }) } // WithTTL sets the TTL of the cache. // It has no effect when used with Get(). func WithTTL[K comparable, V any](ttl time.Duration) Option[K, V] { - return optionFunc[K, V](func(opts *options[K, V]) { + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { opts.ttl = ttl + return opts }) } @@ -51,8 +61,9 @@ func WithTTL[K comparable, V any](ttl time.Duration) Option[K, V] { // If version tracking is disabled, the version is always -1. // It has no effect when used with Get(). func WithVersion[K comparable, V any](enable bool) Option[K, V] { - return optionFunc[K, V](func(opts *options[K, V]) { - opts.enableVersionTracking = enable + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { + opts.itemOpts = append(opts.itemOpts, WithItemVersion[K, V](enable)) + return opts }) } @@ -60,8 +71,9 @@ func WithVersion[K comparable, V any](enable bool) Option[K, V] { // When passing into Get(), it sets an ephemeral loader that // is used instead of the cache's default one. func WithLoader[K comparable, V any](l Loader[K, V]) Option[K, V] { - return optionFunc[K, V](func(opts *options[K, V]) { + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { opts.loader = l + return opts }) } @@ -71,7 +83,64 @@ func WithLoader[K comparable, V any](l Loader[K, V]) Option[K, V] { // When used with Get(), it overrides the default value of the // cache. func WithDisableTouchOnHit[K comparable, V any]() Option[K, V] { - return optionFunc[K, V](func(opts *options[K, V]) { + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { opts.disableTouchOnHit = true + return opts + }) +} + +// WithMaxCost sets the maximum cost the cache is allowed to use (e.g. the used memory). +// The actual cost calculation for each inserted item happens by making use of the +// callback CostFunc. +// It has no effect when used with Get(). +func WithMaxCost[K comparable, V any](s uint64, callback CostFunc[K, V]) Option[K, V] { + return optionFunc[K, V](func(opts options[K, V]) options[K, V] { + opts.maxCost = s + opts.itemOpts = append(opts.itemOpts, WithItemCostFunc(callback)) + return opts + }) +} + +// ItemOption sets a specific item option on item creation. +type ItemOption[K comparable, V any] interface { + apply(item *Item[K, V]) +} + +// itemOptionFunc wraps a function and implements the itemOption interface. +type itemOptionFunc[K comparable, V any] func(*Item[K, V]) + +// apply calls the wrapped function. +func (fn itemOptionFunc[K, V]) apply(item *Item[K, V]) { + fn(item) +} + +// applyItemOptions applies the provided option values to the Item. +// Note that this function needs to be called only when creating a new item, +// because we don't use the Item's mutex here. +func applyItemOptions[K comparable, V any](item *Item[K, V], opts ...ItemOption[K, V]) { + for i := range opts { + opts[i].apply(item) + } +} + +// WithItemVersion activates item version tracking. +// If version tracking is disabled, the version is always -1. +func WithItemVersion[K comparable, V any](enable bool) ItemOption[K, V] { + return itemOptionFunc[K, V](func(item *Item[K, V]) { + if enable { + item.version = 0 + } else { + item.version = -1 + } + }) +} + +// WithItemCostFunc configures an item's cost calculation function. +// A nil value disables an item's cost calculation. +func WithItemCostFunc[K comparable, V any](costFunc CostFunc[K, V]) ItemOption[K, V] { + return itemOptionFunc[K, V](func(item *Item[K, V]) { + if costFunc != nil { + item.calculateCost = costFunc + } }) } diff --git a/vendor/modules.txt b/vendor/modules.txt index df2a1357f..523bdba62 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -805,8 +805,8 @@ github.com/jbenet/go-context/io # github.com/jellydator/ttlcache/v2 v2.11.1 ## explicit; go 1.15 github.com/jellydator/ttlcache/v2 -# github.com/jellydator/ttlcache/v3 v3.3.0 -## explicit; go 1.18 +# github.com/jellydator/ttlcache/v3 v3.4.0 +## explicit; go 1.23.0 github.com/jellydator/ttlcache/v3 # github.com/jinzhu/now v1.1.5 ## explicit; go 1.12