Skip to main content

deloxide/core/locks/
mutex.rs

1use crate::core::detector;
2use crate::core::locks::{
3    NEXT_LOCK_ID,
4    contention::{ContentionState, SlowWaiter},
5};
6
7use crate::core::types::{LockId, ThreadId, get_current_thread_id};
8#[cfg(feature = "logging-and-visualization")]
9use crate::core::{Events, logger};
10use parking_lot::{Mutex as ParkingLotMutex, MutexGuard as ParkingLotMutexGuard};
11use std::ops::{Deref, DerefMut};
12use std::sync::atomic::{AtomicUsize, Ordering};
13
14/// A wrapper around a mutex that tracks lock operations for deadlock detection
15///
16/// The Mutex provides the same interface as a standard mutex but adds
17/// deadlock detection by tracking lock acquisition and release operations. It's
18/// a drop-in replacement for std::sync::Mutex that enables deadlock detection.
19///
20/// # Example
21///
22/// ```rust
23/// use deloxide::Mutex;
24/// use std::sync::Arc;
25/// use std::thread;
26///
27/// // Initialize detectors (not shown here)
28///
29/// // Create a tracked mutex
30/// let mutex = Arc::new(Mutex::new(42));
31/// let mutex_clone = Arc::clone(&mutex);
32///
33/// // Use it just like a regular mutex
34/// thread::spawn(move || {
35///     let mut data = mutex.lock();
36///     *data += 1;
37/// });
38///
39/// // In another thread
40/// let mut data = mutex_clone.lock();
41/// *data += 10;
42/// ```
43pub struct Mutex<T> {
44    /// Unique identifier for this mutex
45    id: LockId,
46    /// The wrapped mutex
47    inner: ParkingLotMutex<T>,
48    /// Thread that created this mutex
49    creator_thread_id: ThreadId,
50    /// Owner and contention state used by the detector handshake.
51    state: MutexState,
52}
53
54struct MutexState {
55    /// Stores the ThreadId of the current owner (0 if unlocked).
56    owner: AtomicUsize,
57    /// Number of blocking slow-path operations that may depend on this lock.
58    contention: ContentionState,
59}
60
61/// Guard for a Mutex, reports lock release when dropped
62///
63/// The MutexGuard provides the same interface as a standard mutex guard, but
64/// additionally reports lock release to the deadlock detector when dropped. This
65/// ensures that the detector's state is kept up to date with actual lock states.
66pub struct MutexGuard<'a, T> {
67    /// Thread that owns this guard
68    thread_id: ThreadId,
69    /// Lock that this guard is for
70    lock_id: LockId,
71    /// The inner MutexGuard
72    guard: ParkingLotMutexGuard<'a, T>,
73    /// Shared owner/contention state consulted on release.
74    state: &'a MutexState,
75    /// Whether this lock acquisition was tracked by the global detector
76    tracked_globally: bool,
77}
78
79impl<T> Mutex<T> {
80    /// Create a new Mutex with an automatically assigned ID
81    ///
82    /// # Arguments
83    /// * `value` - The initial value to store in the mutex
84    ///
85    /// # Returns
86    /// A new Mutex containing the provided value
87    ///
88    /// # Example
89    ///
90    /// ```rust
91    /// use deloxide::Mutex;
92    ///
93    /// let mutex = Mutex::new(42);
94    /// ```
95    pub fn new(value: T) -> Self {
96        let id = NEXT_LOCK_ID.fetch_add(1, Ordering::SeqCst);
97        let creator_thread_id = get_current_thread_id();
98
99        // Register the lock with the detector, including creator thread info
100        detector::mutex::create_mutex(id, Some(creator_thread_id));
101
102        Mutex {
103            id,
104            inner: ParkingLotMutex::new(value),
105            creator_thread_id,
106            state: MutexState {
107                owner: AtomicUsize::new(0),
108                contention: ContentionState::new(),
109            },
110        }
111    }
112
113    /// Get the ID of this mutex
114    ///
115    /// # Returns
116    /// The unique identifier assigned to this mutex
117    pub fn id(&self) -> LockId {
118        self.id
119    }
120
121    /// Get the ID of the thread that created this mutex
122    ///
123    /// # Returns
124    /// The thread ID of the creator thread
125    pub fn creator_thread_id(&self) -> ThreadId {
126        self.creator_thread_id
127    }
128
129    /// Acquire the lock, blocking if necessary
130    ///
131    /// Uses a contention handshake and physical recheck to narrow ownership races.
132    ///
133    /// Uses the Optimistic Fast Path: attempts to acquire the lock cheaply first.
134    /// Only interacts with the global deadlock detector if the lock is contented.
135    ///
136    /// # Example
137    ///
138    /// ```rust
139    /// use deloxide::Mutex;
140    ///
141    /// let mutex = Mutex::new(42);
142    /// {
143    ///     let guard = mutex.lock();
144    ///     assert_eq!(*guard, 42);
145    /// } // lock is automatically released when guard goes out of scope
146    /// ```
147    pub fn lock(&self) -> MutexGuard<'_, T> {
148        let thread_id = get_current_thread_id();
149        let tid_usize = thread_id;
150
151        // Optimistic Fast Path (Disabled during stress testing to ensure full detector coverage)
152        #[cfg(not(feature = "stress-test"))]
153        if let Some(guard) = self.inner.try_lock() {
154            self.state.owner.store(tid_usize, Ordering::Release);
155            let tracked_globally =
156                cfg!(feature = "lock-order-graph") || self.state.contention.has_waiters();
157
158            #[cfg(feature = "logging-and-visualization")]
159            {
160                if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
161                    logger::log_interaction_event(thread_id, self.id, Events::MutexAttempt);
162                }
163            }
164
165            if tracked_globally {
166                detector::mutex::complete_acquire(thread_id, self.id);
167            }
168
169            #[cfg(feature = "logging-and-visualization")]
170            {
171                if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
172                    logger::log_interaction_event(thread_id, self.id, Events::MutexAcquired);
173                }
174            }
175
176            return MutexGuard {
177                thread_id,
178                lock_id: self.id,
179                guard,
180                state: &self.state,
181                tracked_globally,
182            };
183        }
184
185        // Slow Path (Contention)
186        let slow_waiter = self.state.contention.register();
187        let (rechecked_guard, deadlock_info) = detector::mutex::acquire_slow_with_recheck(
188            thread_id,
189            self.id,
190            || self.inner.try_lock(),
191            || {
192                let owner = self.state.owner.load(Ordering::Acquire);
193                (owner != 0).then_some(owner as ThreadId)
194            },
195        );
196
197        if let Some(info) = deadlock_info {
198            detector::deadlock_handling::process_deadlock(info);
199        }
200
201        if let Some(guard) = rechecked_guard {
202            self.state.owner.store(tid_usize, Ordering::Release);
203            drop(slow_waiter);
204            return MutexGuard {
205                thread_id,
206                lock_id: self.id,
207                guard,
208                state: &self.state,
209                tracked_globally: true,
210            };
211        }
212
213        let guard = self.inner.lock();
214        self.state.owner.store(tid_usize, Ordering::Release);
215        detector::mutex::complete_acquire(thread_id, self.id);
216        drop(slow_waiter);
217
218        MutexGuard {
219            thread_id,
220            lock_id: self.id,
221            guard,
222            state: &self.state,
223            tracked_globally: true,
224        }
225    }
226
227    /// Try to acquire the lock without blocking
228    ///
229    /// Returns Some(guard) if successful, None if the lock is held.
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// use deloxide::Mutex;
235    ///
236    /// let mutex = Mutex::new(42);
237    ///
238    /// // Non-blocking attempt to acquire the lock
239    /// if let Some(guard) = mutex.try_lock() {
240    ///     // Lock was acquired
241    ///     assert_eq!(*guard, 42);
242    /// } else {
243    ///     // Lock was already held by another thread
244    ///     println!("Lock already held by another thread");
245    /// }
246    /// ```
247    pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
248        let thread_id = get_current_thread_id();
249        let tid_usize = thread_id;
250
251        if let Some(guard) = self.inner.try_lock() {
252            self.state.owner.store(tid_usize, Ordering::Release);
253            let tracked_globally =
254                cfg!(feature = "lock-order-graph") || self.state.contention.has_waiters();
255
256            #[cfg(feature = "logging-and-visualization")]
257            {
258                if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
259                    logger::log_interaction_event(thread_id, self.id, Events::MutexAttempt);
260                }
261            }
262
263            if tracked_globally {
264                detector::mutex::complete_acquire(thread_id, self.id);
265            }
266
267            #[cfg(feature = "logging-and-visualization")]
268            {
269                if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
270                    logger::log_interaction_event(thread_id, self.id, Events::MutexAcquired);
271                }
272            }
273
274            Some(MutexGuard {
275                thread_id,
276                lock_id: self.id,
277                guard,
278                state: &self.state,
279                tracked_globally,
280            })
281        } else {
282            None
283        }
284    }
285
286    /// Consumes this mutex, returning the underlying data
287    ///
288    /// # Example
289    ///
290    /// ```rust
291    /// use deloxide::Mutex;
292    ///
293    /// let mutex = Mutex::new(42);
294    /// let value = mutex.into_inner();
295    /// assert_eq!(value, 42);
296    /// ```
297    pub fn into_inner(self) -> T
298    where
299        T: Sized,
300    {
301        // We need to prevent Drop from running since we're manually extracting the value
302        // First, manually drop the detector tracking
303        detector::mutex::destroy_mutex(self.id);
304
305        // Use ManuallyDrop to prevent the automatic Drop implementation
306        let mutex = std::mem::ManuallyDrop::new(self);
307
308        // Safety: We're taking ownership and preventing double-drop
309        unsafe { std::ptr::read(&mutex.inner) }.into_inner()
310    }
311
312    /// Returns a mutable reference to the underlying data
313    ///
314    /// Since this call borrows the Mutex mutably, no actual locking needs to
315    /// take place – the mutable borrow statically guarantees no locks exist.
316    ///
317    /// # Example
318    ///
319    /// ```rust
320    /// use deloxide::Mutex;
321    ///
322    /// let mut mutex = Mutex::new(0);
323    /// *mutex.get_mut() = 10;
324    /// assert_eq!(*mutex.lock(), 10);
325    /// ```
326    pub fn get_mut(&mut self) -> &mut T {
327        self.inner.get_mut()
328    }
329}
330
331impl<T> Drop for Mutex<T> {
332    fn drop(&mut self) {
333        // Register the lock destruction with the detector
334        detector::mutex::destroy_mutex(self.id);
335    }
336}
337
338impl<T> Deref for MutexGuard<'_, T> {
339    type Target = T;
340
341    fn deref(&self) -> &Self::Target {
342        self.guard.deref()
343    }
344}
345
346impl<T> DerefMut for MutexGuard<'_, T> {
347    fn deref_mut(&mut self) -> &mut Self::Target {
348        self.guard.deref_mut()
349    }
350}
351
352impl<'a, T> MutexGuard<'a, T> {
353    /// Get the inner parking_lot MutexGuard for condvar operations
354    ///
355    /// This method is used internally by Condvar to access the underlying
356    /// parking_lot guard for wait operations.
357    pub(crate) fn inner_guard(&mut self) -> &mut ParkingLotMutexGuard<'a, T> {
358        &mut self.guard
359    }
360
361    /// Get the lock ID associated with this guard
362    ///
363    /// Returns the unique identifier of the mutex this guard protects.
364    pub(crate) fn lock_id(&self) -> LockId {
365        self.lock_id
366    }
367
368    /// Keep Condvar reacquisition visible to mutex fast-path owners.
369    pub(crate) fn register_condvar_waiter(&self) -> SlowWaiter<'a> {
370        self.state.contention.register()
371    }
372
373    /// Clear local ownership tracking (used internally by Condvar)
374    pub(crate) fn clear_ownership(&self) {
375        self.state.owner.store(0, Ordering::Release);
376    }
377
378    /// Restore local ownership tracking (used internally by Condvar)
379    pub(crate) fn restore_ownership(&self) {
380        self.state.owner.store(self.thread_id, Ordering::Release);
381    }
382
383    pub(crate) fn mark_tracked_globally(&mut self) {
384        self.tracked_globally = true;
385    }
386
387    #[cfg(all(test, not(feature = "lock-order-graph")))]
388    pub(crate) fn is_tracked_globally(&self) -> bool {
389        self.tracked_globally
390    }
391}
392
393impl<T> Drop for MutexGuard<'_, T> {
394    fn drop(&mut self) {
395        // 1. Clear local ownership first
396        self.state.owner.store(0, Ordering::Release);
397
398        // 2. Report lock release (detector and/or logger)
399        if self.tracked_globally || self.state.contention.has_waiters() {
400            detector::mutex::release_mutex(self.thread_id, self.lock_id);
401        } else {
402            #[cfg(feature = "logging-and-visualization")]
403            if logger::LOGGING_ENABLED.load(Ordering::Relaxed) {
404                logger::log_interaction_event(self.thread_id, self.lock_id, Events::MutexReleased);
405            }
406        }
407    }
408}
409
410// Trait implementations for better compatibility with std
411
412impl<T: Default> Default for Mutex<T> {
413    /// Creates a `Mutex<T>`, with the Default value for T
414    fn default() -> Mutex<T> {
415        Mutex::new(Default::default())
416    }
417}
418
419impl<T> From<T> for Mutex<T> {
420    /// Creates a new mutex in an unlocked state ready for use
421    /// This is equivalent to Mutex::new
422    fn from(t: T) -> Self {
423        Mutex::new(t)
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use std::mem::size_of;
431    use std::sync::{Arc, mpsc};
432    use std::time::{Duration, Instant};
433
434    #[test]
435    fn mutex_guard_keeps_one_tracking_reference() {
436        let maximum_size = size_of::<ParkingLotMutexGuard<'static, ()>>() + 4 * size_of::<usize>();
437
438        assert!(
439            size_of::<MutexGuard<'static, ()>>() <= maximum_size,
440            "guard stores more than one tracking reference"
441        );
442    }
443
444    #[test]
445    fn blocking_mutex_wait_is_visible_until_acquisition() {
446        let lock = Arc::new(Mutex::new(()));
447        let owner = lock.lock();
448        let waiter_lock = Arc::clone(&lock);
449        let (acquired_tx, acquired_rx) = mpsc::channel();
450
451        let waiter = std::thread::spawn(move || {
452            let _guard = waiter_lock.lock();
453            acquired_tx.send(()).unwrap();
454        });
455
456        let deadline = Instant::now() + Duration::from_secs(1);
457        while !lock.state.contention.has_waiters() && Instant::now() < deadline {
458            std::thread::yield_now();
459        }
460        assert!(lock.state.contention.has_waiters());
461
462        drop(owner);
463        acquired_rx.recv_timeout(Duration::from_secs(1)).unwrap();
464        waiter.join().unwrap();
465        assert!(!lock.state.contention.has_waiters());
466    }
467}