Skip to content

Commit

Permalink
Add PeekAll method (#58)
Browse files Browse the repository at this point in the history
* Add PeekAll method

* Fix comments
  • Loading branch information
erni27 authored Jan 16, 2024
1 parent 9ae8586 commit 20ba4c2
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 3 deletions.
55 changes: 55 additions & 0 deletions imcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,32 @@ func (c *Cache[K, V]) peekMultiple(now time.Time, keys ...K) map[K]V {
return got
}

// PeekAll returns a copy of all entries in the cache without
// actively evicting the encountered entry if it is expired and
// updating the entry's sliding expiration.
//
// If the max entries limit is set, it doesn't update
// the encountered entry's position in the eviction queue.
func (c *Cache[K, V]) PeekAll() map[K]V {
return c.peekAll(time.Now())
}

func (c *Cache[K, V]) peekAll(now time.Time) map[K]V {
c.mu.RLock()
defer c.mu.RUnlock()
if c.closed {
return nil
}
got := make(map[K]V, len(c.m))
for key, node := range c.m {
if node.entry().expired(now) {
continue
}
got[key] = node.entry().val
}
return got
}

// Set sets the value for the given key.
// If the entry already exists, it is replaced.
//
Expand Down Expand Up @@ -966,6 +992,35 @@ func (s *Sharded[K, V]) PeekMultiple(keys ...K) map[K]V {
return result
}

// PeekAll returns a copy of all entries in the cache without
// actively evicting the encountered entry if it is expired and
// updating the entry's sliding expiration.
//
// If the max entries limit is set, it doesn't update
// the encountered entry's position in the eviction queue.
func (s *Sharded[K, V]) PeekAll() map[K]V {
now := time.Now()
var n int
ms := make([]map[K]V, 0, len(s.shards))
for _, shard := range s.shards {
m := shard.peekAll(now)
// If Cache.peekAll returns nil, it means that the shard is closed
// hence Sharded is closed too.
if m == nil {
return nil
}
n += len(m)
ms = append(ms, m)
}
all := make(map[K]V, n)
for _, m := range ms {
for key, val := range m {
all[key] = val
}
}
return all
}

// Set sets the value for the given key.
// If the entry already exists, it is replaced.
//
Expand Down
83 changes: 80 additions & 3 deletions imcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type imcache[K comparable, V any] interface {
GetAll() map[K]V
Peek(key K) (v V, present bool)
PeekMultiple(keys ...K) map[K]V
PeekAll() map[K]V
Set(key K, val V, exp Expiration)
GetOrSet(key K, val V, exp Expiration) (v V, present bool)
Replace(key K, val V, exp Expiration) (present bool)
Expand Down Expand Up @@ -346,6 +347,48 @@ func TestImcache_PeekMultiple_SlidingExpiration(t *testing.T) {
}
}

func TestImcache_PeekAll(t *testing.T) {
for _, cache := range caches {
t.Run(cache.name, func(t *testing.T) {
c := cache.create()
c.Set("foo", "foo", WithNoExpiration())
c.Set("foobar", "foobar", WithNoExpiration())
c.Set("barfoo", "barfoo", WithNoExpiration())
c.Set("bar", "bar", WithExpiration(time.Nanosecond))
time.Sleep(time.Nanosecond)
want := map[string]string{
"foo": "foo",
"foobar": "foobar",
"barfoo": "barfoo",
}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Errorf("got imcache.PeekAll() = %v, want %v", got, want)
}
})
}
}

func TestImcache_PeekAll_SlidingExpiration(t *testing.T) {
for _, cache := range caches {
t.Run(cache.name, func(t *testing.T) {
c := cache.create()
c.Set("foo", "foo", WithSlidingExpiration(500*time.Millisecond))
c.Set("bar", "bar", WithSlidingExpiration(400*time.Millisecond))
c.Set("foobar", "foobar", WithNoExpiration())
time.Sleep(300 * time.Millisecond)
want := map[string]string{"foo": "foo", "bar": "bar", "foobar": "foobar"}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Fatalf("got imcache.PeekAll() = %v, want %v", got, want)
}
time.Sleep(300 * time.Millisecond)
want = map[string]string{"foobar": "foobar"}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Fatalf("got imcache.PeekAll() = %v, want %v", got, want)
}
})
}
}

