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
|
|
@ -20,8 +20,8 @@
|
|||
//!
|
||||
//! [`Dep::Resource`]: crate::Dep::Resource
|
||||
|
||||
use crate::ResourceName;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A set of named counting semaphores.
|
||||
///
|
||||
|
|
@ -30,22 +30,22 @@ use std::collections::HashMap;
|
|||
/// units atomically via [`ResourceTable::try_acquire_all`] /
|
||||
/// [`ResourceTable::release_all`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResourceTable {
|
||||
/// Configured capacities, keyed by name. Missing ⇒ `default_capacity`.
|
||||
capacities: HashMap<ResourceName, u32>,
|
||||
/// Units currently held, keyed by name. Missing ⇒ 0.
|
||||
held: HashMap<ResourceName, u32>,
|
||||
/// Capacity assumed for a name with no configured entry.
|
||||
pub struct ResourceTable<R> {
|
||||
/// Configured capacities, keyed by resource. Missing ⇒ `default_capacity`.
|
||||
capacities: HashMap<R, u32>,
|
||||
/// Units currently held, keyed by resource. Missing ⇒ 0.
|
||||
held: HashMap<R, u32>,
|
||||
/// Capacity assumed for a resource with no configured entry.
|
||||
default_capacity: u32,
|
||||
}
|
||||
|
||||
impl Default for ResourceTable {
|
||||
impl<R: Clone + Eq + Hash> Default for ResourceTable<R> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceTable {
|
||||
impl<R: Clone + Eq + Hash> ResourceTable<R> {
|
||||
/// An empty table whose unconfigured names default to capacity 1.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
|
|
@ -61,14 +61,14 @@ impl ResourceTable {
|
|||
/// Overwrites any previous capacity for that name. Lowering capacity below
|
||||
/// the currently-held count is allowed — the table simply reports zero
|
||||
/// available until enough is released; it never rejects a config change.
|
||||
pub fn set_capacity(&mut self, name: ResourceName, capacity: u32) {
|
||||
pub fn set_capacity(&mut self, name: R, capacity: u32) {
|
||||
self.capacities.insert(name, capacity);
|
||||
}
|
||||
|
||||
/// The capacity of a name — its configured value, or the default (1) if it
|
||||
/// was never configured.
|
||||
#[must_use]
|
||||
pub fn capacity(&self, name: &ResourceName) -> u32 {
|
||||
pub fn capacity(&self, name: &R) -> u32 {
|
||||
self.capacities
|
||||
.get(name)
|
||||
.copied()
|
||||
|
|
@ -77,13 +77,13 @@ impl ResourceTable {
|
|||
|
||||
/// Units of a name currently held (0 if none).
|
||||
#[must_use]
|
||||
pub fn held(&self, name: &ResourceName) -> u32 {
|
||||
pub fn held(&self, name: &R) -> u32 {
|
||||
self.held.get(name).copied().unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Units of a name available to acquire right now (`capacity - held`).
|
||||
#[must_use]
|
||||
pub fn available(&self, name: &ResourceName) -> u32 {
|
||||
pub fn available(&self, name: &R) -> u32 {
|
||||
self.capacity(name).saturating_sub(self.held(name))
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ impl ResourceTable {
|
|||
/// `false` and leaves the table completely untouched. A request for more
|
||||
/// units than a name's capacity can therefore never succeed — the scheduler
|
||||
/// should reject such a node at insert time so it does not wait forever.
|
||||
pub fn try_acquire_all(&mut self, reqs: &[(ResourceName, u32)]) -> bool {
|
||||
pub fn try_acquire_all(&mut self, reqs: &[(R, u32)]) -> bool {
|
||||
let wanted = aggregate(reqs);
|
||||
// All-or-nothing: bail before mutating if any request cannot be met.
|
||||
for (name, &count) in &wanted {
|
||||
|
|
@ -113,7 +113,7 @@ impl ResourceTable {
|
|||
///
|
||||
/// Duplicate names are summed. Releasing more than is held saturates at zero
|
||||
/// rather than underflowing, so a double release is harmless.
|
||||
pub fn release_all(&mut self, reqs: &[(ResourceName, u32)]) {
|
||||
pub fn release_all(&mut self, reqs: &[(R, u32)]) {
|
||||
for (name, count) in aggregate(reqs) {
|
||||
if let Some(h) = self.held.get_mut(name) {
|
||||
*h = h.saturating_sub(count);
|
||||
|
|
@ -123,8 +123,8 @@ impl ResourceTable {
|
|||
}
|
||||
|
||||
/// Sum a request list into per-name totals so duplicate names are one entry.
|
||||
fn aggregate(reqs: &[(ResourceName, u32)]) -> HashMap<&ResourceName, u32> {
|
||||
let mut wanted: HashMap<&ResourceName, u32> = HashMap::new();
|
||||
fn aggregate<R: Eq + Hash>(reqs: &[(R, u32)]) -> HashMap<&R, u32> {
|
||||
let mut wanted: HashMap<&R, u32> = HashMap::new();
|
||||
for (name, count) in reqs {
|
||||
*wanted.entry(name).or_insert(0) += *count;
|
||||
}
|
||||
|
|
@ -135,8 +135,8 @@ fn aggregate(reqs: &[(ResourceName, u32)]) -> HashMap<&ResourceName, u32> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn res(name: &str) -> ResourceName {
|
||||
ResourceName(name.to_owned())
|
||||
fn res(name: &str) -> String {
|
||||
name.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue