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}