-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add tests to run the lru implementation concurrently to ensure the appropriate locking mechanism are used
- Loading branch information
1 parent
3ebe5d9
commit 222358b
Showing
6 changed files
with
158 additions
and
7 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
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 |
---|---|---|
@@ -1,2 +1,4 @@ | ||
# zwis | ||
# Zwis | ||
An in-memory cache system in Go that supports expirable cache entries and various cache eviction policies including Least Frequently Used (LFU), Least Recently Used (LRU), and Adaptive Replacement Cache (ARC). | ||
|
||
[![zwis library workflow](https://github.com/NonsoAmadi10/zwis/actions/workflows/main.yaml/badge.svg)](https://github.com/NonsoAmadi10/zwis/actions/workflows/main.yaml) |
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,48 @@ | ||
package zwis_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/NonsoAmadi10/zwis/zwis" | ||
) | ||
|
||
func TestLRUCacheConcurrency(t *testing.T) { | ||
cache := zwis.NewLRUCache(100) | ||
ctx := context.Background() | ||
|
||
var wg sync.WaitGroup | ||
wg.Add(2) | ||
|
||
// Concurrent writes | ||
go func() { | ||
defer wg.Done() | ||
for i := 0; i < 1000; i++ { | ||
cache.Set(ctx, fmt.Sprintf("key%d", i), i, 0) | ||
} | ||
}() | ||
|
||
// Concurrent reads | ||
go func() { | ||
defer wg.Done() | ||
for i := 0; i < 1000; i++ { | ||
cache.Get(ctx, fmt.Sprintf("key%d", i)) | ||
} | ||
}() | ||
|
||
// Wait for both goroutines to finish | ||
wg.Wait() | ||
|
||
// Additional verification | ||
for i := 900; i < 1000; i++ { | ||
key := fmt.Sprintf("key%d", i) | ||
value, ok := cache.Get(ctx, key) | ||
if !ok { | ||
t.Errorf("Expected key %s to be in cache, but it wasn't", key) | ||
} else if value != i { | ||
t.Errorf("Expected value for key %s to be %d, but got %v", key, i, value) | ||
} | ||
} | ||
} |
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 |
---|---|---|
@@ -1 +1,102 @@ | ||
package zwis | ||
|
||
import ( | ||
"container/list" | ||
"context" | ||
"sync" | ||
"time" | ||
) | ||
|
||
type LRUCache struct { | ||
capacity int | ||
cache map[interface{}]*list.Element | ||
list *list.List | ||
mutex sync.RWMutex | ||
} | ||
|
||
type entry struct { | ||
key interface{} | ||
value interface{} | ||
expiration time.Time | ||
} | ||
|
||
func NewLRUCache(capacity int) *LRUCache { | ||
return &LRUCache{ | ||
capacity: capacity, | ||
cache: make(map[interface{}]*list.Element), | ||
list: list.New(), | ||
} | ||
} | ||
|
||
func (lru *LRUCache) Get(ctx context.Context, key interface{}) (interface{}, bool) { | ||
lru.mutex.RLock() | ||
elem, ok := lru.cache[key] | ||
lru.mutex.RUnlock() | ||
|
||
if !ok { | ||
return nil, false | ||
} | ||
|
||
lru.mutex.Lock() | ||
defer lru.mutex.Unlock() | ||
|
||
entry := elem.Value.(*entry) | ||
if !entry.expiration.IsZero() && entry.expiration.Before(time.Now()) { | ||
lru.removeElement(elem) | ||
return nil, false | ||
} | ||
|
||
lru.list.MoveToFront(elem) | ||
return entry.value, true | ||
} | ||
|
||
func (lru *LRUCache) Set(ctx context.Context, key, value interface{}, ttl time.Duration) { | ||
lru.mutex.Lock() | ||
defer lru.mutex.Unlock() | ||
|
||
var expiration time.Time | ||
if ttl > 0 { | ||
expiration = time.Now().Add(ttl) | ||
} | ||
|
||
if elem, ok := lru.cache[key]; ok { | ||
lru.list.MoveToFront(elem) | ||
elem.Value.(*entry).value = value | ||
elem.Value.(*entry).expiration = expiration | ||
} else { | ||
if lru.list.Len() >= lru.capacity { | ||
lru.removeOldest() | ||
} | ||
elem := lru.list.PushFront(&entry{key, value, expiration}) | ||
lru.cache[key] = elem | ||
} | ||
} | ||
|
||
func (lru *LRUCache) Delete(ctx context.Context, key interface{}) { | ||
lru.mutex.Lock() | ||
defer lru.mutex.Unlock() | ||
|
||
if elem, ok := lru.cache[key]; ok { | ||
lru.removeElement(elem) | ||
} | ||
} | ||
|
||
func (lru *LRUCache) Clear(ctx context.Context) { | ||
lru.mutex.Lock() | ||
defer lru.mutex.Unlock() | ||
|
||
lru.list.Init() | ||
lru.cache = make(map[interface{}]*list.Element) | ||
} | ||
|
||
func (lru *LRUCache) removeOldest() { | ||
oldest := lru.list.Back() | ||
if oldest != nil { | ||
lru.removeElement(oldest) | ||
} | ||
} | ||
|
||
func (lru *LRUCache) removeElement(elem *list.Element) { | ||
lru.list.Remove(elem) | ||
delete(lru.cache, elem.Value.(*entry).key) | ||
} |
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
File renamed without changes.