( ctx context.Context, candidates []K, refreshFromOrigin func(context.Context) (V, K, error), op func(error) Op, )
| 472 | } |
| 473 | |
| 474 | func (c *cache[K, V]) SWRWithFallback( |
| 475 | ctx context.Context, |
| 476 | candidates []K, |
| 477 | refreshFromOrigin func(context.Context) (V, K, error), |
| 478 | op func(error) Op, |
| 479 | ) (V, CacheHit, error) { |
| 480 | start := time.Now() |
| 481 | now := c.clock.Now() |
| 482 | |
| 483 | // Check all candidate keys for cache hits |
| 484 | for _, key := range candidates { |
| 485 | e, ok := c.get(ctx, key) |
| 486 | if !ok { |
| 487 | continue |
| 488 | } |
| 489 | |
| 490 | // Found in cache |
| 491 | if now.Before(e.Fresh) { |
| 492 | // Fresh - return immediately |
| 493 | c.recordTiming(ctx, "cache_swr_fallback", "fresh", start) |
| 494 | return e.Value, e.Hit, nil |
| 495 | } |
| 496 | |
| 497 | if now.Before(e.Stale) { |
| 498 | // Stale - return but queue background revalidation with deduplication |
| 499 | c.inflightMu.Lock() |
| 500 | if !c.inflightRefreshes[key] { |
| 501 | c.inflightRefreshes[key] = true |
| 502 | dedupeKey := key // capture for closure |
| 503 | c.revalidateC <- func() { |
| 504 | c.revalidateWithCanonicalKey(context.WithoutCancel(ctx), dedupeKey, refreshFromOrigin, op) |
| 505 | } |
| 506 | } |
| 507 | c.inflightMu.Unlock() |
| 508 | c.recordTiming(ctx, "cache_swr_fallback", "stale", start) |
| 509 | return e.Value, e.Hit, nil |
| 510 | } |
| 511 | |
| 512 | // Expired - delete and continue checking other candidates |
| 513 | c.otter.Delete(key) |
| 514 | } |
| 515 | |
| 516 | // Cache miss on all candidates - fetch from origin |
| 517 | v, canonicalKey, err := refreshFromOrigin(ctx) |
| 518 | c.recordTiming(ctx, "cache_swr_fallback", "miss", start) |
| 519 | |
| 520 | operation := op(err) |
| 521 | |
| 522 | if err != nil { |
| 523 | var zero V |
| 524 | return zero, Miss, err |
| 525 | } |
| 526 | |
| 527 | var hit CacheHit |
| 528 | switch operation { |
| 529 | case WriteValue: |
| 530 | c.Set(ctx, canonicalKey, v) |
| 531 | hit = Hit |
nothing calls this directly
no test coverage detected