use std::sync::{OnceLock, RwLock};
use tokio::sync::Semaphore;
const DEFAULT_CAP: usize = 15;
static CAP: RwLock<usize> = RwLock::new(DEFAULT_CAP);
static SEM: OnceLock<Semaphore> = OnceLock::new();
pub fn init_session_sem(cap: usize) {
let cap = cap.max(1);
*CAP.write().unwrap() = cap;
let _ = SEM.set(Semaphore::new(cap));
}
#[allow(dead_code)]
pub fn session_concurrency_cap() -> usize {
*CAP.read().unwrap()
}
pub fn session_sem() -> &'static Semaphore {
SEM.get_or_init(|| Semaphore::new(*CAP.read().unwrap()))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
#[test]
fn default_cap_is_fifteen() {
assert_eq!(DEFAULT_CAP, 15);
}
#[test]
fn session_concurrency_cap_returns_at_least_one() {
let cap = session_concurrency_cap();
assert!(cap >= 1);
}
#[tokio::test]
async fn semaphore_limits_concurrent_acquires() {
let sem = Arc::new(Semaphore::new(2));
let p1 = sem.try_acquire().expect("first permit");
let p2 = sem.try_acquire().expect("second permit");
assert!(
sem.try_acquire().is_err(),
"third acquire should fail (cap=2)"
);
drop(p1);
let _p3 = sem.try_acquire().expect("permit after release");
drop(p2);
}
#[tokio::test]
async fn queued_job_starts_when_slot_opens() {
let sem = Arc::new(Semaphore::new(1));
let permit = sem.try_acquire().expect("initial permit");
let sem2 = Arc::clone(&sem);
let started = Arc::new(std::sync::atomic::AtomicBool::new(false));
let started2 = Arc::clone(&started);
let handle = tokio::spawn(async move {
let _p = sem2.acquire().await.unwrap();
started2.store(true, std::sync::atomic::Ordering::SeqCst);
});
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
assert!(
!started.load(std::sync::atomic::Ordering::SeqCst),
"should be waiting for slot"
);
drop(permit);
handle.await.unwrap();
assert!(
started.load(std::sync::atomic::Ordering::SeqCst),
"should have started after slot freed"
);
}
}