Summary
run_periodic_cleanup creates its interval with tokio::time::interval(...) and never sets a missed-tick behavior, so it inherits MissedTickBehavior::Burst and fires an immediate tick at startup. The sibling run_relay_status_refresher in the same file deliberately sets MissedTickBehavior::Delay — the inconsistency is the tell that this was an oversight.
Location
src/app.rs:303 — let mut interval = tokio::time::interval(Duration::from_secs(60)); (no set_missed_tick_behavior).
- Contrast
src/app.rs:325-326 — the refresher sets interval.set_missed_tick_behavior(MissedTickBehavior::Delay).
Root cause
tokio::time::interval defaults to Burst, and its first tick() always resolves immediately. So cleanup() runs once right at task startup (redundant on a fresh/empty cache), and after any scheduling stall longer than the 60s period the interval fires catch-up ticks back-to-back with no spacing. cleanup() takes the durable-dedup write lock across unrelated sweeps (see #304), so a burst of catch-up cleanups compounds that contention instead of spreading it out.
Trigger scenario
Runtime starvation (e.g. a saturated single-threaded executor under an unwrap flood, cf. #61 / #239) delays the cleanup task > 60s. When it resumes, Burst runs several cleanup() passes in immediate succession, each grabbing the dedup write lock and serializing concurrent mark_seen appends.
Suggested fix
Set interval.set_missed_tick_behavior(MissedTickBehavior::Delay) to match the refresher, and (optionally) consume the immediate first tick if the startup pass is unwanted.
Why this is not a duplicate
#304 / #123 / #20 concern cleanup's internal lock scope and LRU scan efficiency; #240 concerns supervision of the task. None address the interval's missed-tick policy or the redundant immediate startup tick.
Summary
run_periodic_cleanupcreates its interval withtokio::time::interval(...)and never sets a missed-tick behavior, so it inheritsMissedTickBehavior::Burstand fires an immediate tick at startup. The siblingrun_relay_status_refresherin the same file deliberately setsMissedTickBehavior::Delay— the inconsistency is the tell that this was an oversight.Location
src/app.rs:303—let mut interval = tokio::time::interval(Duration::from_secs(60));(noset_missed_tick_behavior).src/app.rs:325-326— the refresher setsinterval.set_missed_tick_behavior(MissedTickBehavior::Delay).Root cause
tokio::time::intervaldefaults toBurst, and its firsttick()always resolves immediately. Socleanup()runs once right at task startup (redundant on a fresh/empty cache), and after any scheduling stall longer than the 60s period the interval fires catch-up ticks back-to-back with no spacing.cleanup()takes the durable-dedup write lock across unrelated sweeps (see #304), so a burst of catch-up cleanups compounds that contention instead of spreading it out.Trigger scenario
Runtime starvation (e.g. a saturated single-threaded executor under an unwrap flood, cf. #61 / #239) delays the cleanup task > 60s. When it resumes,
Burstruns severalcleanup()passes in immediate succession, each grabbing the dedup write lock and serializing concurrentmark_seenappends.Suggested fix
Set
interval.set_missed_tick_behavior(MissedTickBehavior::Delay)to match the refresher, and (optionally) consume the immediate first tick if the startup pass is unwanted.Why this is not a duplicate
#304 / #123 / #20 concern cleanup's internal lock scope and LRU scan efficiency; #240 concerns supervision of the task. None address the interval's missed-tick policy or the redundant immediate startup tick.