Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,28 +101,29 @@ Based on all the benchmarks across four different caching libraries:

v2 and v3 use Go generics and achieve significant performance improvements over v1:

- v2 is approximately **38-42% faster** than v1 for basic operations
- v2 is approximately **28-42% faster** than v1 for basic operations
- v3 maintains the performance gains of v2 while being compatible with the Hashicorp `simplelru` interface
- Recent optimizations have improved performance across all versions

#### Performance Comparison

| Operation | v1 | v2 | v3 | Improvement v1→v3 |
|-------------------------|-----------|-----------|-----------|-------------------|
| Random LRU (no expire) | 272.4 ns/op | 160.1 ns/op | 158.1 ns/op | ~42% faster |
| Frequency LRU (no expire) | 261.6 ns/op | 152.8 ns/op | 150.9 ns/op | ~42% faster |
| Random LRU (with expire) | 286.5 ns/op | 177.6 ns/op | 175.3 ns/op | ~39% faster |
| Frequency LRU (with expire) | 279.6 ns/op | 170.3 ns/op | 168.1 ns/op | ~40% faster |
| Random LRU (no expire) | 188.3 ns/op | 127.5 ns/op | 132.3 ns/op | ~30% faster |
| Frequency LRU (no expire) | 180.3 ns/op | 127.4 ns/op | 128.1 ns/op | ~29% faster |
| Random LRU (with expire) | 191.9 ns/op | 129.7 ns/op | 130.7 ns/op | ~32% faster |
| Frequency LRU (with expire) | 181.3 ns/op | 126.7 ns/op | 131.2 ns/op | ~28% faster |

#### Cross-Library Comparison

Recent benchmarks comparing expirable-cache with other popular Go caching libraries:

