#![forbid(unsafe_code)]
use std::sync::{
OnceLock,
atomic::{AtomicU64, Ordering},
};
use crate::{
sync::{Condvar, Mutex, MutexGuard},
thread::{self, Thread},
time::{Duration, Instant},
};
pub trait WaitGate {
fn current(&self) -> u64;
fn signal(&self);
fn wait_timeout(&self, since: u64, timeout: Duration) -> bool;
}
pub struct CondvarGate<S> {
cv: Condvar,
state: Mutex<S>,
}
impl<S> CondvarGate<S> {
pub fn new(state: S) -> Self {
Self {
state: Mutex::new(state),
cv: Condvar::default(),
}
}
pub fn lock(&self) -> MutexGuard<'_, S> {
self.state.lock()
}
pub fn notify_all(&self) {
self.cv.notify_all();
}
#[must_use]
pub fn wait<'a>(&self, guard: MutexGuard<'a, S>) -> MutexGuard<'a, S> {
self.cv.wait(guard)
}
#[must_use]
pub fn wait_until<'a>(&self, guard: MutexGuard<'a, S>, deadline: Instant) -> MutexGuard<'a, S> {
self.cv.wait_timeout(guard, deadline)
}
}
impl<S: Default> Default for CondvarGate<S> {
fn default() -> Self {
Self {
state: Mutex::new(S::default()),
cv: Condvar::default(),
}
}
}
impl WaitGate for CondvarGate<u64> {
fn current(&self) -> u64 {
*self.lock()
}
fn signal(&self) {
{
let mut guard = self.lock();
*guard = guard.wrapping_add(1);
}
self.cv.notify_all();
}
fn wait_timeout(&self, since: u64, timeout: Duration) -> bool {
let deadline = Instant::now() + timeout;
let mut guard = self.lock();
loop {
if *guard != since {
return true;
}
if Instant::now() >= deadline {
return false;
}
guard = self.cv.wait_timeout(guard, deadline);
}
}
}
#[derive(Default)]
pub struct ThreadGate {
seq: AtomicU64,
waiter: OnceLock<Thread>,
}
impl ThreadGate {
fn register(&self) {
if self.waiter.get().is_none() {
let _ = self.waiter.set(thread::current());
}
}
}
impl WaitGate for ThreadGate {
fn current(&self) -> u64 {
self.seq.load(Ordering::Acquire)
}
fn signal(&self) {
self.seq.fetch_add(1, Ordering::Release);
if let Some(waiter) = self.waiter.get() {
thread::unpark(waiter);
}
}
fn wait_timeout(&self, since: u64, timeout: Duration) -> bool {
self.register();
let deadline = Instant::now() + timeout;
loop {
if self.seq.load(Ordering::Acquire) != since {
return true;
}
let now = Instant::now();
if now >= deadline {
return self.seq.load(Ordering::Acquire) != since;
}
thread::park_timeout(deadline - now);
}
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use std::sync::Arc;
use kithara_test_utils::kithara;
use super::*;
use crate::thread;
#[kithara::test]
fn edge_signal_advances_current() {
let g = CondvarGate::<u64>::default();
let s0 = g.current();
g.signal();
assert_ne!(g.current(), s0, "signal must advance the edge counter");
}
#[kithara::test]
fn edge_wait_timeout_expires_without_signal() {
let g = CondvarGate::<u64>::default();
let s0 = g.current();
assert!(
!g.wait_timeout(s0, Duration::from_millis(10)),
"no signal: wait_timeout returns false"
);
}
#[kithara::test]
fn guarded_state_wait_until_predicate() {
let g = Arc::new(CondvarGate::new(false));
let setter = Arc::clone(&g);
let join = thread::spawn_named("gate-state-set", move || {
thread::sleep(Duration::from_millis(5));
*setter.lock() = true;
setter.notify_all();
});
let mut guard = g.lock();
while !*guard {
guard = g.wait(guard);
}
assert!(*guard);
drop(guard);
join.join().expect("setter thread");
}
#[kithara::test]
fn thread_gate_wait_timeout_expires_without_signal() {
let g = ThreadGate::default();
let s0 = g.current();
assert!(!g.wait_timeout(s0, Duration::from_millis(10)));
}
#[kithara::test]
fn thread_gate_wakes_on_cross_thread_signal() {
let g = Arc::new(ThreadGate::default());
let s0 = g.current();
let signaller = Arc::clone(&g);
let join = thread::spawn_named("threadgate-signal", move || {
thread::sleep(Duration::from_millis(5));
signaller.signal();
});
assert!(
g.wait_timeout(s0, Duration::from_secs(5)),
"cross-thread signal must wake before the backstop"
);
join.join().expect("signaller thread");
}
#[kithara::test]
fn thread_gate_signal_before_wait_is_not_lost() {
let g = ThreadGate::default();
let s0 = g.current();
g.signal();
assert!(g.wait_timeout(s0, Duration::from_millis(10)));
}
}