use std::sync::{Condvar, Mutex};
pub(super) struct InstancePool {
inner: Mutex<InstancePoolInner>,
condv: Condvar,
}
struct InstancePoolInner {
available: u32,
per_engine_limit: u32,
locked: Option<u64>,
}
impl InstancePool {
pub fn new(available: u32, per_engine_limit: u32) -> InstancePool {
InstancePool {
inner: Mutex::new(InstancePoolInner {
available,
per_engine_limit,
locked: None,
}),
condv: Condvar::new(),
}
}
pub fn put(&self) {
let mut guard = self.inner.lock().unwrap();
guard.available += 1;
if guard.available >= guard.per_engine_limit {
guard.locked = None;
self.condv.notify_one();
}
}
pub fn take(&self, id: u64) {
let mut guard = self.inner.lock().unwrap();
guard = self
.condv
.wait_while(guard, |p| p.locked.unwrap_or(id) != id)
.unwrap();
assert!(
guard.available > 0,
"no instances available: we must have exceeded our stack depth"
);
guard.available -= 1;
if guard.available < guard.per_engine_limit {
guard.locked = Some(id);
}
}
}
#[test]
fn test_instance_pool() {
let pool = InstancePool::new(12, 10);
std::thread::scope(|scope| {
pool.take(1);
pool.take(2);
assert_eq!(pool.inner.lock().unwrap().locked, None);
pool.take(1);
assert_eq!(pool.inner.lock().unwrap().locked, Some(1));
let t1 = scope.spawn(|| {
for _ in 0..9 {
pool.take(2)
}
});
std::thread::sleep(std::time::Duration::from_millis(10));
for _ in 0..8 {
pool.take(1);
}
assert_eq!(pool.inner.lock().unwrap().available, 1);
assert_eq!(pool.inner.lock().unwrap().locked, Some(1));
for _ in 0..10 {
pool.put();
}
t1.join().unwrap();
assert_eq!(pool.inner.lock().unwrap().locked, Some(2));
assert_eq!(pool.inner.lock().unwrap().available, 2);
for _ in 0..8 {
pool.put();
}
assert_eq!(pool.inner.lock().unwrap().locked, None);
});
}