| Operation | [go-pkgz/expirable-cache](https://github.com/go-pkgz/expirable-cache) | [patrickmn/go-cache](https://github.com/patrickmn/go-cache) | [jellydator/ttlcache](https://github.com/jellydator/ttlcache) | [dgraph-io/ristretto](https://github.com/dgraph-io/ristretto) |
|-----------|-----------------|----------|----------|-----------|
| Set | 69.65 ns/op | 84.63 ns/op | 430.7 ns/op | 793.8 ns/op |
| Get | 78.27 ns/op | 66.29 ns/op | 193.1 ns/op | 82.61 ns/op |
| Set+Get | 67.69 ns/op | 68.97 ns/op | 242.8 ns/op | 197.6 ns/op |
| Real-world scenario | 79.98 ns/op | 70.60 ns/op | 200.0 ns/op | 85.88 ns/op |
| Set | 69.14 ns/op | 82.67 ns/op | 448.8 ns/op | 820.0 ns/op |
| Get | 78.12 ns/op | 63.81 ns/op | 190.9 ns/op | 84.23 ns/op |
| Set+Get | 66.62 ns/op | 67.94 ns/op | 253.9 ns/op | 198.2 ns/op |
| Real-world scenario | 78.83 ns/op | 70.24 ns/op | 198.0 ns/op | 83.40 ns/op |
| Memory allocations | Lowest | Low | Medium | Highest |

<details>
Expand Down
50 changes: 25 additions & 25 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,37 @@ goos: darwin
goarch: arm64
pkg: github.com/go-pkgz/expirable-cache/benchmarks
cpu: Apple M3
BenchmarkGoCache_Set-8 13150846 84.63 ns/op 68 B/op 1 allocs/op
BenchmarkGoCache_Get-8 17746971 66.29 ns/op 3 B/op 0 allocs/op
BenchmarkGoCache_SetAndGet-8 17334808 68.97 ns/op 36 B/op 1 allocs/op
BenchmarkTTLCache_Set-8 2818526 430.7 ns/op 4 B/op 0 allocs/op
BenchmarkTTLCache_Get-8 6197169 193.1 ns/op 51 B/op 1 allocs/op
BenchmarkTTLCache_SetAndGet-8 4923871 242.8 ns/op 28 B/op 1 allocs/op
BenchmarkExpirableCache_Set-8 17001220 69.65 ns/op 4 B/op 0 allocs/op
BenchmarkExpirableCache_Get-8 15427915 78.27 ns/op 3 B/op 0 allocs/op
BenchmarkExpirableCache_SetAndGet-8 17600006 67.69 ns/op 4 B/op 0 allocs/op
BenchmarkRistretto_Set-8 1502190 793.8 ns/op 262 B/op 5 allocs/op
BenchmarkRistretto_Get-8 14158246 82.61 ns/op 27 B/op 2 allocs/op
BenchmarkRistretto_SetAndGet-8 6852046 197.6 ns/op 96 B/op 2 allocs/op
BenchmarkGoCache_GetWithTypeAssertion-8 17942817 67.64 ns/op 3 B/op 0 allocs/op
BenchmarkTTLCache_GetWithoutTypeAssertion-8 6029751 199.0 ns/op 51 B/op 1 allocs/op
BenchmarkExpirableCache_GetWithoutTypeAssertion-8 15133410 79.33 ns/op 3 B/op 0 allocs/op
BenchmarkRistretto_GetWithTypeAssertion-8 14187883 86.12 ns/op 27 B/op 2 allocs/op
BenchmarkGoCache_RealWorldScenario-8 16314248 70.60 ns/op 4 B/op 0 allocs/op
BenchmarkTTLCache_RealWorldScenario-8 5891400 200.0 ns/op 52 B/op 1 allocs/op
BenchmarkExpirableCache_RealWorldScenario-8 14579878 79.98 ns/op 3 B/op 0 allocs/op
BenchmarkRistretto_RealWorldScenario-8 12897400 85.88 ns/op 28 B/op 2 allocs/op
BenchmarkGoCache_Set-8 12169099 82.67 ns/op 68 B/op 1 allocs/op
BenchmarkGoCache_Get-8 18444500 63.81 ns/op 3 B/op 0 allocs/op
BenchmarkGoCache_SetAndGet-8 17692840 67.94 ns/op 36 B/op 1 allocs/op
BenchmarkTTLCache_Set-8 2707100 448.8 ns/op 5 B/op 0 allocs/op
BenchmarkTTLCache_Get-8 6269760 190.9 ns/op 51 B/op 1 allocs/op
BenchmarkTTLCache_SetAndGet-8 4806730 253.9 ns/op 28 B/op 1 allocs/op
BenchmarkExpirableCache_Set-8 17192541 69.14 ns/op 4 B/op 0 allocs/op
BenchmarkExpirableCache_Get-8 15239731 78.12 ns/op 3 B/op 0 allocs/op
BenchmarkExpirableCache_SetAndGet-8 17954317 66.62 ns/op 4 B/op 0 allocs/op
BenchmarkRistretto_Set-8 1456555 820.0 ns/op 262 B/op 5 allocs/op
BenchmarkRistretto_Get-8 14321246 84.23 ns/op 27 B/op 2 allocs/op
BenchmarkRistretto_SetAndGet-8 5989928 198.2 ns/op 96 B/op 2 allocs/op
BenchmarkGoCache_GetWithTypeAssertion-8 17971783 66.44 ns/op 3 B/op 0 allocs/op
BenchmarkTTLCache_GetWithoutTypeAssertion-8 6114782 197.9 ns/op 51 B/op 1 allocs/op
BenchmarkExpirableCache_GetWithoutTypeAssertion-8 15095240 78.39 ns/op 3 B/op 0 allocs/op
BenchmarkRistretto_GetWithTypeAssertion-8 14458339 83.09 ns/op 27 B/op 2 allocs/op
BenchmarkGoCache_RealWorldScenario-8 16622580 70.24 ns/op 4 B/op 0 allocs/op
BenchmarkTTLCache_RealWorldScenario-8 6038209 198.0 ns/op 52 B/op 1 allocs/op
BenchmarkExpirableCache_RealWorldScenario-8 14718102 78.83 ns/op 3 B/op 0 allocs/op
BenchmarkRistretto_RealWorldScenario-8 13030276 83.40 ns/op 28 B/op 2 allocs/op
```

## Summary of Results

| Operation | [go-pkgz/expirable-cache](https://github.com/go-pkgz/expirable-cache) | [patrickmn/go-cache](https://github.com/patrickmn/go-cache) | [jellydator/ttlcache](https://github.com/jellydator/ttlcache) | [dgraph-io/ristretto](https://github.com/dgraph-io/ristretto) |
|-----------|-----------------|----------|----------|-----------|
| Set | 69.65 ns/op | 84.63 ns/op | 430.7 ns/op | 793.8 ns/op |
| Get | 78.27 ns/op | 66.29 ns/op | 193.1 ns/op | 82.61 ns/op |
| Set+Get | 67.69 ns/op | 68.97 ns/op | 242.8 ns/op | 197.6 ns/op |
| Real-world scenario | 79.98 ns/op | 70.60 ns/op | 200.0 ns/op | 85.88 ns/op |
| Memory allocations (Set) | 4 B/op | 68 B/op | 4 B/op | 262 B/op |
| Set | 69.14 ns/op | 82.67 ns/op | 448.8 ns/op | 820.0 ns/op |
| Get | 78.12 ns/op | 63.81 ns/op | 190.9 ns/op | 84.23 ns/op |
| Set+Get | 66.62 ns/op | 67.94 ns/op | 253.9 ns/op | 198.2 ns/op |
| Real-world scenario | 78.83 ns/op | 70.24 ns/op | 198.0 ns/op | 83.40 ns/op |
| Memory allocations (Set) | 4 B/op | 68 B/op | 5 B/op | 262 B/op |
| Memory allocations (Get) | 3 B/op | 3 B/op | 51 B/op | 27 B/op |

## Analysis
Expand Down
15 changes: 8 additions & 7 deletions benchmarks/go.sum
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
27 changes: 13 additions & 14 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ func NewCache(options ...Option) (Cache, error) {

// Set key, ttl of 0 would use cache-wide TTL
func (c *cacheImpl) Set(key string, value interface{}, ttl time.Duration) {
c.Lock()
defer c.Unlock()
now := time.Now()
if ttl == 0 {
ttl = c.ttl
}
now := time.Now()
c.Lock()
defer c.Unlock()

// Check for existing item
if ent, ok := c.items[key]; ok {
Expand All @@ -102,7 +102,10 @@ func (c *cacheImpl) Set(key string, value interface{}, ttl time.Duration) {

// Remove oldest entry if it is expired, only in case of non-default TTL.
if c.ttl != noEvictionTTL || ttl != noEvictionTTL {
c.removeOldestIfExpired()
ent := c.evictList.Back()
if ent != nil && now.After(ent.Value.(*cacheItem).expiresAt) {
c.removeElement(ent)
}
}

// Verify size not exceeded
Expand Down Expand Up @@ -192,11 +195,14 @@ func (c *cacheImpl) RemoveOldest() {

// DeleteExpired clears cache of expired items
func (c *cacheImpl) DeleteExpired() {
now := time.Now()
c.Lock()
defer c.Unlock()
for _, key := range c.keys() {
if time.Now().After(c.items[key].Value.(*cacheItem).expiresAt) {
c.removeElement(c.items[key])
var nextEnt *list.Element
for ent := c.evictList.Back(); ent != nil; ent = nextEnt {
nextEnt = ent.Prev()
if now.After(ent.Value.(*cacheItem).expiresAt) {
c.removeElement(ent)
}
}
}
Expand Down Expand Up @@ -245,13 +251,6 @@ func (c *cacheImpl) removeOldest() {
}
}

// removeOldest removes the oldest item from the cache in case it's already expired. Has to be called with lock!
func (c *cacheImpl) removeOldestIfExpired() {
ent := c.evictList.Back()
if ent != nil && time.Now().After(ent.Value.(*cacheItem).expiresAt) {
c.removeElement(ent)
}
}

// removeElement is used to remove a given list element from the cache. Has to be called with lock!
func (c *cacheImpl) removeElement(e *list.Element) {
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
27 changes: 13 additions & 14 deletions v2/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ func NewCache[K comparable, V any]() Cache[K, V] {

// Set key, ttl of 0 would use cache-wide TTL
func (c *cacheImpl[K, V]) Set(key K, value V, ttl time.Duration) {
c.Lock()
defer c.Unlock()
now := time.Now()
if ttl == 0 {
ttl = c.ttl
}
now := time.Now()
c.Lock()
defer c.Unlock()

// Check for existing item
if ent, ok := c.items[key]; ok {
Expand All @@ -96,7 +96,10 @@ func (c *cacheImpl[K, V]) Set(key K, value V, ttl time.Duration) {

// Remove oldest entry if it is expired, only in case of non-default TTL.
if c.ttl != noEvictionTTL || ttl != noEvictionTTL {
c.removeOldestIfExpired()
ent := c.evictList.Back()
if ent != nil && now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}

// Verify size not exceeded
Expand Down Expand Up @@ -188,11 +191,14 @@ func (c *cacheImpl[K, V]) RemoveOldest() {

// DeleteExpired clears cache of expired items
func (c *cacheImpl[K, V]) DeleteExpired() {
now := time.Now()
c.Lock()
defer c.Unlock()
for _, key := range c.keys() {
if time.Now().After(c.items[key].Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(c.items[key])
var nextEnt *list.Element
for ent := c.evictList.Back(); ent != nil; ent = nextEnt {
nextEnt = ent.Prev()
if now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}
}
Expand Down Expand Up @@ -241,13 +247,6 @@ func (c *cacheImpl[K, V]) removeOldest() {
}
}

// removeOldest removes the oldest item from the cache in case it's already expired. Has to be called with lock!
func (c *cacheImpl[K, V]) removeOldestIfExpired() {
ent := c.evictList.Back()
if ent != nil && time.Now().After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}

// removeElement is used to remove a given list element from the cache. Has to be called with lock!
func (c *cacheImpl[K, V]) removeElement(e *list.Element) {
Expand Down
2 changes: 0 additions & 2 deletions v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
38 changes: 17 additions & 21 deletions v3/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,12 @@ func (c *cacheImpl[K, V]) Set(key K, value V, ttl time.Duration) {
// Returns false if there was no eviction: the item was already in the cache,
// or the size was not exceeded.
func (c *cacheImpl[K, V]) addWithTTL(key K, value V, ttl time.Duration) (evicted bool) {
c.Lock()
defer c.Unlock()
now := time.Now()
if ttl == 0 {
ttl = c.ttl
}

now := time.Now()
c.Lock()
defer c.Unlock()
// Check for existing item
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
Expand All @@ -117,7 +116,10 @@ func (c *cacheImpl[K, V]) addWithTTL(key K, value V, ttl time.Duration) (evicted

// Remove the oldest entry if it is expired, only in case of non-default TTL.
if c.ttl != noEvictionTTL || ttl != noEvictionTTL {
c.removeOldestIfExpired()
ent := c.evictList.Back()
if ent != nil && now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}

evict := c.maxKeys > 0 && len(c.items) > c.maxKeys
Expand Down Expand Up @@ -197,15 +199,14 @@ func (c *cacheImpl[K, V]) Keys() []K {
// Values returns a slice of the values in the cache, from oldest to newest.
// Expired entries are filtered out.
func (c *cacheImpl[K, V]) Values() []V {
c.Lock()
defer c.Unlock()
values := make([]V, 0, len(c.items))
now := time.Now()
c.Lock()
defer c.Unlock()
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
if now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
continue
if !now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
values = append(values, ent.Value.(*cacheItem[K, V]).value)
}
values = append(values, ent.Value.(*cacheItem[K, V]).value)
}
return values
}
Expand Down Expand Up @@ -291,11 +292,14 @@ func (c *cacheImpl[K, V]) GetOldest() (key K, value V, ok bool) {

// DeleteExpired clears cache of expired items
func (c *cacheImpl[K, V]) DeleteExpired() {
now := time.Now()
c.Lock()
defer c.Unlock()
for _, key := range c.keys() {
if time.Now().After(c.items[key].Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(c.items[key])
var nextEnt *list.Element
for ent := c.evictList.Back(); ent != nil; ent = nextEnt {
nextEnt = ent.Prev()
if now.After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}
}
Expand Down Expand Up @@ -344,14 +348,6 @@ func (c *cacheImpl[K, V]) removeOldest() {
}
}

// removeOldest removes the oldest item from the cache in case it's already expired. Has to be called with lock!
func (c *cacheImpl[K, V]) removeOldestIfExpired() {
ent := c.evictList.Back()
if ent != nil && time.Now().After(ent.Value.(*cacheItem[K, V]).expiresAt) {
c.removeElement(ent)
}
}

// removeElement is used to remove a given list element from the cache. Has to be called with lock!
func (c *cacheImpl[K, V]) removeElement(e *list.Element) {
c.evictList.Remove(e)
Expand Down
Loading