github.com/outbrain/consul@v1.4.5/agent/cache/cache_test.go (about) 1 package cache 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "sync" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 ) 16 17 // Test a basic Get with no indexes (and therefore no blocking queries). 18 func TestCacheGet_noIndex(t *testing.T) { 19 t.Parallel() 20 21 require := require.New(t) 22 23 typ := TestType(t) 24 defer typ.AssertExpectations(t) 25 c := TestCache(t) 26 c.RegisterType("t", typ, nil) 27 28 // Configure the type 29 typ.Static(FetchResult{Value: 42}, nil).Times(1) 30 31 // Get, should fetch 32 req := TestRequest(t, RequestInfo{Key: "hello"}) 33 result, meta, err := c.Get("t", req) 34 require.NoError(err) 35 require.Equal(42, result) 36 require.False(meta.Hit) 37 38 // Get, should not fetch since we already have a satisfying value 39 result, meta, err = c.Get("t", req) 40 require.NoError(err) 41 require.Equal(42, result) 42 require.True(meta.Hit) 43 44 // Sleep a tiny bit just to let maybe some background calls happen 45 // then verify that we still only got the one call 46 time.Sleep(20 * time.Millisecond) 47 typ.AssertExpectations(t) 48 } 49 50 // Test a basic Get with no index and a failed fetch. 51 func TestCacheGet_initError(t *testing.T) { 52 t.Parallel() 53 54 require := require.New(t) 55 56 typ := TestType(t) 57 defer typ.AssertExpectations(t) 58 c := TestCache(t) 59 c.RegisterType("t", typ, nil) 60 61 // Configure the type 62 fetcherr := fmt.Errorf("error") 63 typ.Static(FetchResult{}, fetcherr).Times(2) 64 65 // Get, should fetch 66 req := TestRequest(t, RequestInfo{Key: "hello"}) 67 result, meta, err := c.Get("t", req) 68 require.Error(err) 69 require.Nil(result) 70 require.False(meta.Hit) 71 72 // Get, should fetch again since our last fetch was an error 73 result, meta, err = c.Get("t", req) 74 require.Error(err) 75 require.Nil(result) 76 require.False(meta.Hit) 77 78 // Sleep a tiny bit just to let maybe some background calls happen 79 // then verify that we still only got the one call 80 time.Sleep(20 * time.Millisecond) 81 typ.AssertExpectations(t) 82 } 83 84 // Test a cached error is replaced by a successful result. See 85 // https://github.com/hashicorp/consul/issues/4480 86 func TestCacheGet_cachedErrorsDontStick(t *testing.T) { 87 t.Parallel() 88 89 require := require.New(t) 90 91 typ := TestType(t) 92 defer typ.AssertExpectations(t) 93 c := TestCache(t) 94 c.RegisterType("t", typ, nil) 95 96 // Configure the type 97 fetcherr := fmt.Errorf("initial error") 98 // First fetch errors, subsequent fetches are successful and then block 99 typ.Static(FetchResult{}, fetcherr).Times(1) 100 typ.Static(FetchResult{Value: 42, Index: 123}, nil).Times(1) 101 // We trigger this to return same value to simulate a timeout. 102 triggerCh := make(chan time.Time) 103 typ.Static(FetchResult{Value: 42, Index: 123}, nil).WaitUntil(triggerCh) 104 105 // Get, should fetch and get error 106 req := TestRequest(t, RequestInfo{Key: "hello"}) 107 result, meta, err := c.Get("t", req) 108 require.Error(err) 109 require.Nil(result) 110 require.False(meta.Hit) 111 112 // Get, should fetch again since our last fetch was an error, but get success 113 result, meta, err = c.Get("t", req) 114 require.NoError(err) 115 require.Equal(42, result) 116 require.False(meta.Hit) 117 118 // Now get should block until timeout and then get the same response NOT the 119 // cached error. 120 getCh1 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 121 Key: "hello", 122 MinIndex: 123, 123 // We _don't_ set a timeout here since that doesn't trigger the bug - the 124 // bug occurs when the Fetch call times out and returns the same value when 125 // an error is set. If it returns a new value the blocking loop works too. 126 })) 127 time.AfterFunc(50*time.Millisecond, func() { 128 // "Timeout" the Fetch after a short time. 129 close(triggerCh) 130 }) 131 select { 132 case result := <-getCh1: 133 t.Fatalf("result or error returned before an update happened. "+ 134 "If this is nil look above for the error log: %v", result) 135 case <-time.After(100 * time.Millisecond): 136 // It _should_ keep blocking for a new value here 137 } 138 139 // Sleep a tiny bit just to let maybe some background calls happen 140 // then verify the calls. 141 time.Sleep(20 * time.Millisecond) 142 typ.AssertExpectations(t) 143 } 144 145 // Test a Get with a request that returns a blank cache key. This should 146 // force a backend request and skip the cache entirely. 147 func TestCacheGet_blankCacheKey(t *testing.T) { 148 t.Parallel() 149 150 require := require.New(t) 151 152 typ := TestType(t) 153 defer typ.AssertExpectations(t) 154 c := TestCache(t) 155 c.RegisterType("t", typ, nil) 156 157 // Configure the type 158 typ.Static(FetchResult{Value: 42}, nil).Times(2) 159 160 // Get, should fetch 161 req := TestRequest(t, RequestInfo{Key: ""}) 162 result, meta, err := c.Get("t", req) 163 require.NoError(err) 164 require.Equal(42, result) 165 require.False(meta.Hit) 166 167 // Get, should not fetch since we already have a satisfying value 168 result, meta, err = c.Get("t", req) 169 require.NoError(err) 170 require.Equal(42, result) 171 require.False(meta.Hit) 172 173 // Sleep a tiny bit just to let maybe some background calls happen 174 // then verify that we still only got the one call 175 time.Sleep(20 * time.Millisecond) 176 typ.AssertExpectations(t) 177 } 178 179 // Test that Get blocks on the initial value 180 func TestCacheGet_blockingInitSameKey(t *testing.T) { 181 t.Parallel() 182 183 typ := TestType(t) 184 defer typ.AssertExpectations(t) 185 c := TestCache(t) 186 c.RegisterType("t", typ, nil) 187 188 // Configure the type 189 triggerCh := make(chan time.Time) 190 typ.Static(FetchResult{Value: 42}, nil).WaitUntil(triggerCh).Times(1) 191 192 // Perform multiple gets 193 getCh1 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 194 getCh2 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 195 196 // They should block 197 select { 198 case <-getCh1: 199 t.Fatal("should block (ch1)") 200 case <-getCh2: 201 t.Fatal("should block (ch2)") 202 case <-time.After(50 * time.Millisecond): 203 } 204 205 // Trigger it 206 close(triggerCh) 207 208 // Should return 209 TestCacheGetChResult(t, getCh1, 42) 210 TestCacheGetChResult(t, getCh2, 42) 211 } 212 213 // Test that Get with different cache keys both block on initial value 214 // but that the fetches were both properly called. 215 func TestCacheGet_blockingInitDiffKeys(t *testing.T) { 216 t.Parallel() 217 218 require := require.New(t) 219 220 typ := TestType(t) 221 defer typ.AssertExpectations(t) 222 c := TestCache(t) 223 c.RegisterType("t", typ, nil) 224 225 // Keep track of the keys 226 var keysLock sync.Mutex 227 var keys []string 228 229 // Configure the type 230 triggerCh := make(chan time.Time) 231 typ.Static(FetchResult{Value: 42}, nil). 232 WaitUntil(triggerCh). 233 Times(2). 234 Run(func(args mock.Arguments) { 235 keysLock.Lock() 236 defer keysLock.Unlock() 237 keys = append(keys, args.Get(1).(Request).CacheInfo().Key) 238 }) 239 240 // Perform multiple gets 241 getCh1 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 242 getCh2 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "goodbye"})) 243 244 // They should block 245 select { 246 case <-getCh1: 247 t.Fatal("should block (ch1)") 248 case <-getCh2: 249 t.Fatal("should block (ch2)") 250 case <-time.After(50 * time.Millisecond): 251 } 252 253 // Trigger it 254 close(triggerCh) 255 256 // Should return both! 257 TestCacheGetChResult(t, getCh1, 42) 258 TestCacheGetChResult(t, getCh2, 42) 259 260 // Verify proper keys 261 sort.Strings(keys) 262 require.Equal([]string{"goodbye", "hello"}, keys) 263 } 264 265 // Test a get with an index set will wait until an index that is higher 266 // is set in the cache. 267 func TestCacheGet_blockingIndex(t *testing.T) { 268 t.Parallel() 269 270 typ := TestType(t) 271 defer typ.AssertExpectations(t) 272 c := TestCache(t) 273 c.RegisterType("t", typ, nil) 274 275 // Configure the type 276 triggerCh := make(chan time.Time) 277 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 278 typ.Static(FetchResult{Value: 12, Index: 5}, nil).Once() 279 typ.Static(FetchResult{Value: 42, Index: 6}, nil).WaitUntil(triggerCh) 280 281 // Fetch should block 282 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 283 Key: "hello", MinIndex: 5})) 284 285 // Should block 286 select { 287 case <-resultCh: 288 t.Fatal("should block") 289 case <-time.After(50 * time.Millisecond): 290 } 291 292 // Wait a bit 293 close(triggerCh) 294 295 // Should return 296 TestCacheGetChResult(t, resultCh, 42) 297 } 298 299 // Test a get with an index set will timeout if the fetch doesn't return 300 // anything. 301 func TestCacheGet_blockingIndexTimeout(t *testing.T) { 302 t.Parallel() 303 304 typ := TestType(t) 305 defer typ.AssertExpectations(t) 306 c := TestCache(t) 307 c.RegisterType("t", typ, nil) 308 309 // Configure the type 310 triggerCh := make(chan time.Time) 311 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 312 typ.Static(FetchResult{Value: 12, Index: 5}, nil).Once() 313 typ.Static(FetchResult{Value: 42, Index: 6}, nil).WaitUntil(triggerCh) 314 315 // Fetch should block 316 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 317 Key: "hello", MinIndex: 5, Timeout: 200 * time.Millisecond})) 318 319 // Should block 320 select { 321 case <-resultCh: 322 t.Fatal("should block") 323 case <-time.After(50 * time.Millisecond): 324 } 325 326 // Should return after more of the timeout 327 select { 328 case result := <-resultCh: 329 require.Equal(t, 12, result) 330 case <-time.After(300 * time.Millisecond): 331 t.Fatal("should've returned") 332 } 333 } 334 335 // Test a get with an index set with requests returning an error 336 // will return that error. 337 func TestCacheGet_blockingIndexError(t *testing.T) { 338 t.Parallel() 339 340 typ := TestType(t) 341 defer typ.AssertExpectations(t) 342 c := TestCache(t) 343 c.RegisterType("t", typ, nil) 344 345 // Configure the type 346 var retries uint32 347 fetchErr := fmt.Errorf("test fetch error") 348 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 349 typ.Static(FetchResult{Value: nil, Index: 5}, fetchErr).Run(func(args mock.Arguments) { 350 atomic.AddUint32(&retries, 1) 351 }) 352 353 // First good fetch to populate catch 354 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 355 TestCacheGetChResult(t, resultCh, 1) 356 357 // Fetch should not block and should return error 358 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 359 Key: "hello", MinIndex: 7, Timeout: 1 * time.Minute})) 360 TestCacheGetChResult(t, resultCh, nil) 361 362 // Wait a bit 363 time.Sleep(100 * time.Millisecond) 364 365 // Check the number 366 actual := atomic.LoadUint32(&retries) 367 require.True(t, actual < 10, fmt.Sprintf("actual: %d", actual)) 368 } 369 370 // Test that if a Type returns an empty value on Fetch that the previous 371 // value is preserved. 372 func TestCacheGet_emptyFetchResult(t *testing.T) { 373 t.Parallel() 374 375 require := require.New(t) 376 377 typ := TestType(t) 378 defer typ.AssertExpectations(t) 379 c := TestCache(t) 380 c.RegisterType("t", typ, nil) 381 382 stateCh := make(chan int, 1) 383 384 // Configure the type 385 typ.Static(FetchResult{Value: 42, State: 31, Index: 1}, nil).Times(1) 386 // Return different State, it should NOT be ignored 387 typ.Static(FetchResult{Value: nil, State: 32}, nil).Run(func(args mock.Arguments) { 388 // We should get back the original state 389 opts := args.Get(0).(FetchOptions) 390 require.NotNil(opts.LastResult) 391 stateCh <- opts.LastResult.State.(int) 392 }) 393 394 // Get, should fetch 395 req := TestRequest(t, RequestInfo{Key: "hello"}) 396 result, meta, err := c.Get("t", req) 397 require.NoError(err) 398 require.Equal(42, result) 399 require.False(meta.Hit) 400 401 // Get, should not fetch since we already have a satisfying value 402 req = TestRequest(t, RequestInfo{ 403 Key: "hello", MinIndex: 1, Timeout: 100 * time.Millisecond}) 404 result, meta, err = c.Get("t", req) 405 require.NoError(err) 406 require.Equal(42, result) 407 require.False(meta.Hit) 408 409 // State delivered to second call should be the result from first call. 410 select { 411 case state := <-stateCh: 412 require.Equal(31, state) 413 case <-time.After(20 * time.Millisecond): 414 t.Fatal("timed out") 415 } 416 417 // Next request should get the SECOND returned state even though the fetch 418 // returns nil and so the previous result is used. 419 req = TestRequest(t, RequestInfo{ 420 Key: "hello", MinIndex: 1, Timeout: 100 * time.Millisecond}) 421 result, meta, err = c.Get("t", req) 422 require.NoError(err) 423 require.Equal(42, result) 424 require.False(meta.Hit) 425 select { 426 case state := <-stateCh: 427 require.Equal(32, state) 428 case <-time.After(20 * time.Millisecond): 429 t.Fatal("timed out") 430 } 431 432 // Sleep a tiny bit just to let maybe some background calls happen 433 // then verify that we still only got the one call 434 time.Sleep(20 * time.Millisecond) 435 typ.AssertExpectations(t) 436 } 437 438 // Test that a type registered with a periodic refresh will perform 439 // that refresh after the timer is up. 440 func TestCacheGet_periodicRefresh(t *testing.T) { 441 t.Parallel() 442 443 typ := TestType(t) 444 defer typ.AssertExpectations(t) 445 c := TestCache(t) 446 c.RegisterType("t", typ, &RegisterOptions{ 447 Refresh: true, 448 RefreshTimer: 100 * time.Millisecond, 449 RefreshTimeout: 5 * time.Minute, 450 }) 451 452 // This is a bit weird, but we do this to ensure that the final 453 // call to the Fetch (if it happens, depends on timing) just blocks. 454 triggerCh := make(chan time.Time) 455 defer close(triggerCh) 456 457 // Configure the type 458 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 459 typ.Static(FetchResult{Value: 12, Index: 5}, nil).Once() 460 typ.Static(FetchResult{Value: 12, Index: 5}, nil).WaitUntil(triggerCh) 461 462 // Fetch should block 463 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 464 TestCacheGetChResult(t, resultCh, 1) 465 466 // Fetch again almost immediately should return old result 467 time.Sleep(5 * time.Millisecond) 468 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 469 TestCacheGetChResult(t, resultCh, 1) 470 471 // Wait for the timer 472 time.Sleep(200 * time.Millisecond) 473 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 474 TestCacheGetChResult(t, resultCh, 12) 475 } 476 477 // Test that a type registered with a periodic refresh will perform 478 // that refresh after the timer is up. 479 func TestCacheGet_periodicRefreshMultiple(t *testing.T) { 480 t.Parallel() 481 482 typ := TestType(t) 483 defer typ.AssertExpectations(t) 484 c := TestCache(t) 485 c.RegisterType("t", typ, &RegisterOptions{ 486 Refresh: true, 487 RefreshTimer: 0 * time.Millisecond, 488 RefreshTimeout: 5 * time.Minute, 489 }) 490 491 // This is a bit weird, but we do this to ensure that the final 492 // call to the Fetch (if it happens, depends on timing) just blocks. 493 trigger := make([]chan time.Time, 3) 494 for i := range trigger { 495 trigger[i] = make(chan time.Time) 496 } 497 498 // Configure the type 499 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 500 typ.Static(FetchResult{Value: 12, Index: 5}, nil).Once().WaitUntil(trigger[0]) 501 typ.Static(FetchResult{Value: 24, Index: 6}, nil).Once().WaitUntil(trigger[1]) 502 typ.Static(FetchResult{Value: 42, Index: 7}, nil).WaitUntil(trigger[2]) 503 504 // Fetch should block 505 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 506 TestCacheGetChResult(t, resultCh, 1) 507 508 // Fetch again almost immediately should return old result 509 time.Sleep(5 * time.Millisecond) 510 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 511 TestCacheGetChResult(t, resultCh, 1) 512 513 // Trigger the next, sleep a bit, and verify we get the next result 514 close(trigger[0]) 515 time.Sleep(100 * time.Millisecond) 516 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 517 TestCacheGetChResult(t, resultCh, 12) 518 519 // Trigger the next, sleep a bit, and verify we get the next result 520 close(trigger[1]) 521 time.Sleep(100 * time.Millisecond) 522 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 523 TestCacheGetChResult(t, resultCh, 24) 524 } 525 526 // Test that a refresh performs a backoff. 527 func TestCacheGet_periodicRefreshErrorBackoff(t *testing.T) { 528 t.Parallel() 529 530 typ := TestType(t) 531 defer typ.AssertExpectations(t) 532 c := TestCache(t) 533 c.RegisterType("t", typ, &RegisterOptions{ 534 Refresh: true, 535 RefreshTimer: 0, 536 RefreshTimeout: 5 * time.Minute, 537 }) 538 539 // Configure the type 540 var retries uint32 541 fetchErr := fmt.Errorf("test fetch error") 542 typ.Static(FetchResult{Value: 1, Index: 4}, nil).Once() 543 typ.Static(FetchResult{Value: nil, Index: 5}, fetchErr).Run(func(args mock.Arguments) { 544 atomic.AddUint32(&retries, 1) 545 }) 546 547 // Fetch 548 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 549 TestCacheGetChResult(t, resultCh, 1) 550 551 // Sleep a bit. The refresh will quietly fail in the background. What we 552 // want to verify is that it doesn't retry too much. "Too much" is hard 553 // to measure since its CPU dependent if this test is failing. But due 554 // to the short sleep below, we can calculate about what we'd expect if 555 // backoff IS working. 556 time.Sleep(500 * time.Millisecond) 557 558 // Fetch should work, we should get a 1 still. Errors are ignored. 559 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 560 TestCacheGetChResult(t, resultCh, 1) 561 562 // Check the number 563 actual := atomic.LoadUint32(&retries) 564 require.True(t, actual < 10, fmt.Sprintf("actual: %d", actual)) 565 } 566 567 // Test that a badly behaved RPC that returns 0 index will perform a backoff. 568 func TestCacheGet_periodicRefreshBadRPCZeroIndexErrorBackoff(t *testing.T) { 569 t.Parallel() 570 571 typ := TestType(t) 572 defer typ.AssertExpectations(t) 573 c := TestCache(t) 574 c.RegisterType("t", typ, &RegisterOptions{ 575 Refresh: true, 576 RefreshTimer: 0, 577 RefreshTimeout: 5 * time.Minute, 578 }) 579 580 // Configure the type 581 var retries uint32 582 typ.Static(FetchResult{Value: 0, Index: 0}, nil).Run(func(args mock.Arguments) { 583 atomic.AddUint32(&retries, 1) 584 }) 585 586 // Fetch 587 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 588 TestCacheGetChResult(t, resultCh, 0) 589 590 // Sleep a bit. The refresh will quietly fail in the background. What we 591 // want to verify is that it doesn't retry too much. "Too much" is hard 592 // to measure since its CPU dependent if this test is failing. But due 593 // to the short sleep below, we can calculate about what we'd expect if 594 // backoff IS working. 595 time.Sleep(500 * time.Millisecond) 596 597 // Fetch should work, we should get a 0 still. Errors are ignored. 598 resultCh = TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 599 TestCacheGetChResult(t, resultCh, 0) 600 601 // Check the number 602 actual := atomic.LoadUint32(&retries) 603 require.True(t, actual < 10, fmt.Sprintf("%d retries, should be < 10", actual)) 604 } 605 606 // Test that fetching with no index makes an initial request with no index, but 607 // then ensures all background refreshes have > 0. This ensures we don't end up 608 // with any index 0 loops from background refreshed while also returning 609 // immediately on the initial request if there is no data written to that table 610 // yet. 611 func TestCacheGet_noIndexSetsOne(t *testing.T) { 612 t.Parallel() 613 614 typ := TestType(t) 615 defer typ.AssertExpectations(t) 616 c := TestCache(t) 617 c.RegisterType("t", typ, &RegisterOptions{ 618 Refresh: true, 619 RefreshTimer: 0, 620 RefreshTimeout: 5 * time.Minute, 621 }) 622 623 // Simulate "well behaved" RPC with no data yet but returning 1 624 { 625 first := int32(1) 626 627 typ.Static(FetchResult{Value: 0, Index: 1}, nil).Run(func(args mock.Arguments) { 628 opts := args.Get(0).(FetchOptions) 629 isFirst := atomic.SwapInt32(&first, 0) 630 if isFirst == 1 { 631 assert.Equal(t, uint64(0), opts.MinIndex) 632 } else { 633 assert.True(t, opts.MinIndex > 0, "minIndex > 0") 634 } 635 }) 636 637 // Fetch 638 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 639 TestCacheGetChResult(t, resultCh, 0) 640 641 // Sleep a bit so background refresh happens 642 time.Sleep(100 * time.Millisecond) 643 } 644 645 // Same for "badly behaved" RPC that returns 0 index and no data 646 { 647 first := int32(1) 648 649 typ.Static(FetchResult{Value: 0, Index: 0}, nil).Run(func(args mock.Arguments) { 650 opts := args.Get(0).(FetchOptions) 651 isFirst := atomic.SwapInt32(&first, 0) 652 if isFirst == 1 { 653 assert.Equal(t, uint64(0), opts.MinIndex) 654 } else { 655 assert.True(t, opts.MinIndex > 0, "minIndex > 0") 656 } 657 }) 658 659 // Fetch 660 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 661 TestCacheGetChResult(t, resultCh, 0) 662 663 // Sleep a bit so background refresh happens 664 time.Sleep(100 * time.Millisecond) 665 } 666 } 667 668 // Test that the backend fetch sets the proper timeout. 669 func TestCacheGet_fetchTimeout(t *testing.T) { 670 t.Parallel() 671 672 require := require.New(t) 673 674 typ := TestType(t) 675 defer typ.AssertExpectations(t) 676 c := TestCache(t) 677 678 // Register the type with a timeout 679 timeout := 10 * time.Minute 680 c.RegisterType("t", typ, &RegisterOptions{ 681 RefreshTimeout: timeout, 682 }) 683 684 // Configure the type 685 var actual time.Duration 686 typ.Static(FetchResult{Value: 42}, nil).Times(1).Run(func(args mock.Arguments) { 687 opts := args.Get(0).(FetchOptions) 688 actual = opts.Timeout 689 }) 690 691 // Get, should fetch 692 req := TestRequest(t, RequestInfo{Key: "hello"}) 693 result, meta, err := c.Get("t", req) 694 require.NoError(err) 695 require.Equal(42, result) 696 require.False(meta.Hit) 697 698 // Test the timeout 699 require.Equal(timeout, actual) 700 } 701 702 // Test that entries expire 703 func TestCacheGet_expire(t *testing.T) { 704 t.Parallel() 705 706 require := require.New(t) 707 708 typ := TestType(t) 709 defer typ.AssertExpectations(t) 710 c := TestCache(t) 711 712 // Register the type with a timeout 713 c.RegisterType("t", typ, &RegisterOptions{ 714 LastGetTTL: 400 * time.Millisecond, 715 }) 716 717 // Configure the type 718 typ.Static(FetchResult{Value: 42}, nil).Times(2) 719 720 // Get, should fetch 721 req := TestRequest(t, RequestInfo{Key: "hello"}) 722 result, meta, err := c.Get("t", req) 723 require.NoError(err) 724 require.Equal(42, result) 725 require.False(meta.Hit) 726 727 // Wait for a non-trivial amount of time to sanity check the age increases at 728 // least this amount. Note that this is not a fudge for some timing-dependent 729 // background work it's just ensuring a non-trivial time elapses between the 730 // request above and below serilaly in this thread so short time is OK. 731 time.Sleep(5 * time.Millisecond) 732 733 // Get, should not fetch, verified via the mock assertions above 734 req = TestRequest(t, RequestInfo{Key: "hello"}) 735 result, meta, err = c.Get("t", req) 736 require.NoError(err) 737 require.Equal(42, result) 738 require.True(meta.Hit) 739 require.True(meta.Age > 5*time.Millisecond) 740 741 // Sleep for the expiry 742 time.Sleep(500 * time.Millisecond) 743 744 // Get, should fetch 745 req = TestRequest(t, RequestInfo{Key: "hello"}) 746 result, meta, err = c.Get("t", req) 747 require.NoError(err) 748 require.Equal(42, result) 749 require.False(meta.Hit) 750 751 // Sleep a tiny bit just to let maybe some background calls happen 752 // then verify that we still only got the one call 753 time.Sleep(20 * time.Millisecond) 754 typ.AssertExpectations(t) 755 } 756 757 // Test that entries reset their TTL on Get 758 func TestCacheGet_expireResetGet(t *testing.T) { 759 t.Parallel() 760 761 require := require.New(t) 762 763 typ := TestType(t) 764 defer typ.AssertExpectations(t) 765 c := TestCache(t) 766 767 // Register the type with a timeout 768 c.RegisterType("t", typ, &RegisterOptions{ 769 LastGetTTL: 150 * time.Millisecond, 770 }) 771 772 // Configure the type 773 typ.Static(FetchResult{Value: 42}, nil).Times(2) 774 775 // Get, should fetch 776 req := TestRequest(t, RequestInfo{Key: "hello"}) 777 result, meta, err := c.Get("t", req) 778 require.NoError(err) 779 require.Equal(42, result) 780 require.False(meta.Hit) 781 782 // Fetch multiple times, where the total time is well beyond 783 // the TTL. We should not trigger any fetches during this time. 784 for i := 0; i < 5; i++ { 785 // Sleep a bit 786 time.Sleep(50 * time.Millisecond) 787 788 // Get, should not fetch 789 req = TestRequest(t, RequestInfo{Key: "hello"}) 790 result, meta, err = c.Get("t", req) 791 require.NoError(err) 792 require.Equal(42, result) 793 require.True(meta.Hit) 794 } 795 796 time.Sleep(200 * time.Millisecond) 797 798 // Get, should fetch 799 req = TestRequest(t, RequestInfo{Key: "hello"}) 800 result, meta, err = c.Get("t", req) 801 require.NoError(err) 802 require.Equal(42, result) 803 require.False(meta.Hit) 804 805 // Sleep a tiny bit just to let maybe some background calls happen 806 // then verify that we still only got the one call 807 time.Sleep(20 * time.Millisecond) 808 typ.AssertExpectations(t) 809 } 810 811 // Test a Get with a request that returns the same cache key across 812 // two different "types" returns two separate results. 813 func TestCacheGet_duplicateKeyDifferentType(t *testing.T) { 814 t.Parallel() 815 816 require := require.New(t) 817 818 typ := TestType(t) 819 defer typ.AssertExpectations(t) 820 typ2 := TestType(t) 821 defer typ2.AssertExpectations(t) 822 823 c := TestCache(t) 824 c.RegisterType("t", typ, nil) 825 c.RegisterType("t2", typ2, nil) 826 827 // Configure the types 828 typ.Static(FetchResult{Value: 100}, nil) 829 typ2.Static(FetchResult{Value: 200}, nil) 830 831 // Get, should fetch 832 req := TestRequest(t, RequestInfo{Key: "foo"}) 833 result, meta, err := c.Get("t", req) 834 require.NoError(err) 835 require.Equal(100, result) 836 require.False(meta.Hit) 837 838 // Get from t2 with same key, should fetch 839 req = TestRequest(t, RequestInfo{Key: "foo"}) 840 result, meta, err = c.Get("t2", req) 841 require.NoError(err) 842 require.Equal(200, result) 843 require.False(meta.Hit) 844 845 // Get from t again with same key, should cache 846 req = TestRequest(t, RequestInfo{Key: "foo"}) 847 result, meta, err = c.Get("t", req) 848 require.NoError(err) 849 require.Equal(100, result) 850 require.True(meta.Hit) 851 852 // Sleep a tiny bit just to let maybe some background calls happen 853 // then verify that we still only got the one call 854 time.Sleep(20 * time.Millisecond) 855 typ.AssertExpectations(t) 856 typ2.AssertExpectations(t) 857 } 858 859 // Test that Get partitions the caches based on DC so two equivalent requests 860 // to different datacenters are automatically cached even if their keys are 861 // the same. 862 func TestCacheGet_partitionDC(t *testing.T) { 863 t.Parallel() 864 865 c := TestCache(t) 866 c.RegisterType("t", &testPartitionType{}, nil) 867 868 // Perform multiple gets 869 getCh1 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 870 Datacenter: "dc1", Key: "hello"})) 871 getCh2 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 872 Datacenter: "dc9", Key: "hello"})) 873 874 // Should return both! 875 TestCacheGetChResult(t, getCh1, "dc1") 876 TestCacheGetChResult(t, getCh2, "dc9") 877 } 878 879 // Test that Get partitions the caches based on token so two equivalent requests 880 // with different ACL tokens do not return the same result. 881 func TestCacheGet_partitionToken(t *testing.T) { 882 t.Parallel() 883 884 c := TestCache(t) 885 c.RegisterType("t", &testPartitionType{}, nil) 886 887 // Perform multiple gets 888 getCh1 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 889 Token: "", Key: "hello"})) 890 getCh2 := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{ 891 Token: "foo", Key: "hello"})) 892 893 // Should return both! 894 TestCacheGetChResult(t, getCh1, "") 895 TestCacheGetChResult(t, getCh2, "foo") 896 } 897 898 // testPartitionType implements Type for testing that simply returns a value 899 // comprised of the request DC and ACL token, used for testing cache 900 // partitioning. 901 type testPartitionType struct{} 902 903 func (t *testPartitionType) Fetch(opts FetchOptions, r Request) (FetchResult, error) { 904 info := r.CacheInfo() 905 return FetchResult{ 906 Value: fmt.Sprintf("%s%s", info.Datacenter, info.Token), 907 }, nil 908 } 909 910 func (t *testPartitionType) SupportsBlocking() bool { 911 return true 912 } 913 914 // Test that background refreshing reports correct Age in failure and happy 915 // states. 916 func TestCacheGet_refreshAge(t *testing.T) { 917 t.Parallel() 918 919 require := require.New(t) 920 921 typ := TestType(t) 922 defer typ.AssertExpectations(t) 923 c := TestCache(t) 924 c.RegisterType("t", typ, &RegisterOptions{ 925 Refresh: true, 926 RefreshTimer: 0, 927 RefreshTimeout: 5 * time.Minute, 928 }) 929 930 // Configure the type 931 var index, shouldFail uint64 932 933 typ.On("Fetch", mock.Anything, mock.Anything). 934 Return(func(o FetchOptions, r Request) FetchResult { 935 idx := atomic.LoadUint64(&index) 936 if atomic.LoadUint64(&shouldFail) == 1 { 937 t.Logf("Failing Fetch at index %d", idx) 938 return FetchResult{Value: nil, Index: idx} 939 } 940 if o.MinIndex == idx { 941 t.Logf("Sleeping Fetch at index %d", idx) 942 // Simulate waiting for a new value 943 time.Sleep(5 * time.Millisecond) 944 } 945 t.Logf("Returning Fetch at index %d", idx) 946 return FetchResult{Value: int(idx * 2), Index: idx} 947 }, func(o FetchOptions, r Request) error { 948 if atomic.LoadUint64(&shouldFail) == 1 { 949 return errors.New("test error") 950 } 951 return nil 952 }) 953 954 // Set initial index/value 955 atomic.StoreUint64(&index, 4) 956 957 // Fetch 958 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 959 TestCacheGetChResult(t, resultCh, 8) 960 961 { 962 // Wait a few milliseconds after initial fetch to check age is not reporting 963 // actual age. 964 time.Sleep(2 * time.Millisecond) 965 966 // Fetch again, non-blocking 967 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 968 require.NoError(err) 969 require.Equal(8, result) 970 require.True(meta.Hit) 971 // Age should be zero since background refresh was "active" 972 require.Equal(time.Duration(0), meta.Age) 973 } 974 975 // Now fail the next background sync 976 atomic.StoreUint64(&shouldFail, 1) 977 978 // Wait until the current request times out and starts failing. The request 979 // should take a maximum of 5ms to return but give it some headroom to allow 980 // it to finish 5ms sleep, unblock and next background request to be attemoted 981 // and fail and state updated in noisy CI... We might want to retry if this is 982 // still flaky but see if a longer wait is sufficient for now. 983 time.Sleep(50 * time.Millisecond) 984 985 var lastAge time.Duration 986 { 987 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 988 require.NoError(err) 989 require.Equal(8, result) 990 require.True(meta.Hit) 991 // Age should be non-zero since background refresh was "active" 992 require.True(meta.Age > 0) 993 lastAge = meta.Age 994 } 995 // Wait a bit longer - age should increase by at least this much 996 time.Sleep(5 * time.Millisecond) 997 { 998 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 999 require.NoError(err) 1000 require.Equal(8, result) 1001 require.True(meta.Hit) 1002 require.True(meta.Age > (lastAge + (1 * time.Millisecond))) 1003 } 1004 1005 // Now unfail the background refresh 1006 atomic.StoreUint64(&shouldFail, 0) 1007 1008 // And update the data so we can see when the background task is working again 1009 // (won't be immediate due to backoff on the errors). 1010 atomic.AddUint64(&index, 1) 1011 1012 t0 := time.Now() 1013 1014 timeout := true 1015 // Allow up to 5 seconds since the error backoff is likely to have kicked in 1016 // and causes this to take different amounts of time depending on how quickly 1017 // the test thread got down here relative to the failures. 1018 for attempts := 0; attempts < 50; attempts++ { 1019 time.Sleep(100 * time.Millisecond) 1020 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 1021 // Should never error even if background is failing as we have cached value 1022 require.NoError(err) 1023 require.True(meta.Hit) 1024 // Got the new value! 1025 if result == 10 { 1026 // Age should be zero since background refresh is "active" again 1027 t.Logf("Succeeded after %d attempts", attempts) 1028 require.Equal(time.Duration(0), meta.Age) 1029 timeout = false 1030 break 1031 } 1032 } 1033 require.False(timeout, "failed to observe update after %s", time.Since(t0)) 1034 } 1035 1036 func TestCacheGet_nonRefreshAge(t *testing.T) { 1037 t.Parallel() 1038 1039 require := require.New(t) 1040 1041 typ := TestType(t) 1042 defer typ.AssertExpectations(t) 1043 c := TestCache(t) 1044 c.RegisterType("t", typ, &RegisterOptions{ 1045 Refresh: false, 1046 LastGetTTL: 100 * time.Millisecond, 1047 }) 1048 1049 // Configure the type 1050 var index uint64 1051 1052 typ.On("Fetch", mock.Anything, mock.Anything). 1053 Return(func(o FetchOptions, r Request) FetchResult { 1054 idx := atomic.LoadUint64(&index) 1055 return FetchResult{Value: int(idx * 2), Index: idx} 1056 }, nil) 1057 1058 // Set initial index/value 1059 atomic.StoreUint64(&index, 4) 1060 1061 // Fetch 1062 resultCh := TestCacheGetCh(t, c, "t", TestRequest(t, RequestInfo{Key: "hello"})) 1063 TestCacheGetChResult(t, resultCh, 8) 1064 1065 var lastAge time.Duration 1066 { 1067 // Wait a few milliseconds after initial fetch to check age IS reporting 1068 // actual age. 1069 time.Sleep(5 * time.Millisecond) 1070 1071 // Fetch again, non-blocking 1072 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 1073 require.NoError(err) 1074 require.Equal(8, result) 1075 require.True(meta.Hit) 1076 require.True(meta.Age > (5 * time.Millisecond)) 1077 lastAge = meta.Age 1078 } 1079 1080 // Wait for expiry 1081 time.Sleep(200 * time.Millisecond) 1082 1083 { 1084 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 1085 require.NoError(err) 1086 require.Equal(8, result) 1087 require.False(meta.Hit) 1088 // Age should smaller again 1089 require.True(meta.Age < lastAge) 1090 } 1091 1092 { 1093 // Wait for a non-trivial amount of time to sanity check the age increases at 1094 // least this amount. Note that this is not a fudge for some timing-dependent 1095 // background work it's just ensuring a non-trivial time elapses between the 1096 // request above and below serilaly in this thread so short time is OK. 1097 time.Sleep(5 * time.Millisecond) 1098 1099 // Fetch again, non-blocking 1100 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{Key: "hello"})) 1101 require.NoError(err) 1102 require.Equal(8, result) 1103 require.True(meta.Hit) 1104 require.True(meta.Age > (5 * time.Millisecond)) 1105 lastAge = meta.Age 1106 } 1107 1108 // Now verify that setting MaxAge results in cache invalidation 1109 { 1110 result, meta, err := c.Get("t", TestRequest(t, RequestInfo{ 1111 Key: "hello", 1112 MaxAge: 1 * time.Millisecond, 1113 })) 1114 require.NoError(err) 1115 require.Equal(8, result) 1116 require.False(meta.Hit) 1117 // Age should smaller again 1118 require.True(meta.Age < lastAge) 1119 } 1120 } 1121 1122 func TestCacheGet_nonBlockingType(t *testing.T) { 1123 t.Parallel() 1124 1125 typ := TestTypeNonBlocking(t) 1126 defer typ.AssertExpectations(t) 1127 c := TestCache(t) 1128 c.RegisterType("t", typ, nil) 1129 1130 // Configure the type 1131 typ.Static(FetchResult{Value: 42, Index: 1}, nil).Once() 1132 typ.Static(FetchResult{Value: 43, Index: 2}, nil).Twice(). 1133 Run(func(args mock.Arguments) { 1134 opts := args.Get(0).(FetchOptions) 1135 // MinIndex should never be set for a non-blocking type. 1136 require.Equal(t, uint64(0), opts.MinIndex) 1137 }) 1138 1139 require := require.New(t) 1140 1141 // Get, should fetch 1142 req := TestRequest(t, RequestInfo{Key: "hello"}) 1143 result, meta, err := c.Get("t", req) 1144 require.NoError(err) 1145 require.Equal(42, result) 1146 require.False(meta.Hit) 1147 1148 // Get, should not fetch since we have a cached value 1149 req = TestRequest(t, RequestInfo{Key: "hello"}) 1150 result, meta, err = c.Get("t", req) 1151 require.NoError(err) 1152 require.Equal(42, result) 1153 require.True(meta.Hit) 1154 1155 // Get, should not attempt to fetch with blocking even if requested. The 1156 // assertions below about the value being the same combined with the fact the 1157 // mock will only return that value on first call suffice to show that 1158 // blocking request is not being attempted. 1159 req = TestRequest(t, RequestInfo{ 1160 Key: "hello", 1161 MinIndex: 1, 1162 Timeout: 10 * time.Minute, 1163 }) 1164 result, meta, err = c.Get("t", req) 1165 require.NoError(err) 1166 require.Equal(42, result) 1167 require.True(meta.Hit) 1168 1169 time.Sleep(10 * time.Millisecond) 1170 1171 // Get with a max age should fetch again 1172 req = TestRequest(t, RequestInfo{Key: "hello", MaxAge: 5 * time.Millisecond}) 1173 result, meta, err = c.Get("t", req) 1174 require.NoError(err) 1175 require.Equal(43, result) 1176 require.False(meta.Hit) 1177 1178 // Get with a must revalidate should fetch again even without a delay. 1179 req = TestRequest(t, RequestInfo{Key: "hello", MustRevalidate: true}) 1180 result, meta, err = c.Get("t", req) 1181 require.NoError(err) 1182 require.Equal(43, result) 1183 require.False(meta.Hit) 1184 1185 // Sleep a tiny bit just to let maybe some background calls happen 1186 // then verify that we still only got the one call 1187 time.Sleep(20 * time.Millisecond) 1188 typ.AssertExpectations(t) 1189 }