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,18 +18,25 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ResourceName;
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::resources::ResourceTable;
|
||||
|
||||
/// A [`ResourceTable`] shared between the scheduler and the live guards that
|
||||
/// release back into it on drop. Cheap to clone — an `Rc` refcount bump.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SharedResources(Rc<RefCell<ResourceTable>>);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SharedResources<R>(Rc<RefCell<ResourceTable<R>>>);
|
||||
|
||||
impl SharedResources {
|
||||
impl<R: Clone + Eq + Hash> Default for SharedResources<R> {
|
||||
fn default() -> Self {
|
||||
Self::new(ResourceTable::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Clone + Eq + Hash> SharedResources<R> {
|
||||
/// Wrap an existing table so guards can release into it.
|
||||
#[must_use]
|
||||
pub fn new(table: ResourceTable) -> Self {
|
||||
pub fn new(table: ResourceTable<R>) -> Self {
|
||||
Self(Rc::new(RefCell::new(table)))
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +48,7 @@ impl SharedResources {
|
|||
/// an over-capacity request can never succeed — same all-or-nothing
|
||||
/// semantics as [`ResourceTable::try_acquire_all`].
|
||||
#[must_use]
|
||||
pub fn acquire(&self, reqs: Vec<(ResourceName, u32)>) -> Option<ResourceGuard> {
|
||||
pub fn acquire(&self, reqs: Vec<(R, u32)>) -> Option<ResourceGuard<R>> {
|
||||
if self.0.borrow_mut().try_acquire_all(&reqs) {
|
||||
Some(ResourceGuard(Acq::Owned {
|
||||
table: self.clone(),
|
||||
|
|
@ -57,18 +64,18 @@ impl SharedResources {
|
|||
/// Keep the closure short: it holds a shared borrow, so calling
|
||||
/// [`SharedResources::acquire`] (a mutable borrow) from inside it would
|
||||
/// panic on the overlapping `RefCell` borrow.
|
||||
pub fn with<R>(&self, f: impl FnOnce(&ResourceTable) -> R) -> R {
|
||||
pub fn with<T>(&self, f: impl FnOnce(&ResourceTable<R>) -> T) -> T {
|
||||
f(&self.0.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
/// How a [`ResourceGuard`] relates to the units it represents.
|
||||
#[derive(Debug)]
|
||||
enum Acq {
|
||||
enum Acq<R> {
|
||||
/// Owns real units; drop releases them back into the shared table.
|
||||
Owned {
|
||||
table: SharedResources,
|
||||
reqs: Vec<(ResourceName, u32)>,
|
||||
table: SharedResources<R>,
|
||||
reqs: Vec<(R, u32)>,
|
||||
},
|
||||
/// Re-entrant reuse of a resource an ancestor group already holds; drop
|
||||
/// releases nothing.
|
||||
|
|
@ -78,9 +85,9 @@ enum Acq {
|
|||
/// An RAII grant of resources. Dropping it releases exactly what was acquired
|
||||
/// (nothing, for a borrowed re-entrant guard).
|
||||
#[derive(Debug)]
|
||||
pub struct ResourceGuard(Acq);
|
||||
pub struct ResourceGuard<R: Clone + Eq + Hash>(Acq<R>);
|
||||
|
||||
impl ResourceGuard {
|
||||
impl<R: Clone + Eq + Hash> ResourceGuard<R> {
|
||||
/// A borrowed (re-entrant) guard that owns no units and releases nothing on
|
||||
/// drop. The scheduler hands one to a sub-node that depends on a resource
|
||||
/// its ancestor group already holds, so the shared unit is released once —
|
||||
|
|
@ -93,7 +100,7 @@ impl ResourceGuard {
|
|||
/// The `(name, count)` units this guard releases on drop — empty when it is
|
||||
/// a borrowed re-entrant guard.
|
||||
#[must_use]
|
||||
pub fn held(&self) -> &[(ResourceName, u32)] {
|
||||
pub fn held(&self) -> &[(R, u32)] {
|
||||
match &self.0 {
|
||||
Acq::Owned { reqs, .. } => reqs,
|
||||
Acq::Borrowed => &[],
|
||||
|
|
@ -108,7 +115,7 @@ impl ResourceGuard {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for ResourceGuard {
|
||||
impl<R: Clone + Eq + Hash> Drop for ResourceGuard<R> {
|
||||
fn drop(&mut self) {
|
||||
if let Acq::Owned { table, reqs } = &self.0 {
|
||||
table.0.borrow_mut().release_all(reqs);
|
||||
|
|
@ -120,11 +127,11 @@ impl Drop for ResourceGuard {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn res(name: &str) -> ResourceName {
|
||||
ResourceName(name.to_owned())
|
||||
fn res(name: &str) -> String {
|
||||
name.to_owned()
|
||||
}
|
||||
|
||||
fn shared_with(slots: u32) -> SharedResources {
|
||||
fn shared_with(slots: u32) -> SharedResources<String> {
|
||||
let mut t = ResourceTable::new();
|
||||
t.set_capacity(res("build-slot"), slots);
|
||||
SharedResources::new(t)
|
||||
|
|
@ -161,7 +168,7 @@ mod tests {
|
|||
let _owner = sr.acquire(vec![(agent.clone(), 1)]).expect("fits");
|
||||
sr.with(|t| assert_eq!(t.available(&agent), 0));
|
||||
{
|
||||
let b = ResourceGuard::borrowed();
|
||||
let b = ResourceGuard::<String>::borrowed();
|
||||
assert!(!b.is_owning());
|
||||
assert!(b.held().is_empty());
|
||||
} // borrowed drop is a no-op
|
||||
|
|
@ -179,7 +186,7 @@ mod tests {
|
|||
.expect("group takes the lock");
|
||||
{
|
||||
// A sub-node reuses the group's lock: borrowed, no re-acquire.
|
||||
let _sub = ResourceGuard::borrowed();
|
||||
let _sub = ResourceGuard::<String>::borrowed();
|
||||
sr.with(|t| assert_eq!(t.available(&agent), 0));
|
||||
} // sub-node done — must NOT free the shared lock
|
||||
sr.with(|t| assert_eq!(t.available(&agent), 0));
|
||||
|
|
|
|||
Loading…
Reference in a new issue