Watch
0
0
Fork
You've already forked hyperhive
0

c0re: keep power intent in memory, drop agent_power/kv tables and the stop snapshot

This commit is contained in:
damocles 2026-11-07 01:22:18 +01:00 • committed by mara
commit b77fa9edc3

View file

@ -8,7 +8,7 @@
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use anyhow::{Context as _, Result};
use anyhow::Result;
use swarm_wire::agent_closures::AgentClosure;
use hive_jobq::NodeId;
@ -305,7 +305,10 @@ async fn dispatch(
run_emit_rebuilt(host, coord, agent, coord.job_queue.root_of(id), *ok).await;
Ok(())
}
NodeKind::SetWanted { up, .. } => run_set_wanted(coord, agent, *up),
NodeKind::SetWanted { up, .. } => {
run_set_wanted(coord, agent, *up);
Ok(())
}
// Braces carry no work of their own; completing one lets it reach
// `Finishing` so the nodes under it start. What they declare stays held
// until their whole subtree settles.
@ -425,7 +428,7 @@ async fn run_purge_state(host: &impl Host, agent: &str) {
/// Post-destroy bookkeeping. Infallible by construction: every step is
/// warn-and-continue, because the destroy it follows has already succeeded and
/// none of this is undoable — a failed agent-set refresh or power-store write is a
/// none of this is undoable — a failed agent-set refresh is a
/// bookkeeping drift to log, not a reason to red a DAG whose container is
/// already gone.
async fn run_destroy_bookkeeping(host: &impl Host, coord: &Arc<Coordinator>, agent: &str) {
@ -433,11 +436,9 @@ async fn run_destroy_bookkeeping(host: &impl Host, coord: &Arc<Coordinator>, age
if let Err(e) = host.refresh_agent_set().await {
tracing::warn!(error = ?e, %agent, "agent_set refresh after destroy failed");
}
// Drop the durable power intent — a future agent of the same name seeds
// fresh from its observed state.
if let Err(e) = coord.power.remove(agent) {
tracing::warn!(%agent, error = ?e, "agent_power: remove on destroy failed");
}
// Drop the power intent — a future agent of the same name seeds fresh from
// its observed state.
coord.power.remove(agent);
host.notify(
format!("destroyed:{agent}"),
format!("agent '{agent}' destroyed"),
@ -467,25 +468,18 @@ async fn run_emit_rebuilt(
host.notify(format!("rebuilt:{agent}"), summary).await;
}
/// Write the agent's durable power intent — the DAG-node form of the old
/// pre-submit `set_wanted` side effect. Store-only (no container touch), so
/// Record the agent's power intent — the DAG-node form of the old pre-submit
/// `set_wanted` side effect. Memory-only (no container touch), so
/// build-slot-exempt; but it declares the agent's lifecycle lease
/// (`Resource::Agent`) so the whole power-op DAG is atomic per-agent.
/// The downstream `Reconcile` reads the intent this writes. Unlike the old
/// warn-and-continue write, a failed write fails the node (cancel-downstream
/// cancels the `Reconcile`) rather than letting it converge to a stale
/// intent — that atomicity is the point of moving it into the DAG.
fn run_set_wanted(coord: &Arc<Coordinator>, agent: &str, up: bool) -> Result<()> {
/// The downstream `Reconcile` reads the intent this writes.
fn run_set_wanted(coord: &Arc<Coordinator>, agent: &str, up: bool) {
let wanted = if up {
crate::power::Wanted::Up
} else {
crate::power::Wanted::Offline
};
coord
.power
.set(agent, wanted)
.with_context(|| format!("set wanted={} for agent {agent}", <&str>::from(wanted)))?;
Ok(())
coord.power.set(agent, wanted);
}
/// The rebuild's preamble: runtime-dir prep, the archetype and agent-set
@ -719,7 +713,7 @@ async fn run_reconcile(
name: &str,
) -> Result<Option<NodeKind>> {
let running = host.is_running(name).await;
let wanted = coord.power.get_or_seed(name, running)?;
let wanted = coord.power.get_or_seed(name, running);
Ok(match reconcile_action(wanted, running) {
ReconcileAction::Start => Some(NodeKind::Start {
agent: name.to_owned(),
@ -780,13 +774,11 @@ async fn run_stop(host: &impl Host, coord: &Arc<Coordinator>, name: &str) -> Res
/// noop when already stopped.
async fn run_stop_for_update(host: &impl Host, coord: &Arc<Coordinator>, name: &str) -> Result<()> {
if host.is_running(name).await {
// Seed a missing agent_power row from the PRE-stop observation
// Seed a missing intent from the PRE-stop observation
// — the DAG's tail `Reconcile` observes only the mechanically
// stopped state and would otherwise seed a running-but-unknown
// agent as `Offline`, stranding it down after its own rebuild.
if let Err(e) = coord.power.get_or_seed(name, true) {
tracing::warn!(%name, error = ?e, "agent_power: pre-stop seed failed");
}
coord.power.get_or_seed(name, true);
host.kill(name).await?;
coord.rescan_containers_and_emit().await;
}