diff --git a/fastcache.go b/fastcache.go index 9358a2b..c977bf4 100644 --- a/fastcache.go +++ b/fastcache.go @@ -393,9 +393,12 @@ func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) { goto end } chunk := chunks[chunkIdx] + if chunk == nil { + atomic.AddUint64(&b.corruptions, 1) + goto end + } idx %= chunkSize if idx+4 >= chunkSize { - // Corrupted data during the load from file. Just skip it. atomic.AddUint64(&b.corruptions, 1) goto end } @@ -404,7 +407,6 @@ func (b *bucket) Get(dst, k []byte, h uint64, returnDst bool) ([]byte, bool) { valLen := (uint64(kvLenBuf[2]) << 8) | uint64(kvLenBuf[3]) idx += 4 if idx+keyLen+valLen >= chunkSize { - // Corrupted data during the load from file. Just skip it. atomic.AddUint64(&b.corruptions, 1) goto end } diff --git a/fastcache_test.go b/fastcache_test.go index 24dce7e..ea5c3cb 100644 --- a/fastcache_test.go +++ b/fastcache_test.go @@ -6,6 +6,8 @@ import ( "sync" "testing" "time" + + xxhash "github.com/cespare/xxhash/v2" ) func TestCacheSmall(t *testing.T) { @@ -222,6 +224,38 @@ func testCacheGetSet(c *Cache, itemsCount int) error { return nil } +func TestBucketGetNilChunkNoCorruptionPanic(t *testing.T) { + var b bucket + b.Init(64 * 1024) + + k := []byte("testkey") + v := []byte("testvalue") + h := xxhash.Sum64(k) + b.Set(k, v, h) + + got, found := b.Get(nil, k, h, true) + if !found || string(got) != "testvalue" { + t.Fatalf("expected to find key before corruption; found=%v val=%q", found, got) + } + + b.mu.Lock() + mapVal := b.m[h] + chunkIdx := (mapVal & ((1 << bucketSizeBits) - 1)) / chunkSize + b.chunks[chunkIdx] = nil + b.mu.Unlock() + + got, found = b.Get(nil, k, h, true) + if found { + t.Fatalf("expected miss for nil chunk, but got found=true val=%q", got) + } + + var s Stats + b.UpdateStats(&s) + if s.Corruptions != 1 { + t.Fatalf("expected 1 corruption; got %d", s.Corruptions) + } +} + func TestCacheResetUpdateStatsSetConcurrent(t *testing.T) { c := New(12334)