refactor(jobq): make the crate generic over the resource type R
Replace the concrete ResourceName(String) with a type parameter R: Clone + Eq + Hash threaded end-to-end (Dep<R>, Node<N,R>, Graph<N,R>, ResourceTable<R>, ResourceGuard<R>/SharedResources<R>, Scheduler<N,R>). The crate no longer hard-codes the resource identity; the consumer picks the concrete type (a String, or an enum like BuildSlot/Agent(name)) at the port. Tests use String as the concrete R. Pure type-parameter thread-through, no logic change. 25 tests green, clippy pedantic clean.
This commit is contained in:
parent
7ffc13dc86
commit
b4bcf8b6e4
4 changed files with 105 additions and 100 deletions
|
|
@ -18,10 +18,11 @@
|
|||
//! the eager `AfterOk` failure cascade are layered on top of this owned core.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::guard::{ResourceGuard, SharedResources};
|
||||
use crate::resources::ResourceTable;
|
||||
use crate::{Dep, DepWhen, Graph, GraphError, NodeId, ResourceName, State};
|
||||
use crate::{Dep, DepWhen, Graph, GraphError, NodeId, State};
|
||||
|
||||
/// The result of a node's own execution, reported to [`Scheduler::complete`].
|
||||
///
|
||||
|
|
@ -37,23 +38,23 @@ pub enum Outcome {
|
|||
|
||||
/// Drives a [`Graph`] over a shared resource pool: claim runnable nodes, hold
|
||||
/// their resources for the subtree's lifetime, release on subtree-terminal.
|
||||
pub struct Scheduler<N> {
|
||||
graph: Graph<N>,
|
||||
resources: SharedResources,
|
||||
pub struct Scheduler<N, R: Clone + Eq + Hash> {
|
||||
graph: Graph<N, R>,
|
||||
resources: SharedResources<R>,
|
||||
/// Owned resource guards, keyed by the node that acquired them. Dropped
|
||||
/// (releasing the units) when that node's whole subtree is terminal.
|
||||
owned: HashMap<NodeId, Vec<ResourceGuard>>,
|
||||
owned: HashMap<NodeId, Vec<ResourceGuard<R>>>,
|
||||
/// The single re-entrancy slot per `(ancestor-holder, resource)`: the id of
|
||||
/// the descendant currently *borrowing* that ancestor's lock. Present ⇒ the
|
||||
/// slot is taken, so no other descendant may re-enter the same lock until
|
||||
/// the borrower's subtree is terminal — "only one node at a time within".
|
||||
borrow_slots: HashMap<(NodeId, ResourceName), NodeId>,
|
||||
borrow_slots: HashMap<(NodeId, R), NodeId>,
|
||||
}
|
||||
|
||||
impl<N> Scheduler<N> {
|
||||
impl<N, R: Clone + Eq + Hash> Scheduler<N, R> {
|
||||
/// A scheduler over `graph` with `resources` as the capacity pool.
|
||||
#[must_use]
|
||||
pub fn new(graph: Graph<N>, resources: ResourceTable) -> Self {
|
||||
pub fn new(graph: Graph<N, R>, resources: ResourceTable<R>) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
resources: SharedResources::new(resources),
|
||||
|
|
@ -64,7 +65,7 @@ impl<N> Scheduler<N> {
|
|||
|
||||
/// The graph, for inspection (state, hierarchy, UI rendering).
|
||||
#[must_use]
|
||||
pub fn graph(&self) -> &Graph<N> {
|
||||
pub fn graph(&self) -> &Graph<N, R> {
|
||||
&self.graph
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +78,7 @@ impl<N> Scheduler<N> {
|
|||
pub fn append(
|
||||
&mut self,
|
||||
payload: N,
|
||||
deps: Vec<Dep>,
|
||||
deps: Vec<Dep<R>>,
|
||||
parent: Option<NodeId>,
|
||||
) -> Result<NodeId, GraphError> {
|
||||
self.graph.insert(payload, deps, parent)
|
||||
|
|
@ -138,7 +139,7 @@ impl<N> Scheduler<N> {
|
|||
|
||||
/// The nearest ancestor of `id` that *owns* (holds real units of) `name`,
|
||||
/// or `None` if no ancestor holds it (⇒ `id` must own-acquire it itself).
|
||||
fn ancestor_owning(&self, id: NodeId, name: &ResourceName) -> Option<NodeId> {
|
||||
fn ancestor_owning(&self, id: NodeId, name: &R) -> Option<NodeId> {
|
||||
let mut cursor = self.graph.node(id)?.parent;
|
||||
while let Some(ancestor) = cursor {
|
||||
if self.node_owns(ancestor, name) {
|
||||
|
|
@ -150,7 +151,7 @@ impl<N> Scheduler<N> {
|
|||
}
|
||||
|
||||
/// Whether node `holder` holds an owned guard covering resource `name`.
|
||||
fn node_owns(&self, holder: NodeId, name: &ResourceName) -> bool {
|
||||
fn node_owns(&self, holder: NodeId, name: &R) -> bool {
|
||||
self.owned.get(&holder).is_some_and(|guards| {
|
||||
guards
|
||||
.iter()
|
||||
|
|
@ -246,7 +247,7 @@ impl<N> Scheduler<N> {
|
|||
}
|
||||
|
||||
/// The `(name, count)` resource units `id` must hold to run.
|
||||
fn resource_reqs(&self, id: NodeId) -> Vec<(ResourceName, u32)> {
|
||||
fn resource_reqs(&self, id: NodeId) -> Vec<(R, u32)> {
|
||||
let Some(node) = self.graph.node(id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
|
@ -264,25 +265,25 @@ impl<N> Scheduler<N> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn res(name: &str) -> ResourceName {
|
||||
ResourceName(name.to_owned())
|
||||
fn res(name: &str) -> String {
|
||||
name.to_owned()
|
||||
}
|
||||
|
||||
/// A graph + a resource table with `build-slot` set to `slots`.
|
||||
fn scheduler_with_slots(slots: u32) -> Scheduler<&'static str> {
|
||||
fn scheduler_with_slots(slots: u32) -> Scheduler<&'static str, String> {
|
||||
let mut table = ResourceTable::new();
|
||||
table.set_capacity(res("build-slot"), slots);
|
||||
Scheduler::new(Graph::new(), table)
|
||||
}
|
||||
|
||||
fn slot_dep() -> Vec<Dep> {
|
||||
fn slot_dep() -> Vec<Dep<String>> {
|
||||
vec![Dep::Resource {
|
||||
name: res("build-slot"),
|
||||
count: 1,
|
||||
}]
|
||||
}
|
||||
|
||||
fn resource_dep(name: &str) -> Vec<Dep> {
|
||||
fn resource_dep(name: &str) -> Vec<Dep<String>> {
|
||||
vec![Dep::Resource {
|
||||
name: res(name),
|
||||
count: 1,
|
||||
|
|
@ -379,7 +380,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn failed_after_ok_dep_cancels_dependents_but_after_any_still_runs() {
|
||||
let mut s = Scheduler::new(Graph::new(), ResourceTable::new());
|
||||
let mut s: Scheduler<&str, String> = Scheduler::new(Graph::new(), ResourceTable::new());
|
||||
let root = s.append("root", vec![], None).expect("root");
|
||||
let strong1 = s
|
||||
.append(
|
||||
|
|
|
|||
Loading…
Reference in a new issue