use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
task::Poll,
thread,
};
use futures::executor::block_on;
use ntest::timeout;
use super::{counting_waker, handoff, poll_once, poll_with, ready};
use crate::lock::Barrier;
#[test]
fn waiters_wait_until_the_last_task_arrives() {
let barrier = Barrier::new(3);
let mut first = Box::pin(barrier.wait());
let mut second = Box::pin(barrier.wait());
assert!(poll_once(&mut first).is_pending());
assert!(poll_once(&mut second).is_pending());
assert!(poll_once(&mut first).is_pending());
assert!(poll_once(&mut second).is_pending());
let mut third = Box::pin(barrier.wait());
assert!(poll_once(&mut third).is_ready());
assert!(poll_once(&mut first).is_ready());
assert!(poll_once(&mut second).is_ready());
}
#[test]
fn the_task_that_completes_the_round_is_its_leader() {
let barrier = Barrier::new(3);
let mut first = Box::pin(barrier.wait());
let mut second = Box::pin(barrier.wait());
let mut third = Box::pin(barrier.wait());
assert!(poll_once(&mut third).is_pending());
assert!(poll_once(&mut first).is_pending());
let Poll::Ready(leader) = poll_once(&mut second) else {
panic!("the last task to arrive completes the round");
};
let Poll::Ready(third) = poll_once(&mut third) else {
panic!("the arrival that completes the round releases the tasks waiting");
};
let Poll::Ready(first) = poll_once(&mut first) else {
panic!("the arrival that completes the round releases the tasks waiting");
};
assert!(leader.is_leader());
assert!(!third.is_leader());
assert!(!first.is_leader());
}
#[test]
fn a_barrier_is_reusable_for_further_rounds() {
let barrier = Barrier::new(2);
for _ in 0..3 {
let mut first = Box::pin(barrier.wait());
let mut second = Box::pin(barrier.wait());
assert!(poll_once(&mut first).is_pending());
let Poll::Ready(leader) = poll_once(&mut second) else {
panic!("the second arrival completes a round of two");
};
let Poll::Ready(follower) = poll_once(&mut first) else {
panic!("the second arrival releases the first");
};
assert!(leader.is_leader());
assert!(!follower.is_leader());
}
}
#[test]
fn a_task_released_by_a_round_is_not_held_by_the_next() {
let barrier = Barrier::new(2);
let mut slow = Box::pin(barrier.wait());
assert!(poll_once(&mut slow).is_pending());
assert!(ready(barrier.wait()).is_leader());
let mut early = Box::pin(barrier.wait());
assert!(poll_once(&mut early).is_pending());
let Poll::Ready(slow) = poll_once(&mut slow) else {
panic!("a task whose round is complete is released");
};
assert!(!slow.is_leader());
assert!(poll_once(&mut early).is_pending());
let Poll::Ready(leader) = poll_once(&mut Box::pin(barrier.wait())) else {
panic!("the arrival of the second task completes the next round");
};
assert!(leader.is_leader());
assert!(poll_once(&mut early).is_ready());
}
#[test]
fn a_barrier_for_zero_or_one_task_completes_every_wait_at_once() {
for n in [0, 1] {
let barrier = Barrier::new(n);
for _ in 0..3 {
assert!(ready(barrier.wait()).is_leader());
}
}
}
#[test]
fn a_wait_that_was_never_polled_does_not_count() {
let barrier = Barrier::new(2);
let mut unpolled = Box::pin(barrier.wait());
let mut waiting = Box::pin(barrier.wait());
assert!(poll_once(&mut waiting).is_pending());
let Poll::Ready(leader) = poll_once(&mut unpolled) else {
panic!("the arrival of the second task completes the round");
};
assert!(leader.is_leader());
assert!(poll_once(&mut waiting).is_ready());
}
#[test]
fn a_wait_dropped_before_the_trip_takes_its_arrival_back() {
let barrier = Barrier::new(3);
let mut leaving = Box::pin(barrier.wait());
let mut first = Box::pin(barrier.wait());
assert!(poll_once(&mut leaving).is_pending());
assert!(poll_once(&mut first).is_pending());
drop(leaving);
let mut second = Box::pin(barrier.wait());
assert!(poll_once(&mut second).is_pending());
assert!(poll_once(&mut first).is_pending());
let Poll::Ready(leader) = poll_once(&mut Box::pin(barrier.wait())) else {
panic!("the third arrival completes the round");
};
assert!(leader.is_leader());
assert!(poll_once(&mut first).is_ready());
assert!(poll_once(&mut second).is_ready());
}
#[test]
fn a_wait_dropped_after_its_round_is_complete_does_not_count_towards_the_next() {
let barrier = Barrier::new(2);
let mut released = Box::pin(barrier.wait());
assert!(poll_once(&mut released).is_pending());
assert!(ready(barrier.wait()).is_leader());
drop(released);
let mut first = Box::pin(barrier.wait());
assert!(poll_once(&mut first).is_pending());
assert!(ready(barrier.wait()).is_leader());
assert!(poll_once(&mut first).is_ready());
}
#[test]
fn a_barrier_whose_waiters_all_gave_up_starts_again() {
let barrier = Barrier::new(2);
let mut first = Box::pin(barrier.wait());
assert!(poll_once(&mut first).is_pending());
drop(first);
let mut second = Box::pin(barrier.wait());
assert!(poll_once(&mut second).is_pending());
assert!(ready(barrier.wait()).is_leader());
assert!(poll_once(&mut second).is_ready());
}
#[test]
fn a_trip_wakes_every_task_that_waits() {
let barrier = Barrier::new(3);
let (first_woken, first_waker) = counting_waker();
let (second_woken, second_waker) = counting_waker();
let (dropped_woken, dropped_waker) = counting_waker();
let mut first = Box::pin(barrier.wait());
let mut dropped = Box::pin(barrier.wait());
let mut second = Box::pin(barrier.wait());
assert!(poll_with(&mut first, &first_waker).is_pending());
assert!(poll_with(&mut dropped, &dropped_waker).is_pending());
drop(dropped);
assert!(poll_with(&mut second, &second_waker).is_pending());
assert_eq!(first_woken.load(Ordering::SeqCst), 0);
assert_eq!(second_woken.load(Ordering::SeqCst), 0);
assert!(ready(barrier.wait()).is_leader());
assert_eq!(first_woken.load(Ordering::SeqCst), 1);
assert_eq!(second_woken.load(Ordering::SeqCst), 1);
assert_eq!(dropped_woken.load(Ordering::SeqCst), 0);
assert!(poll_with(&mut first, &first_waker).is_ready());
assert!(poll_with(&mut second, &second_waker).is_ready());
}
#[test]
fn a_barrier_and_its_result_print() {
let barrier = Barrier::new(2);
assert_eq!(format!("{barrier:?}"), "Barrier { n: 2, waiting: 0 }");
let mut waiting = Box::pin(barrier.wait());
assert!(poll_once(&mut waiting).is_pending());
assert_eq!(format!("{barrier:?}"), "Barrier { n: 2, waiting: 1 }");
let leader = ready(barrier.wait());
assert_eq!(format!("{barrier:?}"), "Barrier { n: 2, waiting: 0 }");
assert_eq!(
format!("{leader:?}"),
"BarrierWaitResult { is_leader: true }"
);
let Poll::Ready(follower) = poll_once(&mut waiting) else {
panic!("the arrival that completes the round releases the tasks waiting");
};
assert_eq!(
format!("{follower:?}"),
"BarrierWaitResult { is_leader: false }"
);
}
#[test]
#[timeout(15000)]
fn every_round_has_one_leader_and_nobody_leaves_before_the_last_arrives() {
let (threads, rounds) = if cfg!(miri) { (3, 20) } else { (6, 2000) };
let barrier = Arc::new(Barrier::new(threads));
let arrivals = Arc::new(AtomicUsize::new(0));
let leaders: Arc<Vec<_>> = Arc::new((0..rounds).map(|_| AtomicUsize::new(0)).collect());
let workers: Vec<_> = (0..threads)
.map(|_| {
let barrier = barrier.clone();
let arrivals = arrivals.clone();
let leaders = leaders.clone();
thread::spawn(move || {
for round in 0..rounds {
arrivals.fetch_add(1, Ordering::SeqCst);
if block_on(barrier.wait()).is_leader() {
leaders[round].fetch_add(1, Ordering::SeqCst);
}
let arrived = arrivals.load(Ordering::SeqCst);
assert!(arrived >= (round + 1) * threads);
assert!(arrived < (round + 2) * threads);
}
})
})
.collect();
for worker in workers {
worker.join().unwrap();
}
for round_leaders in leaders.iter() {
assert_eq!(round_leaders.load(Ordering::SeqCst), 1);
}
}
#[test]
#[timeout(15000)]
fn a_trip_racing_a_wait_is_not_missed() {
let rounds = if cfg!(miri) { 20 } else { 20_000 };
handoff(
Arc::new(Barrier::new(2)),
rounds,
|barrier, start_waiting| {
start_waiting();
block_on(barrier.wait());
},
|barrier| {
block_on(barrier.wait());
},
);
}