Skip to main content

darkbio_clock/
sync.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//! Mutexes and condition variables whose waits follow a clock.
8//!
9//! Both mirror std's. A condvar takes its clock when created and waits on this
10//! module's mutex, which otherwise works like std's.
11//!
12//! Wait for a condition until a deadline, checking it again after every return:
13//!
14//! ```
15//! # #[cfg(feature = "test-clock")] {
16//! use darkbio_clock::TestClock;
17//! use darkbio_clock::sync::{Condvar, Mutex};
18//! use std::thread;
19//! use std::time::Duration;
20//!
21//! let mut tester = TestClock::new();
22//! let clock = tester.clock();
23//! let (ready, condvar) = (Mutex::new(false), Condvar::new(&clock));
24//! let deadline = clock.now() + Duration::from_secs(5);
25//!
26//! thread::scope(|scope| {
27//!     let worker = scope.spawn(|| {
28//!         let mut guard = ready.lock().unwrap();
29//!         while !*guard {
30//!             let (next, result) = condvar.wait_deadline(guard, deadline).unwrap();
31//!             guard = next;
32//!             if result.timed_out() {
33//!                 return *guard;
34//!             }
35//!         }
36//!         true
37//!     });
38//!     tester.wait_blocked(1);
39//!     tester.advance(Duration::from_secs(5));
40//!     assert!(!worker.join().unwrap());
41//! });
42//! # }
43//! ```
44
45use crate::{Clock, Waiter, primitives};
46use std::fmt;
47use std::ops::{Deref, DerefMut};
48use std::sync::{LockResult, PoisonError, TryLockError, TryLockResult};
49use std::time::Instant;
50
51/// A mutual exclusion lock that this module's condvars can wait on.
52///
53/// It wraps std's mutex, keeping its locking and poisoning.
54pub struct Mutex<T: ?Sized> {
55    /// The wrapped std mutex, or loom's under model checking, which holds the value.
56    inner: primitives::Mutex<T>,
57}
58
59impl<T> Mutex<T> {
60    /// Creates an unlocked mutex holding `value`.
61    #[cfg(not(all(test, loom)))]
62    pub const fn new(value: T) -> Self {
63        Self {
64            inner: primitives::Mutex::new(value),
65        }
66    }
67
68    /// Creates an unlocked mutex holding `value`, without the `const` that
69    /// loom's mutex cannot offer.
70    #[cfg(all(test, loom))]
71    pub fn new(value: T) -> Self {
72        Self {
73            inner: primitives::Mutex::new(value),
74        }
75    }
76
77    /// Consumes the mutex and returns its value, inside an error if the mutex
78    /// is poisoned.
79    pub fn into_inner(self) -> LockResult<T> {
80        self.inner.into_inner()
81    }
82}
83
84impl<T: ?Sized> Mutex<T> {
85    /// Blocks until the mutex is free, then locks it.
86    ///
87    /// If a thread panicked while holding the mutex, the guard comes back
88    /// inside an error.
89    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
90        match self.inner.lock() {
91            Ok(inner) => Ok(MutexGuard { inner, mutex: self }),
92            Err(err) => Err(PoisonError::new(MutexGuard {
93                inner: err.into_inner(),
94                mutex: self,
95            })),
96        }
97    }
98
99    /// Locks the mutex if it is free, without blocking.
100    ///
101    /// Fails with `WouldBlock` while the mutex is locked. If a thread panicked
102    /// while holding the mutex, the guard comes back inside `Poisoned`.
103    pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
104        match self.inner.try_lock() {
105            Ok(inner) => Ok(MutexGuard { inner, mutex: self }),
106            Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock),
107            Err(TryLockError::Poisoned(err)) => {
108                Err(TryLockError::Poisoned(PoisonError::new(MutexGuard {
109                    inner: err.into_inner(),
110                    mutex: self,
111                })))
112            }
113        }
114    }
115
116    /// Reports whether a thread panicked while holding the mutex.
117    #[cfg(not(all(test, loom)))]
118    pub fn is_poisoned(&self) -> bool {
119        self.inner.is_poisoned()
120    }
121
122    /// Clears the poisoned state, marking the value as recovered.
123    #[cfg(not(all(test, loom)))]
124    pub fn clear_poison(&self) {
125        self.inner.clear_poison();
126    }
127
128    /// Borrows the value mutably, inside an error if the mutex is poisoned.
129    ///
130    /// The mutable borrow of the mutex rules out other users, so this takes
131    /// no lock.
132    pub fn get_mut(&mut self) -> LockResult<&mut T> {
133        self.inner.get_mut()
134    }
135}
136
137impl<T: Default> Default for Mutex<T> {
138    /// Creates an unlocked mutex holding the value type's default.
139    fn default() -> Self {
140        Self::new(T::default())
141    }
142}
143
144impl<T> From<T> for Mutex<T> {
145    /// Creates an unlocked mutex holding `value`.
146    fn from(value: T) -> Self {
147        Self::new(value)
148    }
149}
150
151impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
152    /// Shows the value if the mutex is free, never blocking, as std does.
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        self.inner.fmt(f)
155    }
156}
157
158/// Exclusive access to a mutex's value, which unlocks the mutex on drop.
159///
160/// Like std's guard, it cannot be sent to another thread. Sharing it between
161/// threads needs the value to be `Send` as well as `Sync`, one bound more than
162/// std's guard needs.
163#[must_use = "if unused the Mutex will immediately unlock"]
164pub struct MutexGuard<'a, T: ?Sized + 'a> {
165    /// The wrapped guard, which unlocks the mutex and poisons it on a panic.
166    inner: primitives::MutexGuard<'a, T>,
167    /// The mutex a condvar wait relocks.
168    mutex: &'a Mutex<T>,
169}
170
171impl<T: ?Sized> Deref for MutexGuard<'_, T> {
172    /// The value the mutex protects.
173    type Target = T;
174
175    /// Borrows the value.
176    fn deref(&self) -> &T {
177        &self.inner
178    }
179}
180
181impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
182    /// Borrows the value mutably.
183    fn deref_mut(&mut self) -> &mut T {
184        &mut self.inner
185    }
186}
187
188impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
189    /// Formats the value, as std's guard does.
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        fmt::Debug::fmt(&**self, f)
192    }
193}
194
195impl<T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'_, T> {
196    /// Formats the value, as std's guard does.
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        fmt::Display::fmt(&**self, f)
199    }
200}
201
202/// A condition variable whose deadline waits follow a clock.
203///
204/// It mirrors std's, except that it takes its clock when created, and its
205/// timed wait takes an absolute deadline. A wait releases the mutex while it
206/// blocks and relocks it before returning. Notifications are not buffered, and
207/// a wait may also return spuriously, so callers recheck their condition after
208/// every return.
209///
210/// Unlike std's, a wait that starts while its thread unwinds from a panic, on
211/// a guard taken before the panic, poisons the mutex as it releases it.
212pub struct Condvar {
213    /// Parks and wakes this condvar's waits, timing them on its clock.
214    waiter: Waiter,
215}
216
217impl Condvar {
218    /// Creates a condition variable whose deadlines are read from `clock`.
219    #[must_use]
220    pub fn new(clock: &Clock) -> Self {
221        Self {
222            waiter: clock.waiter(),
223        }
224    }
225
226    /// Releases the mutex and blocks until notified, then relocks it.
227    ///
228    /// If a thread panicked while holding the mutex, the relocked guard comes
229    /// back inside an error.
230    pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult<MutexGuard<'a, T>> {
231        self.wait_inner(guard, None).0
232    }
233
234    /// Blocks while `condition` holds, checking it with the mutex locked.
235    ///
236    /// Returns with the mutex locked once `condition` is false. If a thread
237    /// panicked while holding the mutex, the relocked guard comes back inside
238    /// an error.
239    pub fn wait_while<'a, T, F>(
240        &self,
241        mut guard: MutexGuard<'a, T>,
242        mut condition: F,
243    ) -> LockResult<MutexGuard<'a, T>>
244    where
245        F: FnMut(&mut T) -> bool,
246    {
247        while condition(&mut *guard) {
248            guard = self.wait(guard)?;
249        }
250        Ok(guard)
251    }
252
253    /// Releases the mutex and blocks until notified or until the clock reaches
254    /// `deadline`, then relocks it.
255    ///
256    /// A deadline already reached returns at once. As with std's, the result
257    /// reports a timeout only if the deadline ended the wait, however late the
258    /// relock. A notification this wait takes wins over a reached deadline. If
259    /// a thread panicked while holding the mutex, the guard and the result come
260    /// back inside an error.
261    pub fn wait_deadline<'a, T>(
262        &self,
263        guard: MutexGuard<'a, T>,
264        deadline: Instant,
265    ) -> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)> {
266        // Report how the wait ended, so a late relock cannot turn a notification into a timeout
267        let (guard, notified) = self.wait_inner(guard, Some(deadline));
268        let result = WaitTimeoutResult(!notified);
269
270        // Hand back the guard and the result even from a poisoned mutex
271        match guard {
272            Ok(guard) => Ok((guard, result)),
273            Err(err) => Err(PoisonError::new((err.into_inner(), result))),
274        }
275    }
276
277    /// Wakes one thread waiting on this condvar, if any. The others keep waiting.
278    pub fn notify_one(&self) {
279        self.waiter.signal.notify_one();
280    }
281
282    /// Wakes every thread waiting on this condvar.
283    pub fn notify_all(&self) {
284        self.waiter.signal.notify_all();
285    }
286
287    /// Waits with the mutex released until notified or until the clock reaches
288    /// `deadline`, then relocks it, reporting whether a notification ended the wait.
289    fn wait_inner<'a, T>(
290        &self,
291        guard: MutexGuard<'a, T>,
292        deadline: Option<Instant>,
293    ) -> (LockResult<MutexGuard<'a, T>>, bool) {
294        // Start the wait under the caller's mutex, so that a notification sent
295        // after the caller's last check cannot slip past it
296        let MutexGuard { inner, mutex } = guard;
297        let mut state = self.waiter.signal.lock();
298        let start = state.start();
299        #[cfg(test)]
300        if let Some(hook) = self
301            .waiter
302            .clock
303            .paused
304            .as_ref()
305            .and_then(|paused| paused.take_hook(false))
306        {
307            // Let tests act while the caller's mutex is still held
308            drop(state);
309            hook(self.waiter.timer(deadline));
310            state = self.waiter.signal.lock();
311        }
312
313        // Release the caller's mutex, keeping the signal locked until the park
314        drop(inner);
315
316        // Park until notified or the deadline passes, and note which while the signal is locked
317        let (state, notified) = self.waiter.park(state, start, deadline);
318
319        // Unlock the signal first, since notifiers take the two locks the other way round
320        drop(state);
321        (mutex.lock(), notified)
322    }
323}
324
325impl fmt::Debug for Condvar {
326    /// Shows the clock, never the wakeup bookkeeping.
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        f.debug_struct("Condvar")
329            .field("clock", &self.waiter.clock)
330            .finish_non_exhaustive()
331    }
332}
333
334/// Whether the clock's deadline ended a wait.
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub struct WaitTimeoutResult(
337    /// Whether the deadline ended the wait.
338    bool,
339);
340
341impl WaitTimeoutResult {
342    /// Returns whether the deadline ended the wait.
343    #[must_use]
344    pub fn timed_out(&self) -> bool {
345        self.0
346    }
347}
348
349// The tests live in src/tests, loaded from here so that they keep this
350// module's private items in reach
351#[cfg(all(test, not(loom)))]
352#[cfg_attr(coverage_nightly, coverage(off))]
353#[path = "tests/sync.rs"]
354mod tests;