Skip to content

Add new function GetWithRefresh() #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ type Cache[K comparable, V any] interface {
// and the return value indicates that the key was not found.
Get(key K) (V, bool)

// GetAndRefresh returns the value associated with the key, setting it as the most
// recently used item.
// The lifetime of the found cache item is refreshed, even if it was already expired.
GetAndRefresh(key K, lifetime time.Duration) (V, bool)

// Peek looks up a key's value from the cache, without changing its recent-ness.
// If the found entry is already expired, the evict function is called.
Peek(key K) (V, bool)
Expand Down
44 changes: 43 additions & 1 deletion lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,26 @@ func (lru *LRU[K, V]) findKey(hash uint32, key K) (uint32, bool) {
}
}

func (lru *LRU[K, V]) findKeyNoExpire(hash uint32, key K) (uint32, bool) {
_, startPos := lru.hashToPos(hash)
if startPos == emptyBucket {
return emptyBucket, false
}

pos := startPos
for {
if key == lru.elements[pos].key {
return pos, true
}

pos = lru.elements[pos].nextBucket
if pos == startPos {
// Key not found
return emptyBucket, false
}
}
}

// Len returns the number of elements stored in the cache.
func (lru *LRU[K, V]) Len() int {
return int(lru.len)
Expand Down Expand Up @@ -448,6 +468,28 @@ func (lru *LRU[K, V]) get(hash uint32, key K) (value V, ok bool) {
return
}

// GetAndRefresh returns the value associated with the key, setting it as the most
// recently used item.
// The lifetime of the found cache item is refreshed, even if it was already expired.
func (lru *LRU[K, V]) GetAndRefresh(key K, lifetime time.Duration) (V, bool) {
return lru.getAndRefresh(lru.hash(key), key, lifetime)
}

func (lru *LRU[K, V]) getAndRefresh(hash uint32, key K, lifetime time.Duration) (value V, ok bool) {
if pos, ok := lru.findKeyNoExpire(hash, key); ok {
if pos != lru.head {
lru.unlinkElement(pos)
lru.setHead(pos)
}
lru.metrics.Hits++
lru.elements[pos].expire = expire(lifetime)
return lru.elements[pos].value, ok
}

lru.metrics.Misses++
return
}

// Peek looks up a key's value from the cache, without changing its recent-ness.
// If the found entry is already expired, the evict function is called.
func (lru *LRU[K, V]) Peek(key K) (value V, ok bool) {
Expand Down Expand Up @@ -482,7 +524,7 @@ func (lru *LRU[K, V]) Remove(key K) (removed bool) {
}

func (lru *LRU[K, V]) remove(hash uint32, key K) (removed bool) {
if pos, ok := lru.findKey(hash, key); ok {
if pos, ok := lru.findKeyNoExpire(hash, key); ok {
lru.removeAt(pos)
return ok
}
Expand Down
23 changes: 22 additions & 1 deletion lru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,33 @@ func TestSyncedLRU_AddWithExpire(t *testing.T) {
testCacheAddWithExpire(t, makeSyncedLRU(t, 2, nil))
}

func testCacheAddWithRefresh(t *testing.T, cache Cache[uint64, uint64]) {
cache.AddWithLifetime(1, 2, 100*time.Millisecond)
cache.AddWithLifetime(2, 3, 100*time.Millisecond)
_, ok := cache.Get(1)
FatalIf(t, !ok, "Failed to get")

time.Sleep(101 * time.Millisecond)
_, ok = cache.GetAndRefresh(1, 0)
FatalIf(t, !ok, "Unexpected expiration")
_, ok = cache.GetAndRefresh(2, 0)
FatalIf(t, !ok, "Unexpected expiration")
}

func TestLRU_AddWithRefresh(t *testing.T) {
testCacheAddWithRefresh(t, makeCache(t, 2, nil))
}

func TestSyncedLRU_AddWithRefresh(t *testing.T) {
testCacheAddWithRefresh(t, makeSyncedLRU(t, 2, nil))
}

func TestLRUMatch(t *testing.T) {
testCacheMatch(t, makeCache(t, 2, nil), 128)
}

func TestSyncedLRUMatch(t *testing.T) {
testCacheMatch(t, makeCache(t, 2, nil), 128)
testCacheMatch(t, makeSyncedLRU(t, 2, nil), 128)
}

// Test that Go map and the Cache stay in sync when adding
Expand Down
14 changes: 14 additions & 0 deletions shardedlru.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ func (lru *ShardedLRU[K, V]) Get(key K) (value V, ok bool) {
return
}

// GetAndRefresh returns the value associated with the key, setting it as the most
// recently used item.
// The lifetime of the found cache item is refreshed, even if it was already expired.
func (lru *ShardedLRU[K, V]) GetAndRefresh(key K, lifetime time.Duration) (value V, ok bool) {
hash := lru.hash(key)
shard := (hash >> 16) & lru.mask

lru.mus[shard].Lock()
value, ok = lru.lrus[shard].getAndRefresh(hash, key, lifetime)
lru.mus[shard].Unlock()

return
}

// Peek looks up a key's value from the cache, without changing its recent-ness.
// If the found entry is already expired, the evict function is called.
func (lru *ShardedLRU[K, V]) Peek(key K) (value V, ok bool) {
Expand Down
1 change: 1 addition & 0 deletions shardedlru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func TestShardedRaceCondition(t *testing.T) {
call(func() { _ = lru.AddWithLifetime(1, 1, 0) })
call(func() { _ = lru.Add(1, 1) })
call(func() { _, _ = lru.Get(1) })
call(func() { _, _ = lru.GetAndRefresh(1, 0) })
call(func() { _, _ = lru.Peek(1) })
call(func() { _ = lru.Contains(1) })
call(func() { _ = lru.Remove(1) })
Expand Down
13 changes: 13 additions & 0 deletions syncedlru.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ func (lru *SyncedLRU[K, V]) Get(key K) (value V, ok bool) {
return
}

// GetAndRefresh returns the value associated with the key, setting it as the most
// recently used item.
// The lifetime of the found cache item is refreshed, even if it was already expired.
func (lru *SyncedLRU[K, V]) GetAndRefresh(key K, lifetime time.Duration) (value V, ok bool) {
hash := lru.lru.hash(key)

lru.mu.Lock()
value, ok = lru.lru.getAndRefresh(hash, key, lifetime)
lru.mu.Unlock()

return
}

// Peek looks up a key's value from the cache, without changing its recent-ness.
// If the found entry is already expired, the evict function is called.
func (lru *SyncedLRU[K, V]) Peek(key K) (value V, ok bool) {
Expand Down
1 change: 1 addition & 0 deletions syncedlru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestSyncedRaceCondition(t *testing.T) {
call(func() { _ = lru.AddWithLifetime(1, 1, 0) })
call(func() { _ = lru.Add(1, 1) })
call(func() { _, _ = lru.Get(1) })
call(func() { _, _ = lru.GetAndRefresh(1, 0) })
call(func() { _, _ = lru.Peek(1) })
call(func() { _ = lru.Contains(1) })
call(func() { _ = lru.Remove(1) })
Expand Down
Loading