diff --git a/cache.go b/cache.go index e868595..5b82f6b 100644 --- a/cache.go +++ b/cache.go @@ -49,6 +49,12 @@ func (c *Cache[K, V]) Get(key K) (value V, ok bool) { return c.shards[hash&c.mask].Get(hash, key) } +// SlidingGet returns value for key and reset the expires with TTL(aka, Sliding Cache). +func (c *Cache[K, V]) SlidingGet(key K) (value V, ok bool) { + hash := uint32(c.hasher.Hash(key)) + return c.shards[hash&c.mask].SlidingGet(hash, key) +} + // Peek returns value for key, but does not modify its recency. func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { hash := uint32(c.hasher.Hash(key)) diff --git a/cache_test.go b/cache_test.go index 8d565df..054cbf1 100644 --- a/cache_test.go +++ b/cache_test.go @@ -92,6 +92,22 @@ func TestCacheEviction(t *testing.T) { } } +func TestCacheSlidingGet(t *testing.T) { + l := newWithShards[string, int](1, 256) + + l.SetWithTTL("foobar", 42, 400*time.Millisecond) + + time.Sleep(200 * time.Millisecond) + if v, ok := l.SlidingGet("foobar"); !ok || v != 42 { + t.Errorf("foobar should be set to 42: %v,", v) + } + + time.Sleep(300 * time.Millisecond) + if v, ok := l.Get("foobar"); !ok || v != 42 { + t.Errorf("foobar should be still set to 42: %v,", v) + } +} + func TestCachePeek(t *testing.T) { l := New[int, int](64) diff --git a/list.go b/list.go index 2740708..a224e8a 100644 --- a/list.go +++ b/list.go @@ -6,6 +6,7 @@ type node[K comparable, V any] struct { key K value V expires int64 + ttl int64 next uint32 prev uint32 } diff --git a/shard.go b/shard.go index 3989531..54083e8 100644 --- a/shard.go +++ b/shard.go @@ -38,6 +38,31 @@ func (s *shard[K, V]) Get(hash uint32, key K) (value V, ok bool) { return } +func (s *shard[K, V]) SlidingGet(hash uint32, key K) (value V, ok bool) { + s.mu.Lock() + + if index, exists := s.table.Get(hash, key); exists { + if expires := s.list.nodes[index].expires; expires == 0 { + s.list.MoveToFront(index) + value = s.list.nodes[index].value + ok = true + } else if now := atomic.LoadInt64(&clock); now < expires { + s.list.MoveToFront(index) + s.list.nodes[index].expires = atomic.LoadInt64(&clock) + s.list.nodes[index].ttl + value = s.list.nodes[index].value + ok = true + } else { + s.list.MoveToBack(index) + s.list.nodes[index].value = value + s.table.Delete(hash, key) + } + } + + s.mu.Unlock() + + return +} + func (s *shard[K, V]) Peek(hash uint32, key K) (value V, ok bool) { s.mu.Lock() @@ -61,6 +86,7 @@ func (s *shard[K, V]) Set(hash uint32, hashfun func(K) uint64, key K, value V, t s.list.MoveToFront(index) node.value = value if ttl > 0 { + node.ttl = int64(ttl) node.expires = atomic.LoadInt64(&clock) + int64(ttl) } prev = previousValue @@ -76,6 +102,7 @@ func (s *shard[K, V]) Set(hash uint32, hashfun func(K) uint64, key K, value V, t node.key = key node.value = value if ttl > 0 { + node.ttl = int64(ttl) node.expires = atomic.LoadInt64(&clock) + int64(ttl) } s.table.Set(hash, key, index)