#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![forbid(unsafe_code)]
pub mod sync;
mod primitives;
#[cfg(feature = "crossbeam")]
mod timers;
#[cfg(feature = "crossbeam")]
#[cfg_attr(docsrs, doc(cfg(feature = "crossbeam")))]
pub use crossbeam_channel;
#[cfg(any(test, feature = "test-clock"))]
mod paused;
#[cfg(feature = "test-clock")]
pub use paused::TestClock;
#[cfg(all(test, not(feature = "test-clock")))]
use paused::TestClock;
use std::collections::VecDeque;
use std::fmt;
use std::sync::Arc;
#[cfg(test)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant, SystemTime};
use primitives::{Condvar, Mutex, MutexGuard};
const MAX_REAL_WAIT: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Clone)]
#[non_exhaustive]
pub struct Clock {
#[cfg(any(test, feature = "test-clock"))]
paused: Option<Arc<paused::Paused>>,
}
impl Clock {
pub const fn real() -> Self {
Self {
#[cfg(any(test, feature = "test-clock"))]
paused: None,
}
}
#[must_use]
pub fn now(&self) -> Instant {
#[cfg(any(test, feature = "test-clock"))]
if let Some(paused) = &self.paused {
return paused.now();
}
Instant::now()
}
#[must_use]
pub fn elapsed(&self, since: Instant) -> Duration {
self.now().saturating_duration_since(since)
}
#[must_use]
pub fn system_time(&self) -> SystemTime {
#[cfg(any(test, feature = "test-clock"))]
if let Some(paused) = &self.paused {
return paused.system_time();
}
SystemTime::now()
}
pub fn sleep(&self, duration: Duration) {
#[cfg(any(test, feature = "test-clock"))]
if self.paused.is_some() {
self.sleep_until(self.now() + duration);
return;
}
let start = Instant::now();
loop {
let elapsed = start.elapsed();
if elapsed >= duration {
return;
}
std::thread::sleep((duration - elapsed).min(MAX_REAL_WAIT));
}
}
pub fn sleep_until(&self, deadline: Instant) {
#[cfg(any(test, feature = "test-clock"))]
if self.paused.is_some() {
self.waiter().wait(Some(deadline));
return;
}
loop {
let now = Instant::now();
if now >= deadline {
return;
}
std::thread::sleep((deadline - now).min(MAX_REAL_WAIT));
}
}
fn identity(&self) -> Option<*const ()> {
#[cfg(any(test, feature = "test-clock"))]
if let Some(paused) = &self.paused {
return Some(Arc::as_ptr(paused).cast());
}
None
}
fn waiter(&self) -> Waiter {
Waiter {
clock: self.clone(),
signal: Arc::new(Signal::default()),
}
}
}
#[cfg(not(any(test, feature = "test-clock")))]
const _: () = assert!(size_of::<Clock>() == 0);
#[cfg(not(any(test, feature = "test-clock")))]
const _: () = assert!(std::mem::needs_drop::<Clock>());
impl Drop for Clock {
fn drop(&mut self) {}
}
impl PartialEq for Clock {
fn eq(&self, other: &Self) -> bool {
self.identity() == other.identity()
}
}
impl Eq for Clock {}
impl fmt::Debug for Clock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(any(test, feature = "test-clock"))]
let snapshot = self.paused.as_ref().map(|paused| paused.snapshot());
let mut clock = f.debug_struct("Clock");
#[cfg(any(test, feature = "test-clock"))]
if let Some((advanced, system_time)) = snapshot {
return clock
.field("paused", &true)
.field("advanced", &advanced)
.field("system_time", &system_time)
.finish();
}
clock.field("paused", &false).finish()
}
}
struct Waiter {
clock: Clock,
signal: Arc<Signal>,
}
impl Waiter {
#[cfg(any(test, feature = "test-clock"))]
fn wait(&self, deadline: Option<Instant>) -> bool {
let mut state = self.signal.lock();
let start = state.start();
self.park(state, start, deadline).1
}
fn timer(&self, deadline: Option<Instant>) -> Option<Instant> {
#[cfg(any(test, feature = "test-clock"))]
if self.clock.paused.is_some() {
return None;
}
deadline
}
fn park<'a>(
&'a self,
mut state: MutexGuard<'a, SignalState>,
start: u64,
deadline: Option<Instant>,
) -> (MutexGuard<'a, SignalState>, bool) {
let timer = self.timer(deadline);
#[cfg(any(test, feature = "test-clock"))]
let mut blocked = None;
let notified = loop {
if state.claim(start) {
break true;
}
let remaining =
deadline.map(|deadline| deadline.saturating_duration_since(self.clock.now()));
if remaining.is_some_and(|remaining| remaining.is_zero()) {
break false;
}
#[cfg(test)]
if let Some(hook) = self
.clock
.paused
.as_ref()
.and_then(|paused| paused.take_hook(blocked.is_some()))
{
drop(state);
hook(timer);
state = self.signal.lock();
continue;
}
#[cfg(any(test, feature = "test-clock"))]
if blocked.is_none() {
if let Some(paused) = &self.clock.paused {
blocked = paused.block(deadline, &self.signal);
if blocked.is_none() {
break false;
}
}
}
#[cfg(test)]
{
state.parks += 1;
self.signal.parked.notify_all();
}
state = match timer.and(remaining) {
None => self
.signal
.changed
.wait(state)
.expect("waiter signal not poisoned"),
Some(remaining) => {
self.signal
.changed
.wait_timeout(state, remaining.min(MAX_REAL_WAIT))
.expect("waiter signal not poisoned")
.0
}
};
};
state.retire(start);
#[cfg(any(test, feature = "test-clock"))]
drop(blocked);
(state, notified)
}
}
#[derive(Default)]
struct Signal {
state: Mutex<SignalState>,
changed: Condvar,
#[cfg(test)]
parked: Condvar,
#[cfg(test)]
wakes: AtomicUsize,
}
#[derive(Default)]
struct SignalState {
notifications: u64,
broadcast: u64,
waiting: usize,
pending: VecDeque<u64>,
#[cfg(test)]
parks: usize,
}
impl SignalState {
fn start(&mut self) -> u64 {
self.waiting += 1;
self.notifications
}
fn claim(&mut self, start: u64) -> bool {
if self.broadcast > start {
return true;
}
if let Some(index) = self.pending.iter().position(|&sent| sent > start) {
self.pending.remove(index);
return true;
}
false
}
fn retire(&mut self, start: u64) {
if self.broadcast <= start {
self.waiting -= 1;
}
}
}
impl Signal {
fn lock(&self) -> MutexGuard<'_, SignalState> {
self.state.lock().expect("waiter signal not poisoned")
}
fn notify_one(&self) {
let mut state = self.lock();
state.notifications += 1;
if state.pending.len() < state.waiting {
let sent = state.notifications;
state.pending.push_back(sent);
self.changed.notify_one();
}
}
fn notify_all(&self) {
let mut state = self.lock();
state.notifications += 1;
state.broadcast = state.notifications;
state.pending.clear();
state.waiting = 0;
self.changed.notify_all();
}
#[cfg(any(test, feature = "test-clock"))]
fn wake(&self) {
#[cfg(test)]
self.wakes.fetch_add(1, Ordering::SeqCst);
let _state = self.lock();
self.changed.notify_all();
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests;