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:
parent
d1ee3d8cc5
commit
3cb7be29cf
5 changed files with 106 additions and 19 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue