fix(container): an absent container is not proof of uninstallation
installed_app_ids judged installation on live containers alone. Watched on archi-dev-box within the hour: lnd read as ABSENT, then as EXISTS again. Containers on this node come and go — the boot reconciler logs "previously-running app has no container after boot — recreating" for bitcoin-knots and electrumx repeatedly — so a momentary gap looked exactly like a removal, and the reaper would have taken a healthy companion's unit with it. ORPHAN_GRACE narrows that window but cannot close it: nothing bounds how long a gap lasts. An app now counts as installed if its container exists in any state OR its container name is in the durable last-running snapshot. That snapshot is what crash_recovery itself calls "installation evidence" and what reconcile_all_with_mode already trusts to recreate a previously-running app whose container vanished — the same signal, for the same reason, now shared rather than reinvented. Only fedimint is a true orphan on this box: it appears in no adoption list and has no quadlet unit of its own. lnd is installed and merely flapping, which is a separate bug. Container suite 215/215. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7567c25c00
commit
adc3c444cd
@@ -1686,14 +1686,27 @@ impl ProdContainerOrchestrator {
|
||||
/// from `Some(vec![])`: a caller that removes things on absence must not
|
||||
/// treat "I could not look" as "nothing is installed".
|
||||
///
|
||||
/// The presence of a container — in ANY state — is the whole test. It
|
||||
/// deliberately does NOT carry over the `user_stopped` / `disabled`
|
||||
/// filters the old `manifest_ids` applied: a stopped app is still an
|
||||
/// installed app, its container still
|
||||
/// exists (exited), and treating it as uninstalled would make stopping an
|
||||
/// app tear its companion down and rebuild it on the next start. "Is it
|
||||
/// installed" and "is it currently meant to be running" are different
|
||||
/// questions, and only the first one belongs here.
|
||||
/// An app counts as installed if its container exists in ANY state, OR its
|
||||
/// container name appears in the durable last-running snapshot.
|
||||
///
|
||||
/// Both halves are load-bearing. The snapshot is what `crash_recovery`
|
||||
/// itself calls "installation evidence" and what `reconcile_all_with_mode`
|
||||
/// already trusts to recreate a previously-running app whose container
|
||||
/// vanished. It is needed here because a container's absence is NOT proof
|
||||
/// of uninstallation: containers on this node demonstrably come and go —
|
||||
/// `lnd` read as absent and then present again within the hour, and the
|
||||
/// boot reconciler logs "previously-running app has no container after
|
||||
/// boot — recreating" for bitcoin-knots and electrumx repeatedly. Judging
|
||||
/// on live containers alone would let a momentary gap look like a removal
|
||||
/// and cost a healthy companion its unit.
|
||||
///
|
||||
/// This also deliberately does NOT carry over the `user_stopped` /
|
||||
/// `disabled` filters the old `manifest_ids` applied: a stopped app is
|
||||
/// still an installed app, its container still exists (exited), and
|
||||
/// treating it as uninstalled would make stopping an app tear its
|
||||
/// companion down and rebuild it on the next start. "Is it installed" and
|
||||
/// "is it currently meant to be running" are different questions, and only
|
||||
/// the first one belongs here.
|
||||
pub async fn installed_app_ids(&self) -> Option<Vec<String>> {
|
||||
let present: std::collections::HashSet<String> = self
|
||||
.runtime
|
||||
@@ -1703,12 +1716,16 @@ impl ProdContainerOrchestrator {
|
||||
.into_iter()
|
||||
.map(|c| c.name)
|
||||
.collect();
|
||||
let was_running = crate::crash_recovery::load_last_running_names(&self.data_dir).await;
|
||||
let state = self.state.read().await;
|
||||
Some(
|
||||
state
|
||||
.manifests
|
||||
.iter()
|
||||
.filter(|(_, lm)| present.contains(&compute_container_name(&lm.manifest)))
|
||||
.filter(|(_, lm)| {
|
||||
let container = compute_container_name(&lm.manifest);
|
||||
present.contains(&container) || was_running.contains(&container)
|
||||
})
|
||||
.map(|(app_id, _)| app_id.clone())
|
||||
.collect(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user