max / makenotwork
1 file changed,
+8 insertions,
-3 deletions
| @@ -323,10 +323,15 @@ pub async fn idempotency_middleware( | |||
| 323 | 323 | .map(|e| e.elapsed().as_secs() < NEG_TTL_SECS) | |
| 324 | 324 | .unwrap_or(false); | |
| 325 | 325 | ||
| 326 | - | // Periodic GC to keep the map bounded under sustained burst (every ~1k | |
| 327 | - | // misses we walk the map and drop expired entries). | |
| 326 | + | // Periodic GC to keep the map bounded: sweep every ~1k misses, OR whenever | |
| 327 | + | // the map exceeds a hard size cap. The tick alone let a flood of unique | |
| 328 | + | // keys (each missing exactly once) sit up to 1024-deep between sweeps; the | |
| 329 | + | // size trigger bounds memory under that pattern regardless of tick phase | |
| 330 | + | // (ultra-fuzz Run #1 Perf LOW). | |
| 331 | + | const NEG_CACHE_MAX_ENTRIES: usize = 8192; | |
| 328 | 332 | static GC_TICK: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); | |
| 329 | - | if GC_TICK.fetch_add(1, std::sync::atomic::Ordering::Relaxed).is_multiple_of(1024) { | |
| 333 | + | let tick = GC_TICK.fetch_add(1, std::sync::atomic::Ordering::Relaxed); | |
| 334 | + | if tick.is_multiple_of(1024) || neg_cache.len() > NEG_CACHE_MAX_ENTRIES { | |
| 330 | 335 | neg_cache.retain(|_, t| t.elapsed().as_secs() < NEG_TTL_SECS); | |
| 331 | 336 | } | |
| 332 | 337 |