github.com/dgraph-io/ristretto@v0.1.2-0.20240116140435-c67e07994f91/cache_test.go (about) 1 package ristretto 2 3 import ( 4 "fmt" 5 "math/rand" 6 "runtime" 7 "strconv" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/dgraph-io/ristretto/z" 14 "github.com/stretchr/testify/require" 15 ) 16 17 var wait = time.Millisecond * 10 18 19 func TestCacheKeyToHash(t *testing.T) { 20 keyToHashCount := 0 21 c, err := NewCache(&Config[int, int]{ 22 NumCounters: 10, 23 MaxCost: 1000, 24 BufferItems: 64, 25 IgnoreInternalCost: true, 26 KeyToHash: func(key int) (uint64, uint64) { 27 keyToHashCount++ 28 return z.KeyToHash(key) 29 }, 30 }) 31 require.NoError(t, err) 32 if c.Set(1, 1, 1) { 33 time.Sleep(wait) 34 val, ok := c.Get(1) 35 require.True(t, ok) 36 require.NotNil(t, val) 37 c.Del(1) 38 } 39 require.Equal(t, 3, keyToHashCount) 40 } 41 42 func TestCacheMaxCost(t *testing.T) { 43 charset := "abcdefghijklmnopqrstuvwxyz0123456789" 44 key := func() []byte { 45 k := make([]byte, 2) 46 for i := range k { 47 k[i] = charset[rand.Intn(len(charset))] 48 } 49 return k 50 } 51 c, err := NewCache(&Config[[]byte, string]{ 52 NumCounters: 12960, // 36^2 * 10 53 MaxCost: 1e6, // 1mb 54 BufferItems: 64, 55 Metrics: true, 56 }) 57 require.NoError(t, err) 58 stop := make(chan struct{}, 8) 59 for i := 0; i < 8; i++ { 60 go func() { 61 for { 62 select { 63 case <-stop: 64 return 65 default: 66 time.Sleep(time.Millisecond) 67 68 k := key() 69 if _, ok := c.Get(k); !ok { 70 val := "" 71 if rand.Intn(100) < 10 { 72 val = "test" 73 } else { 74 val = strings.Repeat("a", 1000) 75 } 76 c.Set(key(), val, int64(2+len(val))) 77 } 78 } 79 } 80 }() 81 } 82 for i := 0; i < 20; i++ { 83 time.Sleep(time.Second) 84 cacheCost := c.Metrics.CostAdded() - c.Metrics.CostEvicted() 85 t.Logf("total cache cost: %d\n", cacheCost) 86 require.True(t, float64(cacheCost) <= float64(1e6*1.05)) 87 } 88 for i := 0; i < 8; i++ { 89 stop <- struct{}{} 90 } 91 } 92 93 func TestUpdateMaxCost(t *testing.T) { 94 c, err := NewCache(&Config[int, int]{ 95 NumCounters: 10, 96 MaxCost: 10, 97 BufferItems: 64, 98 }) 99 require.NoError(t, err) 100 require.Equal(t, int64(10), c.MaxCost()) 101 require.True(t, c.Set(1, 1, 1)) 102 time.Sleep(wait) 103 _, ok := c.Get(1) 104 // Set is rejected because the cost of the entry is too high 105 // when accounting for the internal cost of storing the entry. 106 require.False(t, ok) 107 108 // Update the max cost of the cache and retry. 109 c.UpdateMaxCost(1000) 110 require.Equal(t, int64(1000), c.MaxCost()) 111 require.True(t, c.Set(1, 1, 1)) 112 time.Sleep(wait) 113 val, ok := c.Get(1) 114 require.True(t, ok) 115 require.NotNil(t, val) 116 c.Del(1) 117 } 118 119 func TestNewCache(t *testing.T) { 120 _, err := NewCache(&Config[int, int]{ 121 NumCounters: 0, 122 }) 123 require.Error(t, err) 124 125 _, err = NewCache(&Config[int, int]{ 126 NumCounters: 100, 127 MaxCost: 0, 128 }) 129 require.Error(t, err) 130 131 _, err = NewCache(&Config[int, int]{ 132 NumCounters: 100, 133 MaxCost: 10, 134 BufferItems: 0, 135 }) 136 require.Error(t, err) 137 138 c, err := NewCache(&Config[int, int]{ 139 NumCounters: 100, 140 MaxCost: 10, 141 BufferItems: 64, 142 Metrics: true, 143 }) 144 require.NoError(t, err) 145 require.NotNil(t, c) 146 } 147 148 func TestNilCache(t *testing.T) { 149 var c *Cache[int, int] 150 val, ok := c.Get(1) 151 require.False(t, ok) 152 require.Zero(t, val) 153 154 require.False(t, c.Set(1, 1, 1)) 155 c.Del(1) 156 c.Clear() 157 c.Close() 158 } 159 160 func TestMultipleClose(t *testing.T) { 161 var c *Cache[int, int] 162 c.Close() 163 164 var err error 165 c, err = NewCache(&Config[int, int]{ 166 NumCounters: 100, 167 MaxCost: 10, 168 BufferItems: 64, 169 Metrics: true, 170 }) 171 require.NoError(t, err) 172 c.Close() 173 c.Close() 174 } 175 176 func TestSetAfterClose(t *testing.T) { 177 c, err := newTestCache() 178 require.NoError(t, err) 179 require.NotNil(t, c) 180 181 c.Close() 182 require.False(t, c.Set(1, 1, 1)) 183 } 184 185 func TestClearAfterClose(t *testing.T) { 186 c, err := newTestCache() 187 require.NoError(t, err) 188 require.NotNil(t, c) 189 190 c.Close() 191 c.Clear() 192 } 193 194 func TestGetAfterClose(t *testing.T) { 195 c, err := newTestCache() 196 require.NoError(t, err) 197 require.NotNil(t, c) 198 199 require.True(t, c.Set(1, 1, 1)) 200 c.Close() 201 202 _, ok := c.Get(1) 203 require.False(t, ok) 204 } 205 206 func TestDelAfterClose(t *testing.T) { 207 c, err := newTestCache() 208 require.NoError(t, err) 209 require.NotNil(t, c) 210 211 require.True(t, c.Set(1, 1, 1)) 212 c.Close() 213 214 c.Del(1) 215 } 216 217 func TestCacheProcessItems(t *testing.T) { 218 m := &sync.Mutex{} 219 evicted := make(map[uint64]struct{}) 220 c, err := NewCache(&Config[int, int]{ 221 NumCounters: 100, 222 MaxCost: 10, 223 BufferItems: 64, 224 IgnoreInternalCost: true, 225 Cost: func(value int) int64 { 226 return int64(value) 227 }, 228 OnEvict: func(item *Item[int]) { 229 m.Lock() 230 defer m.Unlock() 231 evicted[item.Key] = struct{}{} 232 }, 233 }) 234 require.NoError(t, err) 235 236 var key uint64 237 var conflict uint64 238 239 key, conflict = z.KeyToHash(1) 240 c.setBuf <- &Item[int]{ 241 flag: itemNew, 242 Key: key, 243 Conflict: conflict, 244 Value: 1, 245 Cost: 0, 246 } 247 time.Sleep(wait) 248 require.True(t, c.cachePolicy.Has(1)) 249 require.Equal(t, int64(1), c.cachePolicy.Cost(1)) 250 251 key, conflict = z.KeyToHash(1) 252 c.setBuf <- &Item[int]{ 253 flag: itemUpdate, 254 Key: key, 255 Conflict: conflict, 256 Value: 2, 257 Cost: 0, 258 } 259 time.Sleep(wait) 260 require.Equal(t, int64(2), c.cachePolicy.Cost(1)) 261 262 key, conflict = z.KeyToHash(1) 263 c.setBuf <- &Item[int]{ 264 flag: itemDelete, 265 Key: key, 266 Conflict: conflict, 267 } 268 time.Sleep(wait) 269 key, conflict = z.KeyToHash(1) 270 val, ok := c.storedItems.Get(key, conflict) 271 require.False(t, ok) 272 require.Zero(t, val) 273 require.False(t, c.cachePolicy.Has(1)) 274 275 key, conflict = z.KeyToHash(2) 276 c.setBuf <- &Item[int]{ 277 flag: itemNew, 278 Key: key, 279 Conflict: conflict, 280 Value: 2, 281 Cost: 3, 282 } 283 key, conflict = z.KeyToHash(3) 284 c.setBuf <- &Item[int]{ 285 flag: itemNew, 286 Key: key, 287 Conflict: conflict, 288 Value: 3, 289 Cost: 3, 290 } 291 key, conflict = z.KeyToHash(4) 292 c.setBuf <- &Item[int]{ 293 flag: itemNew, 294 Key: key, 295 Conflict: conflict, 296 Value: 3, 297 Cost: 3, 298 } 299 key, conflict = z.KeyToHash(5) 300 c.setBuf <- &Item[int]{ 301 flag: itemNew, 302 Key: key, 303 Conflict: conflict, 304 Value: 3, 305 Cost: 5, 306 } 307 time.Sleep(wait) 308 m.Lock() 309 require.NotEqual(t, 0, len(evicted)) 310 m.Unlock() 311 312 defer func() { 313 require.NotNil(t, recover()) 314 }() 315 c.Close() 316 c.setBuf <- &Item[int]{flag: itemNew} 317 } 318 319 func TestCacheGet(t *testing.T) { 320 c, err := NewCache(&Config[int, int]{ 321 NumCounters: 100, 322 MaxCost: 10, 323 BufferItems: 64, 324 IgnoreInternalCost: true, 325 Metrics: true, 326 }) 327 require.NoError(t, err) 328 329 key, conflict := z.KeyToHash(1) 330 i := Item[int]{ 331 Key: key, 332 Conflict: conflict, 333 Value: 1, 334 } 335 c.storedItems.Set(&i) 336 val, ok := c.Get(1) 337 require.True(t, ok) 338 require.NotNil(t, val) 339 340 val, ok = c.Get(2) 341 require.False(t, ok) 342 require.Zero(t, val) 343 344 // 0.5 and not 1.0 because we tried Getting each item twice 345 require.Equal(t, 0.5, c.Metrics.Ratio()) 346 347 c = nil 348 val, ok = c.Get(0) 349 require.False(t, ok) 350 require.Zero(t, val) 351 } 352 353 // retrySet calls SetWithTTL until the item is accepted by the cache. 354 func retrySet(t *testing.T, c *Cache[int, int], key, value int, cost int64, ttl time.Duration) { 355 for { 356 if set := c.SetWithTTL(key, value, cost, ttl); !set { 357 time.Sleep(wait) 358 continue 359 } 360 361 time.Sleep(wait) 362 val, ok := c.Get(key) 363 require.True(t, ok) 364 require.NotNil(t, val) 365 require.Equal(t, value, val) 366 return 367 } 368 } 369 370 func TestCacheSet(t *testing.T) { 371 c, err := NewCache(&Config[int, int]{ 372 NumCounters: 100, 373 MaxCost: 10, 374 IgnoreInternalCost: true, 375 BufferItems: 64, 376 Metrics: true, 377 }) 378 require.NoError(t, err) 379 380 retrySet(t, c, 1, 1, 1, 0) 381 382 c.Set(1, 2, 2) 383 val, ok := c.storedItems.Get(z.KeyToHash(1)) 384 require.True(t, ok) 385 require.Equal(t, 2, val) 386 387 c.stop <- struct{}{} 388 for i := 0; i < setBufSize; i++ { 389 key, conflict := z.KeyToHash(1) 390 c.setBuf <- &Item[int]{ 391 flag: itemUpdate, 392 Key: key, 393 Conflict: conflict, 394 Value: 1, 395 Cost: 1, 396 } 397 } 398 require.False(t, c.Set(2, 2, 1)) 399 require.Equal(t, uint64(1), c.Metrics.SetsDropped()) 400 close(c.setBuf) 401 close(c.stop) 402 403 c = nil 404 require.False(t, c.Set(1, 1, 1)) 405 } 406 407 func TestCacheInternalCost(t *testing.T) { 408 c, err := NewCache(&Config[int, int]{ 409 NumCounters: 100, 410 MaxCost: 10, 411 BufferItems: 64, 412 Metrics: true, 413 }) 414 require.NoError(t, err) 415 416 // Get should return false because the cache's cost is too small to storedItems the item 417 // when accounting for the internal cost. 418 c.SetWithTTL(1, 1, 1, 0) 419 time.Sleep(wait) 420 _, ok := c.Get(1) 421 require.False(t, ok) 422 } 423 424 func TestRecacheWithTTL(t *testing.T) { 425 c, err := NewCache(&Config[int, int]{ 426 NumCounters: 100, 427 MaxCost: 10, 428 IgnoreInternalCost: true, 429 BufferItems: 64, 430 Metrics: true, 431 }) 432 433 require.NoError(t, err) 434 435 // Set initial value for key = 1 436 insert := c.SetWithTTL(1, 1, 1, 5*time.Second) 437 require.True(t, insert) 438 time.Sleep(2 * time.Second) 439 440 // Get value from cache for key = 1 441 val, ok := c.Get(1) 442 require.True(t, ok) 443 require.NotNil(t, val) 444 require.Equal(t, 1, val) 445 446 // Wait for expiration 447 time.Sleep(5 * time.Second) 448 449 // The cached value for key = 1 should be gone 450 val, ok = c.Get(1) 451 require.False(t, ok) 452 require.Zero(t, val) 453 454 // Set new value for key = 1 455 insert = c.SetWithTTL(1, 2, 1, 5*time.Second) 456 require.True(t, insert) 457 time.Sleep(2 * time.Second) 458 459 // Get value from cache for key = 1 460 val, ok = c.Get(1) 461 require.True(t, ok) 462 require.NotNil(t, val) 463 require.Equal(t, 2, val) 464 } 465 466 func TestCacheSetWithTTL(t *testing.T) { 467 m := &sync.Mutex{} 468 evicted := make(map[uint64]struct{}) 469 c, err := NewCache(&Config[int, int]{ 470 NumCounters: 100, 471 MaxCost: 10, 472 IgnoreInternalCost: true, 473 BufferItems: 64, 474 Metrics: true, 475 OnEvict: func(item *Item[int]) { 476 m.Lock() 477 defer m.Unlock() 478 evicted[item.Key] = struct{}{} 479 }, 480 }) 481 require.NoError(t, err) 482 483 retrySet(t, c, 1, 1, 1, time.Second) 484 485 // Sleep to make sure the item has expired after execution resumes. 486 time.Sleep(2 * time.Second) 487 val, ok := c.Get(1) 488 require.False(t, ok) 489 require.Zero(t, val) 490 491 // Sleep to ensure that the bucket where the item was stored has been cleared 492 // from the expiraton map. 493 time.Sleep(5 * time.Second) 494 m.Lock() 495 require.Equal(t, 1, len(evicted)) 496 _, ok = evicted[1] 497 require.True(t, ok) 498 m.Unlock() 499 500 // Verify that expiration times are overwritten. 501 retrySet(t, c, 2, 1, 1, time.Second) 502 retrySet(t, c, 2, 2, 1, 100*time.Second) 503 time.Sleep(3 * time.Second) 504 val, ok = c.Get(2) 505 require.True(t, ok) 506 require.Equal(t, 2, val) 507 508 // Verify that entries with no expiration are overwritten. 509 retrySet(t, c, 3, 1, 1, 0) 510 retrySet(t, c, 3, 2, 1, time.Second) 511 time.Sleep(3 * time.Second) 512 val, ok = c.Get(3) 513 require.False(t, ok) 514 require.Zero(t, val) 515 } 516 517 func TestCacheDel(t *testing.T) { 518 c, err := NewCache(&Config[int, int]{ 519 NumCounters: 100, 520 MaxCost: 10, 521 BufferItems: 64, 522 }) 523 require.NoError(t, err) 524 525 c.Set(1, 1, 1) 526 c.Del(1) 527 // The deletes and sets are pushed through the setbuf. It might be possible 528 // that the delete is not processed before the following get is called. So 529 // wait for a millisecond for things to be processed. 530 time.Sleep(time.Millisecond) 531 val, ok := c.Get(1) 532 require.False(t, ok) 533 require.Zero(t, val) 534 535 c = nil 536 defer func() { 537 require.Nil(t, recover()) 538 }() 539 c.Del(1) 540 } 541 542 func TestCacheDelWithTTL(t *testing.T) { 543 c, err := NewCache(&Config[int, int]{ 544 NumCounters: 100, 545 MaxCost: 10, 546 IgnoreInternalCost: true, 547 BufferItems: 64, 548 }) 549 require.NoError(t, err) 550 retrySet(t, c, 3, 1, 1, 10*time.Second) 551 time.Sleep(1 * time.Second) 552 // Delete the item 553 c.Del(3) 554 // Ensure the key is deleted. 555 val, ok := c.Get(3) 556 require.False(t, ok) 557 require.Zero(t, val) 558 } 559 560 func TestCacheGetTTL(t *testing.T) { 561 c, err := NewCache(&Config[int, int]{ 562 NumCounters: 100, 563 MaxCost: 10, 564 IgnoreInternalCost: true, 565 BufferItems: 64, 566 Metrics: true, 567 }) 568 require.NoError(t, err) 569 570 // try expiration with valid ttl item 571 { 572 expiration := time.Second * 5 573 retrySet(t, c, 1, 1, 1, expiration) 574 575 val, ok := c.Get(1) 576 require.True(t, ok) 577 require.Equal(t, 1, val) 578 579 ttl, ok := c.GetTTL(1) 580 require.True(t, ok) 581 require.WithinDuration(t, 582 time.Now().Add(expiration), time.Now().Add(ttl), 1*time.Second) 583 584 c.Del(1) 585 586 ttl, ok = c.GetTTL(1) 587 require.False(t, ok) 588 require.Equal(t, ttl, time.Duration(0)) 589 } 590 // try expiration with no ttl 591 { 592 retrySet(t, c, 2, 2, 1, time.Duration(0)) 593 594 val, ok := c.Get(2) 595 require.True(t, ok) 596 require.Equal(t, 2, val) 597 598 ttl, ok := c.GetTTL(2) 599 require.True(t, ok) 600 require.Equal(t, ttl, time.Duration(0)) 601 } 602 // try expiration with missing item 603 { 604 ttl, ok := c.GetTTL(3) 605 require.False(t, ok) 606 require.Equal(t, ttl, time.Duration(0)) 607 } 608 // try expiration with expired item 609 { 610 expiration := time.Second 611 retrySet(t, c, 3, 3, 1, expiration) 612 613 val, ok := c.Get(3) 614 require.True(t, ok) 615 require.Equal(t, 3, val) 616 617 time.Sleep(time.Second) 618 619 ttl, ok := c.GetTTL(3) 620 require.False(t, ok) 621 require.Equal(t, ttl, time.Duration(0)) 622 } 623 } 624 625 func TestCacheClear(t *testing.T) { 626 c, err := NewCache(&Config[int, int]{ 627 NumCounters: 100, 628 MaxCost: 10, 629 IgnoreInternalCost: true, 630 BufferItems: 64, 631 Metrics: true, 632 }) 633 require.NoError(t, err) 634 635 for i := 0; i < 10; i++ { 636 c.Set(i, i, 1) 637 } 638 time.Sleep(wait) 639 require.Equal(t, uint64(10), c.Metrics.KeysAdded()) 640 641 c.Clear() 642 require.Equal(t, uint64(0), c.Metrics.KeysAdded()) 643 644 for i := 0; i < 10; i++ { 645 val, ok := c.Get(i) 646 require.False(t, ok) 647 require.Zero(t, val) 648 } 649 } 650 651 func TestCacheMetrics(t *testing.T) { 652 c, err := NewCache(&Config[int, int]{ 653 NumCounters: 100, 654 MaxCost: 10, 655 IgnoreInternalCost: true, 656 BufferItems: 64, 657 Metrics: true, 658 }) 659 require.NoError(t, err) 660 661 for i := 0; i < 10; i++ { 662 c.Set(i, i, 1) 663 } 664 time.Sleep(wait) 665 m := c.Metrics 666 require.Equal(t, uint64(10), m.KeysAdded()) 667 } 668 669 func TestMetrics(t *testing.T) { 670 newMetrics() 671 } 672 673 func TestNilMetrics(t *testing.T) { 674 var m *Metrics 675 for _, f := range []func() uint64{ 676 m.Hits, 677 m.Misses, 678 m.KeysAdded, 679 m.KeysEvicted, 680 m.CostEvicted, 681 m.SetsDropped, 682 m.SetsRejected, 683 m.GetsDropped, 684 m.GetsKept, 685 } { 686 require.Equal(t, uint64(0), f()) 687 } 688 } 689 690 func TestMetricsAddGet(t *testing.T) { 691 m := newMetrics() 692 m.add(hit, 1, 1) 693 m.add(hit, 2, 2) 694 m.add(hit, 3, 3) 695 require.Equal(t, uint64(6), m.Hits()) 696 697 m = nil 698 m.add(hit, 1, 1) 699 require.Equal(t, uint64(0), m.Hits()) 700 } 701 702 func TestMetricsRatio(t *testing.T) { 703 m := newMetrics() 704 require.Equal(t, float64(0), m.Ratio()) 705 706 m.add(hit, 1, 1) 707 m.add(hit, 2, 2) 708 m.add(miss, 1, 1) 709 m.add(miss, 2, 2) 710 require.Equal(t, 0.5, m.Ratio()) 711 712 m = nil 713 require.Equal(t, float64(0), m.Ratio()) 714 } 715 716 func TestMetricsString(t *testing.T) { 717 m := newMetrics() 718 m.add(hit, 1, 1) 719 m.add(miss, 1, 1) 720 m.add(keyAdd, 1, 1) 721 m.add(keyUpdate, 1, 1) 722 m.add(keyEvict, 1, 1) 723 m.add(costAdd, 1, 1) 724 m.add(costEvict, 1, 1) 725 m.add(dropSets, 1, 1) 726 m.add(rejectSets, 1, 1) 727 m.add(dropGets, 1, 1) 728 m.add(keepGets, 1, 1) 729 require.Equal(t, uint64(1), m.Hits()) 730 require.Equal(t, uint64(1), m.Misses()) 731 require.Equal(t, 0.5, m.Ratio()) 732 require.Equal(t, uint64(1), m.KeysAdded()) 733 require.Equal(t, uint64(1), m.KeysUpdated()) 734 require.Equal(t, uint64(1), m.KeysEvicted()) 735 require.Equal(t, uint64(1), m.CostAdded()) 736 require.Equal(t, uint64(1), m.CostEvicted()) 737 require.Equal(t, uint64(1), m.SetsDropped()) 738 require.Equal(t, uint64(1), m.SetsRejected()) 739 require.Equal(t, uint64(1), m.GetsDropped()) 740 require.Equal(t, uint64(1), m.GetsKept()) 741 742 require.NotEqual(t, 0, len(m.String())) 743 744 m = nil 745 require.Equal(t, 0, len(m.String())) 746 747 require.Equal(t, "unidentified", stringFor(doNotUse)) 748 } 749 750 func TestCacheMetricsClear(t *testing.T) { 751 c, err := NewCache(&Config[int, int]{ 752 NumCounters: 100, 753 MaxCost: 10, 754 BufferItems: 64, 755 Metrics: true, 756 }) 757 require.NoError(t, err) 758 759 c.Set(1, 1, 1) 760 stop := make(chan struct{}) 761 go func() { 762 for { 763 select { 764 case <-stop: 765 return 766 default: 767 c.Get(1) 768 } 769 } 770 }() 771 time.Sleep(wait) 772 c.Clear() 773 stop <- struct{}{} 774 c.Metrics = nil 775 c.Metrics.Clear() 776 } 777 778 func init() { 779 // Set bucketSizeSecs to 1 to avoid waiting too much during the tests. 780 bucketDurationSecs = 1 781 } 782 783 func TestBlockOnClear(t *testing.T) { 784 c, err := NewCache(&Config[int, int]{ 785 NumCounters: 100, 786 MaxCost: 10, 787 BufferItems: 64, 788 Metrics: false, 789 }) 790 require.NoError(t, err) 791 defer c.Close() 792 793 done := make(chan struct{}) 794 795 go func() { 796 for i := 0; i < 10; i++ { 797 c.Wait() 798 } 799 close(done) 800 }() 801 802 for i := 0; i < 10; i++ { 803 c.Clear() 804 } 805 806 select { 807 case <-done: 808 // We're OK 809 case <-time.After(1 * time.Second): 810 t.Fatalf("timed out while waiting on cache") 811 } 812 } 813 814 // Regression test for bug https://github.com/dgraph-io/ristretto/issues/167 815 func TestDropUpdates(t *testing.T) { 816 originalSetBugSize := setBufSize 817 defer func() { setBufSize = originalSetBugSize }() 818 819 test := func() { 820 // dropppedMap stores the items dropped from the cache. 821 droppedMap := make(map[int]struct{}) 822 lastEvictedSet := int64(-1) 823 824 var err error 825 handler := func(_ interface{}, value interface{}) { 826 v := value.(string) 827 lastEvictedSet, err = strconv.ParseInt(v, 10, 32) 828 require.NoError(t, err) 829 830 _, ok := droppedMap[int(lastEvictedSet)] 831 if ok { 832 panic(fmt.Sprintf("val = %+v was dropped but it got evicted. Dropped items: %+v\n", 833 lastEvictedSet, droppedMap)) 834 } 835 } 836 837 // This is important. The race condition shows up only when the setBuf 838 // is full and that's why we reduce the buf size here. The test will 839 // try to fill up the setbuf to it's capacity and then perform an 840 // update on a key. 841 setBufSize = 10 842 843 c, err := NewCache(&Config[int, string]{ 844 NumCounters: 100, 845 MaxCost: 10, 846 BufferItems: 64, 847 Metrics: true, 848 OnEvict: func(item *Item[string]) { 849 handler(nil, item.Value) 850 }, 851 }) 852 require.NoError(t, err) 853 854 for i := 0; i < 5*setBufSize; i++ { 855 v := fmt.Sprintf("%0100d", i) 856 // We're updating the same key. 857 if !c.Set(0, v, 1) { 858 // The race condition doesn't show up without this sleep. 859 time.Sleep(time.Microsecond) 860 droppedMap[i] = struct{}{} 861 } 862 } 863 // Wait for all items to be processed: prevents next c.Set from getting dropped 864 c.Wait() 865 // This will cause eviction from the cache. 866 require.True(t, c.Set(1, "", 10)) 867 868 // Close() calls Clear(), which can cause the (key, value) pair of (1, nil) to be passed to 869 // the OnEvict() callback if it was still in the setBuf. This fixes a panic in OnEvict: 870 // "interface {} is nil, not string" 871 c.Wait() 872 c.Close() 873 874 require.NotEqual(t, -1, lastEvictedSet) 875 } 876 877 // Run the test 100 times since it's not reliable. 878 for i := 0; i < 100; i++ { 879 test() 880 } 881 } 882 883 func TestRistrettoCalloc(t *testing.T) { 884 maxCacheSize := 1 << 20 885 config := &Config[int, []byte]{ 886 // Use 5% of cache memory for storing counters. 887 NumCounters: int64(float64(maxCacheSize) * 0.05 * 2), 888 MaxCost: int64(float64(maxCacheSize) * 0.95), 889 BufferItems: 64, 890 Metrics: true, 891 OnExit: func(val []byte) { 892 z.Free(val) 893 }, 894 } 895 r, err := NewCache(config) 896 require.NoError(t, err) 897 defer r.Close() 898 899 var wg sync.WaitGroup 900 for i := 0; i < runtime.NumCPU(); i++ { 901 wg.Add(1) 902 go func() { 903 defer wg.Done() 904 rd := rand.New(rand.NewSource(time.Now().UnixNano())) 905 for i := 0; i < 10000; i++ { 906 k := rd.Intn(10000) 907 v := z.Calloc(256, "test") 908 rd.Read(v) 909 if !r.Set(k, v, 256) { 910 z.Free(v) 911 } 912 if rd.Intn(10) == 0 { 913 r.Del(k) 914 } 915 } 916 }() 917 } 918 wg.Wait() 919 r.Clear() 920 require.Zero(t, z.NumAllocBytes()) 921 } 922 923 func TestRistrettoCallocTTL(t *testing.T) { 924 maxCacheSize := 1 << 20 925 config := &Config[int, []byte]{ 926 // Use 5% of cache memory for storing counters. 927 NumCounters: int64(float64(maxCacheSize) * 0.05 * 2), 928 MaxCost: int64(float64(maxCacheSize) * 0.95), 929 BufferItems: 64, 930 Metrics: true, 931 OnExit: func(val []byte) { 932 z.Free(val) 933 }, 934 } 935 r, err := NewCache(config) 936 require.NoError(t, err) 937 defer r.Close() 938 939 var wg sync.WaitGroup 940 for i := 0; i < runtime.NumCPU(); i++ { 941 wg.Add(1) 942 go func() { 943 defer wg.Done() 944 rd := rand.New(rand.NewSource(time.Now().UnixNano())) 945 for i := 0; i < 10000; i++ { 946 k := rd.Intn(10000) 947 v := z.Calloc(256, "test") 948 rd.Read(v) 949 if !r.SetWithTTL(k, v, 256, time.Second) { 950 z.Free(v) 951 } 952 if rd.Intn(10) == 0 { 953 r.Del(k) 954 } 955 } 956 }() 957 } 958 wg.Wait() 959 time.Sleep(5 * time.Second) 960 require.Zero(t, z.NumAllocBytes()) 961 } 962 963 func newTestCache() (*Cache[int, int], error) { 964 return NewCache(&Config[int, int]{ 965 NumCounters: 100, 966 MaxCost: 10, 967 BufferItems: 64, 968 Metrics: true, 969 }) 970 } 971 972 func TestCacheWithTTL(t *testing.T) { 973 // There may be a race condition, so run the test multiple times. 974 const try = 10 975 976 for i := 0; i < try; i++ { 977 t.Run(strconv.Itoa(i), func(t *testing.T) { 978 c, err := NewCache(&Config[int, int]{ 979 NumCounters: 100, 980 MaxCost: 1000, 981 BufferItems: 64, 982 Metrics: true, 983 }) 984 985 require.NoError(t, err) 986 987 // Set initial value for key = 1 988 insert := c.SetWithTTL(1, 1, 1, 800*time.Millisecond) 989 require.True(t, insert) 990 991 time.Sleep(100 * time.Millisecond) 992 993 // Get value from cache for key = 1 994 val, ok := c.Get(1) 995 require.True(t, ok) 996 require.NotNil(t, val) 997 require.Equal(t, 1, val) 998 999 time.Sleep(1200 * time.Millisecond) 1000 1001 val, ok = c.Get(1) 1002 require.False(t, ok) 1003 require.Zero(t, val) 1004 }) 1005 } 1006 }