job_queue: declare a node's resources where the node is constructed
Resources were derived from the node's kind: `templates::node` called `NodeKind::resource_deps()`, which fanned out to `needs_build_slot` / `needs_lease` / `needs_meta_window`. That made the requirement a property of the *kind*, so a kind that happened to run under an ancestor already holding the resource could get away with declaring nothing. Three did. `Start`, `Stop` and `PostSwap` appear in none of the three predicates, and that was only safe because one construction site fans them out from inside a lease-holding `Reconcile` — a fact about today's DAG shape, not about the nodes. Each of the 41 construction sites now says what it holds. `Start` / `Stop` / `PostSwap` declare the agent lease; per the contract that is a re-entrant borrow, which a new test pins rather than argues. `running_transients` reads the node's declared deps instead of re-deriving from the kind. That closes the blank-pill gap: the pill went blank during container start, stop and the post-swap tail because the declaration was missing, not because the filter was wrong. The deleted predicates carried the only written record of three design decisions; each moved to the `Resource` variant it constrains rather than dying with its function.
This commit is contained in:
parent
1aa88463a0
commit
10dbdb444d
9 changed files with 256 additions and 177 deletions
|
|
@ -3,8 +3,16 @@
|
|||
//! `hive-jobq` is generic over a resource type `R: Clone + Eq + Hash` and a node
|
||||
//! payload `N`; here `R` is [`Resource`] and `N` is [`NodeKind`] directly (each
|
||||
//! variant carries the agent it targets).
|
||||
|
||||
use super::model::NodeKind;
|
||||
//!
|
||||
//! **A node's resources are declared where the node is constructed**, not
|
||||
//! derived from its kind — see `templates::node`. Deriving them made the
|
||||
//! requirement a property of the *kind*, which let a kind that happened to run
|
||||
//! under a holding ancestor declare nothing at all.
|
||||
//!
|
||||
//! All of a node's resource edges are acquired **atomically**
|
||||
//! (`try_acquire_all`) — a node never holds one resource while waiting on
|
||||
//! another, so the multi-resource nodes (a `MetaLock` wants a build slot *and*
|
||||
//! the meta window) cannot deadlock against each other.
|
||||
|
||||
/// The two resource classes the queue gates concurrency on, as the crate's
|
||||
/// generic resource type `R`.
|
||||
|
|
@ -19,10 +27,20 @@ pub enum Resource {
|
|||
/// a DAG's first container-affecting node for that agent and re-entered by
|
||||
/// the rest of that agent's subtree via the crate's recursive lock, so two
|
||||
/// DAGs never interleave container ops on one agent.
|
||||
///
|
||||
/// Nodes that touch the *store or meta repo* rather than the running
|
||||
/// container do not declare it — `MetaSync`, `Prebuild`, `Provision`,
|
||||
/// `MetaLock`, `WritePermFile`, `Reparent`. That exemption is what lets a
|
||||
/// `Prebuild` overlap another DAG's work on the same agent. `Provision`
|
||||
/// precedes the container's existence entirely, so the lease is first taken
|
||||
/// at the `Create` it feeds.
|
||||
Agent(String),
|
||||
/// The meta-repo mutation window — a global singleton (default capacity 1)
|
||||
/// held by any node that mutates the meta repo, so two meta mutations never
|
||||
/// interleave. Replaces the former runtime `meta::exclusive()` mutex: a
|
||||
/// interleave. That exclusion is load-bearing: a commit landing inside
|
||||
/// another node's staged `prepare_deploy`→`finalize_deploy` window would
|
||||
/// sweep the staged `flake.lock` into its own commit and neuter
|
||||
/// `abort_deploy`. Replaces the former runtime `meta::exclusive()` mutex: a
|
||||
/// `MutexGuard` cannot span scheduler nodes, but a resource held by a
|
||||
/// subtree root *can* — which is what lets the two-phase deploy
|
||||
/// (`prepare_deploy` stages `flake.lock` uncommitted across the whole
|
||||
|
|
@ -30,35 +48,12 @@ pub enum Resource {
|
|||
/// decomposed into sub-nodes instead of one opaque node. Descendants of a
|
||||
/// holder re-enter it through the crate's recursive lock, exactly like
|
||||
/// [`Resource::Agent`].
|
||||
///
|
||||
/// Deliberately **not** declared by `Prebuild`: the window must stay off the
|
||||
/// multi-minute toplevel build, which only *reads* the store. Holding a
|
||||
/// hive-global cap-1 across it would serialize every agent's rebuild behind
|
||||
/// every other's — which is why the meta preamble is its own `MetaSync`
|
||||
/// node, and a sibling of `Prebuild` rather than its parent (a resource is
|
||||
/// held across the holder's whole subtree).
|
||||
MetaWindow,
|
||||
}
|
||||
|
||||
impl NodeKind {
|
||||
/// The resources this node must acquire to run — `(name, units)` — derived from
|
||||
/// its kind + agent: a build slot for nix-heavy kinds
|
||||
/// ([`NodeKind::needs_build_slot`]) and the agent lease for
|
||||
/// container-affecting kinds ([`NodeKind::needs_lease`]). Lease-exempt
|
||||
/// container ops (`Start` / `Stop`, fanned out by a lease-holding
|
||||
/// `Reconcile`) hold no lease of their own — they re-enter the ancestor's
|
||||
/// `Agent` lock through the crate's recursive re-entrancy. Meta-mutating
|
||||
/// kinds ([`NodeKind::needs_meta_window`]) additionally take the global
|
||||
/// [`Resource::MetaWindow`].
|
||||
///
|
||||
/// All of a node's resource edges are acquired **atomically**
|
||||
/// (`try_acquire_all`) — a node never holds one resource while waiting on
|
||||
/// another, so the multi-resource kinds (a `MetaLock` wants a build slot
|
||||
/// *and* the meta window) cannot deadlock against each other.
|
||||
pub fn resource_deps(&self) -> Vec<(Resource, u32)> {
|
||||
let mut deps = Vec::new();
|
||||
if self.needs_build_slot() {
|
||||
deps.push((Resource::BuildSlot, 1));
|
||||
}
|
||||
if self.needs_lease() {
|
||||
deps.push((Resource::Agent(self.agent().to_owned()), 1));
|
||||
}
|
||||
if self.needs_meta_window() {
|
||||
deps.push((Resource::MetaWindow, 1));
|
||||
}
|
||||
deps
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue