Skip to content

Commit b8ccccd

Browse files
committed
eviction: fix Clock and CAWOLFU Evict to return correct keys before pooling; tests: add LRU, Clock, CAWOLFU eviction tests
1 parent cc65373 commit b8ccccd

5 files changed

Lines changed: 148 additions & 3 deletions

File tree

pkg/eviction/cawolfu.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ func (c *CAWOLFU) Evict() (string, bool) {
6767
if err == nil {
6868
c.length--
6969

70+
// Preserve key before resetting the node for pool reuse
71+
evictedKey := node.key
7072
resetCAWOLFUNode(node)
7173
c.nodePool.Put(node)
7274

73-
return node.key, true
75+
return evictedKey, true
7476
}
7577
// If map/list out of sync, forcibly clean up
7678
resetCAWOLFUNode(node)

pkg/eviction/cawolfu_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package eviction
2+
3+
import "testing"
4+
5+
func TestCAWOLFU_EvictsLeastFrequentTail(t *testing.T) {
6+
c, err := NewCAWOLFU(2)
7+
if err != nil {
8+
t.Fatalf("NewCAWOLFU error: %v", err)
9+
}
10+
11+
c.Set("a", 1)
12+
c.Set("b", 2)
13+
// bump 'a' so 'b' is less frequent
14+
if _, ok := c.Get("a"); !ok {
15+
t.Fatalf("expected to get 'a'")
16+
}
17+
18+
// Insert 'c' -> evict tail ('b')
19+
c.Set("c", 3)
20+
if _, ok := c.Get("b"); ok {
21+
t.Fatalf("expected 'b' to be evicted")
22+
}
23+
if _, ok := c.Get("a"); !ok {
24+
t.Fatalf("expected 'a' to remain in cache")
25+
}
26+
if v, ok := c.Get("c"); !ok || v.(int) != 3 {
27+
t.Fatalf("expected 'c'=3 in cache, got %v, ok=%v", v, ok)
28+
}
29+
}
30+
31+
func TestCAWOLFU_EvictMethodOrder(t *testing.T) {
32+
c, err := NewCAWOLFU(2)
33+
if err != nil {
34+
t.Fatalf("NewCAWOLFU error: %v", err)
35+
}
36+
37+
c.Set("a", 1)
38+
c.Set("b", 2)
39+
// Without additional access, tail is 'a' (inserted first with same count)
40+
key, ok := c.Evict()
41+
if !ok || key != "a" {
42+
t.Fatalf("expected to evict 'a' first, got %q ok=%v", key, ok)
43+
}
44+
key, ok = c.Evict()
45+
if !ok || key != "b" {
46+
t.Fatalf("expected to evict 'b' second, got %q ok=%v", key, ok)
47+
}
48+
}

pkg/eviction/clock.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ func (c *ClockAlgorithm) Evict() (string, bool) {
5757
if item.AccessCount > 0 {
5858
item.AccessCount--
5959
} else {
60-
delete(c.keys, item.Key)
60+
// Preserve key before zeroing the item back to the pool
61+
evictedKey := item.Key
62+
delete(c.keys, evictedKey)
6163
c.itemPoolManager.Put(item)
6264
c.items[c.hand] = nil
6365

64-
return item.Key, true
66+
return evictedKey, true
6567
}
6668

6769
c.hand = (c.hand + 1) % c.capacity

pkg/eviction/clock_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package eviction
2+
3+
import "testing"
4+
5+
func TestClock_EvictsWhenHandFindsColdPage(t *testing.T) {
6+
clk, err := NewClockAlgorithm(2)
7+
if err != nil {
8+
t.Fatalf("NewClockAlgorithm error: %v", err)
9+
}
10+
11+
clk.Set("a", 1)
12+
clk.Set("b", 2)
13+
14+
// Touch 'a' once to increment its AccessCount; leave 'b' cold
15+
if _, ok := clk.Get("a"); !ok {
16+
t.Fatalf("expected to get 'a'")
17+
}
18+
19+
// Eviction may require multiple passes due to access count decrements.
20+
// Loop until 'b' is evicted (within a few attempts).
21+
var (
22+
key string
23+
ok bool
24+
)
25+
for i := 0; i < 3; i++ {
26+
key, ok = clk.Evict()
27+
if ok && key == "b" {
28+
break
29+
}
30+
}
31+
if !ok || key != "b" {
32+
t.Fatalf("expected to evict 'b' first within retries, got %q ok=%v", key, ok)
33+
}
34+
35+
// Now evict the remaining 'a', possibly requiring one more pass.
36+
key, ok = clk.Evict()
37+
if !ok {
38+
key, ok = clk.Evict()
39+
}
40+
if !ok || key != "a" {
41+
t.Fatalf("expected to evict 'a' second, got %q ok=%v", key, ok)
42+
}
43+
}

pkg/eviction/lru_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package eviction
2+
3+
import "testing"
4+
5+
func TestLRU_EvictsLeastRecentlyUsedOnSet(t *testing.T) {
6+
lru, err := NewLRUAlgorithm(2)
7+
if err != nil {
8+
t.Fatalf("NewLRUAlgorithm error: %v", err)
9+
}
10+
11+
lru.Set("a", 1)
12+
lru.Set("b", 2)
13+
14+
// Access "a" so that "b" becomes the least recently used
15+
if _, ok := lru.Get("a"); !ok {
16+
t.Fatalf("expected to get 'a'")
17+
}
18+
19+
// Insert "c"; should evict "b"
20+
lru.Set("c", 3)
21+
if _, ok := lru.Get("b"); ok {
22+
t.Fatalf("expected 'b' to be evicted")
23+
}
24+
if _, ok := lru.Get("a"); !ok {
25+
t.Fatalf("expected 'a' to remain in cache")
26+
}
27+
if v, ok := lru.Get("c"); !ok || v.(int) != 3 {
28+
t.Fatalf("expected 'c'=3 in cache, got %v, ok=%v", v, ok)
29+
}
30+
}
31+
32+
func TestLRU_EvictMethodOrder(t *testing.T) {
33+
lru, err := NewLRUAlgorithm(2)
34+
if err != nil {
35+
t.Fatalf("NewLRUAlgorithm error: %v", err)
36+
}
37+
38+
lru.Set("a", 1)
39+
lru.Set("b", 2)
40+
41+
// After two inserts, tail should be "a"
42+
key, ok := lru.Evict()
43+
if !ok || key != "a" {
44+
t.Fatalf("expected to evict 'a' first, got %q ok=%v", key, ok)
45+
}
46+
key, ok = lru.Evict()
47+
if !ok || key != "b" {
48+
t.Fatalf("expected to evict 'b' second, got %q ok=%v", key, ok)
49+
}
50+
}

0 commit comments

Comments
 (0)