05-lru-cache: implement mutex-guarded LRU over container/list
map[string]*list.Element + doubly linked list; Get/Put move hits to the front, Put evicts from the back when at capacity, and every method (including Len) takes the mutex. Add TestLenRacesWithPut, which calls Len concurrently with a writer — the existing test only read Len after wg.Wait(), so an unsynchronized Len would never trip the race detector there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MpK9niZm4jcs3mAJbxMfi
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBasicPutGet(t *testing.T) {
|
||||
@@ -82,3 +83,42 @@ func TestConcurrentAccess(t *testing.T) {
|
||||
t.Fatalf("capacity exceeded under concurrency: %d", c.Len())
|
||||
}
|
||||
}
|
||||
|
||||
// TestLenRacesWithPut calls Len concurrently WITH writers, not after them.
|
||||
// TestConcurrentAccess only reads Len after wg.Wait(), which establishes
|
||||
// happens-before with every writer — so an unsynchronized Len can't race
|
||||
// there. The race detector reports only races that actually occur.
|
||||
func TestLenRacesWithPut(t *testing.T) {
|
||||
c := NewLRU(64)
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() { // writer
|
||||
defer wg.Done()
|
||||
for i := 0; ; i++ {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
c.Put(fmt.Sprintf("k%d", i%128), i)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() { // reader, concurrently
|
||||
defer wg.Done()
|
||||
for i := 0; i < 10000; i++ {
|
||||
if n := c.Len(); n > 64 {
|
||||
t.Errorf("Len observed %d > capacity 64 (mid-Put state leaked)", n)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() { wg.Wait() }()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user