Skip to main content

darkbio_clock/
paused.rs

1// clock-rs: virtual clock for testing blocking code
2// Copyright 2026 Dark Bio AG. All rights reserved.
3//
4// Use of this source code is governed by a BSD-style
5// license that can be found in the LICENSE file.
6
7//! Paused clocks, which move only when a test advances them.
8
9use crate::{Clock, Signal};
10use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
11use std::time::{Duration, Instant};
12
13#[cfg_attr(docsrs, doc(cfg(feature = "test-clock")))]
14impl Clock {
15    /// Creates a paused clock, set to the current real time. It moves only
16    /// when [`Clock::advance`] or [`Clock::advance_to`] moves it.
17    pub fn paused() -> Self {
18        let now = Instant::now();
19        Self {
20            paused: Some(Arc::new(Paused {
21                start: now,
22                state: Mutex::new(PausedState {
23                    now,
24                    signals: Vec::new(),
25                }),
26            })),
27        }
28    }
29
30    /// Moves a paused clock forward by `by` and wakes all its waiters. It
31    /// returns once they are notified, without waiting for them to act. A zero
32    /// step does nothing.
33    ///
34    /// # Panics
35    ///
36    /// Panics on a real clock, or if the new time overflows [`Instant`]. The time
37    /// is unchanged after a panic.
38    pub fn advance(&self, by: Duration) {
39        self.advance_with(|now| {
40            now.checked_add(by)
41                .expect("clock advance overflows Instant")
42        });
43    }
44
45    /// Moves a paused clock forward to `target` and wakes all its waiters. It
46    /// returns once they are notified, without waiting for them to act.
47    /// Advancing to the current time does nothing.
48    ///
49    /// # Panics
50    ///
51    /// Panics on a real clock, or if `target` is earlier than the current time.
52    /// The time is unchanged after a panic.
53    pub fn advance_to(&self, target: Instant) {
54        self.advance_with(|now| {
55            assert!(target >= now, "clock cannot go backwards");
56            target
57        });
58    }
59
60    /// Moves a paused clock to the time `next` picks from the current one, then
61    /// wakes its waiters. `next` panics on a bad step before anything changes.
62    fn advance_with(&self, next: impl FnOnce(Instant) -> Instant) {
63        let Some(paused) = &self.paused else {
64            panic!("real clock cannot be advanced");
65        };
66        let mut state = paused.lock();
67        let now = next(state.now);
68        if now == state.now {
69            return;
70        }
71        state.now = now;
72        let mut signals = Vec::with_capacity(state.signals.len());
73        state.signals.retain(|signal| match signal.upgrade() {
74            Some(signal) => {
75                signals.push(signal);
76                true
77            }
78            None => false,
79        });
80        drop(state);
81
82        // Wake outside the clock lock, so woken threads can read the time at once
83        for signal in signals {
84            signal.notify_all();
85        }
86    }
87}
88
89impl PartialEq for Clock {
90    /// Compares identity. Real clocks are all equal; a paused clock equals only
91    /// its own clones.
92    fn eq(&self, other: &Self) -> bool {
93        match (&self.paused, &other.paused) {
94            (None, None) => true,
95            (Some(ours), Some(theirs)) => Arc::ptr_eq(ours, theirs),
96            _ => false,
97        }
98    }
99}
100
101impl Eq for Clock {}
102
103/// A paused clock's state, shared by its clones and its waiters.
104pub(crate) struct Paused {
105    /// Time the clock started at, to show how far it has advanced.
106    start: Instant,
107    /// Current time and the waiters to wake when it moves.
108    state: Mutex<PausedState>,
109}
110
111/// Mutable part of a paused clock.
112struct PausedState {
113    /// Current time of the clock.
114    now: Instant,
115    /// Signals of the clock's waiters. Entries of dropped waiters are pruned
116    /// when advancing and before the list grows.
117    signals: Vec<Weak<Signal>>,
118}
119
120impl Paused {
121    /// Returns the clock's current time.
122    pub(crate) fn now(&self) -> Instant {
123        self.lock().now
124    }
125
126    /// Returns how far the clock has advanced since its creation.
127    pub(crate) fn advanced(&self) -> Duration {
128        self.lock().now - self.start
129    }
130
131    /// Registers a waiter's signal to wake on every advance.
132    pub(crate) fn register(&self, signal: &Arc<Signal>) {
133        let mut state = self.lock();
134        // Prune dropped waiters before the list grows, so it tracks the live ones
135        if state.signals.len() == state.signals.capacity() {
136            state.signals.retain(|signal| signal.strong_count() > 0);
137        }
138        state.signals.push(Arc::downgrade(signal));
139    }
140
141    /// Locks the state. An advance panics before it writes anything, so a lock
142    /// poisoned by that panic still guards a consistent state.
143    fn lock(&self) -> MutexGuard<'_, PausedState> {
144        self.state.lock().unwrap_or_else(PoisonError::into_inner)
145    }
146}