From f552e6bd11fc61408c8251b3810456d65c846e50 Mon Sep 17 00:00:00 2001 From: Daniel Ferstay Date: Fri, 15 May 2020 11:03:56 -0700 Subject: [PATCH] Add support for expire after types: on-write, on-access This commit allows caches to be configured with an ExpireAfterType that controls how entries are expired from the cache; two types are supported. * ExpireAfterWrite specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry's creation, or the most recent replacement of its value. * ExpireAfterAccess specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after the entry's creation, the most recent replacement of its value, or its last access. ExpireAfterType defaults to ExpireAfterWrite for backwards-compatibility. Signed-off-by: Daniel Ferstay --- cache.go | 27 +++++++++++++++++++++++++++ cache_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/cache.go b/cache.go index 59680b0..91ac5d2 100644 --- a/cache.go +++ b/cache.go @@ -60,6 +60,20 @@ const ( ActiveExpiration ) +// ExpireAfterType enumerates the expire after types. +type ExpireAfterType int + +const ( + // ExpireAfterWrite specifies that each entry should be automatically removed from the cache + // once a fixed duration has elapsed after the entry's creation, or the most recent replacement of its value. + ExpireAfterWrite ExpireAfterType = iota + + // ExpireAfterAccess specifies that each entry should be automatically removed from the cache + // once a fixed duration has elapsed after the entry's creation, the most recent replacement of its value, + // or its last access. + ExpireAfterAccess +) + // Config configures the cache. type Config struct { // Maximum number of items in the cache @@ -73,6 +87,8 @@ type Config struct { MinAge time.Duration // Type of key expiration: Passive or Active ExpirationType ExpirationType + // Type of the expiration time: AfterWrite or AfterAccess + ExpireAfterType ExpireAfterType // For active expiration, how often to iterate over the keyspace. Defaults // to the MaxAge ExpirationInterval time.Duration @@ -96,6 +112,7 @@ type Cache struct { minAge time.Duration maxAge time.Duration expirationType ExpirationType + expireAfterType ExpireAfterType expirationInterval time.Duration onEviction func(key, value interface{}) onExpiration func(key, value interface{}) @@ -144,6 +161,12 @@ func New(config Config) *Cache { interval = config.MaxAge } + expireAfterType := config.ExpireAfterType + if expireAfterType <= 0 { + // default to ExpireAfterWrite + expireAfterType = ExpireAfterWrite + } + seed := rand.NewSource(time.Now().UnixNano()) cache := &Cache{ @@ -151,6 +174,7 @@ func New(config Config) *Cache { maxAge: config.MaxAge, minAge: minAge, expirationType: config.ExpirationType, + expireAfterType: expireAfterType, expirationInterval: interval, onEviction: config.OnEviction, onExpiration: config.OnExpiration, @@ -209,6 +233,9 @@ func (cache *Cache) Get(key interface{}) (interface{}, bool) { if element, ok := cache.items[key]; ok { entry := element.Value.(*cacheEntry) + if cache.expireAfterType == ExpireAfterAccess { + entry.timestamp = cache.getTimestamp() + } if cache.maxAge == 0 || time.Since(entry.timestamp) <= cache.maxAge { cache.evictionList.MoveToFront(element) cache.hits++ diff --git a/cache_test.go b/cache_test.go index 82d4651..3b3cec4 100644 --- a/cache_test.go +++ b/cache_test.go @@ -109,6 +109,34 @@ func TestExpiration(t *testing.T) { assert.False(t, eviction) } +func TestExpirationAfterAccess(t *testing.T) { + var k, v interface{} + var eviction bool + + cache := New(Config{ + Capacity: 1, + MaxAge: time.Millisecond, + ExpireAfterType: ExpireAfterAccess, + OnExpiration: func(key, value interface{}) { + k = key + v = value + }, + OnEviction: func(key, value interface{}) { + eviction = true + }, + }) + + cache.Set("foo", 1) + <-time.After(time.Millisecond * 2) + + val, ok := cache.Get("foo") + assert.True(t, ok) + assert.NotNil(t, val) + assert.Nil(t, k) + assert.Nil(t, v) + assert.False(t, eviction) +} + type MockRandGenerator struct { startAt int64 incr int64