job_queue: drop Claim, claim_ready and the completion wrappers
c0re's run loop now goes through hive_jobq's claim_next seam, so the host layer no longer needs its own claim/complete vocabulary. exec::run_node takes (NodeId, &NodeKind) instead of a &Claim snapshot. The agent already rides the payload, and the DAG id is a derived read (JobQueue::dag_of) that only three arms want, so it is taken per-arm rather than eagerly for every node. Two arms (WritePermFile, Reparent) re-matched the kind behind a bail! that could never fire; the match arm already destructures the payload, so they take it directly now. Deleted from the c0re layer: - struct Claim - JobQueue::claim_ready - JobQueue::complete_node / complete_node_growing - scheduler::NodeDone / handle_completion Completion happens inside the future claim_next hands back, so "ran the node but forgot to complete it" is not expressible on the production path any more. The node done / node failed logging moved with it -- it lived in handle_completion but is not dead code. claim_ready and the completion wrappers were left with no non-test callers, so the tests carry them as ClaimReady / CompleteNode extension traits over the crate primitives. JobQueue::new_job stays: run_worker still mints an empty builder on a failed outcome.
This commit is contained in:
parent
be1060e52f
commit
ab53f6710d
4 changed files with 263 additions and 255 deletions
|
|
@ -88,18 +88,6 @@ pub struct RunningTransient {
|
|||
pub since: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// A node claimed for execution — everything the executor needs, snapshotted at
|
||||
/// claim time.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Claim {
|
||||
pub dag_id: u64,
|
||||
pub node_id: NodeId,
|
||||
pub kind: NodeKind,
|
||||
/// The agent this node targets (its own, not a DAG-level field). Empty for
|
||||
/// the agentless [`NodeKind::MetaLock`] + [`NodeKind::Dag`] container nodes.
|
||||
pub agent: String,
|
||||
}
|
||||
|
||||
/// An owned read-view of a DAG container's carried metadata ([`NodeKind::Dag`]).
|
||||
/// Derived on read from the container node — the data has a single home (the
|
||||
/// node payload); this is not a stored side-table.
|
||||
|
|
@ -245,88 +233,41 @@ impl JobQueue {
|
|||
Ok(container.get())
|
||||
}
|
||||
|
||||
/// Claim every currently-runnable node, acquiring its resources, and mark it
|
||||
/// `Running`. Delegates readiness + resource acquisition to the crate's
|
||||
/// settle loop; builds a [`Claim`] per started node from its payload + its
|
||||
/// DAG container's metadata. The container node itself is claimed like any
|
||||
/// other (its executor is an instant no-op that lets its subtree start).
|
||||
pub fn claim_ready(&self) -> Vec<Claim> {
|
||||
let mut inner = self.lock();
|
||||
let inner = &mut *inner;
|
||||
let started = inner.settle();
|
||||
let mut claims = Vec::with_capacity(started.len());
|
||||
for id in started {
|
||||
let Some(node) = inner.graph().node(id) else {
|
||||
continue;
|
||||
};
|
||||
let kind = node.payload.clone();
|
||||
let agent = node.payload.agent().to_owned();
|
||||
let Some(container) = inner.graph().root_of(id) else {
|
||||
continue;
|
||||
};
|
||||
claims.push(Claim {
|
||||
dag_id: container.get(),
|
||||
node_id: id,
|
||||
kind,
|
||||
agent,
|
||||
});
|
||||
// `started_at` is stamped on the graph `Node` by the scheduler's
|
||||
// transition to `Running` — no host-side copy needed.
|
||||
}
|
||||
claims
|
||||
/// The scheduler itself, for `hive_jobq`'s run-loop seam
|
||||
/// (`Scheduler::claim_next`), which takes exactly this type.
|
||||
///
|
||||
/// Handing out the `Arc` rather than wrapping each crate call keeps the
|
||||
/// host from growing a parallel API: the run loop uses `hive_jobq`'s
|
||||
/// functions directly, and this module stays the thin glue it is being
|
||||
/// reduced to.
|
||||
pub(crate) fn sched(&self) -> &Arc<Mutex<Sched>> {
|
||||
&self.sched
|
||||
}
|
||||
|
||||
/// Mark a claimed node terminal, recording its outcome + (truncated) error.
|
||||
/// The crate releases the node's build slot immediately and cascades the
|
||||
/// `AfterOk` failure cancellation + subtree lease release.
|
||||
///
|
||||
/// Nothing is returned: a DAG's terminal side effects are its own tail nodes
|
||||
/// ([`NodeKind::ResolveApproval`] / [`NodeKind::EmitRebuilt`]), which the
|
||||
/// scheduler claims and runs like any other node.
|
||||
pub fn complete_node(&self, node_id: NodeId, result: Result<(), String>) {
|
||||
// Deliberately not `complete_node_growing(.., self.new_job())`: that
|
||||
// would take the lock twice (once to mint an empty builder, once to
|
||||
// complete) to express "grew nothing". The shared part is the outcome
|
||||
// mapping, and that's a free fn.
|
||||
let mut inner = self.lock();
|
||||
inner.complete(node_id, outcome_of(result));
|
||||
drop(inner);
|
||||
self.notify.notify_one();
|
||||
/// The DAG container id owning `node`, for log lines and the dashboard.
|
||||
/// Derived from the graph rather than carried alongside the node — the
|
||||
/// parent axis already knows it.
|
||||
#[must_use]
|
||||
pub fn dag_of(&self, node: NodeId) -> Option<u64> {
|
||||
self.lock().graph().root_of(node).map(NodeId::get)
|
||||
}
|
||||
|
||||
/// A builder for a node to declare more work into while it runs.
|
||||
///
|
||||
/// Handed to [`exec::run_node`] and returned to
|
||||
/// [`JobQueue::complete_node_growing`]. Only `hive_jobq` can construct one,
|
||||
/// which is why this goes through the scheduler rather than
|
||||
/// `Job::default()`.
|
||||
/// Handed to [`exec::run_node`] and returned to the crate's completion.
|
||||
/// Only `hive_jobq` can construct one, which is why this goes through the
|
||||
/// scheduler rather than `Job::default()`.
|
||||
///
|
||||
/// The completion wrappers that used to live beside this (`complete_node`,
|
||||
/// `complete_node_growing`) are **gone**: a node is completed inside the
|
||||
/// future [`hive_jobq::scheduler::Scheduler::claim_next`] hands back, so
|
||||
/// this layer has nothing left to wrap. The tests keep their own extension
|
||||
/// trait for driving completions by hand.
|
||||
#[must_use]
|
||||
pub fn new_job(&self) -> Job {
|
||||
self.lock().new_job()
|
||||
}
|
||||
|
||||
/// [`JobQueue::complete_node`] plus the work the node declared while it ran.
|
||||
///
|
||||
/// `grown` is inserted **under `node_id`** before the completion, so the DAG
|
||||
/// cannot roll terminal with the appended work still pending — the property
|
||||
/// the old two-call `append_subgraph` + `complete_node` sequence had to
|
||||
/// arrange by hand at every call site.
|
||||
pub fn complete_node_growing(&self, node_id: NodeId, result: Result<(), String>, grown: Job) {
|
||||
let mut inner = self.lock();
|
||||
// A rejected grown job is logged, not propagated: the node's own work
|
||||
// already ran, and refusing to complete it here would both misreport
|
||||
// that and wedge the DAG on a node stuck `Running`.
|
||||
if let Err(e) = inner.complete_growing(node_id, outcome_of(result), grown) {
|
||||
tracing::error!(
|
||||
node = node_id.get(),
|
||||
error = %e,
|
||||
"job_queue: work grown by a completing node was rejected"
|
||||
);
|
||||
}
|
||||
drop(inner);
|
||||
self.notify.notify_one();
|
||||
}
|
||||
|
||||
/// Cancel a DAG that hasn't started yet: every work node is still `Pending`,
|
||||
/// so each is cancelled. `false` once any work node is running or terminal —
|
||||
/// an in-flight nix build isn't interruptible.
|
||||
|
|
|
|||
Loading…
Reference in a new issue