v0.30.0
This commit is contained in:
1 parent
1431dd47f0
commit
f3dd34dae4
42 files changed
+950
-133
No files matched your search
Vendored
+14
@@ -103,6 +103,20 @@ impl Cache {
|
||||
{
|
||||
let mut store = self.store.lock().unwrap_or_else(|e| e.into_inner());
|
||||
store.generation = store.generation.wrapping_add(1);
|
||||
// This global publication barrier invalidates every older
|
||||
// ticket and pending COLD demotion. Per-key generations only
|
||||
// need to cover the current purge, not every key ever
|
||||
// purged over the lifetime of the process. Without clearing
|
||||
// them, repeated PURGEs of distinct URLs grow this map
|
||||
// indefinitely even after the objects have been evicted.
|
||||
// Release an unusually large index's backing allocation
|
||||
// after mass invalidations instead of retaining its peak
|
||||
// bucket capacity for the rest of the process lifetime.
|
||||
if store.key_generations.capacity() > 256 {
|
||||
store.key_generations = Default::default();
|
||||
} else {
|
||||
store.key_generations.clear();
|
||||
}
|
||||
}
|
||||
let affected = self.purge_matcher(&matcher);
|
||||
self.counters.purges.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
+74
@@ -395,3 +395,77 @@
|
||||
confirmed_prefix.confirm_wide = true;
|
||||
assert!(cache.invalidate(confirmed_prefix).unwrap().ban_id.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn many_distinct_selective_purges_do_not_retain_tombstones_forever() {
|
||||
let cache = Cache::new(22);
|
||||
store(
|
||||
&cache,
|
||||
"app",
|
||||
"memory.example",
|
||||
"/stable",
|
||||
HeaderMap::new(),
|
||||
&[],
|
||||
"unchanged",
|
||||
)
|
||||
.await;
|
||||
// A production cache may see an unbounded stream of PURGEs for URLs
|
||||
// that will never be cached again. The invalidated-key index must
|
||||
// not remember all of those removed objects indefinitely.
|
||||
for index in 0..512 {
|
||||
let target = format!("/unique-{index}");
|
||||
let old = store(
|
||||
&cache,
|
||||
"app",
|
||||
"memory.example",
|
||||
&target,
|
||||
HeaderMap::new(),
|
||||
&[],
|
||||
"x",
|
||||
)
|
||||
.await;
|
||||
assert!(cache.ticket_current(&old));
|
||||
let result = cache
|
||||
.invalidate(invalidation(
|
||||
InvalidationOperation::Purge,
|
||||
InvalidationSelector::Exact,
|
||||
&target,
|
||||
Some("memory.example"),
|
||||
Some("app"),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(result.affected_entries, 1);
|
||||
assert!(!cache.ticket_current(&old), "pre-purge ticket must remain stale");
|
||||
let state = cache.store.lock().unwrap_or_else(|e| e.into_inner());
|
||||
assert!(
|
||||
state.key_generations.len() <= 1,
|
||||
"key generations must not accumulate for already purged URLs"
|
||||
);
|
||||
}
|
||||
// A new ticket after the invalidation barrier is valid and can fill
|
||||
// the same URL again, preserving ordinary cache behavior.
|
||||
let new = store(
|
||||
&cache,
|
||||
"app",
|
||||
"memory.example",
|
||||
"/unique-0",
|
||||
HeaderMap::new(),
|
||||
&[],
|
||||
"new",
|
||||
)
|
||||
.await;
|
||||
assert!(cache.ticket_current(&new));
|
||||
assert!(cache.get(&new).is_some());
|
||||
let stable = cache
|
||||
.ticket_scoped(
|
||||
22,
|
||||
"app",
|
||||
"http",
|
||||
"memory.example",
|
||||
"origin@127.0.0.1:8080",
|
||||
"/stable",
|
||||
&HeaderMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(cache.get(&stable).is_some(), "unrelated entries must survive");
|
||||
}
|
||||
Reference in new issue
Block a user