From 5e2fceb7fe0adbbd48407fcefd34647340d27196 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 28 Jan 2024 21:45:59 +0530 Subject: [PATCH 1/2] feat: Add Support for PreviousTTL Signed-off-by: Shivam Kumar --- cache.go | 4 ++++ cache_test.go | 22 ++++++++++++++++++++++ item.go | 20 +++++++++++++++----- item_test.go | 8 +++++++- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/cache.go b/cache.go index a1547fc..bf96a0a 100644 --- a/cache.go +++ b/cache.go @@ -148,6 +148,10 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] { c.evict(EvictionReasonCapacityReached, c.items.lru.Back()) } + if ttl == PreviousTTL { + ttl = c.options.ttl + } + // create a new item item := newItem(key, value, ttl, c.options.enableVersionTracking) elem = c.items.lru.PushFront(item) diff --git a/cache_test.go b/cache_test.go index 07bf8c5..a7c0e0c 100644 --- a/cache_test.go +++ b/cache_test.go @@ -189,6 +189,10 @@ func Test_Cache_set(t *testing.T) { Key: existingKey, TTL: DefaultTTL, }, + "Set with existing key and PreviousTTL": { + Key: existingKey, + TTL: PreviousTTL, + }, "Set with new key and eviction caused by small capacity": { Capacity: 3, Key: newKey, @@ -232,6 +236,14 @@ func Test_Cache_set(t *testing.T) { }, ExpectFns: true, }, + "Set with new key and PreviousTTL": { + Key: newKey, + TTL: PreviousTTL, + Metrics: Metrics{ + Insertions: 1, + }, + ExpectFns: true, + }, } for cn, c := range cc { @@ -245,6 +257,9 @@ func Test_Cache_set(t *testing.T) { evictionFnsCalls int ) + // calculated based on how addToCache sets ttl + existingKeyTTL := time.Hour + time.Minute + cache := prepCache(time.Hour, evictedKey, existingKey, "test3") cache.options.capacity = c.Capacity cache.options.ttl = time.Minute * 20 @@ -295,6 +310,13 @@ func Test_Cache_set(t *testing.T) { assert.Equal(t, c.TTL, item.ttl) assert.WithinDuration(t, time.Now(), item.expiresAt, c.TTL) assert.Equal(t, c.Key, cache.items.expQueue[0].Value.(*Item[string, string]).key) + case c.TTL == PreviousTTL: + expectedTTL := cache.options.ttl + if c.Key == existingKey { + expectedTTL = existingKeyTTL + } + assert.Equal(t, expectedTTL, item.ttl) + assert.WithinDuration(t, time.Now(), item.expiresAt, expectedTTL) default: assert.Equal(t, c.TTL, item.ttl) assert.Zero(t, item.expiresAt) diff --git a/item.go b/item.go index 72568e0..738f038 100644 --- a/item.go +++ b/item.go @@ -9,6 +9,10 @@ const ( // NoTTL indicates that an item should never expire. NoTTL time.Duration = -1 + // PreviousTTL indicates that existing TTL of item should be used + // default TTL will be used as fallback if item doesn't exist + PreviousTTL time.Duration = -2 + // DefaultTTL indicates that the default TTL value of the cache // instance should be used. DefaultTTL time.Duration = 0 @@ -58,17 +62,23 @@ func (item *Item[K, V]) update(value V, ttl time.Duration) { defer item.mu.Unlock() item.value = value + + // update version if enabled + if item.version > -1 { + item.version++ + } + + // no need to update ttl or expiry in this case + if ttl == PreviousTTL { + return + } + item.ttl = ttl // reset expiration timestamp because the new TTL may be // 0 or below item.expiresAt = time.Time{} item.touchUnsafe() - - // update version if enabled - if item.version > -1 { - item.version++ - } } // touch updates the item's expiration timestamp. diff --git a/item_test.go b/item_test.go index 06a17f5..708d762 100644 --- a/item_test.go +++ b/item_test.go @@ -31,10 +31,16 @@ func Test_Item_update(t *testing.T) { assert.Equal(t, int64(1), item.version) assert.WithinDuration(t, time.Now().Add(time.Hour), item.expiresAt, time.Minute) + item.update("previous ttl", PreviousTTL) + assert.Equal(t, "previous ttl", item.value) + assert.Equal(t, time.Hour, item.ttl) + assert.Equal(t, int64(2), item.version) + assert.WithinDuration(t, time.Now().Add(time.Hour), item.expiresAt, time.Minute) + item.update("hi", NoTTL) assert.Equal(t, "hi", item.value) assert.Equal(t, NoTTL, item.ttl) - assert.Equal(t, int64(2), item.version) + assert.Equal(t, int64(3), item.version) assert.Zero(t, item.expiresAt) } From e1bc68b2e6e455621c277be9e4b4a4e14a7de01a Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Sun, 4 Feb 2024 18:25:45 +0530 Subject: [PATCH 2/2] Rename PreviousTTL to PreviousOrDefaultTTL for More Clarity Signed-off-by: Shivam Kumar --- cache.go | 2 +- cache_test.go | 10 +++++----- item.go | 6 +++--- item_test.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cache.go b/cache.go index bf96a0a..1ad3afb 100644 --- a/cache.go +++ b/cache.go @@ -148,7 +148,7 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] { c.evict(EvictionReasonCapacityReached, c.items.lru.Back()) } - if ttl == PreviousTTL { + if ttl == PreviousOrDefaultTTL { ttl = c.options.ttl } diff --git a/cache_test.go b/cache_test.go index a7c0e0c..0c91512 100644 --- a/cache_test.go +++ b/cache_test.go @@ -189,9 +189,9 @@ func Test_Cache_set(t *testing.T) { Key: existingKey, TTL: DefaultTTL, }, - "Set with existing key and PreviousTTL": { + "Set with existing key and PreviousOrDefaultTTL": { Key: existingKey, - TTL: PreviousTTL, + TTL: PreviousOrDefaultTTL, }, "Set with new key and eviction caused by small capacity": { Capacity: 3, @@ -236,9 +236,9 @@ func Test_Cache_set(t *testing.T) { }, ExpectFns: true, }, - "Set with new key and PreviousTTL": { + "Set with new key and PreviousOrDefaultTTL": { Key: newKey, - TTL: PreviousTTL, + TTL: PreviousOrDefaultTTL, Metrics: Metrics{ Insertions: 1, }, @@ -310,7 +310,7 @@ func Test_Cache_set(t *testing.T) { assert.Equal(t, c.TTL, item.ttl) assert.WithinDuration(t, time.Now(), item.expiresAt, c.TTL) assert.Equal(t, c.Key, cache.items.expQueue[0].Value.(*Item[string, string]).key) - case c.TTL == PreviousTTL: + case c.TTL == PreviousOrDefaultTTL: expectedTTL := cache.options.ttl if c.Key == existingKey { expectedTTL = existingKeyTTL diff --git a/item.go b/item.go index 738f038..c3c26cf 100644 --- a/item.go +++ b/item.go @@ -9,9 +9,9 @@ const ( // NoTTL indicates that an item should never expire. NoTTL time.Duration = -1 - // PreviousTTL indicates that existing TTL of item should be used + // PreviousOrDefaultTTL indicates that existing TTL of item should be used // default TTL will be used as fallback if item doesn't exist - PreviousTTL time.Duration = -2 + PreviousOrDefaultTTL time.Duration = -2 // DefaultTTL indicates that the default TTL value of the cache // instance should be used. @@ -69,7 +69,7 @@ func (item *Item[K, V]) update(value V, ttl time.Duration) { } // no need to update ttl or expiry in this case - if ttl == PreviousTTL { + if ttl == PreviousOrDefaultTTL { return } diff --git a/item_test.go b/item_test.go index 708d762..0d001a7 100644 --- a/item_test.go +++ b/item_test.go @@ -31,7 +31,7 @@ func Test_Item_update(t *testing.T) { assert.Equal(t, int64(1), item.version) assert.WithinDuration(t, time.Now().Add(time.Hour), item.expiresAt, time.Minute) - item.update("previous ttl", PreviousTTL) + item.update("previous ttl", PreviousOrDefaultTTL) assert.Equal(t, "previous ttl", item.value) assert.Equal(t, time.Hour, item.ttl) assert.Equal(t, int64(2), item.version)