use std::sync::PoisonError;
use crate::primitives::{AtomicUsize, Condvar, Mutex, Ordering};
#[derive(Debug)]
pub struct CountLatch {
remaining: AtomicUsize,
lock: Mutex<()>,
cvar: Condvar,
}
impl CountLatch {
#[must_use]
pub fn new(count: usize) -> Self {
Self {
remaining: AtomicUsize::new(count),
lock: Mutex::new(()),
cvar: Condvar::new(),
}
}
#[must_use]
pub fn remaining(&self) -> usize {
self.remaining.load(Ordering::Acquire)
}
#[must_use]
pub fn is_released(&self) -> bool {
self.remaining() == 0
}
pub fn count_down(&self) {
let mut current = self.remaining.load(Ordering::Acquire);
let reached_zero = loop {
let Some(next) = current.checked_sub(1) else {
return;
};
match self.remaining.compare_exchange_weak(
current,
next,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break next == 0,
Err(actual) => current = actual,
}
};
if reached_zero {
let _guard = self.lock.lock().unwrap_or_else(PoisonError::into_inner);
self.cvar.notify_all();
}
}
pub fn wait(&self) {
if self.is_released() {
return;
}
let mut guard = self.lock.lock().unwrap_or_else(PoisonError::into_inner);
while !self.is_released() {
guard = self
.cvar
.wait(guard)
.unwrap_or_else(PoisonError::into_inner);
}
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn zero_latch_is_released() {
let latch = CountLatch::new(0);
latch.wait();
latch.count_down();
assert_eq!(latch.remaining(), 0);
}
#[test]
fn many_threads_release_once() {
const N: usize = 64;
let latch = Arc::new(CountLatch::new(N));
let handles: Vec<_> = (0..N)
.map(|_| {
let latch = Arc::clone(&latch);
std::thread::spawn(move || latch.count_down())
})
.collect();
latch.wait();
for h in handles {
h.join().unwrap();
}
assert!(latch.is_released());
}
}