use std::sync::Mutex;
use tokio::sync::Semaphore;
pub struct Pool<T> {
items: Mutex<Vec<T>>,
permits: Semaphore,
}
impl<T> Pool<T> {
pub fn new(capacity: usize) -> Self {
Self {
items: Mutex::new(Vec::new()),
permits: Semaphore::new(capacity.min(Semaphore::MAX_PERMITS)),
}
}
pub async fn take_or_create<F>(&self, create: F) -> T
where
F: Fn() -> T,
{
let permit = self.permits.acquire().await.expect("object pool semaphore was closed");
let existing = self.items.lock().expect("object pool lock poisoned").pop();
let item = existing.unwrap_or_else(create);
permit.forget();
item
}
pub fn put(&self, item: T) {
self.items.lock().expect("object pool lock poisoned").push(item);
self.permits.add_permits(1);
}
}
#[cfg(test)]
mod test {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::Pool;
#[tokio::test]
async fn reuses_returned_objects() {
let created = AtomicUsize::new(0);
let pool = Pool::new(5);
let first = pool.take_or_create(|| created.fetch_add(1, Ordering::SeqCst)).await;
pool.put(first);
let second = pool.take_or_create(|| created.fetch_add(1, Ordering::SeqCst)).await;
assert_eq!(first, second);
assert_eq!(created.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn waits_for_returned_object_when_capacity_reached() {
let pool = Arc::new(Pool::new(1));
let taken = pool.take_or_create(|| 1).await;
let waiter = {
let pool = pool.clone();
tokio::spawn(async move { pool.take_or_create(|| unreachable!("pool is at capacity")).await })
};
tokio::task::yield_now().await;
assert!(!waiter.is_finished());
pool.put(taken);
assert_eq!(waiter.await.unwrap(), 1);
}
#[tokio::test]
async fn panicking_creator_does_not_reduce_capacity() {
let pool = Arc::new(Pool::new(1));
let panicking = {
let pool = pool.clone();
tokio::spawn(async move { pool.take_or_create(|| panic!("creation failed")).await })
};
assert!(panicking.await.is_err());
assert_eq!(pool.take_or_create(|| 42).await, 42);
}
#[test]
fn works_without_tokio_runtime() {
smol::block_on(async {
let pool = Pool::new(1);
let value = pool.take_or_create(|| 42).await;
pool.put(value);
assert_eq!(pool.take_or_create(|| 0).await, 42);
});
}
}