#![cfg(feature = "std")]
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use pool_mod::{Error, Manager, Pool};
#[derive(Debug)]
struct RecycleError;
impl fmt::Display for RecycleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("recycle failed")
}
}
impl std::error::Error for RecycleError {}
struct Steer {
created: Arc<AtomicUsize>,
recycle_fails: Arc<AtomicBool>,
}
impl Steer {
fn new() -> (Self, Arc<AtomicUsize>, Arc<AtomicBool>) {
let created = Arc::new(AtomicUsize::new(0));
let recycle_fails = Arc::new(AtomicBool::new(false));
let steer = Steer {
created: Arc::clone(&created),
recycle_fails: Arc::clone(&recycle_fails),
};
(steer, created, recycle_fails)
}
}
impl Manager for Steer {
type Resource = usize;
type Error = RecycleError;
fn create(&self) -> Result<usize, Self::Error> {
Ok(self.created.fetch_add(1, Ordering::SeqCst))
}
fn recycle(&self, _resource: &mut usize) -> Result<(), Self::Error> {
if self.recycle_fails.load(Ordering::SeqCst) {
return Err(RecycleError);
}
Ok(())
}
}
#[test]
fn idle_resource_is_reused_before_timeout() {
let (steer, created, _) = Steer::new();
let pool = Pool::builder(steer)
.max_size(2)
.idle_timeout(Some(Duration::from_secs(3600)))
.build()
.expect("configuration is valid");
drop(pool.get().expect("first checkout"));
drop(pool.get().expect("second checkout"));
assert_eq!(created.load(Ordering::SeqCst), 1);
}
#[test]
fn idle_resource_is_replaced_after_timeout() {
let (steer, created, _) = Steer::new();
let pool = Pool::builder(steer)
.max_size(2)
.idle_timeout(Some(Duration::from_millis(40)))
.build()
.expect("configuration is valid");
drop(pool.get().expect("first checkout")); assert_eq!(created.load(Ordering::SeqCst), 1);
thread::sleep(Duration::from_millis(120));
let fresh = pool.get().expect("a fresh resource");
assert_eq!(*fresh, 1);
assert_eq!(created.load(Ordering::SeqCst), 2);
}
#[test]
fn resource_is_replaced_after_max_lifetime() {
let (steer, created, _) = Steer::new();
let pool = Pool::builder(steer)
.max_size(2)
.max_lifetime(Some(Duration::from_millis(40)))
.build()
.expect("configuration is valid");
drop(pool.get().expect("first checkout"));
thread::sleep(Duration::from_millis(120));
let fresh = pool.get().expect("a fresh resource");
assert_eq!(*fresh, 1);
assert_eq!(created.load(Ordering::SeqCst), 2);
}
#[test]
fn recycle_failure_discards_resource() {
let (steer, _created, recycle_fails) = Steer::new();
let pool = Pool::builder(steer)
.max_size(2)
.build()
.expect("configuration is valid");
recycle_fails.store(true, Ordering::SeqCst);
{
let _resource = pool.get().expect("checkout succeeds");
assert_eq!(pool.status().size, 1);
}
assert_eq!(pool.status().size, 0);
assert_eq!(pool.status().idle, 0);
}
#[test]
fn close_while_borrowed_discards_on_return() {
let (steer, _created, _) = Steer::new();
let pool = Pool::builder(steer)
.max_size(2)
.build()
.expect("configuration is valid");
let borrowed = pool.get().expect("checkout succeeds");
assert_eq!(pool.status().in_use, 1);
pool.close();
assert!(pool.is_closed());
assert_eq!(pool.status().size, 1);
drop(borrowed);
assert_eq!(pool.status().size, 0);
assert!(matches!(pool.get(), Err(Error::Closed)));
}
#[test]
fn waiter_is_woken_when_a_resource_returns() {
let (steer, _created, _) = Steer::new();
let pool = Pool::builder(steer)
.max_size(1)
.build()
.expect("configuration is valid");
let held = pool.get().expect("first checkout takes the only slot");
let waiter = {
let pool = pool.clone();
thread::spawn(move || {
pool.get_timeout(Duration::from_secs(5)).map(|guard| *guard)
})
};
thread::sleep(Duration::from_millis(50));
drop(held);
let got = waiter.join().expect("waiter thread did not panic");
assert!(got.is_ok());
}