diff --git a/hive-c0re/src/socket_server/lifecycle_handlers.rs b/hive-c0re/src/socket_server/lifecycle_handlers.rs index 29182a69..92a9312f 100644 --- a/hive-c0re/src/socket_server/lifecycle_handlers.rs +++ b/hive-c0re/src/socket_server/lifecycle_handlers.rs @@ -158,23 +158,8 @@ pub(super) fn handle_update(coord: &Arc, agent: &str, name: &str) - /// `ListDescendants` — every topological descendant of `agent` with /// its running/stopped state, parents before children. -pub(super) async fn handle_list_descendants(agent: &str) -> Response { +pub(super) async fn handle_list_descendants(coord: &Arc, agent: &str) -> Response { tracing::debug!(%agent, "agent: list descendants"); - // All containers known to nixos-container (running only). - let running_set: std::collections::HashSet = match crate::lifecycle::list().await { - Ok(names) => names - .into_iter() - .filter_map(|c| { - c.strip_prefix(crate::lifecycle::AGENT_PREFIX) - .map(str::to_owned) - }) - .collect(), - Err(e) => { - return Response::Err { - message: format!("list containers failed: {e:#}"), - }; - } - }; // Walk the full topology and collect every descendant. let topo = crate::topology::read(); let mut names: Vec = topo @@ -184,10 +169,27 @@ pub(super) async fn handle_list_descendants(agent: &str) -> Response { .collect(); // Parents before children, then alpha within each tier. crate::auto_update::topology_sort(&mut names, &topo); + // Read from the coordinator's cached container snapshot instead of + // live-querying each container's systemd unit state — the same + // `containers_snapshot()` the dashboard's `/api/state` cold-load path + // already uses, kept fresh by `rescan_containers_and_emit()` on every + // mutation plus the crash-watcher's periodic poll. Avoids N + // `systemctl is-active` subprocess spawns per `list_containers` call; + // per mara, daemons should do the expensive work themselves and serve + // clients a cheap cached read. + let snapshot = coord.containers_snapshot().await; + let running_by_name: std::collections::HashMap<&str, bool> = snapshot + .iter() + .map(|v| (v.name.as_str(), v.running)) + .collect(); let containers = names .into_iter() .map(|name| { - let running = running_set.contains(&name); + // A descendant absent from the snapshot (not yet scanned since + // its own registration, e.g. mid-spawn) reads as not running + // rather than erroring — matches the old membership-check's + // default-false behavior for an unknown name. + let running = running_by_name.get(name.as_str()).copied().unwrap_or(false); hive_sh4re::ContainerInfo { name, running } }) .collect(); diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index e9cfc3d0..c2f65173 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -574,7 +574,7 @@ async fn dispatch(req: &Request, agent: &str, coord: &Arc) -> Respo Request::Restart { name } => handle_restart(coord, agent, name).await, Request::Kill { name } => handle_kill(coord, agent, name).await, Request::Update { name } => handle_update(coord, agent, name), - Request::ListDescendants => handle_list_descendants(agent).await, + Request::ListDescendants => handle_list_descendants(coord, agent).await, Request::RequestInitConfig { name, description } => { handle_request_init_config(coord, agent, name, description.clone()) }