thread-group 1.0.0

std::thread::ThreadGroup prototype
// Necessary for using `Mutex<usize>` for conditional variables
#![allow(clippy::mutex_atomic)]

use std::fmt;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::ThreadId;

struct CondState {
    count: usize,
    panic_id: Option<ThreadId>,
}

/// Enables threads to synchronize the beginning or end of some computation.
pub(crate) struct WaitGroup {
    inner: Arc<Inner>,
}

/// Inner state of a `WaitGroup`.
struct Inner {
    cvar: Condvar,
    state: Mutex<CondState>,
}

impl Default for WaitGroup {
    fn default() -> Self {
        Self {
            inner: Arc::new(Inner {
                cvar: Condvar::new(),
                state: Mutex::new(CondState {
                    count: 1,
                    panic_id: None,
                }),
            }),
        }
    }
}

impl WaitGroup {
    /// Creates a new wait group and returns the single reference to it.
    pub(crate) fn new() -> Self {
        Self::default()
    }

    /// Drops this reference and waits until all other references are dropped.
    ///
    /// # Error
    ///
    /// Throws a `panic_id` if one of the grouped threads panicked.
    pub(crate) fn wait(self) -> Result<(), ThreadId> {
        if let Some(id) = self.inner.state.lock().unwrap().panic_id {
            return Err(id);
        }
        if self.inner.state.lock().unwrap().count == 1 {
            return Ok(());
        }

        let inner = self.inner.clone();
        drop(self);

        let mut guard = inner.state.lock().unwrap();
        while guard.count > 0 {
            guard = inner.cvar.wait(guard).unwrap();
        }
        Ok(())
    }

    pub(crate) fn set_panic_id(&self, id: ThreadId) {
        self.inner.state.lock().unwrap().panic_id = Some(id);
        self.inner.cvar.notify_all();
    }
}

impl Drop for WaitGroup {
    fn drop(&mut self) {
        let mut guard = self.inner.state.lock().unwrap();
        guard.count -= 1;

        if guard.count == 0 {
            self.inner.cvar.notify_all();
        }
    }
}

impl Clone for WaitGroup {
    fn clone(&self) -> WaitGroup {
        let mut guard = self.inner.state.lock().unwrap();
        guard.count += 1;

        WaitGroup {
            inner: self.inner.clone(),
        }
    }
}

impl fmt::Debug for WaitGroup {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let guard = self.inner.state.lock().unwrap();
        f.debug_struct("WaitGroup")
            .field("count", &guard.count)
            .field("panic_id", &guard.panic_id)
            .finish()
    }
}