use super::*;
use std::sync::atomic::AtomicUsize;
use std::sync::mpsc::channel;
use std::time::Duration;
const WAIT: Duration = Duration::from_secs(30);
#[test]
fn dispatcher_rejects_submissions_once_workers_and_queue_are_full() {
let pool = BoundedDispatcher::new(1, 1);
let (started_tx, started_rx) = channel();
let (release_tx, release_rx) = channel::<()>();
assert!(
pool.try_submit(Box::new(move || {
let _ = started_tx.send(());
let _ = release_rx.recv_timeout(WAIT);
})),
"an idle pool must accept the first job"
);
started_rx
.recv_timeout(WAIT)
.expect("the worker never picked up the first job");
let (ran_tx, ran_rx) = channel();
assert!(
pool.try_submit(Box::new(move || {
let _ = ran_tx.send(());
})),
"the single queue slot must accept a second job"
);
assert!(
!pool.try_submit(Box::new(|| {})),
"a third job must be rejected: the only worker is busy and the queue is full"
);
assert_eq!(pool.rejected(), 1, "the rejection must be counted");
let _ = release_tx.send(());
ran_rx
.recv_timeout(WAIT)
.expect("the queued job never ran after the worker was released");
}
#[test]
fn no_more_jobs_run_concurrently_than_the_worker_count() {
const WORKERS: usize = 2;
const JOBS: usize = 16;
let pool = BoundedDispatcher::new(WORKERS, JOBS);
let live = Arc::new(AtomicUsize::new(0));
let peak = Arc::new(AtomicUsize::new(0));
let (done_tx, done_rx) = channel();
for _ in 0..JOBS {
let live = Arc::clone(&live);
let peak = Arc::clone(&peak);
let done = done_tx.clone();
assert!(
pool.try_submit(Box::new(move || {
let now = live.fetch_add(1, Ordering::SeqCst) + 1;
peak.fetch_max(now, Ordering::SeqCst);
std::thread::sleep(Duration::from_millis(20));
live.fetch_sub(1, Ordering::SeqCst);
let _ = done.send(());
})),
"the queue was sized to hold every job"
);
}
drop(done_tx);
for i in 0..JOBS {
done_rx
.recv_timeout(WAIT)
.unwrap_or_else(|e| panic!("job {i} never completed: {e}"));
}
let observed = peak.load(Ordering::SeqCst);
assert!(
observed <= WORKERS,
"{observed} jobs ran concurrently on a {WORKERS}-worker pool"
);
assert_eq!(pool.rejected(), 0, "nothing should have been rejected");
}
#[test]
fn a_fresh_pool_reports_no_drop_ever() {
let pool = BoundedDispatcher::new(1, 1);
assert_eq!(pool.rejected(), 0);
assert_eq!(pool.last_drop_unix_secs(), None);
}
#[test]
fn a_rejection_records_when_it_happened() {
let pool = BoundedDispatcher::new(1, 1);
let (started_tx, started_rx) = channel();
let (release_tx, release_rx) = channel::<()>();
assert!(pool.try_submit(Box::new(move || {
let _ = started_tx.send(());
let _ = release_rx.recv_timeout(WAIT);
})));
started_rx.recv_timeout(WAIT).expect("worker never started");
assert!(pool.try_submit(Box::new(|| {})), "queue slot must accept");
assert!(!pool.try_submit(Box::new(|| {})), "third must be rejected");
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
let at = pool
.last_drop_unix_secs()
.expect("a rejection must record when it happened");
assert!(
now.saturating_sub(at) <= 60,
"drop stamp {at} is not close to now ({now})"
);
let _ = release_tx.send(());
}
#[test]
fn a_truncation_is_counted_apart_from_a_rejection() {
let pool = BoundedDispatcher::new(1, 1);
assert_eq!(pool.truncated(), 0, "a fresh pool has truncated nothing");
assert_eq!(pool.last_truncation_unix_secs(), None);
pool.record_truncation();
assert_eq!(pool.truncated(), 1, "the truncation must be counted");
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
let at = pool
.last_truncation_unix_secs()
.expect("a truncation must record when it happened");
assert!(
now.saturating_sub(at) <= 60,
"truncation stamp {at} is not close to now ({now})"
);
assert_eq!(
pool.rejected(),
0,
"a truncation must not be reported as a pool rejection"
);
assert_eq!(
pool.last_drop_unix_secs(),
None,
"a truncation must not stamp the drop clock"
);
}
#[test]
fn a_panicking_job_does_not_kill_its_worker() {
let pool = BoundedDispatcher::new(1, 4);
assert!(pool.try_submit(Box::new(|| panic!("deliberate test panic"))));
let (ran_tx, ran_rx) = channel();
assert!(pool.try_submit(Box::new(move || {
let _ = ran_tx.send(());
})));
ran_rx
.recv_timeout(WAIT)
.expect("the worker died with the panicking job instead of surviving it");
}