diff --git a/.travis.yml b/.travis.yml index 70b965c..095be4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,15 @@ language: go go: - - 1.13 - - 1.12 + - "1.14" + - "1.13" git: depth: 1 install: - go install -race std - - go get golang.org/x/tools/cmd/cover - - go get golang.org/x/lint/golint + - go install golang.org/x/tools/cmd/cover + - go install golang.org/x/lint/golint - export PATH=$HOME/gopath/bin:$PATH script: diff --git a/Readme.md b/Readme.md index 9c4e8b6..9c537fb 100644 --- a/Readme.md +++ b/Readme.md @@ -8,6 +8,8 @@ 4. Fast and memory efficient 5. Can trigger callback on key expiration 6. Cleanup resources by calling `Close()` at end of lifecycle. + +Note (issue #25): by default, due to historic reasons, the TTL will be reset on each cache hit and you need to explicitly configure the cache to use a TTL that will not get extended. [![Build Status](https://travis-ci.org/ReneKroon/ttlcache.svg?branch=master)](https://travis-ci.org/ReneKroon/ttlcache) @@ -38,7 +40,7 @@ } cache := ttlcache.NewCache() - defer ttlcache.Close() + defer cache.Close() cache.SetTTL(time.Duration(10 * time.Second)) cache.SetExpirationCallback(expirationCallback) @@ -63,7 +65,7 @@ The main differences are: 1. A item can store any kind of object, previously, only strings could be saved -2. Optionally, you can add callbacks to: check if a value should expire, be notified if a value expires, and be notified when new values are added to the cache +2. Optionally, you can add callbacks too: check if a value should expire, be notified if a value expires, and be notified when new values are added to the cache 3. The expiration can be either global or per item 4. Can exist items without expiration time 5. Expirations and callbacks are realtime. Don't have a pooling time to check anymore, now it's done with a heap. diff --git a/cache.go b/cache.go index 0212fc3..f772d0c 100644 --- a/cache.go +++ b/cache.go @@ -80,6 +80,11 @@ select { case shutdownFeedback := <-cache.shutdownSignal: timer.Stop() + cache.mutex.Lock() + if cache.priorityQueue.Len() > 0 { + cache.evictjob() + } + cache.mutex.Unlock() shutdownFeedback <- struct{}{} return case <-timer.C: @@ -90,37 +95,56 @@ continue } - // index will only be advanced if the current entry will not be evicted - i := 0 - for item := cache.priorityQueue.items[i]; item.expired(); item = cache.priorityQueue.items[i] { - - if cache.checkExpireCallback != nil { - if !cache.checkExpireCallback(item.key, item.data) { - item.touch() - cache.priorityQueue.update(item) - i++ - if i == cache.priorityQueue.Len() { - break - } - continue - } - } - - cache.priorityQueue.remove(item) - delete(cache.items, item.key) - if cache.expireCallback != nil { - go cache.expireCallback(item.key, item.data) - } - if cache.priorityQueue.Len() == 0 { - goto done - } - } - done: + cache.cleanjob() cache.mutex.Unlock() case <-cache.expirationNotification: timer.Stop() continue + } + } +} + +func (cache *Cache) evictjob() { + // index will only be advanced if the current entry will not be evicted + i := 0 + for item := cache.priorityQueue.items[i]; ; item = cache.priorityQueue.items[i] { + + cache.priorityQueue.remove(item) + delete(cache.items, item.key) + if cache.expireCallback != nil { + go cache.expireCallback(item.key, item.data) + } + if cache.priorityQueue.Len() == 0 { + return + } + } +} + +func (cache *Cache) cleanjob() { + // index will only be advanced if the current entry will not be evicted + i := 0 + for item := cache.priorityQueue.items[i]; item.expired(); item = cache.priorityQueue.items[i] { + + if cache.checkExpireCallback != nil { + if !cache.checkExpireCallback(item.key, item.data) { + item.touch() + cache.priorityQueue.update(item) + i++ + if i == cache.priorityQueue.Len() { + break + } + continue + } + } + + cache.priorityQueue.remove(item) + delete(cache.items, item.key) + if cache.expireCallback != nil { + go cache.expireCallback(item.key, item.data) + } + if cache.priorityQueue.Len() == 0 { + return } } } diff --git a/cache_test.go b/cache_test.go index 2dc4852..3bc3ec2 100644 --- a/cache_test.go +++ b/cache_test.go @@ -16,6 +16,65 @@ func TestMain(m *testing.M) { goleak.VerifyTestMain(m) } + +// Issue #28: call expirationCallback automatically on cache.Close() +func TestCache_ExpirationOnClose(t *testing.T) { + + cache := NewCache() + + success := make(chan struct{}) + defer close(success) + + cache.SetTTL(time.Hour * 100) + cache.SetExpirationCallback(func(key string, value interface{}) { + t.Logf("%s\t%v", key, value) + success <- struct{}{} + }) + cache.Set("1", 1) + cache.Set("2", 1) + cache.Set("3", 1) + + found := 0 + cache.Close() + wait := time.NewTimer(time.Millisecond * 100) + for found != 3 { + select { + case <-success: + found++ + case <-wait.C: + t.Fail() + } + } + +} + +// # Issue 29: After Close() the behaviour of Get, Set, Remove is not defined. +/* +func TestCache_ModifyAfterClose(t *testing.T) { + cache := NewCache() + + cache.SetTTL(time.Hour * 100) + cache.SetExpirationCallback(func(key string, value interface{}) { + t.Logf("%s\t%v", key, value) + }) + cache.Set("1", 1) + cache.Set("2", 1) + cache.Set("3", 1) + + cache.Close() + + cache.Get("broken3") + cache.Set("broken", 1) + cache.Remove("broken2") + + wait := time.NewTimer(time.Millisecond * 100) + + select { + case <-wait.C: + t.Fail() + } + +}*/ // Issue #23: Goroutine leak on closing. When adding a close method i would like to see // that it can be called in a repeated way without problems. diff --git a/go.mod b/go.mod index 51ad6c6..6806b28 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ReneKroon/ttlcache -go 1.12 +go 1.14 require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 707f018..5701e60 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ 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/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=