Watch
0
0
Fork
You've already forked hyperhive
0

hive-c0re: bound the read-only container listing and systemctl status calls

priv_client::list_containers now waits at most 30s (LIST_WAIT) for
hive-priv's `nixos-container list`, via the existing `within` helper. Its
periodic callers (crash_watch, agent_sockets, swarm_agent_status, wanted,
mcp_sockets, swarm_status via container_view) already treat an Err as a
skipped cycle, so a hung listing no longer stops their loops for good.

lifecycle::is_running / unit_state run `systemctl is-active` with
kill_on_drop and a 10s timeout (IS_ACTIVE_TIMEOUT); a timeout warns and
reads as not-running / UnitState::Other, the same as a systemctl that
could not be spawned. The new lifecycle::running_state returns None for
that case, and crash_watch keeps the agent's previous observation on None
so a hung systemctl is neither reported as a crash nor as a recovery.

`call` itself and every mutating op (start/stop/destroy/send-snapshot/
daemon-reload) stay unbounded.

Closes #5061
This commit is contained in:
atlas 2026-10-09 15:30:32 +02:00
commit 3cb7be29cf
5 changed files with 106 additions and 19 deletions

View file

@ -45,7 +45,8 @@ pub fn spawn(coord: Arc<Coordinator>) {
};
let logical = logical.to_owned();
sub_agents.push(logical.clone());
if lifecycle::is_running(&logical).await {
let state = lifecycle::running_state(&logical).await;
if counts_as_running(state, prev_running.contains(&logical)) {
current_running.insert(logical.clone());
}
if hive_types::Ident::parse(&logical)
@ -114,6 +115,12 @@ fn plan_cycle(list_result: anyhow::Result<Vec<String>>) -> CyclePlan {
}
}
/// An unreadable unit state (`None`) keeps the agent's last observed state,
/// so a hung or failed `systemctl` is neither a crash nor a recovery.
fn counts_as_running(state: Option<bool>, was_running: bool) -> bool {
state.unwrap_or(was_running)
}
fn emit_crash_transitions(coord: &Coordinator, prev: &HashSet<String>, current: &HashSet<String>) {
let transients = coord.transient_snapshot();
// Operator actions whose RAII guard already cleared but only just;
@ -234,6 +241,15 @@ mod tests {
}
}
#[test]
fn unreadable_unit_state_keeps_the_last_observation() {
assert!(counts_as_running(None, true), "no crash on a timeout");
assert!(!counts_as_running(None, false), "no recovery on a timeout");
// Control: a readable stop still drops out of the running set.
assert!(!counts_as_running(Some(false), true));
assert!(counts_as_running(Some(true), false));
}
#[test]
fn deliberate_when_either_source_says_so() {
// Active guard, and the race repro: a lifecycle action completes and