func TestImcache_Set(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -1159,6 +1202,9 @@ func TestImcache_Close(t *testing.T) {
if got := c.PeekMultiple("foo", "bar"); got != nil {
t.Errorf("imcache.PeekMultiple(_) = %v, want %v", got, nil)
}
if got := c.PeekAll(); got != nil {
t.Errorf("imcache.PeekAll() = %v, want %v", got, nil)
}
v, ok := c.GetOrSet("foo", "bar", WithNoExpiration())
if ok {
t.Error("imcache.GetOrSet(_, _, _) = _, true, want _, false")
Expand Down Expand Up @@ -1296,6 +1342,27 @@ func TestImcache_PeekMultiple_EvictionCallback(t *testing.T) {
}
}

func TestImcache_PeekAll_EvictionCallback(t *testing.T) {
evictioncMock := &evictionCallbackMock[string, string]{}
for _, cache := range cachesWithEvictionCallback {
t.Run(cache.name, func(t *testing.T) {
defer evictioncMock.Reset()
c := cache.create(evictioncMock.Callback)
c.Set("foo", "foo", WithExpiration(time.Nanosecond))
c.Set("bar", "bar", WithExpiration(time.Nanosecond))
c.Set("foobar", "foobar", WithNoExpiration())
time.Sleep(time.Nanosecond)
want := map[string]string{"foobar": "foobar"}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Fatalf("got imcache.PeekAll() = %v, want %v", got, want)
}
evictioncMock.HasNotBeenCalledWith(t, "foo", "foo", EvictionReasonExpired)
evictioncMock.HasNotBeenCalledWith(t, "bar", "bar", EvictionReasonExpired)
evictioncMock.HasEventuallyBeenCalledTimes(t, 0)
})
}
}

func TestImcache_Set_EvictionCallback(t *testing.T) {
evictioncMock := &evictionCallbackMock[string, string]{}
for _, cache := range cachesWithEvictionCallback {
Expand Down Expand Up @@ -1631,6 +1698,11 @@ func TestCache_MaxEntriesLimit_EvictionPolicyLRU(t *testing.T) {
if _, ok := c.Get("nine"); !ok {
t.Fatal("got Cache.Get(_) = _, false, want _, true")
}
// PeekAll should not change the LRU queue.
want := map[string]int{"eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Fatalf("got Cache.PeekAll() = %v, want %v", got, want)
}
// LRU queue: nine -> twelve -> eleven -> eight -> ten.
c.Set("thirteen", 13, WithNoExpiration())
// LRU queue: thirteen -> nine -> twelve -> eleven -> eight.
Expand All @@ -1649,7 +1721,7 @@ func TestCache_MaxEntriesLimit_EvictionPolicyLRU(t *testing.T) {
t.Fatal("got Cache.Get(_) = _, false, want _, true")
}
// PeekMultiple shouldn't move the entires to the front of the queue.
want := map[string]int{"fourteen": 14, "fifteen": 15}
want = map[string]int{"fourteen": 14, "fifteen": 15}
if got := c.PeekMultiple("fourteen", "fifteen"); !reflect.DeepEqual(got, want) {
t.Fatalf("got Cache.PeekMultiple(_) = %v, want %v", got, want)
}
Expand Down Expand Up @@ -1827,7 +1899,7 @@ func TestCache_MaxEntriesLimit_EvictionPolicyLFU(t *testing.T) {
// LFU queue: six -> five -> four -> three -> two.
evicted(t, "one", 1, EvictionReasonMaxEntriesExceeded)

// Get should update the entries frequency.
// Get should update the entry frequency.
if _, ok := c.Get("two"); !ok {
t.Fatal("got Cache.Get(_) = _, false, want _, true")
}
Expand Down Expand Up @@ -1864,7 +1936,7 @@ func TestCache_MaxEntriesLimit_EvictionPolicyLFU(t *testing.T) {
// Replace should update the frequency of the entry if the entry already exists.
c.Set("eight", 8, WithNoExpiration())
// LFU queue: two -> eight -> six -> five -> four.
// PeekMultiple shouldn't update the entries frequency.
// PeekMultiple shouldn't update the frequencies.
want := map[string]int{"four": 4, "five": 5}
if got := c.PeekMultiple("four", "five"); !reflect.DeepEqual(got, want) {
t.Fatalf("got Cache.PeekMultiple(_) = %v, want %v", got, want)
Expand Down Expand Up @@ -1921,6 +1993,11 @@ func TestCache_MaxEntriesLimit_EvictionPolicyLFU(t *testing.T) {
c.GetAll()
// LFU queue: eighteen -> sixteen -> fifteen.
c.Set("twenty", 20, WithNoExpiration())
// PeekAll shouldn't update the entries frequency.
want = map[string]int{"fifteen": 15, "sixteen": 16, "eighteen": 18, "twenty": 20}
if got := c.PeekAll(); !reflect.DeepEqual(got, want) {
t.Fatalf("got Cache.PeekAll() = %v, want %v", got, want)
}
// LFU queue: eighteen -> sixteen -> fifteen -> twenty.
c.Set("twentyone", 21, WithNoExpiration())
// LFU queue: eighteen -> sixteen -> fifteen -> twentyone -> twenty.
Expand Down

0 comments on commit 20ba4c2

Please sign in to comment.