Skip to main content

qubit_budget/resource/budget/
resource_pool.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines finite releasable resource pools.
9
10use crate::resource::InsufficientBudgetError;
11use crate::resource::ResourceLimit;
12use crate::resource::ResourceQuantity;
13use crate::resource::ResourceReleaseError;
14
15/// A finite, non-synchronizing pool of releasable resource capacity.
16///
17/// Acquisition subtracts from `available`; release adds only after checking
18/// the amount is no greater than `in_use`. The object has no lifecycle state,
19/// waiting, fairness, permits or cancellation. An unconfigured dimension is
20/// represented by `Option<ResourcePool<R>> = None`.
21///
22/// # Type Parameters
23///
24/// * `R` - Caller-defined resource value retained for diagnostics.
25/// * `Q` - Exact unsigned quantity used for the capacity and accounting.
26///
27/// # Examples
28///
29/// ```
30/// use qubit_budget::ResourcePool;
31///
32/// let mut pool = ResourcePool::new("workers", 2_u64);
33/// pool.try_acquire(1).expect("one worker should fit");
34/// assert_eq!(pool.in_use(), 1);
35/// pool.release(1).expect("the worker is returned");
36/// assert_eq!(pool.available(), 2);
37/// ```
38#[derive(Debug, PartialEq, Eq)]
39pub struct ResourcePool<R, Q = u64>
40where
41    Q: ResourceQuantity,
42{
43    /// Finite total capacity of the pool.
44    limit: ResourceLimit<R, Q>,
45
46    /// Capacity that is currently available for acquisition.
47    available: Q,
48}
49
50impl<R, Q> ResourcePool<R, Q>
51where
52    Q: ResourceQuantity,
53{
54    /// Creates an entirely available finite pool.
55    ///
56    /// # Parameters
57    ///
58    /// * `resource` - Domain resource value retained in errors.
59    /// * `limit` - Finite pool capacity.
60    ///
61    /// # Returns
62    ///
63    /// A pool with `available == limit`.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use qubit_budget::ResourcePool;
69    ///
70    /// let mut pool = ResourcePool::new("readers", 2_u64);
71    /// pool.try_acquire(1).expect("one reader should fit");
72    /// pool.release(1).expect("the reader is returned explicitly");
73    /// assert_eq!(pool.in_use(), 0);
74    /// ```
75    #[inline]
76    #[must_use]
77    pub const fn new(resource: R, limit: Q) -> Self {
78        Self {
79            limit: ResourceLimit::new(resource, limit),
80            available: limit,
81        }
82    }
83
84    /// Creates an entirely available pool from an immutable resource limit.
85    ///
86    /// # Parameters
87    ///
88    /// * `limit` - Resource identity and finite capacity for this pool.
89    ///
90    /// # Returns
91    ///
92    /// A pool whose available capacity equals the limit maximum.
93    #[inline]
94    #[must_use]
95    pub fn from_limit(limit: ResourceLimit<R, Q>) -> Self {
96        let available = limit.maximum();
97        Self { limit, available }
98    }
99
100    /// Acquires capacity when enough units are available.
101    ///
102    /// # Parameters
103    ///
104    /// * `amount` - Quantity to acquire.
105    ///
106    /// # Returns
107    ///
108    /// `Ok(())` after subtracting the amount, or
109    /// [`InsufficientBudgetError`] with no state change when it does
110    /// not fit.
111    ///
112    /// # Errors
113    ///
114    /// Returns [`InsufficientBudgetError`] when `amount` exceeds current
115    /// availability. The pool remains unchanged in that case.
116    pub fn try_acquire(&mut self, amount: Q) -> Result<(), InsufficientBudgetError<R, Q>>
117    where
118        R: Clone,
119    {
120        if amount > self.available {
121            return Err(InsufficientBudgetError {
122                resource: self.limit.resource().clone(),
123                limit: self.limit.maximum(),
124                remaining: self.available,
125                requested: amount,
126            });
127        }
128        self.available = self.available - amount;
129        Ok(())
130    }
131
132    /// Releases previously acquired capacity.
133    ///
134    /// # Parameters
135    ///
136    /// * `amount` - Quantity to return to the pool.
137    ///
138    /// # Returns
139    ///
140    /// `Ok(())` after increasing availability, or
141    /// [`ResourceReleaseError`] with no state change when the
142    /// amount exceeds current occupancy.
143    ///
144    /// # Errors
145    ///
146    /// Returns [`ResourceReleaseError`] when `amount` exceeds
147    /// current occupancy. The pool remains unchanged in that case.
148    pub fn release(&mut self, amount: Q) -> Result<(), ResourceReleaseError<R, Q>>
149    where
150        R: Clone,
151    {
152        let in_use = self.in_use();
153        if amount > in_use {
154            return Err(ResourceReleaseError::InvalidRelease {
155                resource: self.limit.resource().clone(),
156                limit: self.limit.maximum(),
157                in_use,
158                requested: amount,
159            });
160        }
161        self.available = self.available + amount;
162        Ok(())
163    }
164
165    /// Returns the associated resource.
166    ///
167    /// # Returns
168    ///
169    /// Returns the associated resource.
170    #[must_use]
171    #[inline(always)]
172    pub const fn resource(&self) -> &R {
173        self.limit.resource()
174    }
175
176    /// Returns the immutable resource limit that configures this pool.
177    ///
178    /// # Returns
179    ///
180    /// Returns the immutable resource limit that configures this pool.
181    #[must_use]
182    #[inline(always)]
183    pub const fn resource_limit(&self) -> &ResourceLimit<R, Q> {
184        &self.limit
185    }
186
187    /// Returns the total finite capacity.
188    ///
189    /// # Returns
190    ///
191    /// Returns the total finite capacity.
192    #[must_use]
193    #[inline(always)]
194    pub const fn capacity(&self) -> Q {
195        self.limit.maximum()
196    }
197
198    /// Returns currently available capacity.
199    ///
200    /// # Returns
201    ///
202    /// Returns currently available capacity.
203    #[must_use]
204    #[inline(always)]
205    pub const fn available(&self) -> Q {
206        self.available
207    }
208
209    /// Returns currently acquired capacity.
210    ///
211    /// # Returns
212    ///
213    /// Returns currently acquired capacity.
214    #[inline(always)]
215    #[must_use]
216    pub fn in_use(&self) -> Q {
217        self.limit.maximum() - self.available
218    }
219}