use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use crossbeam_queue::SegQueue;
pub struct UnboundObjectPool<T: Send> {
share: Arc<Share<T>>,
init: Box<dyn Fn() -> T + Send + Sync>,
}
struct Share<T: Send> {
pool: SegQueue<Box<T>>,
release: Box<dyn Fn(&mut T) + Send + Sync>,
}
impl<T: Send + Sync> UnboundObjectPool<T> {
pub fn new(
size: usize,
init: impl Fn() -> T + Send + Sync + 'static,
release: impl Fn(&mut T) + Send + Sync + 'static,
) -> UnboundObjectPool<T> {
let pool = SegQueue::new();
for _ in 0..size {
pool.push(Box::new(init()));
}
UnboundObjectPool {
share: Arc::new(Share {
pool,
release: Box::new(release),
}),
init: Box::new(init),
}
}
pub fn get(&self) -> UnboundObjectPoolRef<T> {
let item = self
.share
.pool
.pop()
.unwrap_or_else(|| Box::new((self.init)()));
UnboundObjectPoolRef {
share: self.share.clone(),
item: Some(item),
}
}
pub fn size(&self) -> usize {
self.share.pool.len()
}
}
pub struct UnboundObjectPoolRef<T: Send> {
share: Arc<Share<T>>,
item: Option<Box<T>>,
}
impl<T: Send> Deref for UnboundObjectPoolRef<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.item.as_deref().expect("item only taken in Drop")
}
}
impl<T: Send> DerefMut for UnboundObjectPoolRef<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.item.as_deref_mut().expect("item only taken in Drop")
}
}
impl<T: Send> Drop for UnboundObjectPoolRef<T> {
fn drop(&mut self) {
let mut item = self.item.take().expect("item only taken once, here");
(self.share.release)(&mut item);
self.share.pool.push(item);
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::*;
#[test]
fn returned_item_is_reused_by_the_next_get() {
let pool = UnboundObjectPool::new(0, || 0i32, |_| {});
assert_eq!(pool.size(), 0);
let item = pool.get();
assert_eq!(pool.size(), 0, "checked out, not sitting in the pool");
drop(item);
assert_eq!(pool.size(), 1, "returned automatically on drop");
let _item2 = pool.get();
assert_eq!(pool.size(), 0, "reused, not left behind");
}
#[test]
fn get_never_fails_even_when_empty() {
let pool = UnboundObjectPool::new(0, || 5i32, |_| {});
assert_eq!(*pool.get(), 5);
assert_eq!(*pool.get(), 5);
}
#[test]
fn release_runs_before_the_item_goes_back_into_the_pool() {
let release_calls = Arc::new(AtomicUsize::new(0));
let counted = release_calls.clone();
let pool = UnboundObjectPool::new(
1,
|| 0i32,
move |_| {
counted.fetch_add(1, Ordering::SeqCst);
},
);
drop(pool.get());
assert_eq!(release_calls.load(Ordering::SeqCst), 1);
}
#[test]
fn dropping_the_last_arc_clone_returns_the_item() {
let pool = UnboundObjectPool::new(0, || 0i32, |_| {});
let shared = Arc::new(pool.get());
let clone = shared.clone();
drop(shared);
assert_eq!(pool.size(), 0, "one clone still alive — not returned yet");
drop(clone);
assert_eq!(pool.size(), 1, "last clone dropped — now it's returned");
}
}