Skip to main content

nuclei/sync/
barrier.rs

1//! A reusable barrier: every group of `n` waits completes together.
2//!
3//! The barrier counts every arrival, ever, in one `AtomicU64`: arrival `i`
4//! (counting from zero) belongs to generation `i / n` and completes it when
5//! `i % n` is `n - 1`. So an arrival learns its generation and whether it
6//! completes it in one `fetch_add`, which cannot fail or retry, whatever
7//! `n` is. On 64-bit targets that is a word; on armv7 and wasm32 it is their
8//! native 64-bit atomic, never an emulated wider one. Waiters carry their
9//! generation; a drain pass grants every waiter of a finished one. A waiter
10//! would miss its generation only if a whole word's worth of generations
11//! finished while one drain pass stood still; the count itself wraps after
12//! 2^64 arrivals, which no process reaches.
13
14#![allow(unsafe_code)]
15
16use core::fmt;
17use core::future::Future;
18use core::pin::Pin;
19use core::task::{Context, Poll};
20
21use super::internal::{AtomicU64, Ordering, UnsafeCell};
22use super::list::List;
23use super::queue::{Arrivals, Policy, QUEUED, Step, WaitQueue};
24use super::waiter::{Cancel, Wait, WakeList, release_queued};
25
26struct Waiting {
27    list: List,
28    /// The generation every waiter in `list` belongs to.
29    generation: usize,
30}
31
32/// A barrier that releases its waiters in groups of `n`.
33///
34/// The `n`-th [`wait`](Self::wait) of a generation completes at once as
35/// its leader and wakes the other `n - 1`; the barrier then starts the
36/// next generation. A wait dropped before its generation completes stays
37/// counted: the generation completes once `n` parties have arrived, and
38/// the dropped one is simply not woken. (Taking the arrival back would
39/// race with the arrival that completes the generation.)
40///
41/// ```
42/// use nuclei::sync::Barrier;
43/// use std::future::Future;
44/// use std::pin::pin;
45/// use std::task::{Context, Waker};
46///
47/// let barrier = Barrier::new(2);
48/// let mut cx = Context::from_waker(Waker::noop());
49/// let mut first = pin!(barrier.wait());
50/// assert!(first.as_mut().poll(&mut cx).is_pending());
51/// let mut second = pin!(barrier.wait());
52/// assert!(second.as_mut().poll(&mut cx).is_ready());
53/// assert!(first.poll(&mut cx).is_ready());
54/// ```
55pub struct Barrier {
56    /// The size of a group; never zero.
57    n: u64,
58    /// Every arrival so far.
59    arrivals: AtomicU64,
60    queue: WaitQueue,
61    waiting: UnsafeCell<Waiting>,
62}
63
64// SAFETY: the waiting list is touched only by the drain role's holder.
65unsafe impl Send for Barrier {}
66// SAFETY: as above.
67unsafe impl Sync for Barrier {}
68
69impl Barrier {
70    loom_const_fn! {
71        /// A barrier for groups of `n` waits; `0` acts as `1`, as it does
72        /// for `std::sync::Barrier`.
73        pub fn new(n: usize) -> Self {
74            Self {
75                n: if n == 0 { 1 } else { n as u64 },
76                arrivals: AtomicU64::new(0),
77                queue: WaitQueue::new(0),
78                waiting: UnsafeCell::new(Waiting { list: List::new(), generation: 0 }),
79            }
80        }
81    }
82
83    /// A future that completes when this wait's generation is complete.
84    pub fn wait(&self) -> BarrierWait<'_> {
85        BarrierWait {
86            barrier: self,
87            generation: None,
88            wait: Wait::new(),
89        }
90    }
91
92    /// Counts an arrival: `Ok` when it completes its generation, else the
93    /// generation it waits in.
94    fn arrive(&self) -> Result<(), usize> {
95        let arrival = self.arrivals.fetch_add(1, Ordering::AcqRel);
96        if arrival % self.n == self.n - 1 {
97            Ok(())
98        } else {
99            Err(self.generation_of(arrival))
100        }
101    }
102
103    /// The generation arrival number `arrival` belongs to, as the payload of
104    /// a waiter carries it (truncated on a target whose word is narrower).
105    fn generation_of(&self, arrival: u64) -> usize {
106        (arrival / self.n) as usize
107    }
108}
109
110impl Policy for Barrier {
111    fn queue(&self) -> &WaitQueue {
112        &self.queue
113    }
114
115    fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, _wake_front: bool) -> usize {
116        let pool = &self.queue;
117        let current = self.generation_of(self.arrivals.load(Ordering::Acquire));
118        // SAFETY: only the drain role's holder runs a pass.
119        self.waiting.with_mut(|waiting| unsafe {
120            let waiting = &mut *waiting;
121            if waiting.generation != current {
122                while let Some(node) = waiting.list.pop_front() {
123                    woken.grant(node);
124                }
125                waiting.generation = current;
126            }
127            for node in arrivals {
128                if node.as_ref().is_cancelled() {
129                    release_queued(node, pool);
130                } else if node.as_ref().payload() != current {
131                    woken.grant(node);
132                } else {
133                    waiting.list.push_back(node);
134                }
135            }
136            waiting.list.tidy(&self.queue);
137            if waiting.list.front(pool).is_none() {
138                QUEUED
139            } else {
140                0
141            }
142        })
143    }
144}
145
146impl Drop for Barrier {
147    fn drop(&mut self) {
148        let pool = &self.queue;
149        // SAFETY: `&mut self` rules out a concurrent pass.
150        self.waiting
151            .with_mut(|waiting| unsafe { (*waiting).list.clear(pool) });
152    }
153}
154
155impl fmt::Debug for Barrier {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_struct("Barrier")
158            .field("n", &self.n)
159            .finish_non_exhaustive()
160    }
161}
162
163/// What a completed [`Barrier::wait`] reports.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub struct BarrierWaitResult {
166    leader: bool,
167}
168
169impl BarrierWaitResult {
170    /// True for exactly one wait per generation: the one that completed
171    /// it.
172    pub fn is_leader(&self) -> bool {
173        self.leader
174    }
175}
176
177/// The future [`Barrier::wait`] returns.
178#[must_use = "futures do nothing unless polled"]
179pub struct BarrierWait<'a> {
180    barrier: &'a Barrier,
181    /// The generation this wait arrived in, once it has.
182    generation: Option<usize>,
183    wait: Wait,
184}
185
186impl Future for BarrierWait<'_> {
187    type Output = BarrierWaitResult;
188
189    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<BarrierWaitResult> {
190        let this = self.get_mut();
191        let barrier = this.barrier;
192        if this.generation.is_none() {
193            match barrier.arrive() {
194                Ok(()) => {
195                    barrier.transition(|state| {
196                        if state & QUEUED != 0 {
197                            Step::Drain(state)
198                        } else {
199                            Step::Set(state)
200                        }
201                    });
202                    return Poll::Ready(BarrierWaitResult { leader: true });
203                }
204                Err(generation) => {
205                    this.generation = Some(generation);
206                    barrier.enqueue(&mut this.wait, generation, cx.waker());
207                    return this
208                        .wait
209                        .poll(&barrier.queue, None)
210                        .map(|_| BarrierWaitResult { leader: false });
211                }
212            }
213        }
214        this.wait
215            .poll(&barrier.queue, Some(cx.waker()))
216            .map(|_| BarrierWaitResult { leader: false })
217    }
218}
219
220impl Drop for BarrierWait<'_> {
221    fn drop(&mut self) {
222        // The arrival stays counted (see `Barrier`); only the node goes.
223        if let Cancel::Withdrawn(_) = self.wait.cancel(&self.barrier.queue) {
224            self.barrier.withdrawn(false);
225        }
226    }
227}
228
229impl fmt::Debug for BarrierWait<'_> {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        f.debug_struct("BarrierWait")
232            .field("generation", &self.generation)
233            .finish_non_exhaustive()
234    }
235}
236
237#[cfg(all(test, not(loom)))]
238mod tests {
239    use super::*;
240    use crate::sync::test_support::{Polled, block_on};
241    use std::sync::Arc;
242    use std::sync::atomic::{AtomicUsize, Ordering as StdOrdering};
243
244    #[test]
245    fn each_generation_releases_together_with_one_leader() {
246        let barrier = Barrier::new(3);
247        for _ in 0..3 {
248            let mut a = Polled::new(barrier.wait());
249            a.pending();
250            let mut b = Polled::new(barrier.wait());
251            b.pending();
252            let leader = Polled::new(barrier.wait()).ready();
253            assert!(leader.is_leader());
254            assert_eq!((a.wakes(), b.wakes()), (1, 1));
255            assert!(!a.ready().is_leader());
256            assert!(!b.ready().is_leader());
257        }
258    }
259
260    #[test]
261    fn a_dropped_wait_stays_counted() {
262        let barrier = Barrier::new(2);
263        Polled::new(barrier.wait()).pending();
264        let leader = Polled::new(barrier.wait()).ready();
265        assert!(leader.is_leader(), "the dropped arrival completed the pair");
266        let mut next = Polled::new(barrier.wait());
267        next.pending();
268        assert!(Polled::new(barrier.wait()).ready().is_leader());
269        assert!(!next.ready().is_leader());
270    }
271
272    #[test]
273    fn a_barrier_of_nothing_acts_as_one() {
274        let barrier = Barrier::new(0);
275        for _ in 0..3 {
276            assert!(Polled::new(barrier.wait()).ready().is_leader());
277        }
278    }
279
280    #[test]
281    #[cfg(target_pointer_width = "64")]
282    fn a_group_larger_than_u32_max_is_a_group_all_the_same() {
283        // Not reachable by waiting that many parties; the count must not
284        // wrap early, and the first arrival must wait.
285        let barrier = Barrier::new(u32::MAX as usize + 2);
286        let mut first = Polled::new(barrier.wait());
287        first.pending();
288        assert_eq!(barrier.n, u64::from(u32::MAX) + 2);
289    }
290
291    #[test]
292    fn threads_meet_at_every_generation() {
293        const ROUNDS: usize = if cfg!(miri) { 3 } else { 200 };
294        let barrier = Arc::new(Barrier::new(4));
295        let leaders = Arc::new(AtomicUsize::new(0));
296        let threads: Vec<_> = (0..4)
297            .map(|_| {
298                let (barrier, leaders) = (Arc::clone(&barrier), Arc::clone(&leaders));
299                std::thread::spawn(move || {
300                    for _ in 0..ROUNDS {
301                        if block_on(barrier.wait()).is_leader() {
302                            leaders.fetch_add(1, StdOrdering::SeqCst);
303                        }
304                    }
305                })
306            })
307            .collect();
308        for thread in threads {
309            thread.join().expect("worker");
310        }
311        assert_eq!(leaders.load(StdOrdering::SeqCst), ROUNDS);
312    }
313}