Skip to main content

shuttle_std/sync/
mutex.rs

1use crate::sync::{LockResult, PoisonError, TryLockError, TryLockResult};
2use crate::sync::{ResourceSignature, ResourceType};
3use shuttle_engine::current;
4use shuttle_engine::future::batch_semaphore::{BatchSemaphore, Fairness};
5use shuttle_engine::runtime::execution::ExecutionState;
6use shuttle_engine::runtime::task::TaskId;
7use shuttle_engine::runtime::thread;
8use std::cell::RefCell;
9use std::fmt::{Debug, Display};
10use std::ops::{Deref, DerefMut};
11use std::panic::{RefUnwindSafe, UnwindSafe};
12use tracing::trace;
13
14/// A mutex, the same as [`std::sync::Mutex`].
15pub struct Mutex<T: ?Sized> {
16    state: RefCell<MutexState>,
17    semaphore: BatchSemaphore,
18    inner: std::sync::Mutex<T>,
19}
20
21/// A mutex guard, the same as [`std::sync::MutexGuard`].
22pub struct MutexGuard<'a, T: ?Sized> {
23    inner: Option<std::sync::MutexGuard<'a, T>>,
24    mutex: &'a Mutex<T>,
25}
26
27#[derive(Debug)]
28struct MutexState {
29    holder: Option<TaskId>,
30}
31
32impl<T> Mutex<T> {
33    /// Creates a new mutex in an unlocked state ready for use.
34    #[track_caller]
35    pub const fn new(value: T) -> Self {
36        Self::new_internal(value, ResourceSignature::new_const(ResourceType::Mutex))
37    }
38
39    pub(crate) const fn new_internal(value: T, signature: ResourceSignature) -> Self {
40        let state = MutexState { holder: None };
41        Self {
42            state: RefCell::new(state),
43            semaphore: BatchSemaphore::const_new_with_signature(1, Fairness::Unfair, signature),
44            inner: std::sync::Mutex::new(value),
45        }
46    }
47}
48
49impl<T: ?Sized> Mutex<T> {
50    /// Acquires a mutex, blocking the current thread until it is able to do so.
51    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
52        let me = current::me();
53
54        let mut state = self.state.borrow_mut();
55        trace!(holder=?state.holder, semaphore=?self.semaphore, "waiting to acquire mutex {:p}", self);
56        drop(state);
57
58        if !self.semaphore.is_closed() {
59            // Detect deadlock due to re-entrancy.
60            state = self.state.borrow_mut();
61            assert!(
62                match &state.holder {
63                    Some(holder) => *holder != me,
64                    None => true,
65                },
66                "deadlock! task {me:?} tried to acquire a Mutex it already holds"
67            );
68            drop(state);
69
70            self.semaphore.acquire_blocking(1).unwrap();
71        } else {
72            // we always need to allow for a context switch to make the previous event visible for completeness
73            thread::switch();
74        }
75
76        state = self.state.borrow_mut();
77        assert!(state.holder.is_none());
78        state.holder = Some(me);
79        drop(state);
80
81        trace!(semaphore=?self.semaphore, "acquired mutex {:p}", self);
82
83        // Grab a `MutexGuard` from the inner lock, which we must be able to acquire here
84        let result = match self.inner.try_lock() {
85            Ok(guard) => Ok(MutexGuard {
86                inner: Some(guard),
87                mutex: self,
88            }),
89            Err(TryLockError::Poisoned(guard)) => Err(PoisonError::new(MutexGuard {
90                inner: Some(guard.into_inner()),
91                mutex: self,
92            })),
93            Err(TryLockError::WouldBlock) => unreachable!("mutex state out of sync"),
94        };
95
96        result
97    }
98
99    /// Attempts to acquire this lock.
100    ///
101    /// If the lock could not be acquired at this time, then Err is returned. This function does not
102    /// block.
103    pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
104        let me = current::me();
105
106        let mut state = self.state.borrow_mut();
107        trace!(holder=?state.holder, semaphore=?self.semaphore, "trying to acquire mutex {:p}", self);
108        drop(state);
109
110        // `try_acquire` is a yield point. We need to let other threads in here so they
111        // (a) may fail a `try_lock` (in case we acquired), or
112        // (b) may release the lock (in case we failed to acquire) so we can succeed in a subsequent `try_lock`.
113        self.semaphore.try_acquire(1).map_err(|_| TryLockError::WouldBlock)?;
114
115        state = self.state.borrow_mut();
116        state.holder = Some(me);
117        drop(state);
118
119        trace!(semaphore=?self.semaphore, "acquired mutex {:p}", self);
120
121        // Grab a `MutexGuard` from the inner lock, which we must be able to acquire here
122        let result = match self.inner.try_lock() {
123            Ok(guard) => Ok(MutexGuard {
124                inner: Some(guard),
125                mutex: self,
126            }),
127            Err(TryLockError::Poisoned(guard)) => Err(TryLockError::Poisoned(PoisonError::new(MutexGuard {
128                inner: Some(guard.into_inner()),
129                mutex: self,
130            }))),
131            Err(TryLockError::WouldBlock) => unreachable!("mutex state out of sync"),
132        };
133
134        result
135    }
136
137    /// Returns a mutable reference to the underlying data.
138    ///
139    /// Since this call borrows the `Mutex` mutably, no actual locking needs to
140    /// take place -- the mutable borrow statically guarantees no locks exist.
141    #[inline]
142    pub fn get_mut(&mut self) -> LockResult<&mut T> {
143        self.inner.get_mut()
144    }
145
146    /// Consumes this mutex, returning the underlying data.
147    pub fn into_inner(self) -> LockResult<T>
148    where
149        T: Sized,
150    {
151        let state = self.state.borrow();
152        assert!(state.holder.is_none());
153
154        // Update the receiver's clock with the Mutex clock
155        self.semaphore.try_acquire(1).unwrap();
156
157        self.inner.into_inner()
158    }
159
160    /// Clear the poisoned state from a mutex.
161    pub fn clear_poison(&self) {
162        self.inner.clear_poison()
163    }
164}
165
166// Safety: Mutex is never actually passed across true threads, only across continuations. The
167// Rc<RefCell<_>> type therefore can't be preempted mid-bookkeeping-operation.
168// TODO we shouldn't need to do this, but RefCell is not Send, and anything we put within a Mutex
169// TODO needs to be Send.
170unsafe impl<T: Send + ?Sized> Send for Mutex<T> {}
171unsafe impl<T: Send + ?Sized> Sync for Mutex<T> {}
172
173// TODO this is the RefCell biting us again
174impl<T: ?Sized> UnwindSafe for Mutex<T> {}
175impl<T: ?Sized> RefUnwindSafe for Mutex<T> {}
176
177impl<T: Default> Default for Mutex<T> {
178    fn default() -> Self {
179        Self::new(Default::default())
180    }
181}
182
183impl<T: ?Sized + Debug> Debug for Mutex<T> {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        Debug::fmt(&self.inner, f)
186    }
187}
188
189impl<'a, T: ?Sized> MutexGuard<'a, T> {
190    /// Release the lock, but return a reference to it so it can be re-acquired later
191    pub(super) fn unlock(self) -> &'a Mutex<T> {
192        self.mutex
193    }
194}
195
196impl<T: ?Sized> Drop for MutexGuard<'_, T> {
197    fn drop(&mut self) {
198        // Execution teardown can unwind the task from the yield point in `release` (see
199        // `ExecutionState::tear_down`). `release` releases the permit even then, and the rest of
200        // unlocking has to happen too, so that the destructors that teardown runs later can lock the
201        // mutex.
202        struct Unlock<'g, 'a, T: ?Sized>(&'g mut MutexGuard<'a, T>);
203        impl<T: ?Sized> Drop for Unlock<'_, '_, T> {
204            #[inline]
205            fn drop(&mut self) {
206                let guard = &mut *self.0;
207                // Release the inner mutex. Teardown unwinding the task is no panic, which mustn't
208                // poison it.
209                let clear_poison = std::thread::panicking()
210                    && !guard.mutex.inner.is_poisoned()
211                    && ExecutionState::unwinding_for_teardown();
212                guard.inner = None;
213                if clear_poison {
214                    guard.mutex.inner.clear_poison();
215                }
216
217                let mut state = guard.mutex.state.borrow_mut();
218                trace!(semaphore=?guard.mutex.semaphore, "releasing mutex {:p}", guard.mutex);
219                state.holder = None;
220            }
221        }
222        let unlock = Unlock(self);
223
224        // Release a permit (this is a yield point)
225        unlock.0.mutex.semaphore.release(1);
226    }
227}
228
229impl<T: ?Sized> Deref for MutexGuard<'_, T> {
230    type Target = T;
231
232    fn deref(&self) -> &Self::Target {
233        self.inner.as_ref().unwrap()
234    }
235}
236
237impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
238    fn deref_mut(&mut self) -> &mut Self::Target {
239        self.inner.as_mut().unwrap()
240    }
241}
242
243impl<T: Debug + ?Sized> Debug for MutexGuard<'_, T> {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        Debug::fmt(&self.inner.as_ref().unwrap(), f)
246    }
247}
248
249impl<T: Display + ?Sized> Display for MutexGuard<'_, T> {
250    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
251        (**self).fmt(f)
252    }
253}
254
255impl<T> shuttle_engine::annotations::WithName for &Mutex<T> {
256    fn with_name_and_kind(self, name: Option<&str>, kind: Option<&str>) -> Self {
257        (&self.semaphore).with_name_and_kind(name, kind.or(Some("shuttle::sync::Mutex")));
258        self
259    }
260}
261
262impl<T> shuttle_engine::annotations::WithName for Mutex<T> {
263    fn with_name_and_kind(self, name: Option<&str>, kind: Option<&str>) -> Self {
264        (&self).with_name_and_kind(name, kind);
265        self
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn unique_resource_signature_mutex() {
275        shuttle_schedulers::check_random(
276            || {
277                let mutex1 = Mutex::new(0);
278                let mutex2 = Mutex::new(0);
279                assert_ne!(mutex1.semaphore.signature(), mutex2.semaphore.signature());
280            },
281            1,
282        );
283    }
284}