#![allow(unsafe_code)]
use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use super::internal::{AtomicU64, Ordering, UnsafeCell};
use super::list::List;
use super::queue::{Arrivals, Policy, QUEUED, Step, WaitQueue};
use super::waiter::{Cancel, Wait, WakeList, release_queued};
struct Waiting {
list: List,
generation: usize,
}
pub struct Barrier {
n: u64,
arrivals: AtomicU64,
queue: WaitQueue,
waiting: UnsafeCell<Waiting>,
}
unsafe impl Send for Barrier {}
unsafe impl Sync for Barrier {}
impl Barrier {
loom_const_fn! {
pub fn new(n: usize) -> Self {
Self {
n: if n == 0 { 1 } else { n as u64 },
arrivals: AtomicU64::new(0),
queue: WaitQueue::new(0),
waiting: UnsafeCell::new(Waiting { list: List::new(), generation: 0 }),
}
}
}
pub fn wait(&self) -> BarrierWait<'_> {
BarrierWait {
barrier: self,
arrival: Arrival::Before,
wait: Wait::new(),
}
}
fn arrive(&self) -> Result<(), usize> {
let arrival = self.arrivals.fetch_add(1, Ordering::AcqRel);
if arrival % self.n == self.n - 1 {
Ok(())
} else {
Err(self.generation_of(arrival))
}
}
fn generation_of(&self, arrival: u64) -> usize {
(arrival / self.n) as usize
}
}
impl Policy for Barrier {
fn queue(&self) -> &WaitQueue {
&self.queue
}
fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, _wake_front: bool) -> usize {
let pool = &self.queue;
let current = self.generation_of(self.arrivals.load(Ordering::Acquire));
self.waiting.with_mut(|waiting| unsafe {
let waiting = &mut *waiting;
if waiting.generation != current {
while let Some(node) = waiting.list.pop_front() {
woken.grant(node);
}
waiting.generation = current;
}
for node in arrivals {
if node.as_ref().is_cancelled() {
release_queued(node, pool);
} else if node.as_ref().payload() != current {
woken.grant(node);
} else {
waiting.list.push_back(node);
}
}
waiting.list.tidy(&self.queue);
if waiting.list.front(pool).is_none() {
QUEUED
} else {
0
}
})
}
}
impl Drop for Barrier {
fn drop(&mut self) {
let pool = &self.queue;
self.waiting
.with_mut(|waiting| unsafe { (*waiting).list.clear(pool) });
}
}
impl fmt::Debug for Barrier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Barrier")
.field("n", &self.n)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BarrierWaitResult {
leader: bool,
}
impl BarrierWaitResult {
pub fn is_leader(&self) -> bool {
self.leader
}
}
#[derive(Clone, Copy, Debug)]
enum Arrival {
Before,
In,
Led,
}
#[must_use = "futures do nothing unless polled"]
pub struct BarrierWait<'a> {
barrier: &'a Barrier,
arrival: Arrival,
wait: Wait,
}
impl Future for BarrierWait<'_> {
type Output = BarrierWaitResult;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<BarrierWaitResult> {
let this = self.get_mut();
let barrier = this.barrier;
match this.arrival {
Arrival::Before => match barrier.arrive() {
Ok(()) => {
this.arrival = Arrival::Led;
barrier.transition(|state| {
if state & QUEUED != 0 {
Step::Drain(state)
} else {
Step::Set(state)
}
});
Poll::Ready(BarrierWaitResult { leader: true })
}
Err(generation) => {
this.arrival = Arrival::In;
barrier.enqueue(&mut this.wait, generation, cx.waker());
this.wait
.poll(&barrier.queue, None)
.map(|_| BarrierWaitResult { leader: false })
}
},
Arrival::In => this
.wait
.poll(&barrier.queue, Some(cx.waker()))
.map(|_| BarrierWaitResult { leader: false }),
Arrival::Led => Poll::Ready(BarrierWaitResult { leader: true }),
}
}
}
impl Drop for BarrierWait<'_> {
fn drop(&mut self) {
if let Cancel::Withdrawn(_, _shelved) = self.wait.cancel(&self.barrier.queue) {
self.barrier.withdrawn(false);
}
}
}
impl fmt::Debug for BarrierWait<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BarrierWait")
.field("arrival", &self.arrival)
.finish_non_exhaustive()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use crate::sync::test_support::{Polled, block_on};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering as StdOrdering};
#[test]
fn each_generation_releases_together_with_one_leader() {
let barrier = Barrier::new(3);
for _ in 0..3 {
let mut a = Polled::new(barrier.wait());
a.pending();
let mut b = Polled::new(barrier.wait());
b.pending();
let leader = Polled::new(barrier.wait()).ready();
assert!(leader.is_leader());
assert_eq!((a.wakes(), b.wakes()), (1, 1));
assert!(!a.ready().is_leader());
assert!(!b.ready().is_leader());
}
}
#[test]
fn a_dropped_wait_stays_counted() {
let barrier = Barrier::new(2);
Polled::new(barrier.wait()).pending();
let leader = Polled::new(barrier.wait()).ready();
assert!(leader.is_leader(), "the dropped arrival completed the pair");
let mut next = Polled::new(barrier.wait());
next.pending();
assert!(Polled::new(barrier.wait()).ready().is_leader());
assert!(!next.ready().is_leader());
}
#[test]
fn polling_the_leaders_finished_wait_again_arrives_nowhere() {
let barrier = Barrier::new(2);
let mut first = Polled::new(barrier.wait());
first.pending();
let mut leader = Polled::new(barrier.wait());
assert!(leader.ready().is_leader());
assert!(leader.ready().is_leader(), "a finished wait stays finished");
assert!(!first.ready().is_leader());
let mut next = Polled::new(barrier.wait());
next.pending();
assert!(Polled::new(barrier.wait()).ready().is_leader());
assert!(!next.ready().is_leader());
}
#[test]
fn a_barrier_of_nothing_acts_as_one() {
let barrier = Barrier::new(0);
for _ in 0..3 {
assert!(Polled::new(barrier.wait()).ready().is_leader());
}
}
#[test]
#[cfg(target_pointer_width = "64")]
fn a_group_larger_than_u32_max_is_a_group_all_the_same() {
let barrier = Barrier::new(u32::MAX as usize + 2);
let mut first = Polled::new(barrier.wait());
first.pending();
assert_eq!(barrier.n, u64::from(u32::MAX) + 2);
}
#[test]
fn threads_meet_at_every_generation() {
const ROUNDS: usize = if cfg!(miri) { 3 } else { 200 };
let barrier = Arc::new(Barrier::new(4));
let leaders = Arc::new(AtomicUsize::new(0));
let threads: Vec<_> = (0..4)
.map(|_| {
let (barrier, leaders) = (Arc::clone(&barrier), Arc::clone(&leaders));
std::thread::spawn(move || {
for _ in 0..ROUNDS {
if block_on(barrier.wait()).is_leader() {
leaders.fetch_add(1, StdOrdering::SeqCst);
}
}
})
})
.collect();
for thread in threads {
thread.join().expect("worker");
}
assert_eq!(leaders.load(StdOrdering::SeqCst), ROUNDS);
}
}