github.com/bhojpur/cache@v0.0.4/pkg/engine/ristretto/cache_test.go (about) 1 package ristretto 2 3 // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved. 4 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 import ( 24 "fmt" 25 "math/rand" 26 "strconv" 27 "strings" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/stretchr/testify/require" 33 ) 34 35 var wait = time.Millisecond * 10 36 37 func TestCacheKeyToHash(t *testing.T) { 38 keyToHashCount := 0 39 c, err := NewCache(&Config{ 40 NumCounters: 10, 41 MaxCost: 1000, 42 BufferItems: 64, 43 IgnoreInternalCost: true, 44 KeyToHash: func(key string) (uint64, uint64) { 45 keyToHashCount++ 46 return defaultStringHash(key) 47 }, 48 }) 49 require.NoError(t, err) 50 if c.SetWithCost("1", 1, 1) { 51 time.Sleep(wait) 52 val, ok := c.Get("1") 53 require.True(t, ok) 54 require.NotNil(t, val) 55 c.Delete("1") 56 } 57 require.Equal(t, 3, keyToHashCount) 58 } 59 60 func TestCacheMaxCost(t *testing.T) { 61 charset := "abcdefghijklmnopqrstuvwxyz0123456789" 62 key := func() string { 63 k := make([]byte, 2) 64 for i := range k { 65 k[i] = charset[rand.Intn(len(charset))] 66 } 67 return string(k) 68 } 69 c, err := NewCache(&Config{ 70 NumCounters: 12960, // 36^2 * 10 71 MaxCost: 1e6, // 1mb 72 BufferItems: 64, 73 Metrics: true, 74 }) 75 require.NoError(t, err) 76 stop := make(chan struct{}, 8) 77 for i := 0; i < 8; i++ { 78 go func() { 79 for { 80 select { 81 case <-stop: 82 return 83 default: 84 time.Sleep(time.Millisecond) 85 86 k := key() 87 if _, ok := c.Get(k); !ok { 88 val := "" 89 if rand.Intn(100) < 10 { 90 val = "test" 91 } else { 92 val = strings.Repeat("a", 1000) 93 } 94 c.SetWithCost(key(), val, int64(2+len(val))) 95 } 96 } 97 } 98 }() 99 } 100 for i := 0; i < 20; i++ { 101 time.Sleep(time.Second) 102 cacheCost := c.Metrics.CostAdded() - c.Metrics.CostEvicted() 103 t.Logf("total cache cost: %d\n", cacheCost) 104 require.True(t, float64(cacheCost) <= float64(1e6*1.05)) 105 } 106 for i := 0; i < 8; i++ { 107 stop <- struct{}{} 108 } 109 } 110 111 func TestUpdateMaxCost(t *testing.T) { 112 c, err := NewCache(&Config{ 113 NumCounters: 10, 114 MaxCost: 10, 115 BufferItems: 64, 116 }) 117 require.NoError(t, err) 118 require.Equal(t, int64(10), c.MaxCapacity()) 119 require.True(t, c.SetWithCost("1", 1, 1)) 120 time.Sleep(wait) 121 _, ok := c.Get("1") 122 // Set is rejected because the cost of the entry is too high 123 // when accounting for the internal cost of storing the entry. 124 require.False(t, ok) 125 126 // Update the max cost of the cache and retry. 127 c.SetCapacity(1000) 128 require.Equal(t, int64(1000), c.MaxCapacity()) 129 require.True(t, c.SetWithCost("1", 1, 1)) 130 time.Sleep(wait) 131 val, ok := c.Get("1") 132 require.True(t, ok) 133 require.NotNil(t, val) 134 c.Delete("1") 135 } 136 137 func TestNewCache(t *testing.T) { 138 _, err := NewCache(&Config{ 139 NumCounters: 0, 140 }) 141 require.Error(t, err) 142 143 _, err = NewCache(&Config{ 144 NumCounters: 100, 145 MaxCost: 0, 146 }) 147 require.Error(t, err) 148 149 _, err = NewCache(&Config{ 150 NumCounters: 100, 151 MaxCost: 10, 152 BufferItems: 0, 153 }) 154 require.Error(t, err) 155 156 c, err := NewCache(&Config{ 157 NumCounters: 100, 158 MaxCost: 10, 159 BufferItems: 64, 160 Metrics: true, 161 }) 162 require.NoError(t, err) 163 require.NotNil(t, c) 164 } 165 166 func TestNilCache(t *testing.T) { 167 var c *Cache 168 val, ok := c.Get("1") 169 require.False(t, ok) 170 require.Nil(t, val) 171 172 require.False(t, c.SetWithCost("1", 1, 1)) 173 c.Delete("1") 174 c.Clear() 175 c.Close() 176 } 177 178 func TestMultipleClose(t *testing.T) { 179 var c *Cache 180 c.Close() 181 182 var err error 183 c, err = NewCache(&Config{ 184 NumCounters: 100, 185 MaxCost: 10, 186 BufferItems: 64, 187 Metrics: true, 188 }) 189 require.NoError(t, err) 190 c.Close() 191 c.Close() 192 } 193 194 func TestSetAfterClose(t *testing.T) { 195 c, err := newTestCache() 196 require.NoError(t, err) 197 require.NotNil(t, c) 198 199 c.Close() 200 require.False(t, c.SetWithCost("1", 1, 1)) 201 } 202 203 func TestClearAfterClose(t *testing.T) { 204 c, err := newTestCache() 205 require.NoError(t, err) 206 require.NotNil(t, c) 207 208 c.Close() 209 c.Clear() 210 } 211 212 func TestGetAfterClose(t *testing.T) { 213 c, err := newTestCache() 214 require.NoError(t, err) 215 require.NotNil(t, c) 216 217 require.True(t, c.SetWithCost("1", 1, 1)) 218 c.Close() 219 220 _, ok := c.Get("2") 221 require.False(t, ok) 222 } 223 224 func TestDelAfterClose(t *testing.T) { 225 c, err := newTestCache() 226 require.NoError(t, err) 227 require.NotNil(t, c) 228 229 require.True(t, c.SetWithCost("1", 1, 1)) 230 c.Close() 231 232 c.Delete("1") 233 } 234 235 func TestCacheProcessItems(t *testing.T) { 236 m := &sync.Mutex{} 237 evicted := make(map[uint64]struct{}) 238 c, err := NewCache(&Config{ 239 NumCounters: 100, 240 MaxCost: 10, 241 BufferItems: 64, 242 IgnoreInternalCost: true, 243 Cost: func(value interface{}) int64 { 244 return int64(value.(int)) 245 }, 246 OnEvict: func(item *Item) { 247 m.Lock() 248 defer m.Unlock() 249 evicted[item.Key] = struct{}{} 250 }, 251 }) 252 require.NoError(t, err) 253 254 var key uint64 255 var conflict uint64 256 257 key, conflict = defaultStringHash("1") 258 c.setBuf <- &Item{ 259 flag: itemNew, 260 Key: key, 261 Conflict: conflict, 262 Value: 1, 263 Cost: 0, 264 } 265 time.Sleep(wait) 266 require.True(t, c.policy.Has(key)) 267 require.Equal(t, int64(1), c.policy.Cost(key)) 268 269 key, conflict = defaultStringHash("1") 270 c.setBuf <- &Item{ 271 flag: itemUpdate, 272 Key: key, 273 Conflict: conflict, 274 Value: 2, 275 Cost: 0, 276 } 277 time.Sleep(wait) 278 require.Equal(t, int64(2), c.policy.Cost(key)) 279 280 key, conflict = defaultStringHash("1") 281 c.setBuf <- &Item{ 282 flag: itemDelete, 283 Key: key, 284 Conflict: conflict, 285 } 286 time.Sleep(wait) 287 key, conflict = defaultStringHash("1") 288 val, ok := c.store.Get(key, conflict) 289 require.False(t, ok) 290 require.Nil(t, val) 291 require.False(t, c.policy.Has(1)) 292 293 key, conflict = defaultStringHash("2") 294 c.setBuf <- &Item{ 295 flag: itemNew, 296 Key: key, 297 Conflict: conflict, 298 Value: 2, 299 Cost: 3, 300 } 301 key, conflict = defaultStringHash("3") 302 c.setBuf <- &Item{ 303 flag: itemNew, 304 Key: key, 305 Conflict: conflict, 306 Value: 3, 307 Cost: 3, 308 } 309 key, conflict = defaultStringHash("4") 310 c.setBuf <- &Item{ 311 flag: itemNew, 312 Key: key, 313 Conflict: conflict, 314 Value: 3, 315 Cost: 3, 316 } 317 key, conflict = defaultStringHash("5") 318 c.setBuf <- &Item{ 319 flag: itemNew, 320 Key: key, 321 Conflict: conflict, 322 Value: 3, 323 Cost: 5, 324 } 325 time.Sleep(wait) 326 m.Lock() 327 require.NotEqual(t, 0, len(evicted)) 328 m.Unlock() 329 330 defer func() { 331 require.NotNil(t, recover()) 332 }() 333 c.Close() 334 c.setBuf <- &Item{flag: itemNew} 335 } 336 337 func TestCacheGet(t *testing.T) { 338 c, err := NewCache(&Config{ 339 NumCounters: 100, 340 MaxCost: 10, 341 BufferItems: 64, 342 IgnoreInternalCost: true, 343 Metrics: true, 344 }) 345 require.NoError(t, err) 346 347 key, conflict := defaultStringHash("1") 348 i := Item{ 349 Key: key, 350 Conflict: conflict, 351 Value: 1, 352 } 353 c.store.Set(&i) 354 val, ok := c.Get("1") 355 require.True(t, ok) 356 require.NotNil(t, val) 357 358 val, ok = c.Get("2") 359 require.False(t, ok) 360 require.Nil(t, val) 361 362 // 0.5 and not 1.0 because we tried Getting each item twice 363 require.Equal(t, 0.5, c.Metrics.Ratio()) 364 365 c = nil 366 val, ok = c.Get("0") 367 require.False(t, ok) 368 require.Nil(t, val) 369 } 370 371 // retrySet calls SetWithCost until the item is accepted by the cache. 372 func retrySet(t *testing.T, c *Cache, key string, value int, cost int64) { 373 for { 374 if set := c.SetWithCost(key, value, cost); !set { 375 time.Sleep(wait) 376 continue 377 } 378 379 time.Sleep(wait) 380 val, ok := c.Get(key) 381 require.True(t, ok) 382 require.NotNil(t, val) 383 require.Equal(t, value, val.(int)) 384 return 385 } 386 } 387 388 func TestCacheSet(t *testing.T) { 389 c, err := NewCache(&Config{ 390 NumCounters: 100, 391 MaxCost: 10, 392 IgnoreInternalCost: true, 393 BufferItems: 64, 394 Metrics: true, 395 }) 396 require.NoError(t, err) 397 398 retrySet(t, c, "1", 1, 1) 399 400 c.SetWithCost("1", 2, 2) 401 val, ok := c.store.Get(defaultStringHash("1")) 402 require.True(t, ok) 403 require.Equal(t, 2, val.(int)) 404 405 c.stop <- struct{}{} 406 for i := 0; i < setBufSize; i++ { 407 key, conflict := defaultStringHash("1") 408 c.setBuf <- &Item{ 409 flag: itemUpdate, 410 Key: key, 411 Conflict: conflict, 412 Value: 1, 413 Cost: 1, 414 } 415 } 416 require.False(t, c.SetWithCost("2", 2, 1)) 417 require.Equal(t, uint64(1), c.Metrics.SetsDropped()) 418 close(c.setBuf) 419 close(c.stop) 420 421 c = nil 422 require.False(t, c.SetWithCost("1", 1, 1)) 423 } 424 425 func TestCacheInternalCost(t *testing.T) { 426 c, err := NewCache(&Config{ 427 NumCounters: 100, 428 MaxCost: 10, 429 BufferItems: 64, 430 Metrics: true, 431 }) 432 require.NoError(t, err) 433 434 // Get should return false because the cache's cost is too small to store the item 435 // when accounting for the internal cost. 436 c.SetWithCost("1", 1, 1) 437 time.Sleep(wait) 438 _, ok := c.Get("1") 439 require.False(t, ok) 440 } 441 442 func TestCacheDel(t *testing.T) { 443 c, err := NewCache(&Config{ 444 NumCounters: 100, 445 MaxCost: 10, 446 BufferItems: 64, 447 }) 448 require.NoError(t, err) 449 450 c.SetWithCost("1", 1, 1) 451 c.Delete("1") 452 // The deletes and sets are pushed through the setbuf. It might be possible 453 // that the delete is not processed before the following get is called. So 454 // wait for a millisecond for things to be processed. 455 time.Sleep(time.Millisecond) 456 val, ok := c.Get("1") 457 require.False(t, ok) 458 require.Nil(t, val) 459 460 c = nil 461 defer func() { 462 require.Nil(t, recover()) 463 }() 464 c.Delete("1") 465 } 466 467 func TestCacheClear(t *testing.T) { 468 c, err := NewCache(&Config{ 469 NumCounters: 100, 470 MaxCost: 10, 471 IgnoreInternalCost: true, 472 BufferItems: 64, 473 Metrics: true, 474 }) 475 require.NoError(t, err) 476 477 for i := 0; i < 10; i++ { 478 c.SetWithCost(strconv.Itoa(i), i, 1) 479 } 480 time.Sleep(wait) 481 require.Equal(t, uint64(10), c.Metrics.KeysAdded()) 482 483 c.Clear() 484 require.Equal(t, uint64(0), c.Metrics.KeysAdded()) 485 486 for i := 0; i < 10; i++ { 487 val, ok := c.Get(strconv.Itoa(i)) 488 require.False(t, ok) 489 require.Nil(t, val) 490 } 491 } 492 493 func TestCacheMetrics(t *testing.T) { 494 c, err := NewCache(&Config{ 495 NumCounters: 100, 496 MaxCost: 10, 497 IgnoreInternalCost: true, 498 BufferItems: 64, 499 Metrics: true, 500 }) 501 require.NoError(t, err) 502 503 for i := 0; i < 10; i++ { 504 c.SetWithCost(strconv.Itoa(i), i, 1) 505 } 506 time.Sleep(wait) 507 m := c.Metrics 508 require.Equal(t, uint64(10), m.KeysAdded()) 509 } 510 511 func TestMetrics(t *testing.T) { 512 newMetrics() 513 } 514 515 func TestNilMetrics(t *testing.T) { 516 var m *Metrics 517 for _, f := range []func() uint64{ 518 m.Hits, 519 m.Misses, 520 m.KeysAdded, 521 m.KeysEvicted, 522 m.CostEvicted, 523 m.SetsDropped, 524 m.SetsRejected, 525 m.GetsDropped, 526 m.GetsKept, 527 } { 528 require.Equal(t, uint64(0), f()) 529 } 530 } 531 532 func TestMetricsAddGet(t *testing.T) { 533 m := newMetrics() 534 m.add(hit, 1, 1) 535 m.add(hit, 2, 2) 536 m.add(hit, 3, 3) 537 require.Equal(t, uint64(6), m.Hits()) 538 539 m = nil 540 m.add(hit, 1, 1) 541 require.Equal(t, uint64(0), m.Hits()) 542 } 543 544 func TestMetricsRatio(t *testing.T) { 545 m := newMetrics() 546 require.Equal(t, float64(0), m.Ratio()) 547 548 m.add(hit, 1, 1) 549 m.add(hit, 2, 2) 550 m.add(miss, 1, 1) 551 m.add(miss, 2, 2) 552 require.Equal(t, 0.5, m.Ratio()) 553 554 m = nil 555 require.Equal(t, float64(0), m.Ratio()) 556 } 557 558 func TestMetricsString(t *testing.T) { 559 m := newMetrics() 560 m.add(hit, 1, 1) 561 m.add(miss, 1, 1) 562 m.add(keyAdd, 1, 1) 563 m.add(keyUpdate, 1, 1) 564 m.add(keyEvict, 1, 1) 565 m.add(costAdd, 1, 1) 566 m.add(costEvict, 1, 1) 567 m.add(dropSets, 1, 1) 568 m.add(rejectSets, 1, 1) 569 m.add(dropGets, 1, 1) 570 m.add(keepGets, 1, 1) 571 require.Equal(t, uint64(1), m.Hits()) 572 require.Equal(t, uint64(1), m.Misses()) 573 require.Equal(t, 0.5, m.Ratio()) 574 require.Equal(t, uint64(1), m.KeysAdded()) 575 require.Equal(t, uint64(1), m.KeysUpdated()) 576 require.Equal(t, uint64(1), m.KeysEvicted()) 577 require.Equal(t, uint64(1), m.CostAdded()) 578 require.Equal(t, uint64(1), m.CostEvicted()) 579 require.Equal(t, uint64(1), m.SetsDropped()) 580 require.Equal(t, uint64(1), m.SetsRejected()) 581 require.Equal(t, uint64(1), m.GetsDropped()) 582 require.Equal(t, uint64(1), m.GetsKept()) 583 584 require.NotEqual(t, 0, len(m.String())) 585 586 m = nil 587 require.Equal(t, 0, len(m.String())) 588 589 require.Equal(t, "unidentified", stringFor(doNotUse)) 590 } 591 592 func TestCacheMetricsClear(t *testing.T) { 593 c, err := NewCache(&Config{ 594 NumCounters: 100, 595 MaxCost: 10, 596 BufferItems: 64, 597 Metrics: true, 598 }) 599 require.NoError(t, err) 600 601 c.SetWithCost("1", 1, 1) 602 stop := make(chan struct{}) 603 go func() { 604 for { 605 select { 606 case <-stop: 607 return 608 default: 609 c.Get("1") 610 } 611 } 612 }() 613 time.Sleep(wait) 614 c.Clear() 615 stop <- struct{}{} 616 c.Metrics = nil 617 c.Metrics.Clear() 618 } 619 620 // Regression test for bug https://github.com/dgraph-io/ristretto/issues/167 621 func TestDropUpdates(t *testing.T) { 622 originalSetBugSize := setBufSize 623 defer func() { setBufSize = originalSetBugSize }() 624 625 test := func() { 626 // dropppedMap stores the items dropped from the cache. 627 droppedMap := make(map[int]struct{}) 628 lastEvictedSet := int64(-1) 629 630 var err error 631 handler := func(_ interface{}, value interface{}) { 632 v := value.(string) 633 lastEvictedSet, err = strconv.ParseInt(string(v), 10, 32) 634 require.NoError(t, err) 635 636 _, ok := droppedMap[int(lastEvictedSet)] 637 if ok { 638 panic(fmt.Sprintf("val = %+v was dropped but it got evicted. Dropped items: %+v\n", 639 lastEvictedSet, droppedMap)) 640 } 641 } 642 643 // This is important. The race condition shows up only when the setBuf 644 // is full and that's why we reduce the buf size here. The test will 645 // try to fill up the setbuf to it's capacity and then perform an 646 // update on a key. 647 setBufSize = 10 648 649 c, err := NewCache(&Config{ 650 NumCounters: 100, 651 MaxCost: 10, 652 BufferItems: 64, 653 Metrics: true, 654 OnEvict: func(item *Item) { 655 if item.Value != nil { 656 handler(nil, item.Value) 657 } 658 }, 659 }) 660 require.NoError(t, err) 661 662 for i := 0; i < 5*setBufSize; i++ { 663 v := fmt.Sprintf("%0100d", i) 664 // We're updating the same key. 665 if !c.SetWithCost("0", v, 1) { 666 // The race condition doesn't show up without this sleep. 667 time.Sleep(time.Microsecond) 668 droppedMap[i] = struct{}{} 669 } 670 } 671 // Wait for all the items to be processed. 672 c.Wait() 673 // This will cause eviction from the cache. 674 require.True(t, c.SetWithCost("1", nil, 10)) 675 c.Close() 676 } 677 678 // Run the test 100 times since it's not reliable. 679 for i := 0; i < 100; i++ { 680 test() 681 } 682 } 683 684 func newTestCache() (*Cache, error) { 685 return NewCache(&Config{ 686 NumCounters: 100, 687 MaxCost: 10, 688 BufferItems: 64, 689 Metrics: true, 690 }) 691 }