rylv_pool/core.rs
1//! Provider-independent pooling primitives.
2//!
3//! [`PoolProvider`] deliberately exposes only the lifecycle operations needed
4//! by [`PoolGuard`]. Providers choose their own storage, identity, routing, and
5//! synchronization strategy.
6
7use std::{
8 convert::Infallible,
9 mem::ManuallyDrop,
10 ops::{Deref, DerefMut},
11};
12
13use stable_deref_trait::StableDeref;
14
15use super::{PoolError, error::catch_operation};
16
17/// A reusable value managed by a pool.
18///
19/// Values must be movable between threads; thread-confined values are rejected.
20///
21/// ```compile_fail,E0277
22/// use std::rc::Rc;
23/// use rylv_pool::PoolItem;
24///
25/// struct LocalOnly(Rc<()>);
26/// impl PoolItem for LocalOnly {
27/// fn reset(&mut self) {}
28/// }
29/// ```
30pub trait PoolItem: Send + 'static {
31 /// Restore a value to its reusable state before it returns to a provider.
32 fn reset(&mut self);
33}
34
35/// Supplies complete take, return, and warming operations for a pool.
36///
37/// The provider owns all policy decisions. It may use a queue, thread-local
38/// storage, sharding, remote-return queues, or another implementation without
39/// exposing those concepts through this trait.
40///
41/// # Reentrancy
42///
43/// `take` and `warm` must invoke `create` without holding an
44/// exclusive borrow or lock that would make reentrant acquisition unsound.
45/// `return_entry` must return a rejected entry to its caller rather than
46/// dropping it while such internal access remains active. Providers must keep
47/// storage invariants valid on unwind. `take` and `warm` should use
48/// [`PoolError::catch`] to capture unwinding panics.
49pub trait PoolProvider<T: PoolItem>: Send + 'static {
50 /// Provider-owned entry with exclusive access to an address-stable value.
51 /// Providers choose its allocation strategy and private routing context.
52 ///
53 /// An entry providing access without `StableDeref` is insufficient:
54 ///
55 /// ```compile_fail,E0277
56 /// use rylv_pool::{PoolError, PoolItem, PoolProvider};
57 /// # struct Item;
58 /// # impl PoolItem for Item { fn reset(&mut self) {} }
59 /// # struct InlineEntry(Item);
60 /// # impl std::ops::Deref for InlineEntry {
61 /// # type Target = Item;
62 /// # fn deref(&self) -> &Item { &self.0 }
63 /// # }
64 /// # impl std::ops::DerefMut for InlineEntry {
65 /// # fn deref_mut(&mut self) -> &mut Item { &mut self.0 }
66 /// # }
67 /// struct Provider;
68 /// impl PoolProvider<Item> for Provider {
69 /// type Entry = InlineEntry;
70 /// # fn take<F, E>(&self, create: F) -> Result<Self::Entry, PoolError<E>>
71 /// # where F: FnOnce() -> Result<Item, E> {
72 /// # create().map(InlineEntry).map_err(PoolError::Factory)
73 /// # }
74 /// # fn return_entry(&self, entry: Self::Entry) -> Result<(), Self::Entry> {
75 /// # Err(entry)
76 /// # }
77 /// # fn warm<F, E>(&self, _: usize, _: F) -> Result<usize, PoolError<E>>
78 /// # where F: FnMut() -> Result<Item, E> { Ok(0) }
79 /// }
80 /// ```
81 type Entry: DerefMut<Target = T> + StableDeref + Send + 'static;
82
83 /// Take a reusable entry, invoking `create` only on a pool miss.
84 ///
85 /// # Errors
86 ///
87 /// Returns the factory error or a captured panic when acquisition fails.
88 fn take<F, E>(&self, create: F) -> Result<Self::Entry, PoolError<E>>
89 where
90 F: FnOnce() -> Result<T, E>;
91
92 /// Return an entry to its pool.
93 ///
94 /// On rejection, ownership must be returned so destruction happens after
95 /// provider access has ended.
96 ///
97 /// # Errors
98 ///
99 /// Returns the unchanged entry when the provider cannot retain or route
100 /// it. The guard then destroys it after provider access has ended.
101 fn return_entry(&self, entry: Self::Entry) -> Result<(), Self::Entry>;
102
103 /// Ensure that up to `count` configured values are available for reuse.
104 ///
105 /// If creation fails after some entries were inserted, those entries stay
106 /// available and the creation error is returned.
107 ///
108 /// # Errors
109 ///
110 /// Returns the factory error or a captured panic while warming the pool.
111 fn warm<F, E>(&self, count: usize, create: F) -> Result<usize, PoolError<E>>
112 where
113 F: FnMut() -> Result<T, E>;
114}
115
116// Keep this helper internal even if its containing module becomes public.
117#[allow(clippy::redundant_pub_crate)]
118pub(super) struct Storage<E, const N: usize> {
119 slots: [Option<E>; N],
120 len: usize,
121}
122
123impl<E, const N: usize> Storage<E, N> {
124 pub(super) const fn new() -> Self {
125 Self {
126 slots: [const { None }; N],
127 len: 0,
128 }
129 }
130
131 #[inline(always)]
132 pub(super) const fn pop(&mut self) -> Option<E> {
133 if self.len == 0 {
134 return None;
135 }
136 self.len -= 1;
137 self.slots[self.len].take()
138 }
139
140 #[inline(always)]
141 pub(super) fn try_push(&mut self, entry: E) -> Result<(), E> {
142 if self.len < N {
143 self.slots[self.len] = Some(entry);
144 self.len += 1;
145 Ok(())
146 } else {
147 Err(entry)
148 }
149 }
150
151 #[inline(always)]
152 pub(super) const fn len(&self) -> usize {
153 self.len
154 }
155
156 /// Retain the newest entries that fit, leaving older overflow in the
157 /// iterator. Only the appended slots are reversed to preserve pop order.
158 pub(super) fn extend_newest_first(&mut self, entries: &mut impl Iterator<Item = E>) {
159 let start = self.len;
160 while self.len < N {
161 let Some(entry) = entries.next() else {
162 break;
163 };
164 self.slots[self.len] = Some(entry);
165 self.len += 1;
166 }
167 self.slots[start..self.len].reverse();
168 }
169}
170
171/// Exclusive, address-stable guard returned by a [`PoolProvider`].
172///
173/// # Address stability
174///
175/// The provider entry implements [`StableDeref`], keeping the value at the same
176/// address for the complete lifetime of the guard, even when the guard moves.
177///
178/// References into the value cannot outlive the guard:
179///
180/// ```compile_fail,E0505
181/// use rylv_pool::{FixedThreadLocalPool, FixedThreadLocalPoolGuard, PoolItem};
182/// # #[derive(Default)]
183/// # struct Item(Vec<u8>);
184/// # impl PoolItem for Item { fn reset(&mut self) { self.0.clear(); } }
185/// # thread_local! {
186/// # static POOL: FixedThreadLocalPool<Item, 1> = FixedThreadLocalPool::new();
187/// # }
188/// let mut guard = FixedThreadLocalPoolGuard::acquire(&POOL).unwrap();
189/// let borrowed = &mut guard.0;
190/// drop(guard);
191/// borrowed.push(1);
192/// ```
193///
194/// A guard cannot provide two simultaneously used mutable references:
195///
196/// ```compile_fail,E0499
197/// use rylv_pool::{FixedThreadLocalPool, FixedThreadLocalPoolGuard, PoolItem};
198/// # #[derive(Default)]
199/// # struct Item(Vec<u8>);
200/// # impl PoolItem for Item { fn reset(&mut self) { self.0.clear(); } }
201/// # thread_local! {
202/// # static POOL: FixedThreadLocalPool<Item, 1> = FixedThreadLocalPool::new();
203/// # }
204/// let mut guard = FixedThreadLocalPoolGuard::acquire(&POOL).unwrap();
205/// let first = &mut guard.0;
206/// let second = &mut guard.0;
207/// first.push(1);
208/// second.push(2);
209/// ```
210///
211/// A guard cannot be cloned to create a second owner:
212///
213/// ```compile_fail,E0599
214/// use rylv_pool::{FixedThreadLocalPool, FixedThreadLocalPoolGuard, PoolItem};
215/// # #[derive(Default)]
216/// # struct Item;
217/// # impl PoolItem for Item { fn reset(&mut self) {} }
218/// # thread_local! {
219/// # static POOL: FixedThreadLocalPool<Item, 1> = FixedThreadLocalPool::new();
220/// # }
221/// let guard = FixedThreadLocalPoolGuard::acquire(&POOL).unwrap();
222/// let duplicate = guard.clone();
223/// ```
224pub struct PoolGuard<T: PoolItem, P: PoolProvider<T>> {
225 node: ManuallyDrop<P::Entry>,
226 provider: P,
227}
228
229impl<T: PoolItem, P: PoolProvider<T>> PoolGuard<T, P> {
230 /// Acquire a value through the supplied provider.
231 ///
232 /// # Errors
233 ///
234 /// Returns a captured panic if acquisition or the default factory unwinds.
235 #[inline(always)]
236 pub fn acquire(provider: P) -> Result<Self, PoolError<Infallible>>
237 where
238 T: Default,
239 {
240 Self::acquire_with(provider, T::default)
241 }
242
243 /// Acquire a value, using `create` only when the provider misses.
244 ///
245 /// # Errors
246 ///
247 /// Returns a captured panic if acquisition or the factory unwinds.
248 #[inline(always)]
249 pub fn acquire_with<F>(provider: P, create: F) -> Result<Self, PoolError<Infallible>>
250 where
251 F: FnOnce() -> T,
252 {
253 Self::try_acquire_with(provider, || Ok::<T, Infallible>(create()))
254 }
255
256 /// Acquire a value using a fallible factory only when the provider misses.
257 ///
258 /// # Errors
259 ///
260 /// Returns the factory error or a captured panic without creating a guard.
261 #[inline(always)]
262 pub fn try_acquire_with<F, E>(provider: P, create: F) -> Result<Self, PoolError<E>>
263 where
264 F: FnOnce() -> Result<T, E>,
265 {
266 Ok(Self {
267 node: ManuallyDrop::new(
268 catch_operation(|| provider.take(create)).map_err(PoolError::Panic)??,
269 ),
270 provider,
271 })
272 }
273}
274
275impl<T: PoolItem, P: PoolProvider<T>> Deref for PoolGuard<T, P> {
276 type Target = T;
277
278 #[inline(always)]
279 fn deref(&self) -> &Self::Target {
280 self.node.deref()
281 }
282}
283
284impl<T: PoolItem, P: PoolProvider<T>> DerefMut for PoolGuard<T, P> {
285 #[inline(always)]
286 fn deref_mut(&mut self) -> &mut Self::Target {
287 self.node.deref_mut()
288 }
289}
290
291// SAFETY: P::Entry implements StableDeref and the guard never replaces it.
292unsafe impl<T: PoolItem, P: PoolProvider<T>> StableDeref for PoolGuard<T, P> {}
293
294impl<T: PoolItem, P: PoolProvider<T>> Drop for PoolGuard<T, P> {
295 #[inline(always)]
296 fn drop(&mut self) {
297 // SAFETY: `node` is initialized by every constructor, `PoolGuard`
298 // cannot be reused after `Drop` starts, and this is its only take.
299 let mut node = unsafe { ManuallyDrop::take(&mut self.node) };
300 node.reset();
301
302 if let Err(node) = self.provider.return_entry(node) {
303 discard_entry(node);
304 }
305 }
306}
307
308/// Destroy rejected entries after provider access has ended.
309#[cold]
310#[inline(never)]
311fn discard_entry<E>(entry: E) {
312 drop(entry);
313}
314
315#[cfg(test)]
316mod tests;