diff --git a/core/archipelago/src/container/prod_orchestrator.rs b/core/archipelago/src/container/prod_orchestrator.rs index 269a38e5..d3e0e23a 100644 --- a/core/archipelago/src/container/prod_orchestrator.rs +++ b/core/archipelago/src/container/prod_orchestrator.rs @@ -301,6 +301,33 @@ fn unrepairable_ownership() -> &'static std::sync::Mutex bool { + const SWEEP_INTERVAL: std::time::Duration = std::time::Duration::from_secs(60 * 60); + static LAST: std::sync::OnceLock< + std::sync::Mutex>, + > = std::sync::OnceLock::new(); + let map = LAST.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())); + let Ok(mut map) = map.lock() else { + return true; + }; + let now = std::time::Instant::now(); + match map.get(name) { + Some(last) if now.duration_since(*last) < SWEEP_INTERVAL => false, + _ => { + map.insert(name.to_string(), now); + true + } + } +} + /// App-agnostic, userns-mapping-proof volume-ownership repair for a RUNNING /// container. /// @@ -1739,6 +1766,11 @@ impl ProdContainerOrchestrator { if crate::app_ops::lifecycle_op_in_flight(&c.name) { continue; } + // Throttled: first pass after the container appears, then + // hourly — not on every 30s tick (see ownership_sweep_due). + if !ownership_sweep_due(&c.name) { + continue; + } if ensure_running_container_ownership(&c.name).await { tracing::info!(container = %c.name, "volume ownership repaired during reconcile — restarting to recover"); let _ = tokio::process::Command::new("podman")