nexus_pool/
local.rs

1//! Single-threaded object pools.
2//!
3//! Two variants:
4//! - [`BoundedPool`]: Fixed capacity, pre-initialized objects
5//! - [`Pool`]: Growable, creates objects on demand via factory
6//!
7//! Both use LIFO ordering for cache locality.
8
9use std::cell::UnsafeCell;
10use std::mem::{ManuallyDrop, MaybeUninit};
11use std::ops::{Deref, DerefMut};
12use std::rc::{Rc, Weak};
13
14// =============================================================================
15// Inner - shared storage for both pool types
16// =============================================================================
17
18#[repr(C)]
19struct Inner<T> {
20    /// Stack of available objects (LIFO)
21    data: UnsafeCell<Vec<T>>,
22
23    /// Reset function - called when object returns to pool
24    reset: UnsafeCell<Box<dyn FnMut(&mut T)>>,
25
26    /// Factory function - only initialized for Pool, not BoundedPool
27    factory: UnsafeCell<MaybeUninit<Box<dyn FnMut() -> T>>>,
28}
29
30impl<T> Inner<T> {
31    /// Create inner for BoundedPool - factory is NOT initialized
32    fn new_bounded<R>(data: Vec<T>, reset: R) -> Self
33    where
34        R: FnMut(&mut T) + 'static,
35    {
36        Self {
37            data: UnsafeCell::new(data),
38            reset: UnsafeCell::new(Box::new(reset)),
39            factory: UnsafeCell::new(MaybeUninit::uninit()),
40        }
41    }
42
43    /// Create inner for Pool - factory IS initialized
44    fn new_growable<F, R>(data: Vec<T>, factory: F, reset: R) -> Self
45    where
46        F: FnMut() -> T + 'static,
47        R: FnMut(&mut T) + 'static,
48    {
49        Self {
50            data: UnsafeCell::new(data),
51            reset: UnsafeCell::new(Box::new(reset)),
52            factory: UnsafeCell::new(MaybeUninit::new(Box::new(factory))),
53        }
54    }
55
56    /// Try to pop from available stack. Used by both pool types.
57    #[inline]
58    fn try_pop(&self) -> Option<T> {
59        // Safety: single-threaded access
60        let data = unsafe { &mut *self.data.get() };
61        data.pop()
62    }
63
64    /// Pop or create via factory.
65    ///
66    /// # Safety
67    ///
68    /// Caller must ensure factory was initialized (i.e., this is Pool, not BoundedPool)
69    #[inline]
70    unsafe fn pop_or_create(&self) -> T {
71        unsafe {
72            let data = &mut *self.data.get();
73            match data.pop() {
74                Some(value) => value,
75                None => {
76                    let factory = &mut *self.factory.get();
77                    (factory.assume_init_mut())()
78                }
79            }
80        }
81    }
82
83    /// Reset and return value to available stack
84    #[inline]
85    fn return_value(&self, value: &mut T) {
86        // Safety: single-threaded access
87        let reset = unsafe { &mut *self.reset.get() };
88        reset(value);
89    }
90
91    /// Push value back to available stack
92    #[inline]
93    fn push(&self, value: T) {
94        // Safety: single-threaded access
95        let data = unsafe { &mut *self.data.get() };
96        data.push(value);
97    }
98
99    #[inline]
100    fn available(&self) -> usize {
101        // Safety: single-threaded access
102        unsafe { (*self.data.get()).len() }
103    }
104
105    #[inline]
106    fn is_empty(&self) -> bool {
107        self.available() == 0
108    }
109}
110
111// =============================================================================
112// BoundedPool - fixed capacity, pre-initialized
113// =============================================================================
114
115/// Fixed-capacity object pool with LIFO reuse.
116///
117/// All objects are pre-initialized at construction. When all objects are
118/// acquired, `try_acquire()` returns `None`.
119///
120/// # Example
121///
122/// ```
123/// use nexus_pool::local::BoundedPool;
124///
125/// let pool = BoundedPool::new(
126///     100,
127///     || Vec::<u8>::with_capacity(1024),
128///     |v| v.clear(),
129/// );
130///
131/// let mut buf = pool.try_acquire().unwrap();
132/// buf.extend_from_slice(b"hello");
133/// // buf auto-returns to pool on drop, clear() is called
134/// ```
135pub struct BoundedPool<T> {
136    inner: Rc<Inner<T>>,
137}
138
139impl<T> BoundedPool<T> {
140    /// Creates a pool with `capacity` pre-initialized objects.
141    ///
142    /// # Arguments
143    ///
144    /// * `capacity` - Number of objects to pre-allocate
145    /// * `init` - Factory function to create each object
146    /// * `reset` - Called when object returns to pool (e.g., `Vec::clear`)
147    ///
148    /// # Panics
149    ///
150    /// Panics if capacity is zero.
151    pub fn new<I, R>(capacity: usize, mut init: I, reset: R) -> Self
152    where
153        I: FnMut() -> T,
154        R: FnMut(&mut T) + 'static,
155    {
156        assert!(capacity > 0, "capacity must be non-zero");
157
158        let mut data = Vec::with_capacity(capacity);
159        for _ in 0..capacity {
160            data.push(init());
161        }
162
163        Self {
164            inner: Rc::new(Inner::new_bounded(data, reset)),
165        }
166    }
167
168    /// Attempts to acquire an object from the pool.
169    ///
170    /// Returns `None` if all objects are currently in use.
171    #[inline]
172    pub fn try_acquire(&self) -> Option<Pooled<T>> {
173        self.inner.try_pop().map(|value| Pooled {
174            value: ManuallyDrop::new(value),
175            inner: Rc::downgrade(&self.inner),
176        })
177    }
178
179    /// Returns the number of available objects.
180    #[inline]
181    pub fn available(&self) -> usize {
182        self.inner.available()
183    }
184
185    /// Returns true if there are no more available objects.
186    #[inline]
187    pub fn is_empty(&self) -> bool {
188        self.inner.is_empty()
189    }
190}
191
192// =============================================================================
193// Pool - growable, creates on demand
194// =============================================================================
195
196/// Growable object pool with LIFO reuse.
197///
198/// Objects are created on demand via the factory function when the pool
199/// is empty. Use `try_acquire()` for the fast path that only returns
200/// pooled objects, or `acquire()` which may create new objects.
201///
202/// # Example
203///
204/// ```
205/// use nexus_pool::local::Pool;
206///
207/// let pool = Pool::new(
208///     || Vec::<u8>::with_capacity(1024),
209///     |v| v.clear(),
210/// );
211///
212/// let mut buf = pool.acquire(); // Creates new object
213/// buf.extend_from_slice(b"hello");
214/// drop(buf); // Returns to pool, clear() is called
215///
216/// let buf2 = pool.acquire(); // Reuses existing (now empty) object
217/// ```
218pub struct Pool<T> {
219    inner: Rc<Inner<T>>,
220}
221
222impl<T> Pool<T> {
223    /// Creates an empty pool with the given factory and reset functions.
224    ///
225    /// # Arguments
226    ///
227    /// * `factory` - Creates new objects when pool is empty
228    /// * `reset` - Called when object returns to pool (e.g., `Vec::clear`)
229    pub fn new<F, R>(factory: F, reset: R) -> Self
230    where
231        F: FnMut() -> T + 'static,
232        R: FnMut(&mut T) + 'static,
233    {
234        Self {
235            inner: Rc::new(Inner::new_growable(Vec::new(), factory, reset)),
236        }
237    }
238
239    /// Creates a pool pre-populated with `capacity` objects.
240    pub fn with_capacity<F, R>(capacity: usize, mut factory: F, reset: R) -> Self
241    where
242        F: FnMut() -> T + 'static,
243        R: FnMut(&mut T) + 'static,
244    {
245        let mut data = Vec::with_capacity(capacity);
246        for _ in 0..capacity {
247            data.push(factory());
248        }
249
250        Self {
251            inner: Rc::new(Inner::new_growable(data, factory, reset)),
252        }
253    }
254
255    /// Acquires an object from the pool, creating one if necessary.
256    ///
257    /// This always succeeds but may allocate if the pool is empty.
258    #[inline]
259    pub fn acquire(&self) -> Pooled<T> {
260        // Safety: Pool always initializes the factory
261        let value = unsafe { self.inner.pop_or_create() };
262        Pooled {
263            value: ManuallyDrop::new(value),
264            inner: Rc::downgrade(&self.inner),
265        }
266    }
267
268    /// Attempts to acquire an object from the pool without creating.
269    ///
270    /// Returns `None` if the pool is empty. This is the fast path.
271    #[inline]
272    pub fn try_acquire(&self) -> Option<Pooled<T>> {
273        self.inner.try_pop().map(|value| Pooled {
274            value: ManuallyDrop::new(value),
275            inner: Rc::downgrade(&self.inner),
276        })
277    }
278
279    /// Returns the number of available objects.
280    #[inline]
281    pub fn available(&self) -> usize {
282        self.inner.available()
283    }
284}
285
286impl<T> Drop for Pool<T> {
287    fn drop(&mut self) {
288        // Drop the factory before Rc drops Inner
289        // Safety: Pool always initializes the factory
290        unsafe {
291            let factory = &mut *self.inner.factory.get();
292            factory.assume_init_drop();
293        }
294    }
295}
296
297// =============================================================================
298// Pooled - RAII guard
299// =============================================================================
300
301/// RAII guard that returns the object to the pool on drop.
302///
303/// The object is always returned to the pool when the guard is dropped.
304/// There is no way to "take" the object out permanently.
305pub struct Pooled<T> {
306    value: ManuallyDrop<T>,
307    inner: Weak<Inner<T>>,
308}
309
310impl<T> Deref for Pooled<T> {
311    type Target = T;
312
313    #[inline]
314    fn deref(&self) -> &T {
315        &self.value
316    }
317}
318
319impl<T> DerefMut for Pooled<T> {
320    #[inline]
321    fn deref_mut(&mut self) -> &mut T {
322        &mut self.value
323    }
324}
325
326impl<T> Drop for Pooled<T> {
327    fn drop(&mut self) {
328        if let Some(inner) = self.inner.upgrade() {
329            // Reset and return to pool
330            inner.return_value(&mut self.value);
331            // Safety: value is valid, we're moving it back to the pool
332            let value = unsafe { ManuallyDrop::take(&mut self.value) };
333            inner.push(value);
334        } else {
335            // Pool is gone, drop the value
336            // Safety: value is valid, needs to be dropped
337            unsafe { ManuallyDrop::drop(&mut self.value) };
338        }
339    }
340}
341
342// =============================================================================
343// Tests
344// =============================================================================
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use std::cell::Cell;
350    use std::rc::Rc as StdRc;
351
352    #[test]
353    fn bounded_pool_basic() {
354        let pool = BoundedPool::new(3, || Vec::<u8>::with_capacity(16), |v| v.clear());
355
356        assert_eq!(pool.available(), 3);
357
358        let mut a = pool.try_acquire().unwrap();
359        assert_eq!(pool.available(), 2);
360
361        a.extend_from_slice(b"hello");
362        assert_eq!(&*a, b"hello");
363
364        let _b = pool.try_acquire().unwrap();
365        let _c = pool.try_acquire().unwrap();
366
367        assert_eq!(pool.available(), 0);
368
369        // Pool exhausted
370        assert!(pool.try_acquire().is_none());
371
372        drop(a);
373        assert_eq!(pool.available(), 1);
374
375        // Can acquire again - and it's been cleared
376        let d = pool.try_acquire().unwrap();
377        assert!(d.is_empty()); // reset was called
378    }
379
380    #[test]
381    fn bounded_pool_reset_called() {
382        let reset_count = StdRc::new(Cell::new(0));
383        let reset_count_clone = reset_count.clone();
384
385        let pool = BoundedPool::new(
386            2,
387            || 0u32,
388            move |_| {
389                reset_count_clone.set(reset_count_clone.get() + 1);
390            },
391        );
392
393        let a = pool.try_acquire().unwrap();
394        assert_eq!(reset_count.get(), 0);
395
396        drop(a);
397        assert_eq!(reset_count.get(), 1);
398
399        let b = pool.try_acquire().unwrap();
400        let c = pool.try_acquire().unwrap();
401        drop(b);
402        drop(c);
403        assert_eq!(reset_count.get(), 3);
404    }
405
406    #[test]
407    fn bounded_pool_outlives_guard() {
408        let guard;
409        {
410            let pool = BoundedPool::new(1, || String::from("test"), |s| s.clear());
411            guard = pool.try_acquire().unwrap();
412        }
413        // Pool dropped, guard still valid
414        assert_eq!(&*guard, "test");
415        // Drop guard - value is dropped, not returned (pool is gone)
416        drop(guard);
417    }
418
419    #[test]
420    fn growable_pool_basic() {
421        let pool = Pool::new(|| Vec::<u8>::with_capacity(16), |v| v.clear());
422
423        assert_eq!(pool.available(), 0);
424
425        // acquire creates new object
426        let mut a = pool.acquire();
427        a.extend_from_slice(b"hello");
428
429        drop(a);
430        assert_eq!(pool.available(), 1);
431
432        // acquire reuses - and it's been cleared
433        let b = pool.acquire();
434        assert!(b.is_empty()); // reset was called
435        assert_eq!(pool.available(), 0);
436    }
437
438    #[test]
439    fn growable_pool_try_acquire() {
440        let pool = Pool::new(|| 42u32, |_| {});
441
442        // Empty pool, try_acquire returns None
443        assert!(pool.try_acquire().is_none());
444
445        // acquire creates
446        let a = pool.acquire();
447        drop(a);
448
449        // Now try_acquire succeeds
450        let b = pool.try_acquire().unwrap();
451        assert_eq!(*b, 42);
452    }
453
454    #[test]
455    fn growable_pool_with_capacity() {
456        let pool = Pool::with_capacity(5, || String::new(), |s| s.clear());
457
458        assert_eq!(pool.available(), 5);
459
460        let _a = pool.try_acquire().unwrap();
461        let _b = pool.try_acquire().unwrap();
462        assert_eq!(pool.available(), 3);
463    }
464
465    #[test]
466    fn growable_pool_outlives_guard() {
467        let guard;
468        {
469            let pool = Pool::new(|| String::from("test"), |s| s.clear());
470            guard = pool.acquire();
471        }
472        // Pool dropped, guard still valid
473        assert_eq!(&*guard, "test");
474        drop(guard);
475    }
476
477    #[test]
478    #[should_panic(expected = "capacity must be non-zero")]
479    fn bounded_pool_zero_capacity_panics() {
480        let _ = BoundedPool::new(0, || (), |_| {});
481    }
482}