refactor(#2772): graph walks belong to jobq, not to its caller
hive-c0re hand-rolled four traversals over a graph it doesn't own, because `Graph` exposed only `node()` and `nodes()`. They are generic — nothing in them knows what a hyperhive DAG is — so they move to `hive-jobq` and core delegates. `Graph` gains `root_of`, `descendants`, `roots`, `is_settled` and `first_error`; they reuse the private `is_descendant` the crate already had for its dep-scope rule. `subtree` gets faster on the way: core walked every node's whole parent chain to the root for every node in the graph, where `is_descendant` stops as soon as it sees the ancestor. `first_error` deliberately looks for the first `Failed` descendant that *carries* an error rather than the first `Failed` one. A node that rolled its failure up from a child holds no error of its own and sorts before that child, so the simpler version reports `None` for the common case and the dashboard loses the reason. The distinction has its own test. `dag_is_terminal` is deleted rather than moved: it was already a plain `state.is_terminal()` read, and its three call sites now ask the graph.
This commit is contained in:
parent
d580270263
commit
4de5a8dd7c
2 changed files with 130 additions and 35 deletions
|
|
@ -480,7 +480,7 @@ impl JobQueue {
|
|||
inner
|
||||
.containers()
|
||||
.into_iter()
|
||||
.filter(|&c| !inner.dag_is_terminal(c))
|
||||
.filter(|&c| !inner.sched.graph().is_settled(c))
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
|
@ -505,25 +505,17 @@ impl QueueInner {
|
|||
})
|
||||
}
|
||||
|
||||
/// The DAG container a node belongs to — walk its parent chain to the root
|
||||
/// (`parent == None`), which is the container. Returns `id` itself for a
|
||||
/// container node.
|
||||
/// The DAG container a node belongs to. A container is exactly a group root,
|
||||
/// so this is the graph's own parent-chain walk.
|
||||
fn dag_of(&self, id: NodeId) -> Option<NodeId> {
|
||||
let mut cur = id;
|
||||
loop {
|
||||
match self.sched.graph().node(cur)?.parent {
|
||||
Some(p) => cur = p,
|
||||
None => return Some(cur),
|
||||
}
|
||||
}
|
||||
self.sched.graph().root_of(id)
|
||||
}
|
||||
|
||||
/// The DAG's work nodes — its `container`'s subtree, excluding the container.
|
||||
fn subtree(&self, container: NodeId) -> Vec<NodeId> {
|
||||
self.sched
|
||||
.graph()
|
||||
.nodes()
|
||||
.filter(|n| n.id != container && self.dag_of(n.id) == Some(container))
|
||||
.descendants(container)
|
||||
.map(|n| n.id)
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -553,15 +545,6 @@ impl QueueInner {
|
|||
})
|
||||
}
|
||||
|
||||
/// True when the DAG has settled — its container has rolled up terminal
|
||||
/// (equivalent to every work node being terminal).
|
||||
fn dag_is_terminal(&self, container: NodeId) -> bool {
|
||||
self.sched
|
||||
.graph()
|
||||
.node(container)
|
||||
.is_some_and(|n| n.state.is_terminal())
|
||||
}
|
||||
|
||||
/// Whether `id` has an edge that accepts a **dropped** dependency — i.e. the
|
||||
/// node exists to report on work that may never run. Used by
|
||||
/// [`JobQueue::cancel`] to decide what to spare, so the decision comes from
|
||||
|
|
@ -574,18 +557,12 @@ impl QueueInner {
|
|||
})
|
||||
}
|
||||
|
||||
/// First failed work node's error (read off the graph `Node`), for the
|
||||
/// dashboard's DAG-level error line.
|
||||
/// First failed work node's error, for the dashboard's DAG-level error line.
|
||||
fn dag_first_error(&self, container: NodeId) -> Option<String> {
|
||||
for id in self.subtree(container) {
|
||||
if let Some(n) = self.sched.graph().node(id)
|
||||
&& n.state == JobState::Failed
|
||||
&& let Some(e) = n.error.clone()
|
||||
{
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
None
|
||||
self.sched
|
||||
.graph()
|
||||
.first_error(container)
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
/// Project a DAG into its wire [`DagView`]: a near-raw view of the
|
||||
|
|
@ -675,7 +652,7 @@ impl QueueInner {
|
|||
if !any_unsettled {
|
||||
return None;
|
||||
}
|
||||
let is_terminal = self.dag_is_terminal(container);
|
||||
let is_terminal = self.sched.graph().is_settled(container);
|
||||
Some(DagView {
|
||||
id: container.get(),
|
||||
source: meta.source,
|
||||
|
|
@ -718,7 +695,7 @@ impl QueueInner {
|
|||
let mut live: Vec<NodeId> = Vec::new();
|
||||
let mut terminal: Vec<(NodeId, i64)> = Vec::new();
|
||||
for c in self.containers() {
|
||||
if self.dag_is_terminal(c) {
|
||||
if self.sched.graph().is_settled(c) {
|
||||
terminal.push((c, self.dag_finished_at(c)));
|
||||
} else {
|
||||
live.push(c);
|
||||
|
|
|
|||
Loading…
Reference in a new issue