Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,19 @@ func main() {

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 demonstrates
how to limit the maximum memory usage of a cache to 5KiB:
implementing custom strategies. The following example shows how to limit
memory usage for cached entries to ~5KiB.
```go
import (
"github.com/jellydator/ttlcache"
"github.com/DmitriyVTitov/size"
)

func main() {
cache := ttlcache.New[string, string](
ttlcache.WithMaxCost[string, string](5120, func(item *ttlcache.Item[string, string]) uint64 {
return uint64(size.Of(item))
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.
Comment thread
swithek marked this conversation as resolved.
Outdated
return len(item.Key) + len(item.Value)
}),
)

Expand Down
4 changes: 2 additions & 2 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1480,8 +1480,8 @@ func prepCache(maxCost uint64, ttl time.Duration, keys ...string) *Cache[string,
if maxCost != 0 {
c.options.maxCost = maxCost
c.options.itemOpts = append(c.options.itemOpts,
WithItemCostFunc(func(item *Item[string, string]) uint64 {
return uint64(len(item.value))
WithItemCostFunc(func(item CostItem[string, string]) uint64 {
return uint64(len(item.Value))
}))
}

Expand Down
34 changes: 23 additions & 11 deletions item.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -57,12 +64,15 @@ func NewItemWithOpts[K comparable, V any](key K, value V, ttl time.Duration, opt
value: value,
ttl: ttl,
version: -1,
calculateCost: func(item *Item[K, V]) uint64 { return 0 },
calculateCost: func(item CostItem[K, V]) uint64 { return 0 },
}

applyItemOptions(item, opts...)
item.touch()
item.cost = item.calculateCost(item)
item.cost = item.calculateCost(CostItem[K, V]{
Key: key,
Value: value,
})

return item
}
Expand All @@ -73,24 +83,26 @@ func (item *Item[K, V]) update(value V, ttl time.Duration) {
defer item.mu.Unlock()

item.value = value
item.cost = item.calculateCost(item)

// update version if enabled
if item.version > -1 {
item.version++
}

// 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.
Expand Down
12 changes: 6 additions & 6 deletions item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Test_newItemWithOpts(t *testing.T) {
assert.Equal(t, int64(-1), item.version)
assert.Equal(t, uint64(0), item.cost)
require.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(0), item.calculateCost(item))
assert.Equal(t, uint64(0), item.calculateCost(CostItem[string, int]{Key: item.key, Value: item.value}))
},
},
"Item with version tracking disabled": {
Expand All @@ -44,7 +44,7 @@ func Test_newItemWithOpts(t *testing.T) {
assert.Equal(t, int64(-1), item.version)
assert.Equal(t, uint64(0), item.cost)
require.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(0), item.calculateCost(item))
assert.Equal(t, uint64(0), item.calculateCost(CostItem[string, int]{Key: item.key, Value: item.value}))
},
},
"Item with version tracking explicitly enabled": {
Expand All @@ -57,20 +57,20 @@ func Test_newItemWithOpts(t *testing.T) {
assert.Equal(t, int64(0), item.version)
assert.Equal(t, uint64(0), item.cost)
require.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(0), item.calculateCost(item))
assert.Equal(t, uint64(0), item.calculateCost(CostItem[string, int]{Key: item.key, Value: item.value}))
},
},
"Item with cost calculation": {
opts: []ItemOption[string, int]{
itemOptionFunc[string, int](func(i *Item[string, int]) {
i.calculateCost = func(item *Item[string, int]) uint64 { return 5 }
i.calculateCost = func(item CostItem[string, int]) uint64 { return 5 }
}),
},
assert: func(t *testing.T, item *Item[string, int]) {
assert.Equal(t, int64(-1), item.version)
assert.Equal(t, uint64(5), item.cost)
require.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(5), item.calculateCost(item))
assert.Equal(t, uint64(5), item.calculateCost(CostItem[string, int]{Key: item.key, Value: item.value}))
},
},
}
Expand Down Expand Up @@ -152,7 +152,7 @@ func Test_Item_update(t *testing.T) {
"With version calculation and version tracking": {
opts: []ItemOption[string, string]{
itemOptionFunc[string, string](func(i *Item[string, string]) {
i.calculateCost = func(item *Item[string, string]) uint64 { return uint64(len(item.value)) }
i.calculateCost = func(item CostItem[string, string]) uint64 { return uint64(len(item.Value)) }
i.version = 0
}),
},
Expand Down
2 changes: 1 addition & 1 deletion options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (fn optionFunc[K, V]) apply(opts options[K, V]) options[K, V] {

// 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 *Item[K, V]) uint64
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 {
Expand Down
10 changes: 5 additions & 5 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ func Test_WithMaxCost(t *testing.T) {
var opts options[string, string]
var item Item[string, string]

opts = WithMaxCost(1024, func(item *Item[string, string]) uint64 { return 1 }).apply(opts)
opts = WithMaxCost(1024, func(item CostItem[string, string]) uint64 { return 1 }).apply(opts)

assert.Equal(t, uint64(1024), opts.maxCost)
assert.Len(t, opts.itemOpts, 1)
opts.itemOpts[0].apply(&item)
assert.Equal(t, uint64(0), item.cost)
assert.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(1), item.calculateCost(&item))
assert.Equal(t, uint64(1), item.calculateCost(CostItem[string, string]{Key: item.key, Value: item.value}))
}

func Test_applyItemOptions(t *testing.T) {
Expand All @@ -114,7 +114,7 @@ func Test_applyItemOptions(t *testing.T) {

applyItemOptions(&item,
WithItemVersion[string, string](true),
WithItemCostFunc(func(item *Item[string, string]) uint64 { return 0 }),
WithItemCostFunc(func(item CostItem[string, string]) uint64 { return 0 }),
)

assert.Equal(t, int64(0), item.version)
Expand All @@ -140,11 +140,11 @@ func Test_WithItemCostFunc(t *testing.T) {

var item Item[string, string]

opt := WithItemCostFunc(func(item *Item[string, string]) uint64 {
opt := WithItemCostFunc(func(item CostItem[string, string]) uint64 {
return 10
})
opt.apply(&item)
assert.Equal(t, uint64(0), item.cost)
require.NotNil(t, item.calculateCost)
assert.Equal(t, uint64(10), item.calculateCost(&item))
assert.Equal(t, uint64(10), item.calculateCost(CostItem[string, string]{Key: item.key, Value: item.value}))
}
Loading