Skip to content

Commit

Permalink
Fix Issue #28 call expiration on Close
Browse files Browse the repository at this point in the history
  • Loading branch information
René Kroon committed Jul 9, 2020
1 parent 1e1b66b commit e7c33ab
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 29 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: go

go:
- 1.13
- 1.12
- "1.14"
- "1.13"
git:
depth: 1

Expand Down
76 changes: 50 additions & 26 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ func (cache *Cache) startExpirationProcessing() {
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:
Expand All @@ -90,32 +95,7 @@ func (cache *Cache) startExpirationProcessing() {
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:
Expand All @@ -125,6 +105,50 @@ func (cache *Cache) startExpirationProcessing() {
}
}

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
}
}
}

// Close calls Purge, and then stops the goroutine that does ttl checking, for a clean shutdown.
// The cache is no longer cleaning up after the first call to Close, repeated calls are safe though.
func (cache *Cache) Close() {
Expand Down
59 changes: 59 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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.
func TestCache_MultipleCloseCalls(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit e7c33ab

Please sign in to comment.