use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::llm::concurrency::Limiter;
#[test]
fn available_reflects_configured_maximum_before_acquisition() {
let limiter = Limiter::new(7);
assert_eq!(
limiter.available(),
7,
"fresh limiter must show full capacity"
);
}
#[tokio::test]
async fn acquire_reduces_and_drop_restores_availability() {
let limiter = Limiter::new(5);
assert_eq!(limiter.available(), 5);
{
let _g1 = limiter.acquire().await;
assert_eq!(limiter.available(), 4, "one slot held");
let _g2 = limiter.acquire().await;
assert_eq!(limiter.available(), 3, "two slots held");
}
assert_eq!(
limiter.available(),
5,
"dropping all guards must restore full capacity"
);
}
#[tokio::test]
async fn limiter_of_one_serialises_two_concurrent_tasks() {
let limiter = Limiter::new(1);
let order = Arc::new(std::sync::Mutex::new(Vec::<&'static str>::new()));
let limiter_a = limiter.clone();
let order_a = order.clone();
let handle_a = tokio::spawn(async move {
let _g = limiter_a.acquire().await;
order_a.lock().unwrap().push("a-hold");
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
order_a.lock().unwrap().push("a-release");
});
let limiter_b = limiter.clone();
let order_b = order.clone();
let handle_b = tokio::spawn(async move {
let _g = limiter_b.acquire().await;
order_b.lock().unwrap().push("b-hold");
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
order_b.lock().unwrap().push("b-release");
});
handle_a.await.expect("a");
handle_b.await.expect("b");
let log = order.lock().unwrap();
let legal_a_first = log.as_slice() == ["a-hold", "a-release", "b-hold", "b-release"];
let legal_b_first = log.as_slice() == ["b-hold", "b-release", "a-hold", "a-release"];
assert!(
legal_a_first || legal_b_first,
"K=1 must serialise: got {log:?}"
);
}
#[tokio::test]
async fn peak_concurrency_never_exceeds_k() {
const N: usize = 50;
const K: usize = 4;
let limiter = Limiter::new(K);
let in_flight = Arc::new(AtomicUsize::new(0));
let peak = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::with_capacity(N);
for _ in 0..N {
let limiter = limiter.clone();
let in_flight = in_flight.clone();
let peak = peak.clone();
handles.push(tokio::spawn(async move {
let _guard = limiter.acquire().await;
let now = in_flight.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
for _ in 0..16 {
tokio::task::yield_now().await;
}
in_flight.fetch_sub(1, Ordering::SeqCst);
}));
}
for h in handles {
h.await.expect("task");
}
let observed = peak.load(Ordering::SeqCst);
assert!(
observed <= K,
"peak concurrency must be <= K ({K}), observed {observed}"
);
assert!(
observed >= 1,
"peak concurrency must be >= 1 for the test to exercise the limiter, observed {observed}"
);
}