use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::sync::Semaphore;
#[derive(Default)]
pub(crate) struct ConcurrencyLimits {
slots: Mutex<HashMap<(String, String), Slot>>,
}
struct Slot {
limit: u32,
semaphore: Arc<Semaphore>,
}
impl ConcurrencyLimits {
pub(crate) fn semaphore(&self, blueprint_id: &str, agent: &str, limit: u32) -> Arc<Semaphore> {
let mut slots = self
.slots
.lock()
.expect("concurrency limits mutex poisoned");
let slot = slots
.entry((blueprint_id.to_string(), agent.to_string()))
.or_insert_with(|| Slot {
limit,
semaphore: Arc::new(Semaphore::new(limit as usize)),
});
if slot.limit != limit {
*slot = Slot {
limit,
semaphore: Arc::new(Semaphore::new(limit as usize)),
};
}
Arc::clone(&slot.semaphore)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_key_and_limit_shares_one_semaphore() {
let t = ConcurrencyLimits::default();
let a = t.semaphore("bp", "coder", 2);
let b = t.semaphore("bp", "coder", 2);
assert!(Arc::ptr_eq(&a, &b));
}
#[test]
fn different_blueprint_or_agent_gets_its_own_semaphore() {
let t = ConcurrencyLimits::default();
let a = t.semaphore("bp", "coder", 2);
assert!(!Arc::ptr_eq(&a, &t.semaphore("bp2", "coder", 2)));
assert!(!Arc::ptr_eq(&a, &t.semaphore("bp", "reader", 2)));
}
#[test]
fn a_changed_limit_replaces_the_semaphore_with_the_new_size() {
let t = ConcurrencyLimits::default();
let old = t.semaphore("bp", "coder", 2);
let new = t.semaphore("bp", "coder", 5);
assert!(!Arc::ptr_eq(&old, &new));
assert_eq!(new.available_permits(), 5);
assert_eq!(old.available_permits(), 2);
}
}