use alloc::vec::Vec;
use core::cell::RefCell;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
const DEFAULT_MAX_POOL_SIZE: usize = 1024;
pub struct Pool<T> {
available: RefCell<Vec<T>>,
factory: fn() -> T,
reset: fn(&mut T),
max_size: usize,
}
impl<T> Pool<T> {
pub fn new(factory: fn() -> T) -> Self {
Self::with_reset(factory, |_| {})
}
pub fn with_reset(factory: fn() -> T, reset: fn(&mut T)) -> Self {
Pool {
available: RefCell::new(Vec::new()),
factory,
reset,
max_size: DEFAULT_MAX_POOL_SIZE,
}
}
pub fn with_max_size(mut self, max_size: usize) -> Self {
self.max_size = max_size;
self
}
pub fn with_initial(self, count: usize) -> Self {
let mut available = self.available.borrow_mut();
for _ in 0..count.min(self.max_size) {
available.push((self.factory)());
}
drop(available);
self
}
pub fn get(&self) -> Pooled<'_, T> {
let obj = self
.available
.borrow_mut()
.pop()
.unwrap_or_else(|| (self.factory)());
Pooled {
pool: self,
value: Some(obj),
}
}
fn return_object(&self, mut obj: T) {
(self.reset)(&mut obj);
let mut available = self.available.borrow_mut();
if available.len() < self.max_size {
available.push(obj);
}
}
pub fn available(&self) -> usize {
self.available.borrow().len()
}
pub fn clear(&self) {
self.available.borrow_mut().clear();
}
}
pub struct Pooled<'a, T> {
pool: &'a Pool<T>,
value: Option<T>,
}
impl<T> Deref for Pooled<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value.as_ref().unwrap()
}
}
impl<T> DerefMut for Pooled<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value.as_mut().unwrap()
}
}
impl<T> Drop for Pooled<'_, T> {
fn drop(&mut self) {
if let Some(obj) = self.value.take() {
self.pool.return_object(obj);
}
}
}
pub struct TypedPool<T, const N: usize> {
storage: RefCell<[MaybeUninit<T>; N]>,
available: RefCell<u64>,
allocated: RefCell<u64>,
factory: fn() -> T,
}
impl<T, const N: usize> TypedPool<T, N> {
pub fn new(factory: fn() -> T) -> Self {
assert!(N <= 64, "TypedPool size must be <= 64");
TypedPool {
storage: RefCell::new([const { MaybeUninit::uninit() }; N]),
available: RefCell::new(0),
allocated: RefCell::new(0),
factory,
}
}
pub fn get(&self) -> TypedPooled<'_, T, N> {
let mut available = self.available.borrow_mut();
if *available != 0 {
let slot = available.trailing_zeros() as usize;
*available &= !(1 << slot);
drop(available);
let storage = self.storage.borrow();
let value = unsafe { storage[slot].as_ptr().read() };
return TypedPooled {
pool: self,
slot: Some(slot),
value: Some(value),
};
}
drop(available);
let mut allocated = self.allocated.borrow_mut();
let all_mask: u64 = if N == 64 { !0 } else { (1u64 << N) - 1 };
let free = all_mask & !*allocated;
if free != 0 {
let slot = free.trailing_zeros() as usize;
*allocated |= 1 << slot;
drop(allocated);
TypedPooled {
pool: self,
slot: Some(slot),
value: Some((self.factory)()),
}
} else {
drop(allocated);
TypedPooled {
pool: self,
slot: None,
value: Some((self.factory)()),
}
}
}
fn return_object(&self, slot: Option<usize>, obj: T) {
if let Some(idx) = slot {
let mut storage = self.storage.borrow_mut();
storage[idx] = MaybeUninit::new(obj);
let mut available = self.available.borrow_mut();
*available |= 1 << idx;
} else {
drop(obj);
}
}
}
impl<T, const N: usize> Drop for TypedPool<T, N> {
fn drop(&mut self) {
let storage = self.storage.get_mut();
let available = self.available.get_mut();
for (i, item) in storage.iter_mut().enumerate() {
if (*available & (1 << i)) != 0 {
unsafe {
item.assume_init_drop();
}
}
}
}
}
pub struct TypedPooled<'a, T, const N: usize> {
pool: &'a TypedPool<T, N>,
slot: Option<usize>,
value: Option<T>,
}
impl<T, const N: usize> Deref for TypedPooled<'_, T, N> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.value.as_ref().unwrap()
}
}
impl<T, const N: usize> DerefMut for TypedPooled<'_, T, N> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value.as_mut().unwrap()
}
}
impl<T, const N: usize> Drop for TypedPooled<'_, T, N> {
fn drop(&mut self) {
if let Some(obj) = self.value.take() {
self.pool.return_object(self.slot, obj);
}
}
}
pub struct ContinuationPool {
small: Pool<SmallContinuation>,
medium: Pool<MediumContinuation>,
}
#[repr(align(8))]
pub struct SmallContinuation {
data: [u8; 64],
}
impl SmallContinuation {
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
#[repr(align(8))]
pub struct MediumContinuation {
data: [u8; 256],
}
impl MediumContinuation {
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.data
}
}
impl Default for SmallContinuation {
fn default() -> Self {
SmallContinuation { data: [0; 64] }
}
}
impl Default for MediumContinuation {
fn default() -> Self {
MediumContinuation { data: [0; 256] }
}
}
impl ContinuationPool {
pub fn new() -> Self {
ContinuationPool {
small: Pool::new(SmallContinuation::default)
.with_max_size(256)
.with_initial(32),
medium: Pool::new(MediumContinuation::default)
.with_max_size(64)
.with_initial(8),
}
}
pub fn get_small(&self) -> Pooled<'_, SmallContinuation> {
self.small.get()
}
pub fn get_medium(&self) -> Pooled<'_, MediumContinuation> {
self.medium.get()
}
pub fn stats(&self) -> PoolStats {
PoolStats {
small_available: self.small.available(),
medium_available: self.medium.available(),
}
}
}
impl Default for ContinuationPool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub small_available: usize,
pub medium_available: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_pool_basic() {
let pool: Pool<Vec<i32>> =
Pool::with_reset(|| Vec::with_capacity(10), std::vec::Vec::clear);
{
let mut v = pool.get();
v.push(1);
v.push(2);
v.push(3);
assert_eq!(v.len(), 3);
}
assert_eq!(pool.available(), 1);
{
let v = pool.get();
assert_eq!(v.len(), 0);
assert!(v.capacity() >= 10);
}
}
#[test]
fn test_pool_max_size() {
let pool: Pool<i32> = Pool::new(|| 0).with_max_size(2);
let a = pool.get();
let b = pool.get();
let c = pool.get();
drop(a);
drop(b);
drop(c);
assert_eq!(pool.available(), 2);
}
#[test]
fn test_pool_initial() {
let pool: Pool<i32> = Pool::new(|| 42).with_initial(5);
assert_eq!(pool.available(), 5);
}
#[test]
fn test_continuation_pool() {
let pool = ContinuationPool::new();
let small = pool.get_small();
assert_eq!(small.data.len(), 64);
let medium = pool.get_medium();
assert_eq!(medium.data.len(), 256);
let stats = pool.stats();
assert!(stats.small_available > 0 || stats.medium_available > 0);
}
#[test]
fn test_typed_pool_reuse() {
static CREATED: AtomicUsize = AtomicUsize::new(0);
CREATED.store(0, Ordering::SeqCst);
let pool = TypedPool::<i32, 2>::new(|| {
CREATED.fetch_add(1, Ordering::SeqCst);
42
});
assert_eq!(CREATED.load(Ordering::SeqCst), 0);
let a = pool.get();
assert_eq!(*a, 42);
assert_eq!(CREATED.load(Ordering::SeqCst), 1);
drop(a);
let b = pool.get();
assert_eq!(*b, 42);
assert_eq!(
CREATED.load(Ordering::SeqCst),
1,
"Expected pool to reuse object, but it created a new one"
);
}
#[test]
fn test_typed_pool_overflow_leak() {
use core::sync::atomic::{AtomicUsize, Ordering};
static DROPPED: AtomicUsize = AtomicUsize::new(0);
struct Tracked;
impl Drop for Tracked {
fn drop(&mut self) {
DROPPED.fetch_add(1, Ordering::SeqCst);
}
}
let pool = TypedPool::<Tracked, 1>::new(|| Tracked);
let a = pool.get();
assert!(a.slot.is_some());
let b = pool.get();
assert!(b.slot.is_none());
DROPPED.store(0, Ordering::SeqCst);
drop(b);
drop(a);
drop(pool);
assert_eq!(DROPPED.load(Ordering::SeqCst), 2, "Memory leak detected!");
}
}