hive-c0re: list_descendants reads cached container snapshot instead of live systemctl calls

This commit is contained in:
damocles 2026-08-01 14:54:47 +02:00 committed by mara
commit 4835ca8c91
2 changed files with 26 additions and 13 deletions

View file

@ -158,7 +158,7 @@ pub(super) fn handle_update(coord: &Arc<Coordinator>, 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<Coordinator>, 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 }
}

View file

@ -574,7 +574,7 @@ async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> 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())
}