github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/cache/lrucache/clockpro_test.go (about) 1 // Copyright 2018. All rights reserved. Use of this source code is governed by 2 // an MIT-style license that can be found in the LICENSE file. 3 4 package lrucache 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "os" 11 "runtime" 12 "strconv" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/stretchr/testify/require" 18 "github.com/zuoyebang/bitalosdb/internal/options" 19 "golang.org/x/exp/rand" 20 ) 21 22 func testNewShards() *LruCache { 23 opts := &options.CacheOptions{ 24 Size: 100, 25 Shards: 1, 26 HashSize: 1024, 27 } 28 cache := newShards(opts) 29 return cache 30 } 31 32 func TestCache(t *testing.T) { 33 // Test data was generated from the python code 34 f, err := os.Open("testdata/cache") 35 require.NoError(t, err) 36 37 opts := &options.CacheOptions{ 38 Size: 200, 39 Shards: 1, 40 HashSize: 1024, 41 } 42 cache := newShards(opts) 43 defer cache.Unref() 44 45 scanner := bufio.NewScanner(f) 46 line := 1 47 48 for scanner.Scan() { 49 fields := bytes.Fields(scanner.Bytes()) 50 51 key, err := strconv.Atoi(string(fields[0])) 52 require.NoError(t, err) 53 54 wantHit := fields[1][0] == 'h' 55 56 var hit bool 57 h := cache.getValue(1, uint64(key)) 58 if v := h.Get(); v == nil { 59 value := cache.Alloc(1) 60 value.Buf()[0] = fields[0][0] 61 cache.setValue(1, uint64(key), value).Release() 62 } else { 63 hit = true 64 if !bytes.Equal(v, fields[0][:1]) { 65 t.Errorf("%d: cache returned bad data: got %s , want %s\n", line, v, fields[0][:1]) 66 } 67 } 68 h.Release() 69 if hit != wantHit { 70 t.Errorf("%d: cache hit mismatch: got %v, want %v\n", line, hit, wantHit) 71 } 72 line++ 73 } 74 } 75 76 func testValue(cache *LruCache, s string, repeat int) *Value { 77 b := bytes.Repeat([]byte(s), repeat) 78 v := cache.Alloc(len(b)) 79 copy(v.Buf(), b) 80 return v 81 } 82 83 func TestCacheDelete(t *testing.T) { 84 cache := testNewShards() 85 defer cache.Unref() 86 87 cache.setValue(1, 0, testValue(cache, "a", 5)).Release() 88 cache.setValue(1, 1, testValue(cache, "a", 5)).Release() 89 cache.setValue(1, 2, testValue(cache, "a", 5)).Release() 90 if expected, size := int64(15), cache.Size(); expected != size { 91 t.Fatalf("expected cache size %d, but found %d", expected, size) 92 } 93 cache.del(1, 1) 94 if expected, size := int64(10), cache.Size(); expected != size { 95 t.Fatalf("expected cache size %d, but found %d", expected, size) 96 } 97 if h := cache.getValue(1, 0); h.Get() == nil { 98 t.Fatalf("expected to find block 0/0") 99 } else { 100 h.Release() 101 } 102 if h := cache.getValue(1, 1); h.Get() != nil { 103 t.Fatalf("expected to not find block 1/0") 104 } else { 105 h.Release() 106 } 107 // Deleting a non-existing block does nothing. 108 cache.del(1, 1) 109 if expected, size := int64(10), cache.Size(); expected != size { 110 t.Fatalf("expected cache size %d, but found %d", expected, size) 111 } 112 } 113 114 func TestEvictAll(t *testing.T) { 115 // Verify that it is okay to evict all of the data from a cache. Previously 116 // this would trigger a nil-pointer dereference. 117 cache := testNewShards() 118 defer cache.Unref() 119 120 cache.setValue(1, 0, testValue(cache, "a", 101)).Release() 121 cache.setValue(1, 1, testValue(cache, "a", 101)).Release() 122 } 123 124 func TestMultipleDBs(t *testing.T) { 125 cache := testNewShards() 126 defer cache.Unref() 127 128 cache.setValue(1, 0, testValue(cache, "a", 5)).Release() 129 cache.setValue(2, 0, testValue(cache, "b", 5)).Release() 130 if expected, size := int64(10), cache.Size(); expected != size { 131 t.Fatalf("expected cache size %d, but found %d", expected, size) 132 } 133 h := cache.getValue(1, 0) 134 if v := h.Get(); string(v) != "aaaaa" { 135 t.Fatalf("expected aaaaa, but found %s", v) 136 } 137 h = cache.getValue(2, 0) 138 if v := h.Get(); string(v) != "bbbbb" { 139 t.Fatalf("expected bbbbb, but found %s", v) 140 } else { 141 h.Release() 142 } 143 } 144 145 func TestZeroSize(t *testing.T) { 146 opts := &options.CacheOptions{ 147 Size: 0, 148 Shards: 1, 149 HashSize: 1024, 150 } 151 cache := newShards(opts) 152 defer cache.Unref() 153 154 cache.setValue(1, 0, testValue(cache, "a", 5)).Release() 155 } 156 157 func TestReserve(t *testing.T) { 158 opts := &options.CacheOptions{ 159 Size: 4, 160 Shards: 2, 161 HashSize: 1024, 162 } 163 cache := newShards(opts) 164 defer cache.Unref() 165 166 cache.setValue(1, 1, testValue(cache, "a", 1)).Release() 167 cache.setValue(1, 2, testValue(cache, "a", 1)).Release() 168 require.EqualValues(t, 2, cache.Size()) 169 r := cache.Reserve(1) 170 require.EqualValues(t, 0, cache.Size()) 171 cache.setValue(1, 1, testValue(cache, "a", 1)).Release() 172 cache.setValue(1, 2, testValue(cache, "a", 1)).Release() 173 cache.setValue(1, 3, testValue(cache, "a", 1)).Release() 174 cache.setValue(1, 4, testValue(cache, "a", 1)).Release() 175 require.EqualValues(t, 2, cache.Size()) 176 r() 177 require.EqualValues(t, 2, cache.Size()) 178 cache.setValue(1, 1, testValue(cache, "a", 1)).Release() 179 cache.setValue(1, 2, testValue(cache, "a", 1)).Release() 180 require.EqualValues(t, 4, cache.Size()) 181 } 182 183 func TestReserveDoubleRelease(t *testing.T) { 184 cache := testNewShards() 185 defer cache.Unref() 186 187 r := cache.Reserve(10) 188 r() 189 190 result := func() (result string) { 191 defer func() { 192 if v := recover(); v != nil { 193 result = fmt.Sprint(v) 194 } 195 }() 196 r() 197 return "" 198 }() 199 const expected = "cache: cache reservation already released" 200 if expected != result { 201 t.Fatalf("expected %q, but found %q", expected, result) 202 } 203 } 204 205 func TestCacheStressSetExisting(t *testing.T) { 206 opts := &options.CacheOptions{ 207 Size: 1, 208 Shards: 1, 209 HashSize: 1024, 210 } 211 cache := newShards(opts) 212 defer cache.Unref() 213 214 var wg sync.WaitGroup 215 for i := 0; i < 10; i++ { 216 wg.Add(1) 217 go func(i int) { 218 defer wg.Done() 219 for j := 0; j < 10000; j++ { 220 cache.setValue(1, uint64(i), testValue(cache, "a", 1)).Release() 221 runtime.Gosched() 222 } 223 }(i) 224 } 225 wg.Wait() 226 } 227 228 func TestCacheSetGet(t *testing.T) { 229 const size = 100000 230 231 opts := &options.CacheOptions{ 232 Size: size, 233 Shards: 1, 234 HashSize: 1024, 235 } 236 cache := newShards(opts) 237 defer cache.Unref() 238 239 for i := 0; i < size; i++ { 240 v := testValue(cache, "a", 1) 241 cache.setValue(1, uint64(i), v).Release() 242 } 243 244 for i := 0; i < size; i++ { 245 h := cache.getValue(1, uint64(i)) 246 if h.Get() == nil { 247 t.Fatalf("failed to lookup value key=%d", i) 248 } 249 h.Release() 250 } 251 } 252 253 func TestCacheSetNil(t *testing.T) { 254 const size = 100000 255 256 opts := &options.CacheOptions{ 257 Size: size, 258 Shards: 1, 259 HashSize: 1024, 260 } 261 262 cache := NewLrucache(opts) 263 defer cache.Unref() 264 265 k := []byte("test1") 266 267 require.NoError(t, cache.Set(k, nil, 0)) 268 _, _, found := cache.Get(k, 0) 269 require.Equal(t, false, found) 270 } 271 272 func BenchmarkCacheGet(b *testing.B) { 273 const size = 100000 274 275 opts := &options.CacheOptions{ 276 Size: size, 277 Shards: 1, 278 HashSize: 1024, 279 } 280 cache := newShards(opts) 281 defer cache.Unref() 282 283 for i := 0; i < size; i++ { 284 v := testValue(cache, "a", 1) 285 cache.setValue(1, uint64(i), v).Release() 286 } 287 288 b.ResetTimer() 289 b.RunParallel(func(pb *testing.PB) { 290 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 291 292 for pb.Next() { 293 h := cache.getValue(1, uint64(rng.Intn(size))) 294 if h.Get() == nil { 295 b.Fatal("failed to lookup value") 296 } 297 h.Release() 298 } 299 }) 300 } 301 302 func TestReserveColdTarget(t *testing.T) { 303 // If coldTarget isn't updated when we call shard.Reserve, 304 // then we unnecessarily remove nodes from the 305 // cache. 306 cache := testNewShards() 307 defer cache.Unref() 308 309 for i := 0; i < 50; i++ { 310 cache.setValue(uint64(i+1), 0, testValue(cache, "a", 1)).Release() 311 } 312 313 if cache.Size() != 50 { 314 require.Equal(t, 50, cache.Size(), "nodes were unnecessarily evicted from the cache") 315 } 316 317 // There won't be enough space left for 50 nodes in the cache after 318 // we call shard.Reserve. This should trigger a call to evict. 319 cache.Reserve(51) 320 321 // If we don't update coldTarget in Reserve then the cache gets emptied to 322 // size 0. In shard.Evict, we loop until shard.Size() < shard.targetSize(). 323 // Therefore, 100 - 51 = 49, but we evict one more node. 324 if cache.Size() != 48 { 325 t.Fatalf("expected positive cache size %d, but found %d", 48, cache.Size()) 326 } 327 }