From 4835ca8c91a03b8ae5f1e99caf761d29d498f1cc Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 1 Aug 2026 14:54:47 +0200 Subject: [PATCH] hive-c0re: list_descendants reads cached container snapshot instead of live systemctl calls --- .../src/socket_server/lifecycle_handlers.rs | 37 +++++++++++++------ hive-c0re/src/socket_server/mod.rs | 2 +- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/hive-c0re/src/socket_server/lifecycle_handlers.rs b/hive-c0re/src/socket_server/lifecycle_handlers.rs index 997558e0..92a9312f 100644 --- a/hive-c0re/src/socket_server/lifecycle_handlers.rs +++ b/hive-c0re/src/socket_server/lifecycle_handlers.rs @@ -158,7 +158,7 @@ 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"); // Walk the full topology and collect every descendant. let topo = crate::topology::read(); @@ -169,16 +169,29 @@ 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); - // Query each container's actual systemd unit state (same source - // `container_view::build_all` uses for the dashboard). `nixos-container - // list` — what this used to key off of via a "known containers" set — - // reports every *registered* container, stopped and ghost-registered - // (machined still holds the name after a crashed/killed process) ones - // included, so membership there is not the same thing as "running". - let mut containers = Vec::with_capacity(names.len()); - for name in names { - let running = crate::lifecycle::is_running(&name).await; - containers.push(hive_sh4re::ContainerInfo { name, running }); - } + // 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| { + // 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(); Response::Containers { containers } } 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()) }