TestCachePenetrationPrevention verifies the complete flow: 1. Cache populated 2. Cache expires 3. Registry fails 4. Concurrent requests don't stampede registry due to rate limiting 5. Stale cache is returned without hitting registry
(t *testing.T)
| 226 | // 4. Concurrent requests don't stampede registry due to rate limiting |
| 227 | // 5. Stale cache is returned without hitting registry |
| 228 | func TestCachePenetrationPrevention(t *testing.T) { |
| 229 | mock := &mockRegistry{ |
| 230 | services: []*registry.Service{ |
| 231 | { |
| 232 | Name: "test.service", |
| 233 | Version: "1.0.0", |
| 234 | Nodes: []*registry.Node{ |
| 235 | {Id: "node1", Address: "localhost:9090"}, |
| 236 | }, |
| 237 | }, |
| 238 | }, |
| 239 | } |
| 240 | |
| 241 | // Type assertion to *cache is necessary to access internal state for verification |
| 242 | c := New(mock, func(o *Options) { |
| 243 | o.TTL = 100 * time.Millisecond |
| 244 | o.Logger = logger.DefaultLogger |
| 245 | // Use short retry interval to test rate limiting |
| 246 | o.MinimumRetryInterval = 5 * time.Second |
| 247 | }).(*cache) |
| 248 | |
| 249 | // Initial request to populate cache |
| 250 | _, err := c.GetService("test.service") |
| 251 | if err != nil { |
| 252 | t.Fatalf("Initial request failed: %v", err) |
| 253 | } |
| 254 | |
| 255 | initialCalls := mock.getCallCount() |
| 256 | if initialCalls != 1 { |
| 257 | t.Fatalf("Expected 1 initial call, got %d", initialCalls) |
| 258 | } |
| 259 | |
| 260 | // Wait for cache to expire (but not past retry interval) |
| 261 | time.Sleep(150 * time.Millisecond) |
| 262 | |
| 263 | // Configure mock to fail with delay |
| 264 | mock.err = errors.New("etcd overloaded") |
| 265 | mock.delay = 100 * time.Millisecond |
| 266 | |
| 267 | // Launch many concurrent requests (simulating stampede) |
| 268 | const concurrency = 50 |
| 269 | var wg sync.WaitGroup |
| 270 | wg.Add(concurrency) |
| 271 | |
| 272 | successCount := int32(0) |
| 273 | |
| 274 | for i := 0; i < concurrency; i++ { |
| 275 | go func() { |
| 276 | defer wg.Done() |
| 277 | services, err := c.GetService("test.service") |
| 278 | // Should return stale cache without error (rate limiting prevents registry call) |
| 279 | if err == nil && len(services) > 0 { |
| 280 | atomic.AddInt32(&successCount, 1) |
| 281 | } |
| 282 | }() |
| 283 | } |
| 284 | |
| 285 | wg.Wait() |
nothing calls this directly
no test coverage detected
searching dependent graphs…