use std::{
future::Future,
panic::{AssertUnwindSafe, catch_unwind},
pin::Pin,
sync::{
Arc, Barrier,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
task::{Context, Poll, Wake, Waker},
thread,
};
use futures::executor::block_on;
use ntest::timeout;
use super::{handoff, linger, poll_once, ready};
use crate::lock::{PATIENCE, Semaphore, SemaphoreGuard};
#[test]
fn a_semaphore_admits_up_to_its_permits_at_once() {
let semaphore = Semaphore::new(2);
let first = ready(semaphore.acquire());
let _second = ready(semaphore.acquire());
let mut third = Box::pin(semaphore.acquire());
assert!(poll_once(&mut third).is_pending());
drop(first);
assert!(poll_once(&mut third).is_ready());
}
#[test]
fn a_release_lets_one_waiter_in() {
let semaphore = Semaphore::new(1);
let guard = ready(semaphore.acquire());
let mut first = Box::pin(semaphore.acquire());
assert!(poll_once(&mut first).is_pending());
let mut second = Box::pin(semaphore.acquire());
assert!(poll_once(&mut second).is_pending());
drop(guard);
let Poll::Ready(guard) = poll_once(&mut first) else {
panic!("the release lets the first waiter in");
};
assert!(poll_once(&mut second).is_pending());
drop(guard);
assert!(poll_once(&mut second).is_ready());
}
#[test]
fn two_releases_in_a_row_let_two_waiters_in() {
let semaphore = Semaphore::new(2);
let first = ready(semaphore.acquire());
let second = ready(semaphore.acquire());
let mut waiters = [Box::pin(semaphore.acquire()), Box::pin(semaphore.acquire())];
for waiter in &mut waiters {
assert!(poll_once(waiter).is_pending());
}
drop(first);
drop(second);
let [first, second] = &mut waiters;
let Poll::Ready(_first) = poll_once(first) else {
panic!("the first release lets the first waiter in");
};
assert!(poll_once(second).is_ready());
}
#[test]
fn add_permits_lets_that_many_waiters_in() {
let semaphore = Semaphore::new(0);
let mut waiters = [
Box::pin(semaphore.acquire()),
Box::pin(semaphore.acquire()),
Box::pin(semaphore.acquire()),
];
for waiter in &mut waiters {
assert!(poll_once(waiter).is_pending());
}
semaphore.add_permits(2);
let [first, second, third] = &mut waiters;
let Poll::Ready(guard) = poll_once(first) else {
panic!("the first of two permits added lets the first waiter in");
};
let Poll::Ready(_second) = poll_once(second) else {
panic!("the second of two permits added lets the second waiter in");
};
assert!(poll_once(third).is_pending());
drop(guard);
assert!(poll_once(third).is_ready());
}
#[test]
fn permits_given_back_can_take_the_count_past_max_permits() {
let semaphore = Semaphore::new(Semaphore::MAX_PERMITS);
let guard = semaphore
.try_acquire()
.expect("a new semaphore has its permits free");
semaphore.add_permits(1);
drop(guard);
semaphore.add_permits(0);
assert_eq!(
format!("{semaphore:?}"),
format!("Semaphore {{ permits: {} }}", Semaphore::MAX_PERMITS + 1)
);
}
#[test]
#[should_panic(expected = "more permits than a semaphore can have free")]
fn add_permits_panics_past_max_permits() {
let semaphore = Semaphore::new(Semaphore::MAX_PERMITS - 1);
semaphore.add_permits(1);
semaphore.add_permits(1);
}
#[test]
#[should_panic(expected = "more permits than a semaphore can have free")]
fn new_panics_past_max_permits() {
let _semaphore = Semaphore::new(Semaphore::MAX_PERMITS + 1);
}
#[test]
fn forget_keeps_the_permit_out_for_good() {
let semaphore = Semaphore::new(2);
ready(semaphore.acquire()).forget();
let guard = semaphore.try_acquire().expect("one permit is left");
assert!(semaphore.try_acquire().is_none());
drop(guard);
let guard = semaphore.try_acquire().expect("the permit left came back");
assert!(semaphore.try_acquire().is_none());
semaphore.add_permits(1);
assert!(semaphore.try_acquire().is_some());
drop(guard);
}
#[test]
fn try_acquire_fails_with_no_permit_free_and_succeeds_once_one_is_back() {
let semaphore = Semaphore::new(2);
let first = semaphore
.try_acquire()
.expect("a new semaphore has its permits free");
let second = ready(semaphore.acquire());
assert!(semaphore.try_acquire().is_none());
drop(first);
let first = semaphore.try_acquire().expect("a permit came back");
assert!(semaphore.try_acquire().is_none());
drop(second);
assert!(semaphore.try_acquire().is_some());
drop(first);
}
#[test]
fn an_arc_guard_goes_to_another_thread_and_gives_its_permit_back_there() {
let semaphore = Arc::new(Semaphore::new(1));
let guard = ready(semaphore.acquire_arc());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::spawn(move || drop(guard)).join().unwrap();
assert!(poll_once(&mut waiter).is_ready());
}
#[test]
fn an_arc_guard_holds_the_arc_until_dropped_or_forgotten() {
let semaphore = Arc::new(Semaphore::new(1));
let guard = ready(semaphore.acquire_arc());
assert_eq!(Arc::strong_count(&semaphore), 2);
assert!(semaphore.try_acquire_arc().is_none());
drop(guard);
assert_eq!(Arc::strong_count(&semaphore), 1);
let guard = semaphore.try_acquire_arc().expect("the permit came back");
assert_eq!(Arc::strong_count(&semaphore), 2);
guard.forget();
assert_eq!(Arc::strong_count(&semaphore), 1);
assert!(semaphore.try_acquire_arc().is_none());
semaphore.add_permits(1);
assert!(semaphore.try_acquire_arc().is_some());
}
#[test]
fn an_acquire_arc_that_waited_long_holds_newcomers_back() {
let semaphore = Arc::new(Semaphore::new(1));
let holder = ready(semaphore.acquire_arc());
let mut waiter = Box::pin(semaphore.acquire_arc());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(holder);
let barging = semaphore
.try_acquire_arc()
.expect("a free permit is taken, whoever waits for one");
assert!(poll_once(&mut waiter).is_pending());
drop(barging);
assert!(semaphore.try_acquire_arc().is_none());
let mut newcomer = Box::pin(semaphore.acquire_arc());
assert!(poll_once(&mut newcomer).is_pending());
let Poll::Ready(guard) = poll_once(&mut waiter) else {
panic!("the release of the barging guard lets the starved waiter in");
};
drop(guard);
assert!(poll_once(&mut newcomer).is_ready());
}
#[test]
fn a_newcomer_takes_a_free_permit_ahead_of_a_waiter() {
let semaphore = Semaphore::new(1);
let guard = ready(semaphore.acquire());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
drop(guard);
let mut newcomer = Box::pin(semaphore.acquire());
let Poll::Ready(newcomer) = poll_once(&mut newcomer) else {
panic!("a free permit is taken at once, whoever waits for one");
};
assert!(poll_once(&mut waiter).is_pending());
drop(newcomer);
assert!(poll_once(&mut waiter).is_ready());
}
#[test]
fn a_waiter_that_waited_long_holds_newcomers_back_from_a_free_permit() {
let semaphore = Semaphore::new(1);
let holder = ready(semaphore.acquire());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(holder);
let barging = ready(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
drop(barging);
assert!(semaphore.try_acquire().is_none());
let mut newcomer = Box::pin(semaphore.acquire());
assert!(poll_once(&mut newcomer).is_pending());
let Poll::Ready(guard) = poll_once(&mut waiter) else {
panic!("the release of the barging guard lets the starved waiter in");
};
drop(guard);
let Poll::Ready(guard) = poll_once(&mut newcomer) else {
panic!("the starved waiter's release lets the newcomer in");
};
drop(guard);
assert!(semaphore.try_acquire().is_some());
}
#[test]
fn a_waiter_waiting_already_takes_a_permit_ahead_of_a_starved_one() {
let semaphore = Semaphore::new(1);
let holder = ready(semaphore.acquire());
let mut starved = Box::pin(semaphore.acquire());
assert!(poll_once(&mut starved).is_pending());
let mut waiting = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiting).is_pending());
thread::sleep(PATIENCE);
drop(holder);
let barging = ready(semaphore.acquire());
assert!(poll_once(&mut starved).is_pending());
drop(barging);
assert!(semaphore.try_acquire().is_none());
let Poll::Ready(guard) = poll_once(&mut waiting) else {
panic!("a waiter that was waiting already takes the free permit");
};
assert!(poll_once(&mut starved).is_pending());
drop(guard);
assert!(poll_once(&mut starved).is_ready());
}
#[test]
fn a_starved_acquire_dropped_lets_newcomers_take_permits_again() {
let semaphore = Semaphore::new(1);
let waiter = starved_acquire(&semaphore);
drop(waiter);
assert!(semaphore.try_acquire().is_some());
}
#[test]
fn a_starved_acquire_dropped_after_its_wake_wakes_the_newcomer_behind_it() {
let semaphore = Semaphore::new(1);
let holder = ready(semaphore.acquire());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(holder);
let barging = ready(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
let mut newcomer = Box::pin(semaphore.acquire());
assert!(poll_once(&mut newcomer).is_pending());
drop(barging);
drop(waiter);
assert!(poll_once(&mut newcomer).is_ready());
}
#[test]
fn a_starved_acquire_wakes_as_many_newcomers_as_there_are_permits_free() {
let semaphore = Semaphore::new(3);
let first = ready(semaphore.acquire());
let second = ready(semaphore.acquire());
let third = ready(semaphore.acquire());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(first);
let barging = ready(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
drop(second);
drop(third);
drop(barging);
let mut newcomers = [
Box::pin(semaphore.acquire()),
Box::pin(semaphore.acquire()),
Box::pin(semaphore.acquire()),
];
for newcomer in &mut newcomers {
assert!(poll_once(newcomer).is_pending());
}
let Poll::Ready(guard) = poll_once(&mut waiter) else {
panic!("the starved waiter takes one of the free permits");
};
let [first, second, third] = &mut newcomers;
let Poll::Ready(_first) = poll_once(first) else {
panic!("the starved waiter wakes the first newcomer for a free permit");
};
let Poll::Ready(_second) = poll_once(second) else {
panic!("the starved waiter wakes the second newcomer for the last free permit");
};
assert!(poll_once(third).is_pending());
drop(guard);
assert!(poll_once(third).is_ready());
}
#[test]
fn a_starved_acquire_stops_holding_newcomers_back_before_it_wakes_them() {
let semaphore = Arc::new(Semaphore::new(1));
let waiter = starved_acquire(&semaphore);
let mut first = Box::pin(semaphore.acquire());
assert!(poll_once(&mut first).is_pending());
let wake = Arc::new(TakeOnWake {
semaphore: semaphore.clone(),
took: AtomicBool::new(false),
});
let waker = Waker::from(wake.clone());
let mut second = Box::pin(semaphore.acquire());
let polled = second.as_mut().poll(&mut Context::from_waker(&waker));
assert!(
polled.is_pending(),
"the starved waiter holds newcomers back"
);
drop(waiter);
assert!(
wake.took.load(Ordering::SeqCst),
"the newcomer woken takes the free permit"
);
}
#[test]
fn a_panicking_waker_woken_as_a_starved_acquire_completes_loses_no_permit() {
let semaphore = Semaphore::new(2);
let first = ready(semaphore.acquire());
let second = ready(semaphore.acquire());
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(first);
let barging = ready(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
drop(barging);
drop(second);
let waker = Waker::from(Arc::new(PanicOnWake));
let mut newcomer = Box::pin(semaphore.acquire());
let polled = newcomer.as_mut().poll(&mut Context::from_waker(&waker));
assert!(
polled.is_pending(),
"the starved waiter holds the newcomer back"
);
assert!(catch_unwind(AssertUnwindSafe(|| poll_once(&mut waiter))).is_err());
drop(waiter);
drop(ready(newcomer));
let _first = semaphore.try_acquire().expect("no permit is lost");
let _second = semaphore.try_acquire().expect("no permit is lost");
}
#[test]
fn a_dropped_acquire_future_does_not_strand_the_next_waiter() {
let semaphore = Semaphore::new(1);
let guard = ready(semaphore.acquire());
let mut first = Box::pin(semaphore.acquire());
assert!(poll_once(&mut first).is_pending());
let mut second = Box::pin(semaphore.acquire());
assert!(poll_once(&mut second).is_pending());
drop(guard);
drop(first);
assert!(poll_once(&mut second).is_ready());
}
#[test]
fn a_semaphore_and_its_guards_print_the_permits_free() {
let semaphore = Arc::new(Semaphore::new(2));
assert_eq!(format!("{semaphore:?}"), "Semaphore { permits: 2 }");
let guard = ready(semaphore.acquire());
assert_eq!(
format!("{guard:?}"),
"SemaphoreGuard(Semaphore { permits: 1 })"
);
let arc_guard = ready(semaphore.acquire_arc());
assert_eq!(
format!("{arc_guard:?}"),
"SemaphoreGuardArc(Semaphore { permits: 0 })"
);
drop(guard);
drop(arc_guard);
assert_eq!(format!("{semaphore:?}"), "Semaphore { permits: 2 }");
}
#[test]
#[timeout(15000)]
fn a_semaphore_admits_no_more_holders_than_its_permits_across_threads() {
let (permits, threads, rounds) = if cfg!(miri) { (2, 3, 20) } else { (3, 8, 1000) };
let semaphore = Arc::new(Semaphore::new(permits));
let holders = Arc::new(AtomicUsize::new(0));
let start = Arc::new(Barrier::new(threads));
let takers: Vec<_> = (0..threads)
.map(|_| {
let semaphore = semaphore.clone();
let holders = holders.clone();
let start = start.clone();
thread::spawn(move || {
start.wait();
for _ in 0..rounds {
let guard = block_on(semaphore.acquire());
let now = holders.fetch_add(1, Ordering::Relaxed) + 1;
assert!(now <= permits, "{now} holders of {permits} permits");
linger();
holders.fetch_sub(1, Ordering::Relaxed);
drop(guard);
}
})
})
.collect();
for taker in takers {
taker.join().unwrap();
}
let guards: Vec<_> = (0..permits)
.map(|_| semaphore.try_acquire().expect("every permit came back"))
.collect();
assert!(semaphore.try_acquire().is_none());
drop(guards);
}
#[test]
#[timeout(15000)]
fn a_release_racing_an_acquire_is_not_missed() {
let rounds = if cfg!(miri) { 20 } else { 20_000 };
handoff(
Arc::new(Semaphore::new(1)),
rounds,
|semaphore, start_waiting| {
let _guard = semaphore
.try_acquire()
.expect("nothing holds the permit between rounds");
start_waiting();
},
|semaphore| drop(block_on(semaphore.acquire())),
);
}
#[test]
#[timeout(15000)]
fn a_starved_acquire_dropped_racing_an_acquire_does_not_strand_it() {
let rounds = if cfg!(miri) { 20 } else { 2_000 };
handoff(
Arc::new(Semaphore::new(1)),
rounds,
|semaphore, start_waiting| {
let starved = starved_acquire(semaphore);
start_waiting();
drop(starved);
},
|semaphore| drop(block_on(semaphore.acquire())),
);
}
#[test]
#[timeout(15000)]
fn a_semaphore_that_three_threads_keep_taking_still_lets_a_fourth_in() {
let semaphore = Arc::new(Semaphore::new(2));
let stop = Arc::new(AtomicBool::new(false));
let start = Arc::new(Barrier::new(4));
let takers: Vec<_> = (0..3)
.map(|_| {
let semaphore = semaphore.clone();
let stop = stop.clone();
let start = start.clone();
thread::spawn(move || {
start.wait();
while !stop.load(Ordering::Relaxed) {
let guard = block_on(semaphore.acquire());
linger();
drop(guard);
}
})
})
.collect();
let waiter = thread::spawn({
let semaphore = semaphore.clone();
let stop = stop.clone();
move || {
start.wait();
let guard = block_on(semaphore.acquire());
stop.store(true, Ordering::Relaxed);
drop(guard);
}
});
waiter.join().unwrap();
for taker in takers {
taker.join().unwrap();
}
let first = semaphore.try_acquire().expect("every permit came back");
assert!(semaphore.try_acquire().is_some());
drop(first);
}
fn starved_acquire(semaphore: &Semaphore) -> Pin<Box<impl Future<Output = SemaphoreGuard<'_>>>> {
let holder = semaphore
.try_acquire()
.expect("nothing holds the permit between rounds");
let mut waiter = Box::pin(semaphore.acquire());
assert!(poll_once(&mut waiter).is_pending());
thread::sleep(PATIENCE);
drop(holder);
let barging = semaphore
.try_acquire()
.expect("the release left the permit free");
assert!(poll_once(&mut waiter).is_pending());
drop(barging);
assert!(
semaphore.try_acquire().is_none(),
"the waiter holds newcomers back"
);
waiter
}
struct TakeOnWake {
semaphore: Arc<Semaphore>,
took: AtomicBool,
}
impl Wake for TakeOnWake {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
let took = poll_once(&mut Box::pin(self.semaphore.acquire())).is_ready();
self.took.store(took, Ordering::SeqCst);
}
}
struct PanicOnWake;
impl Wake for PanicOnWake {
fn wake(self: Arc<Self>) {
panic!("a waker that panics");
}
}