feat(#2484): unify in-DAG growth on append_subgraph (drop append_node)
append_subgraph is the multi-node/multi-agent generalisation of the single-node append_node, so the two in-DAG-growth channels collapse to one: the Reconcile planner now emits its mechanical Start/Stop as a single-node append_subgraph rooted on the reconcile node (stamping claim.agent on the NodeSpec, which append_node inherited implicitly). Removes NodeOutput.append_nodes + its scheduler drain loop and JobQueue::append_node. No behaviour change — a channel unification.
This commit is contained in:
parent
6c4ef5f798
commit
b87eac0a61
3 changed files with 48 additions and 104 deletions
|
|
@ -27,24 +27,19 @@ pub const GRACEFUL_STOP_TIMEOUT: std::time::Duration = std::time::Duration::from
|
|||
/// success.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NodeOutput {
|
||||
/// Mechanical sub-step nodes to append into *this same* DAG at
|
||||
/// runtime, each depending `AfterOk` on the emitting node — e.g. a
|
||||
/// `Reconcile` planner emitting a `Start` / `Stop`. Keeps the sub-step a
|
||||
/// first-class node in the same DAG so the lease-window transient is held
|
||||
/// across it. The scheduler applies these *before* the emitting node's
|
||||
/// completion so the DAG never rolls terminal with the appended work
|
||||
/// still pending.
|
||||
pub append_nodes: Vec<NodeKind>,
|
||||
/// Whole per-agent *subgraphs* to append into *this same* DAG at
|
||||
/// runtime — the multi-node generalisation of `append_nodes`. Each
|
||||
/// inner `Vec<NodeSpec>` is one independent subgraph whose `deps` are
|
||||
/// local (0-based within that subgraph); the scheduler appends each via
|
||||
/// runtime — the single in-DAG-growth channel. Each inner
|
||||
/// `Vec<NodeSpec>` is one independent subgraph whose `deps` are local
|
||||
/// (0-based within that subgraph); the scheduler appends each via
|
||||
/// [`JobQueue::append_subgraph`], which rebases the deps onto the DAG's
|
||||
/// node-id space and roots the subgraph on the emitting node. Both
|
||||
/// `MetaLock` flavours use this to grow one rebuild subgraph per agent
|
||||
/// into their own DAG (the startup sweep's stale agents; the meta-update
|
||||
/// cascade's affected agents) instead of fanning out child DAGs. Same
|
||||
/// before-completion ordering as `append_nodes`.
|
||||
/// node-id space and roots the subgraph on the emitting node. Used both
|
||||
/// for the multi-node case (`MetaLock` growing one rebuild subgraph per
|
||||
/// agent — the startup sweep's stale agents, the meta-update cascade's
|
||||
/// affected agents) and the single-node case (a `Reconcile` planner
|
||||
/// emitting its mechanical `Start` / `Stop` as a one-node subgraph). The
|
||||
/// scheduler applies these *before* the emitting node's completion so the
|
||||
/// DAG never rolls terminal with the appended work still pending — keeping
|
||||
/// the lease-window transient held across the sub-step.
|
||||
pub append_subgraph: Vec<Vec<NodeSpec>>,
|
||||
}
|
||||
|
||||
|
|
@ -293,10 +288,7 @@ async fn run_meta_lock(
|
|||
.iter()
|
||||
.map(|agent| super::templates::rebuild_nodes(agent, true, 0))
|
||||
.collect();
|
||||
return Ok(NodeOutput {
|
||||
append_subgraph,
|
||||
..Default::default()
|
||||
});
|
||||
return Ok(NodeOutput { append_subgraph });
|
||||
}
|
||||
let _progress = coord.meta_update_guard();
|
||||
ctx.step("nix flake update");
|
||||
|
|
@ -320,35 +312,34 @@ async fn run_meta_lock(
|
|||
.iter()
|
||||
.map(|agent| super::templates::rebuild_nodes(agent, false, 0))
|
||||
.collect();
|
||||
Ok(NodeOutput {
|
||||
append_subgraph,
|
||||
..Default::default()
|
||||
})
|
||||
Ok(NodeOutput { append_subgraph })
|
||||
}
|
||||
|
||||
/// Idempotent power-converge *planner*: compare `wanted` (durable
|
||||
/// intent) against observed state and, when they diverge, fan the
|
||||
/// mechanical `Start` / `Stop` out as a first-class node appended to
|
||||
/// *this* DAG (`NodeOutput::append_nodes`). Does no container work
|
||||
/// itself — the sub-step becomes visible in the DAG and the
|
||||
/// lease-window transient (or the sub-step's own node-local guard)
|
||||
/// rides across it.
|
||||
/// *this* DAG (a single-node `NodeOutput::append_subgraph` rooted on
|
||||
/// this node). Does no container work itself — the sub-step becomes
|
||||
/// visible in the DAG and the lease-window transient (or the sub-step's
|
||||
/// own node-local guard) rides across it.
|
||||
async fn run_reconcile(coord: &Arc<Coordinator>, claim: &Claim) -> Result<NodeOutput> {
|
||||
let name = &claim.agent;
|
||||
let running = crate::lifecycle::is_running(name).await;
|
||||
let wanted = coord.power.get_or_seed(name, running)?;
|
||||
let append_nodes = match reconcile_action(wanted, running) {
|
||||
ReconcileAction::Start => vec![NodeKind::Start],
|
||||
ReconcileAction::Stop => vec![NodeKind::Stop],
|
||||
// One node targeting this agent, rooted on this reconcile node. The old
|
||||
// `append_node` inherited the emitter's agent implicitly; `append_subgraph`
|
||||
// carries it on the `NodeSpec`, so stamp `claim.agent` explicitly (same
|
||||
// effect, one in-DAG-growth channel instead of two).
|
||||
let sub = |kind| vec![vec![super::templates::node(name, kind, Vec::new())]];
|
||||
let append_subgraph = match reconcile_action(wanted, running) {
|
||||
ReconcileAction::Start => sub(NodeKind::Start),
|
||||
ReconcileAction::Stop => sub(NodeKind::Stop),
|
||||
ReconcileAction::Noop => {
|
||||
tracing::debug!(%name, wanted = wanted.as_str(), running, "reconcile: noop");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
Ok(NodeOutput {
|
||||
append_nodes,
|
||||
..Default::default()
|
||||
})
|
||||
Ok(NodeOutput { append_subgraph })
|
||||
}
|
||||
|
||||
/// Mechanical container start — the sub-step a `Reconcile` planner fans
|
||||
|
|
|
|||
Loading…
Reference in a new issue