use std::sync::{Arc, Condvar, Mutex};
#[derive(Clone)]
pub struct WaitGroup {
inner: Arc<(Mutex<usize>, Condvar)>,
}
impl WaitGroup {
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new((Mutex::new(0), Condvar::new())),
}
}
pub fn add(&self, n: usize) {
let (lock, _) = &*self.inner;
let mut count = lock.lock().unwrap_or_else(|p| p.into_inner());
*count += n;
}
pub fn done(&self) {
let (lock, cond) = &*self.inner;
let mut count = lock.lock().unwrap_or_else(|p| p.into_inner());
debug_assert!(*count > 0, "WaitGroup::done() called more times than add()");
*count -= 1;
if *count == 0 {
cond.notify_all();
}
}
pub fn wait(&self) {
let (lock, cond) = &*self.inner;
let mut count = lock.lock().unwrap_or_else(|p| p.into_inner());
while *count != 0 {
count = cond.wait(count).unwrap_or_else(|p| p.into_inner());
}
}
#[must_use]
pub fn wait_timeout(&self, timeout: std::time::Duration) -> bool {
let (lock, cond) = &*self.inner;
let deadline = std::time::Instant::now() + timeout;
let mut count = lock.lock().unwrap_or_else(|p| p.into_inner());
while *count != 0 {
let Some(remaining) = deadline.checked_duration_since(std::time::Instant::now()) else {
return false;
};
let (next, res) = cond
.wait_timeout(count, remaining)
.unwrap_or_else(|p| p.into_inner());
count = next;
if res.timed_out() && *count != 0 {
return false;
}
}
true
}
#[must_use]
pub fn guard(&self) -> WaitGroupGuard {
WaitGroupGuard(self.clone())
}
}
impl Default for WaitGroup {
fn default() -> Self {
Self::new()
}
}
pub struct WaitGroupGuard(WaitGroup);
impl Drop for WaitGroupGuard {
fn drop(&mut self) {
self.0.done();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::Arc, thread, time::Duration};
#[test]
fn test_waitgroup_wait_timeout_reaches_zero() {
let wg = WaitGroup::new();
wg.add(1);
let wg2 = wg.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
wg2.done();
});
assert!(wg.wait_timeout(Duration::from_secs(5)));
}
#[test]
fn test_waitgroup_wait_timeout_times_out() {
let wg = WaitGroup::new();
wg.add(1); let start = std::time::Instant::now();
assert!(!wg.wait_timeout(Duration::from_millis(100)));
assert!(start.elapsed() >= Duration::from_millis(100));
}
#[test]
fn test_waitgroup_wait_timeout_zero_count_returns_immediately() {
let wg = WaitGroup::new();
assert!(wg.wait_timeout(Duration::from_secs(5)));
}
#[test]
fn test_waitgroup_basic() {
let wg = WaitGroup::new();
wg.add(3);
for _ in 0..3 {
let wg = wg.clone();
thread::spawn(move || {
let _guard = wg.guard();
thread::sleep(Duration::from_millis(10));
});
}
wg.wait(); }
#[test]
fn test_waitgroup_zero() {
let wg = WaitGroup::new();
wg.wait();
}
#[test]
fn test_waitgroup_add_done() {
let wg = WaitGroup::new();
wg.add(2);
wg.done();
wg.done();
wg.wait();
}
#[test]
fn test_waitgroup_guard_drop() {
let wg = WaitGroup::new();
wg.add(1);
{
let _guard = wg.guard();
}
wg.wait(); }
#[test]
fn test_waitgroup_concurrent() {
let wg = WaitGroup::new();
let results = Arc::new(std::sync::Mutex::new(Vec::new()));
for i in 0..10 {
let wg = wg.clone();
let results = Arc::clone(&results);
wg.add(1);
thread::spawn(move || {
let _guard = wg.guard();
thread::sleep(Duration::from_millis(i as u64));
results.lock().unwrap().push(i);
});
}
wg.wait();
let results = results.lock().unwrap();
assert_eq!(results.len(), 10);
}
}