-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is a simple generic thread safe LRU cache with the minimum functionality for caching limited number of qcow2 L2 tables. Signed-off-by: Nir Soffer <[email protected]>
- Loading branch information
Showing
2 changed files
with
131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package lru | ||
|
||
import ( | ||
"container/list" | ||
"sync" | ||
) | ||
|
||
// Cache keeps recently used values. Safe for concurrent use by multiple | ||
// goroutines. | ||
type Cache[K comparable, V any] struct { | ||
mutex sync.Mutex | ||
entries map[K]*list.Element | ||
recentlyUsed *list.List | ||
capacity int | ||
} | ||
|
||
type cacheEntry[K comparable, V any] struct { | ||
Key K | ||
Value V | ||
} | ||
|
||
// New returns a new empty cache that can hold up to capacity items. | ||
func New[K comparable, V any](capacity int) *Cache[K, V] { | ||
return &Cache[K, V]{ | ||
entries: make(map[K]*list.Element), | ||
recentlyUsed: list.New(), | ||
capacity: capacity, | ||
} | ||
} | ||
|
||
// Get returns the value stored in the cache for a key, or zero value if no | ||
// value is present. The ok result indicates whether value was found in the | ||
// cache. | ||
func (c *Cache[K, V]) Get(key K) (V, bool) { | ||
c.mutex.Lock() | ||
defer c.mutex.Unlock() | ||
|
||
if elem, ok := c.entries[key]; ok { | ||
c.recentlyUsed.MoveToFront(elem) | ||
entry := elem.Value.(*cacheEntry[K, V]) | ||
return entry.Value, true | ||
} | ||
|
||
var missing V | ||
return missing, false | ||
} | ||
|
||
// Add adds key and value to the cache. If the cache is full, the oldest entry | ||
// is removed. If key is already in the cache, value replaces the cached value. | ||
func (c *Cache[K, V]) Add(key K, value V) { | ||
c.mutex.Lock() | ||
defer c.mutex.Unlock() | ||
|
||
if elem, ok := c.entries[key]; ok { | ||
c.recentlyUsed.MoveToFront(elem) | ||
entry := elem.Value.(*cacheEntry[K, V]) | ||
entry.Value = value | ||
return | ||
} | ||
|
||
if len(c.entries) >= c.capacity { | ||
oldest := c.recentlyUsed.Back() | ||
c.recentlyUsed.Remove(oldest) | ||
entry := oldest.Value.(*cacheEntry[K, V]) | ||
delete(c.entries, entry.Key) | ||
} | ||
|
||
entry := &cacheEntry[K, V]{Key: key, Value: value} | ||
c.entries[key] = c.recentlyUsed.PushFront(entry) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package lru | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
type item struct { | ||
key int | ||
value string | ||
} | ||
|
||
func TestCache(t *testing.T) { | ||
items := []item{{0, "0"}, {1, "1"}, {2, "2"}, {3, "3"}, {4, "4"}} | ||
cache := New[int, string](5) | ||
|
||
// Cache is empty. | ||
for _, i := range items { | ||
if _, ok := cache.Get(i.key); ok { | ||
t.Errorf("key %d in cache", i.key) | ||
} | ||
} | ||
|
||
// Add all items to cache. | ||
for _, i := range items { | ||
cache.Add(i.key, i.value) | ||
} | ||
|
||
// Verify that all items are cached. | ||
for _, i := range items { | ||
if value, ok := cache.Get(i.key); !ok { | ||
t.Errorf("cached value for %d missing", i.key) | ||
} else if value != i.value { | ||
t.Errorf("expected %q, got %q", value, i.value) | ||
} | ||
} | ||
|
||
// Adding next item will remove the least used item (0). | ||
cache.Add(5, "5") | ||
|
||
// New item in cache. | ||
if value, ok := cache.Get(5); !ok { | ||
t.Errorf("cached value for 5 missing") | ||
} else if value != "5" { | ||
t.Errorf("expected \"5\", got %q", value) | ||
} | ||
|
||
// Removed item not in cache. | ||
if _, ok := cache.Get(0); ok { | ||
t.Error("key 0 in cache") | ||
} | ||
|
||
// Rest of items not affected. | ||
for _, i := range items[1:] { | ||
if value, ok := cache.Get(i.key); !ok { | ||
t.Errorf("cached value for %d missing", i.key) | ||
} else if value != i.value { | ||
t.Errorf("expected %q, got %q", value, i.value) | ||
} | ||
} | ||
|
||
} |