dtact_util/sync/mutex.rs
1//! Async mutex — the primitive most people mean by "nonblocking mutex".
2
3use super::wait_queue::WaitQueue;
4use std::cell::UnsafeCell;
5use std::ops::{Deref, DerefMut};
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::task::{Context, Poll};
8
9/// An async mutex: `.lock().await` yields the calling task (not the OS
10/// thread) while the lock is held elsewhere, instead of blocking it.
11///
12/// # Errors
13///
14/// None of this type's methods can fail (no lock poisoning — a panic
15/// while holding the guard simply unlocks on unwind, matching
16/// `tokio::sync::Mutex`'s behavior, not `std::sync::Mutex`'s).
17#[repr(align(64))]
18pub struct Mutex<T: ?Sized> {
19 locked: AtomicBool,
20 wait: WaitQueue,
21 data: UnsafeCell<T>,
22}
23
24// SAFETY: `T: Send` is required to send the guarded value across threads
25// (a guard obtained on one thread can be dropped, or the mutex moved, on
26// another); the `locked` CAS is the sole gate on `data` access, so `T:
27// Sync` isn't needed for `Mutex<T>: Sync` the way it would be for a type
28// exposing `&T` without exclusion.
29unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
30unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
31
32impl<T> Mutex<T> {
33 /// Create a new mutex guarding `data`, initially unlocked.
34 #[must_use]
35 pub const fn new(data: T) -> Self {
36 Self {
37 locked: AtomicBool::new(false),
38 wait: WaitQueue::new(),
39 data: UnsafeCell::new(data),
40 }
41 }
42
43 /// Consume the mutex, returning the guarded value.
44 #[inline(always)]
45 pub fn into_inner(self) -> T {
46 self.data.into_inner()
47 }
48}
49
50impl<T: ?Sized> Mutex<T> {
51 /// Acquire the lock, waiting (without blocking the OS thread) if it's
52 /// currently held elsewhere.
53 #[inline(always)]
54 pub async fn lock(&self) -> MutexGuard<'_, T> {
55 std::future::poll_fn(|cx| self.poll_lock(cx)).await
56 }
57
58 /// Acquire the lock if it's immediately available, without waiting.
59 #[must_use]
60 #[inline(always)]
61 pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
62 self.locked
63 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
64 .is_ok()
65 // `then_some` evaluates its argument eagerly — with a guard
66 // type whose `Drop` releases the lock, that would construct
67 // (and, on the `false` branch, immediately drop-and-release)
68 // a guard even when the CAS failed, unlocking a mutex someone
69 // else legitimately holds. `then` with a closure defers
70 // construction until we know the CAS actually succeeded.
71 .then(|| MutexGuard { mutex: self })
72 }
73
74 #[inline]
75 fn poll_lock(&self, cx: &Context<'_>) -> Poll<MutexGuard<'_, T>> {
76 // Skip the immediate fast-path CAS if anyone is already
77 // registered waiting for the lock — otherwise a fresh `.lock()`
78 // call can win the CAS against an already-waiting task every
79 // single time the lock is released, starving it indefinitely
80 // under sustained contention. See `WaitQueue::has_waiters`'s doc
81 // (written up against the identical, confirmed-reproducible bug
82 // in `mpsc`'s bounded channel) for the full explanation. `try_lock`
83 // itself is unaffected — it's an explicit "don't wait" API and
84 // must always attempt the CAS regardless of waiters.
85 if !self.wait.has_waiters()
86 && let Some(guard) = self.try_lock()
87 {
88 return Poll::Ready(guard);
89 }
90 // Register before the re-check below (not after) so a release
91 // that happens between our failed CAS above and this register
92 // can't be missed: `wake_one` always looks at the queue *after*
93 // clearing `locked`, so if we're not in the queue yet when it
94 // runs, its wakeup is lost — registering first, then re-checking,
95 // closes that window the same way every other primitive in this
96 // module does.
97 let token = self.wait.register(cx.waker());
98 if let Some(guard) = self.try_lock() {
99 // Our own CAS succeeded without needing the wake — retract
100 // the registration above so it doesn't sit around as dead
101 // weight for `wake_one` to work around later. See
102 // `WaitQueue::cancel`'s doc for why this matters beyond tidiness.
103 self.wait.cancel(token);
104 return Poll::Ready(guard);
105 }
106 Poll::Pending
107 }
108
109 /// Get mutable access to the guarded value without locking — sound
110 /// because `&mut self` statically proves no other reference (locked
111 /// or not) can exist.
112 #[inline(always)]
113 pub const fn get_mut(&mut self) -> &mut T {
114 self.data.get_mut()
115 }
116}
117
118impl<T: Default> Default for Mutex<T> {
119 fn default() -> Self {
120 Self {
121 locked: AtomicBool::new(false),
122 wait: WaitQueue::new(),
123 data: UnsafeCell::new(T::default()),
124 }
125 }
126}
127
128/// RAII guard for a locked [`Mutex`]. Releases the lock (and wakes one
129/// waiter, if any) on drop.
130#[repr(align(64))]
131pub struct MutexGuard<'a, T: ?Sized> {
132 mutex: &'a Mutex<T>,
133}
134
135// SAFETY: a `MutexGuard` is proof the lock is held, so `&T`/`&mut T`
136// through it never overlaps another guard's access. Sending the guard
137// itself across threads (unlocking on whichever thread drops it) is sound
138// as long as `T: Send`, same as `std`/`tokio`'s own guards.
139unsafe impl<T: ?Sized + Send> Send for MutexGuard<'_, T> {}
140unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
141
142impl<T: ?Sized> Deref for MutexGuard<'_, T> {
143 type Target = T;
144 #[inline(always)]
145 fn deref(&self) -> &T {
146 // SAFETY: holding a `MutexGuard` is exclusive proof of the lock.
147 unsafe { &*self.mutex.data.get() }
148 }
149}
150
151impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
152 #[inline(always)]
153 fn deref_mut(&mut self) -> &mut T {
154 // SAFETY: same as `Deref` above.
155 unsafe { &mut *self.mutex.data.get() }
156 }
157}
158
159impl<T: ?Sized> Drop for MutexGuard<'_, T> {
160 #[inline(always)]
161 fn drop(&mut self) {
162 self.mutex.locked.store(false, Ordering::Release);
163 self.mutex.wait.wake_one();
164 }
165}