anchored_pool/
bounded.rs

1#![expect(
2    unsafe_code,
3    reason = "Use UnsafeCell instead of the needless overhead of RefCell;
4              let unsafe code in Pools rely on PooledResource Drop impl",
5)]
6
7use std::iter;
8use std::{cell::UnsafeCell, rc::Rc};
9
10#[cfg(feature = "clone-behavior")]
11use clone_behavior::{MirroredClone, Speed};
12
13use crate::{
14    other_utils::{ResetNothing, ResetResource, ResourcePoolEmpty},
15    pooled_resource::{PooledResource, SealedPool},
16};
17
18
19/// A resource pool with a fixed number of `Resource`s.
20#[derive(Debug)]
21pub struct BoundedPool<Resource, Reset> {
22    /// Safety: the `UnsafeCell` is only accessed from `Self::try_get`, `Self::available_resources`,
23    /// and `Self::return_resource`. None of them allow a reference to something inside the
24    /// `UnsafeCell` to escape outside the function body, and they do not call each other,
25    /// except possibly via the `reset_resource` callback, which is not run while a borrow to
26    /// the `UnsafeCell` contents is active.
27    pool:           Rc<[UnsafeCell<Option<Resource>>]>,
28    reset_resource: Reset,
29}
30
31impl<Resource, Reset> BoundedPool<Resource, Reset> {
32    /// Create a new `BoundedPool` which has the indicated, fixed number of `Resource`s.
33    ///
34    /// Each `Resource` is immediately initialized, using the provided function.
35    ///
36    /// Whenever a `Resource` is returned to the pool, `reset_resource` is run on it first.
37    #[inline]
38    #[must_use]
39    pub fn new<F>(pool_size: usize, mut init_resource: F, reset_resource: Reset) -> Self
40    where
41        F:     FnMut() -> Resource,
42        Reset: ResetResource<Resource> + Clone,
43    {
44        let mut pool = Vec::new();
45        pool.reserve_exact(pool_size);
46        pool.extend(
47            iter::repeat_with(|| UnsafeCell::new(Some(init_resource()))).take(pool_size),
48        );
49        Self {
50            pool: Rc::from(pool),
51            reset_resource,
52        }
53    }
54}
55
56impl<Resource: Default, Reset> BoundedPool<Resource, Reset> {
57    /// Create a new `BoundedPool` which has the indicated, fixed number of `Resource`s.
58    ///
59    /// Each `Resource` is immediately initialized to its default value.
60    ///
61    /// Whenever a `Resource` is returned to the pool, `reset_resource` is run on it first.
62    #[inline]
63    #[must_use]
64    pub fn new_default(pool_size: usize, reset_resource: Reset) -> Self
65    where
66        Reset: ResetResource<Resource> + Clone,
67    {
68        Self::new(pool_size, Resource::default, reset_resource)
69    }
70}
71
72impl<Resource> BoundedPool<Resource, ResetNothing> {
73    /// Create a new `BoundedPool` which has the indicated, fixed number of `Resource`s.
74    ///
75    /// Each `Resource` is immediately initialized, using the provided function.
76    ///
77    /// When a `Resource` is returned to the pool, it is not reset in any way.
78    #[inline]
79    #[must_use]
80    pub fn new_without_reset<F>(pool_size: usize, init_resource: F) -> Self
81    where
82        F: FnMut() -> Resource,
83    {
84        Self::new(pool_size, init_resource, ResetNothing)
85    }
86}
87
88impl<Resource: Default> BoundedPool<Resource, ResetNothing> {
89    /// Create a new `BoundedPool` which has the indicated, fixed number of `Resource`s.
90    ///
91    /// Each `Resource` is immediately initialized to its default value.
92    ///
93    /// When a `Resource` is returned to the pool, it is not reset in any way.
94    #[inline]
95    #[must_use]
96    pub fn new_default_without_reset(pool_size: usize) -> Self {
97        Self::new(pool_size, Resource::default, ResetNothing)
98    }
99}
100
101impl<Resource, Reset: ResetResource<Resource> + Clone> BoundedPool<Resource, Reset> {
102    /// Get a `Resource` from the pool, if any are available.
103    pub fn try_get(&self) -> Result<PooledResource<Self, Resource>, ResourcePoolEmpty> {
104        self.pool.iter()
105            .enumerate()
106            .find_map(|(slot_idx, slot)| {
107                let slot: *mut Option<Resource> = slot.get();
108                // SAFETY:
109                // We only need to ensure that this access is unique in order for this to be sound.
110                // See the note on `BoundedPool.pool`. This is one of only three functions that
111                // access the `UnsafeCell` contents, and none allow a reference to escape, or call
112                // each other, except possibly in the carefully-handled `reset_resource` callback.
113                let slot: &mut Option<Resource> = unsafe { &mut *slot };
114
115                slot.take().map(|resource| {
116                    let pool = self.clone();
117                    // SAFETY:
118                    // It's safe for the `PooledResource` to call `return_resource` however it
119                    // likes, actually, and thus safe in the restricted guaranteed scenario.
120                    unsafe { PooledResource::new((pool, slot_idx), resource) }
121                })
122            })
123            .ok_or(ResourcePoolEmpty)
124    }
125
126    /// Get a `Resource` from the pool.
127    ///
128    /// # Panics
129    /// Panics if no resources are currently available. As `BoundedPool` is `!Send + !Sync`, no
130    /// resource could ever become available while in the body of this function.
131    #[must_use]
132    pub fn get(&self) -> PooledResource<Self, Resource> {
133        #[expect(
134            clippy::expect_used,
135            reason = "this call would never succeed if it fails once. Also, this is documented.",
136        )]
137        self.try_get().expect(
138            "A single-threaded BoundedPool ran out of `Resource`s and had `get()` called on \
139             it, which can never succeed",
140        )
141    }
142
143    /// Get the total number of `Resource`s in this pool, whether available or in-use.
144    #[inline]
145    #[must_use]
146    pub fn pool_size(&self) -> usize {
147        self.pool.len()
148    }
149
150    /// Get the number of `Resource`s in the pool which are not currently being used.
151    #[must_use]
152    pub fn available_resources(&self) -> usize {
153        self.pool.iter()
154            .map(|slot| {
155                let slot: *const Option<Resource> = slot.get().cast_const();
156                // SAFETY:
157                // We can guarantee that this access is unique, implying that this is sound.
158                // See the note on `BoundedPool.pool`. This is one of only three functions that
159                // access the `UnsafeCell` contents, and none allow a reference to escape, or call
160                // each other, except possibly in the carefully-handled `reset_resource` callback.
161                let slot: &Option<Resource> = unsafe { & *slot };
162
163                if slot.is_some() {
164                    // The resource is available to be taken
165                    1_usize
166                } else {
167                    // The resource is in use
168                    0_usize
169                }
170            })
171            .sum()
172    }
173}
174
175impl<Resource, Reset> SealedPool<Resource> for BoundedPool<Resource, Reset>
176where
177    Reset: ResetResource<Resource> + Clone,
178{
179    type Returner = (Self, usize);
180
181    /// Used by [`PooledResource`] to return a `Resource` to a pool.
182    ///
183    /// # Safety
184    /// Must be called at most once in the `Drop` impl of a `PooledResource` constructed
185    /// via `PooledResource::new`, where `*returner` must be the `returner` value passed
186    /// to `PooledResource::new`.
187    unsafe fn return_resource(returner: &Self::Returner, mut resource: Resource) {
188        let this = &returner.0;
189        let slot_idx = returner.1;
190
191        // Note that we must call this before getting `slot_contents`.
192        this.reset_resource.reset(&mut resource);
193
194        #[expect(
195            clippy::indexing_slicing,
196            reason = "the pool slice's length is never changed after construction, and `slot_idx` \
197                      was a valid index into the slice when the `PooledResource` was made",
198        )]
199        let slot_contents: *mut Option<Resource> = this.pool[slot_idx].get();
200
201        // SAFETY:
202        // We only need to ensure that this access is unique in order for this to be sound.
203        // See the note on `BoundedPool.pool`. This is one of only three functions that access the
204        // `UnsafeCell` contents, and none allow a reference to escape, or call each other,
205        // except possibly in the carefully-handled `reset_resource` callback.
206        let slot_contents: &mut Option<Resource> = unsafe { &mut *slot_contents };
207
208        // Correctness:
209        // `slot_contents` is necessarily `None` right now, we called `Option::take` on the slot
210        // at index `slot_idx` to get a resource. We're putting a resource back into a slot
211        // which was in-use (namely, by the `PooledResource` calling this method).
212        *slot_contents = Some(resource);
213    }
214}
215
216impl<Resource, ResetResource: Clone> Clone for BoundedPool<Resource, ResetResource> {
217    #[inline]
218    fn clone(&self) -> Self {
219        Self {
220            pool:           Rc::clone(&self.pool),
221            reset_resource: self.reset_resource.clone(),
222        }
223    }
224
225    #[inline]
226    fn clone_from(&mut self, source: &Self) {
227        self.pool.clone_from(&source.pool);
228        self.reset_resource.clone_from(&source.reset_resource);
229    }
230}
231
232#[cfg(feature = "clone-behavior")]
233impl<Resource, ResetResource, S> MirroredClone<S> for BoundedPool<Resource, ResetResource>
234where
235    ResetResource: MirroredClone<S>,
236    S:             Speed,
237{
238    #[inline]
239    fn mirrored_clone(&self) -> Self {
240        Self {
241            pool:           Rc::clone(&self.pool),
242            reset_resource: self.reset_resource.mirrored_clone(),
243        }
244    }
245}
246
247
248#[cfg(all(test, not(tests_with_leaks)))]
249mod tests {
250    use std::array;
251    use super::*;
252
253
254    #[test]
255    fn zero_capacity() {
256        let pool: BoundedPool<(), ResetNothing> = BoundedPool::new_default_without_reset(0);
257        assert_eq!(pool.pool_size(), 0);
258        assert_eq!(pool.available_resources(), 0);
259        assert!(pool.try_get().is_err());
260    }
261
262    #[test]
263    #[should_panic]
264    fn zero_capacity_fail() {
265        let pool: BoundedPool<(), ResetNothing> = BoundedPool::new_default_without_reset(0);
266        let unreachable = pool.get();
267        let _: &() = &*unreachable;
268    }
269
270    #[test]
271    fn one_capacity() {
272        let pool: BoundedPool<(), ResetNothing> = BoundedPool::new_default_without_reset(1);
273        let unit = pool.get();
274        assert_eq!(pool.pool_size(), 1);
275        assert_eq!(pool.available_resources(), 0);
276        assert!(pool.try_get().is_err());
277        drop(unit);
278        assert_eq!(pool.available_resources(), 1);
279    }
280
281    #[test]
282    #[should_panic]
283    fn one_capacity_fail() {
284        let pool: BoundedPool<(), ResetNothing> = BoundedPool::new_default_without_reset(1);
285        let _unit = pool.get();
286        assert_eq!(pool.pool_size(), 1);
287        assert_eq!(pool.available_resources(), 0);
288        let _unreachable = pool.get();
289    }
290
291    #[test]
292    fn init_and_reset() {
293        const CAPACITY: usize = 10;
294
295        let pool = BoundedPool::new(CAPACITY, || 1_usize, |int: &mut usize| *int = 1);
296        let integers: [_; CAPACITY] = array::from_fn(|_| pool.get());
297        for (idx, mut integer) in integers.into_iter().enumerate() {
298            assert_eq!(*integer, 1);
299            *integer = idx;
300            assert_eq!(*integer, idx);
301        }
302
303        // They've been reset to 1
304        let integers: [_; CAPACITY] = array::from_fn(|_| pool.get());
305        for integer in integers {
306            assert_eq!(*integer, 1);
307        }
308    }
309
310    #[test]
311    fn no_reset() {
312        const CAPACITY: usize = 10;
313
314        let pool = BoundedPool::new(CAPACITY, || 1_usize, ResetNothing);
315        let integers: [_; CAPACITY] = array::from_fn(|_| pool.get());
316        for (idx, mut integer) in integers.into_iter().enumerate() {
317            assert_eq!(*integer, 1);
318            *integer = idx;
319            assert_eq!(*integer, idx);
320        }
321
322        // They haven't been reset. NOTE: users should not rely on the order.
323        let integers: [_; CAPACITY] = array::from_fn(|_| pool.get());
324        for (idx, integer) in integers.into_iter().enumerate() {
325            assert_eq!(*integer, idx);
326        }
327    }
328
329    /// This test has unspecified behavior that a user should not rely on.
330    #[test]
331    fn init_and_reset_disagreeing() {
332        let pool = BoundedPool::new(2, || 1, |int: &mut i32| *int = 2);
333        let first_int = pool.get();
334        assert_eq!(*first_int, 1);
335        drop(first_int);
336        let mut reset_first_int = pool.get();
337        assert_eq!(*reset_first_int, 2);
338        let second_int = pool.get();
339        assert_eq!(*second_int, 1);
340        *reset_first_int = 3;
341        assert_eq!(*reset_first_int, 3);
342        drop(reset_first_int);
343        let re_reset_first_int = pool.get();
344        assert_eq!(*re_reset_first_int, 2);
345    }
346}