Skip to main content

qubit_budget/resource/budget/
managed_resource_pool.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines a cloneable finite pool that returns capacity through RAII permits.
9
10use std::sync::Arc;
11
12use super::ManagedResourcePermit;
13use super::internal::ManagedResourcePoolInner;
14use crate::resource::InsufficientBudgetError;
15use crate::resource::ResourceLimit;
16use crate::resource::ResourceQuantity;
17
18/// A cloneable finite pool whose acquired capacity is owned by RAII permits.
19///
20/// Clones share one synchronized capacity state. Dropping a returned
21/// [`ManagedResourcePermit`] makes its quantity available again, including
22/// during early returns and panic unwinding. Use [`crate::ResourcePool`] when
23/// explicit `try_acquire`/`release` pairing is preferred.
24///
25/// # Type Parameters
26///
27/// * `R` - Caller-defined resource value retained for diagnostics.
28/// * `Q` - Exact unsigned quantity shared by the pool and its permits.
29///
30/// # Examples
31///
32/// ```
33/// use qubit_budget::ManagedResourcePool;
34///
35/// let pool = ManagedResourcePool::new("workers", 2_u64);
36/// let permit = pool.try_acquire(1).expect("one worker should fit");
37/// assert_eq!(pool.in_use(), 1);
38/// drop(permit);
39/// assert_eq!(pool.available(), 2);
40/// ```
41#[derive(Debug)]
42pub struct ManagedResourcePool<R, Q = u64>
43where
44    Q: ResourceQuantity,
45{
46    /// Synchronized capacity shared by every handle and outstanding permit.
47    inner: Arc<ManagedResourcePoolInner<R, Q>>,
48}
49
50impl<R, Q> Clone for ManagedResourcePool<R, Q>
51where
52    Q: ResourceQuantity,
53{
54    /// Clones the shared handle without duplicating finite capacity.
55    fn clone(&self) -> Self {
56        Self {
57            inner: Arc::clone(&self.inner),
58        }
59    }
60}
61
62impl<R, Q> ManagedResourcePool<R, Q>
63where
64    Q: ResourceQuantity,
65{
66    /// Creates a managed pool with all finite capacity available.
67    ///
68    /// # Parameters
69    ///
70    /// * `resource` - Resource identity retained in acquisition errors.
71    /// * `limit` - Total finite capacity shared by all handles.
72    ///
73    /// # Returns
74    ///
75    /// A new managed pool with `limit` units available.
76    #[must_use]
77    #[inline]
78    pub fn new(resource: R, limit: Q) -> Self {
79        Self::from_limit(ResourceLimit::new(resource, limit))
80    }
81
82    /// Creates a managed pool from an immutable resource limit.
83    ///
84    /// # Parameters
85    ///
86    /// * `limit` - Resource identity and total finite capacity.
87    ///
88    /// # Returns
89    ///
90    /// A new managed pool preserving the supplied limit.
91    #[must_use]
92    #[inline]
93    pub fn from_limit(limit: ResourceLimit<R, Q>) -> Self {
94        Self {
95            inner: Arc::new(ManagedResourcePoolInner::new(limit)),
96        }
97    }
98
99    /// Acquires capacity and returns a permit that releases it on Drop.
100    ///
101    /// # Parameters
102    ///
103    /// * `amount` - Quantity to acquire from current availability.
104    ///
105    /// # Returns
106    ///
107    /// A permit owning `amount` units when they fit.
108    ///
109    /// # Errors
110    ///
111    /// Returns [`InsufficientBudgetError`] when `amount` exceeds current
112    /// availability. Failure leaves the shared pool unchanged. The resource is
113    /// cloned only after releasing the internal lock.
114    pub fn try_acquire(&self, amount: Q) -> Result<ManagedResourcePermit<R, Q>, InsufficientBudgetError<R, Q>>
115    where
116        R: Clone,
117    {
118        let remaining = {
119            let mut available = self.inner.lock_available();
120            if amount <= *available {
121                *available = *available - amount;
122                return Ok(ManagedResourcePermit::new(Arc::clone(&self.inner), amount));
123            }
124            *available
125        };
126        Err(InsufficientBudgetError {
127            resource: self.resource().clone(),
128            limit: self.capacity(),
129            remaining,
130            requested: amount,
131        })
132    }
133
134    /// Returns the resource associated with this shared pool.
135    #[must_use]
136    #[inline(always)]
137    pub fn resource(&self) -> &R {
138        self.inner.limit.resource()
139    }
140
141    /// Returns the immutable resource limit configuring this pool.
142    #[must_use]
143    #[inline(always)]
144    pub fn resource_limit(&self) -> &ResourceLimit<R, Q> {
145        &self.inner.limit
146    }
147
148    /// Returns total finite capacity.
149    #[must_use]
150    #[inline(always)]
151    pub fn capacity(&self) -> Q {
152        self.inner.limit.maximum()
153    }
154
155    /// Returns capacity not currently owned by permits.
156    #[must_use]
157    #[inline(always)]
158    pub fn available(&self) -> Q {
159        *self.inner.lock_available()
160    }
161
162    /// Returns capacity currently owned by permits.
163    #[must_use]
164    #[inline(always)]
165    pub fn in_use(&self) -> Q {
166        self.capacity() - self.available()
167    }
168}