Skip to content

Commit 7faefab

Browse files
committed
fix nil interface panic
1 parent 4f8f972 commit 7faefab

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

internal/blockstm/storage.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,20 @@ type GCachedStorage[V any] struct {
4545
cache sync.Map
4646
}
4747

48+
// cacheEntry boxes V so a cached zero-value any never reaches sync.Map as a nil interface.
49+
type cacheEntry[V any] struct{ v V }
50+
4851
func NewGCachedStorage[V any](storage GStorage[V]) *GCachedStorage[V] {
4952
return &GCachedStorage[V]{GStorage: storage}
5053
}
5154

5255
func (s *GCachedStorage[V]) Get(key []byte) V {
53-
if v, ok := s.cache.Load(string(key)); ok {
54-
return v.(V)
56+
if e, ok := s.cache.Load(string(key)); ok {
57+
return e.(cacheEntry[V]).v
5558
}
5659

5760
v := s.GStorage.Get(key)
58-
s.cache.Store(string(key), v)
61+
s.cache.Store(string(key), cacheEntry[V]{v: v})
5962
return v
6063
}
6164

internal/blockstm/storage_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package blockstm
2+
3+
import (
4+
"sync/atomic"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestGCachedStorage covers hit/miss memoization for both V=[]byte and V=any,
11+
// the ObjKV case is a regression guard against the nil-interface assertion panic.
12+
func TestGCachedStorage(t *testing.T) {
13+
t.Run("KV", func(t *testing.T) {
14+
parent := NewMemDB()
15+
parent.Set([]byte("k"), []byte("v"))
16+
assertCache(t, parent, []byte("v"), nil)
17+
})
18+
19+
t.Run("ObjKV", func(t *testing.T) {
20+
parent := NewObjMemDB()
21+
parent.Set([]byte("k"), "v")
22+
assertCache(t, parent, "v", nil)
23+
})
24+
}
25+
26+
func assertCache[V any](t *testing.T, parent GStorage[V], hitValue, missValue V) {
27+
t.Helper()
28+
counted := &countingStorage[V]{GStorage: parent}
29+
cached := NewGCachedStorage(counted)
30+
31+
for i := 0; i < 3; i++ {
32+
require.Equal(t, hitValue, cached.Get([]byte("k")))
33+
require.Equal(t, missValue, cached.Get([]byte("missing")))
34+
}
35+
require.EqualValues(t, 2, counted.gets.Load(), "each distinct key reads parent exactly once")
36+
}
37+
38+
type countingStorage[V any] struct {
39+
GStorage[V]
40+
gets atomic.Int64
41+
}
42+
43+
func (c *countingStorage[V]) Get(key []byte) V {
44+
c.gets.Add(1)
45+
return c.GStorage.Get(key)
46+
}

0 commit comments

Comments
 (0)