use std::collections::HashMap;
use std::sync::Mutex;
pub trait WorkerPool: Send + Sync {
fn acquire(&self, resource_tag: Option<&str>) -> bool;
fn release(&self, resource_tag: Option<&str>);
fn add_resource(&self, _tag: &str) {}
fn remove_resource(&self, _tag: &str) {}
}
pub struct FixedPool {
max: usize,
active: Mutex<usize>,
}
impl FixedPool {
pub fn new(max_concurrent: usize) -> Self {
Self {
max: max_concurrent,
active: Mutex::new(0),
}
}
}
impl WorkerPool for FixedPool {
fn acquire(&self, _resource_tag: Option<&str>) -> bool {
let mut active = self.active.lock().unwrap();
if *active < self.max {
*active += 1;
true
} else {
false
}
}
fn release(&self, _resource_tag: Option<&str>) {
let mut active = self.active.lock().unwrap();
*active = active.saturating_sub(1);
}
}
pub struct UnlimitedPool;
impl WorkerPool for UnlimitedPool {
fn acquire(&self, _resource_tag: Option<&str>) -> bool {
true
}
fn release(&self, _resource_tag: Option<&str>) {}
}
pub struct DynamicPool {
global_max: usize,
global_active: Mutex<usize>,
tag_slots: Mutex<HashMap<String, (usize, usize)>>, }
impl DynamicPool {
pub fn new(global_max: usize) -> Self {
Self {
global_max,
global_active: Mutex::new(0),
tag_slots: Mutex::new(HashMap::new()),
}
}
pub fn set_tag_limit(&self, tag: &str, max: usize) {
let mut slots = self.tag_slots.lock().unwrap();
let entry = slots.entry(tag.to_string()).or_insert((0, 0));
entry.0 = max;
}
}
impl WorkerPool for DynamicPool {
fn acquire(&self, resource_tag: Option<&str>) -> bool {
let mut global = self.global_active.lock().unwrap();
if *global >= self.global_max {
return false;
}
if let Some(tag) = resource_tag {
let mut slots = self.tag_slots.lock().unwrap();
if let Some((max, active)) = slots.get_mut(tag) {
if *active >= *max {
return false;
}
*active += 1;
}
}
*global += 1;
true
}
fn release(&self, resource_tag: Option<&str>) {
let mut global = self.global_active.lock().unwrap();
*global = global.saturating_sub(1);
if let Some(tag) = resource_tag {
let mut slots = self.tag_slots.lock().unwrap();
if let Some((_, active)) = slots.get_mut(tag) {
*active = active.saturating_sub(1);
}
}
}
fn add_resource(&self, tag: &str) {
let mut slots = self.tag_slots.lock().unwrap();
let entry = slots.entry(tag.to_string()).or_insert((0, 0));
entry.0 += 1;
}
fn remove_resource(&self, tag: &str) {
let mut slots = self.tag_slots.lock().unwrap();
if let Some((max, _)) = slots.get_mut(tag) {
*max = max.saturating_sub(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixed_pool() {
let pool = FixedPool::new(2);
assert!(pool.acquire(None));
assert!(pool.acquire(None));
assert!(!pool.acquire(None)); pool.release(None);
assert!(pool.acquire(None)); }
#[test]
fn test_unlimited_pool() {
let pool = UnlimitedPool;
for _ in 0..1000 {
assert!(pool.acquire(None));
}
}
#[test]
fn test_dynamic_pool_global() {
let pool = DynamicPool::new(2);
assert!(pool.acquire(None));
assert!(pool.acquire(None));
assert!(!pool.acquire(None));
}
#[test]
fn test_dynamic_pool_tags() {
let pool = DynamicPool::new(10);
pool.set_tag_limit("gpu", 1);
assert!(pool.acquire(Some("gpu")));
assert!(!pool.acquire(Some("gpu"))); assert!(pool.acquire(Some("cpu"))); pool.release(Some("gpu"));
assert!(pool.acquire(Some("gpu"))); }
#[test]
fn test_dynamic_pool_add_remove() {
let pool = DynamicPool::new(10);
pool.set_tag_limit("builder", 0);
assert!(!pool.acquire(Some("builder")));
pool.add_resource("builder"); assert!(pool.acquire(Some("builder")));
assert!(!pool.acquire(Some("builder")));
pool.add_resource("builder"); assert!(pool.acquire(Some("builder")));
}
}