use std::sync::Arc;
use std::time::{Duration, Instant};
use tempfile::TempDir;
use tokio::sync::Mutex;
use tokio::task::LocalSet;
use crate::SystemClock;
use crate::rate_limit::config::Limits;
use crate::rate_limit::{AttemptOutcome, RateLimiter};
#[tokio::test(flavor = "current_thread")]
async fn concurrent_in_process_calls_serialise_via_lock_and_spacing() {
let dir = TempDir::new().unwrap();
let limiter = Arc::new(RateLimiter::new(
dir.path().to_path_buf(),
None,
Limits::test_fast(120, 240, 1),
Arc::new(SystemClock),
));
let starts: Arc<Mutex<Vec<Instant>>> = Arc::new(Mutex::new(Vec::new()));
let in_flight: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let max_in_flight: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
let count = 5_u32;
let local = LocalSet::new();
local
.run_until(async {
let mut handles = Vec::with_capacity(count as usize);
for _ in 0..count {
let limiter = limiter.clone();
let starts = starts.clone();
let in_flight = in_flight.clone();
let max_in_flight = max_in_flight.clone();
handles.push(tokio::task::spawn_local(async move {
limiter
.run(false, |snap| {
let starts = starts.clone();
let in_flight = in_flight.clone();
let max_in_flight = max_in_flight.clone();
async move {
starts.lock().await.push(Instant::now());
let mut current = in_flight.lock().await;
*current += 1;
let cur = *current;
drop(current);
let mut high = max_in_flight.lock().await;
if cur > *high {
*high = cur;
}
drop(high);
tokio::time::sleep(Duration::from_millis(40)).await;
*in_flight.lock().await -= 1;
(AttemptOutcome::Success, snap)
}
})
.await
.expect("run succeeded")
}));
}
for h in handles {
let _ = h.await;
}
})
.await;
let starts = starts.lock().await.clone();
assert_eq!(starts.len() as u32, count, "all closures must run");
let mut sorted = starts.clone();
sorted.sort();
for window in sorted.windows(2) {
let gap = window[1].duration_since(window[0]);
assert!(
gap >= Duration::from_millis(110),
"consecutive starts must be spaced by ≥ 110ms (~ base spacing - 10ms tolerance), got {gap:?}",
);
}
let max = *max_in_flight.lock().await;
assert_eq!(
max, 1,
"at most one closure must run at a time; observed {max}"
);
}