etl_core/source/
barrier.rs1use std::sync::atomic::{AtomicUsize, Ordering};
4use std::sync::{Arc, Condvar, Mutex};
5use std::time::{Duration, Instant};
6
7#[derive(Debug)]
8struct Inner {
9 remaining: AtomicUsize,
10 done: Mutex<bool>,
11 cv: Condvar,
12}
13
14#[derive(Clone, Debug)]
23pub struct DrainBarrier {
24 inner: Arc<Inner>,
25}
26
27impl DrainBarrier {
28 #[must_use]
31 pub fn new(parties: usize) -> Self {
32 DrainBarrier {
33 inner: Arc::new(Inner {
34 remaining: AtomicUsize::new(parties),
35 done: Mutex::new(parties == 0),
36 cv: Condvar::new(),
37 }),
38 }
39 }
40
41 pub fn arrive(&self) {
44 let prev = self.inner.remaining.fetch_sub(1, Ordering::AcqRel);
45 debug_assert!(prev > 0, "more arrivals than barrier parties");
46 if prev == 1 {
47 let mut done = self.inner.done.lock().expect("barrier lock");
48 *done = true;
49 self.inner.cv.notify_all();
50 }
51 }
52
53 #[must_use]
55 pub fn remaining(&self) -> usize {
56 self.inner.remaining.load(Ordering::Acquire)
57 }
58
59 #[must_use]
62 pub fn wait(&self, timeout: Duration) -> bool {
63 let deadline = Instant::now() + timeout;
64 let mut done = self.inner.done.lock().expect("barrier lock");
65 while !*done {
66 let now = Instant::now();
67 if now >= deadline {
68 return false;
69 }
70 let (guard, _timeout) = self
71 .inner
72 .cv
73 .wait_timeout(done, deadline - now)
74 .expect("barrier lock");
75 done = guard;
76 }
77 true
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn completes_when_all_arrive() {
87 let barrier = DrainBarrier::new(2);
88 let b2 = barrier.clone();
89 let t = std::thread::spawn(move || {
90 b2.arrive();
91 b2.arrive();
92 });
93 assert!(barrier.wait(Duration::from_secs(5)));
94 assert_eq!(barrier.remaining(), 0);
95 t.join().unwrap();
96 }
97
98 #[test]
99 fn times_out_when_a_party_never_arrives() {
100 let barrier = DrainBarrier::new(2);
101 barrier.arrive();
102 assert!(!barrier.wait(Duration::from_millis(50)));
103 assert_eq!(barrier.remaining(), 1);
104 }
105
106 #[test]
107 fn zero_parties_is_immediately_complete() {
108 let barrier = DrainBarrier::new(0);
109 assert!(barrier.wait(Duration::ZERO));
110 }
111
112 #[test]
113 fn wait_after_completion_returns_immediately() {
114 let barrier = DrainBarrier::new(1);
115 barrier.arrive();
116 assert!(barrier.wait(Duration::ZERO));
117 assert!(barrier.wait(Duration::from_secs(1)));
118 }
119}