use std::collections::VecDeque;
use std::sync::{Mutex, MutexGuard};
#[derive(Debug)]
pub struct Pool<T> {
storage: Mutex<VecDeque<T>>,
max_size: usize,
}
impl<T> Pool<T> {
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
storage: Mutex::new(VecDeque::new()),
max_size,
}
}
fn lock_storage(&self) -> MutexGuard<'_, VecDeque<T>> {
self.storage
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub fn try_get(&self) -> Option<T> {
self.lock_storage().pop_front()
}
pub fn put(&self, item: T) -> bool {
let mut storage = self.lock_storage();
if storage.len() < self.max_size {
storage.push_front(item);
return true;
}
false
}
#[must_use]
pub fn len(&self) -> usize {
self.lock_storage().len()
}
#[must_use]
pub const fn max_size(&self) -> usize {
self.max_size
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
self.lock_storage().clear();
}
}
pub trait Recyclable {
fn reset(&mut self);
}
#[derive(Debug)]
pub struct RecyclingPool<T>
where
T: Recyclable,
{
pool: Pool<T>,
}
impl<T> RecyclingPool<T>
where
T: Recyclable,
{
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
pool: Pool::new(max_size),
}
}
pub fn get_or_create<F>(&self, factory: F) -> T
where
F: FnOnce() -> T,
{
self.pool.try_get().unwrap_or_else(factory)
}
pub fn try_get(&self) -> Option<T> {
self.pool.try_get()
}
pub fn put_recycled(&self, mut item: T) -> bool {
item.reset();
self.pool.put(item)
}
#[must_use]
pub fn len(&self) -> usize {
self.pool.len()
}
#[must_use]
pub const fn max_size(&self) -> usize {
self.pool.max_size()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pool.is_empty()
}
pub fn clear(&self) {
self.pool.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[derive(Debug, PartialEq)]
struct TestObject {
value: u32,
data: String,
}
impl TestObject {
fn new(value: u32) -> Self {
Self {
value,
data: format!("test-{value}"),
}
}
}
impl Recyclable for TestObject {
fn reset(&mut self) {
self.value = 0;
self.data.clear();
}
}
#[test]
fn pool_basic_operations() {
let pool = Pool::new(10);
assert!(pool.is_empty());
let obj = TestObject::new(42);
pool.put(obj);
assert_eq!(pool.len(), 1);
let retrieved = pool.try_get().unwrap();
assert_eq!(retrieved.value, 42);
assert!(pool.is_empty());
}
#[test]
fn pool_capacity_limit() {
let pool = Pool::new(2);
pool.put(TestObject::new(1));
pool.put(TestObject::new(2));
pool.put(TestObject::new(3));
assert_eq!(pool.len(), 2);
}
#[test]
fn pool_recovers_after_poisoned_lock() {
let pool = Arc::new(Pool::new(2));
let poisoned_pool = Arc::clone(&pool);
let poison_result = std::panic::catch_unwind(move || {
let _guard = match poisoned_pool.storage.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
std::panic::panic_any("poison pool mutex for regression test");
});
assert!(poison_result.is_err());
pool.put(TestObject::new(7));
assert_eq!(pool.len(), 1);
assert_eq!(pool.try_get().map(|object| object.value), Some(7));
assert!(pool.is_empty());
}
#[test]
fn recycling_pool_reset() {
let pool = RecyclingPool::new(5);
let obj = pool.get_or_create(|| TestObject::new(42));
assert_eq!(obj.value, 42);
let mut obj = obj;
obj.value = 100;
obj.data = "modified".to_string();
pool.put_recycled(obj);
assert_eq!(pool.len(), 1);
let recycled = pool.get_or_create(|| TestObject::new(999));
assert_eq!(recycled.value, 0); assert!(recycled.data.is_empty()); }
}