Skip to main content

deloxide/core/locks/
condvar.rs

1use crate::core::detector;
2use crate::core::locks::{NEXT_LOCK_ID, mutex::MutexGuard};
3use crate::core::types::{CondvarId, get_current_thread_id};
4use parking_lot::Condvar as ParkingLotCondvar;
5use std::ops::DerefMut;
6use std::sync::atomic::Ordering;
7use std::time::Duration;
8
9/// A wrapper around a condition variable that tracks operations for deadlock detection
10///
11/// The Condvar provides the same interface as a standard condition variable but adds
12/// deadlock detection by tracking wait and notify operations. It's a drop-in replacement
13/// for std::sync::Condvar that enables deadlock detection.
14///
15/// # Example
16///
17/// ```no_run
18/// use deloxide::{Mutex, Condvar};
19/// use std::sync::Arc;
20/// use std::thread;
21///
22/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
23/// let pair2 = Arc::clone(&pair);
24///
25/// // Spawn a thread that waits for the condition
26/// thread::spawn(move || {
27///     let (lock, cvar) = &*pair2;
28///     let mut started = lock.lock();
29///     while !*started {
30///         cvar.wait(&mut started);
31///     }
32/// });
33///
34/// // Signal the condition in the main thread
35/// let (lock, cvar) = &*pair;
36/// let mut started = lock.lock();
37/// *started = true;
38/// cvar.notify_one();
39/// ```
40pub struct Condvar {
41    /// Unique identifier for this condition variable
42    id: CondvarId,
43    /// The wrapped parking_lot condition variable
44    inner: ParkingLotCondvar,
45}
46
47impl Condvar {
48    /// Create a new Condvar with an automatically assigned ID
49    ///
50    /// # Returns
51    /// A new Condvar ready for use with deadlock detection
52    ///
53    /// # Example
54    ///
55    /// ```rust
56    /// use deloxide::Condvar;
57    ///
58    /// let condvar = Condvar::new();
59    /// ```
60    pub fn new() -> Self {
61        let id = NEXT_LOCK_ID.fetch_add(1, Ordering::SeqCst);
62
63        // Register the condvar with the detector
64        detector::condvar::create_condvar(id);
65
66        Condvar {
67            id,
68            inner: ParkingLotCondvar::new(),
69        }
70    }
71
72    /// Get the ID of this condition variable
73    ///
74    /// # Returns
75    /// The unique identifier assigned to this condition variable
76    pub fn id(&self) -> CondvarId {
77        self.id
78    }
79
80    /// Wait on this condition variable, releasing the associated mutex and blocking
81    /// until another thread notifies this condition variable
82    ///
83    /// This method will atomically unlock the mutex specified (represented by the guard)
84    /// and block the current thread. This means that any calls to notify() which happen
85    /// logically after the mutex is unlocked are candidates to wake this thread up.
86    /// When this function call returns, the lock specified will have been re-acquired.
87    ///
88    /// # Arguments
89    /// * `guard` - A mutable reference to a MutexGuard that will be atomically unlocked
90    ///
91    /// # Example
92    ///
93    /// ```rust
94    /// use deloxide::{Mutex, Condvar};
95    /// use std::sync::Arc;
96    ///
97    /// let pair = Arc::new((Mutex::new(false), Condvar::new()));
98    /// let (lock, cvar) = &*pair;
99    ///
100    /// // In a real application, you would use this in a loop:
101    /// // let mut guard = lock.lock();
102    /// // while !*guard {
103    /// //     cvar.wait(&mut guard);
104    /// // }
105    /// ```
106    pub fn wait<'a, T>(&self, guard: &mut MutexGuard<'a, T>) {
107        let thread_id = get_current_thread_id();
108        let mutex_id = guard.lock_id();
109        let _mutex_waiter = guard.register_condvar_waiter();
110
111        // Report wait begin
112        crate::core::detector::condvar::begin_wait(thread_id, self.id, mutex_id);
113
114        // 1. CLEAR OWNERSHIP (Fixes warning & Logic)
115        // We are about to sleep, so we logically release the atomic owner tracking
116        guard.clear_ownership();
117
118        // Explicitly report mutex release to detector
119        crate::core::detector::mutex::release_mutex(thread_id, mutex_id);
120
121        // Perform the actual wait operation
122        self.inner.wait(guard.inner_guard());
123
124        // 2. RESTORE OWNERSHIP (Fixes warning & Logic)
125        // We woke up and hold the lock again
126        guard.restore_ownership();
127
128        // Report mutex reacquisition (this logs MutexAcquired)
129        detector::mutex::complete_acquire(thread_id, mutex_id);
130        guard.mark_tracked_globally();
131
132        // Report wait end (clears cv_woken flag, which allows complete_acquire to work correctly)
133        crate::core::detector::condvar::end_wait(thread_id, self.id, mutex_id);
134
135        // Log condvar wait end AFTER mutex acquisition for logical ordering
136        crate::core::logger::log_interaction_event(
137            thread_id,
138            self.id,
139            crate::core::Events::CondvarWaitEnd,
140        );
141    }
142
143    /// Wait on this condition variable with a timeout
144    ///
145    /// This method will atomically unlock the mutex specified (represented by the guard)
146    /// and block the current thread. The thread will be blocked until another thread
147    /// notifies this condition variable or until the timeout elapses. When this function
148    /// returns, the lock specified will have been re-acquired.
149    ///
150    /// # Arguments
151    /// * `guard` - A mutable reference to a MutexGuard that will be atomically unlocked
152    /// * `timeout` - The maximum duration to wait
153    ///
154    /// # Returns
155    /// `true` if the timeout elapsed, `false` if the condition variable was notified
156    ///
157    /// # Example
158    ///
159    /// ```rust
160    /// use deloxide::{Mutex, Condvar};
161    /// use std::sync::Arc;
162    /// use std::time::Duration;
163    ///
164    /// let pair = Arc::new((Mutex::new(false), Condvar::new()));
165    /// let (lock, cvar) = &*pair;
166    ///
167    /// let mut guard = lock.lock();
168    /// let timed_out = cvar.wait_timeout(&mut guard, Duration::from_millis(100));
169    /// if timed_out {
170    ///     println!("Timed out waiting for condition");
171    /// }
172    /// ```
173    pub fn wait_timeout<'a, T>(&self, guard: &mut MutexGuard<'a, T>, timeout: Duration) -> bool {
174        let thread_id = get_current_thread_id();
175        let mutex_id = guard.lock_id();
176        let _mutex_waiter = guard.register_condvar_waiter();
177
178        crate::core::detector::condvar::begin_wait(thread_id, self.id, mutex_id);
179
180        // 1. CLEAR OWNERSHIP
181        guard.clear_ownership();
182
183        crate::core::detector::mutex::release_mutex(thread_id, mutex_id);
184
185        let wait_result = self.inner.wait_for(guard.inner_guard(), timeout);
186        let timed_out = wait_result.timed_out();
187
188        // 2. RESTORE OWNERSHIP
189        guard.restore_ownership();
190
191        detector::mutex::complete_acquire(thread_id, mutex_id);
192        guard.mark_tracked_globally();
193        crate::core::detector::condvar::end_wait(thread_id, self.id, mutex_id);
194
195        // Log condvar wait end AFTER mutex acquisition for logical ordering
196        crate::core::logger::log_interaction_event(
197            thread_id,
198            self.id,
199            crate::core::Events::CondvarWaitEnd,
200        );
201
202        timed_out
203    }
204
205    /// Blocks the current thread until the provided condition becomes false
206    ///
207    /// This is a convenience method that repeatedly calls `wait` while the condition
208    /// returns true. It's equivalent to a while loop with wait.
209    ///
210    /// # Arguments
211    /// * `guard` - A mutable reference to a MutexGuard
212    /// * `condition` - A closure that returns true while waiting should continue
213    ///
214    /// # Example
215    ///
216    /// ```rust,no_run
217    /// use deloxide::{Mutex, Condvar};
218    /// use std::sync::Arc;
219    ///
220    /// let pair = Arc::new((Mutex::new(true), Condvar::new()));
221    /// let (lock, cvar) = &*pair;
222    ///
223    /// let mut guard = lock.lock();
224    /// // Wait while the value is true (another thread would set it to false)
225    /// cvar.wait_while(&mut guard, |pending| *pending);
226    /// ```
227    pub fn wait_while<'a, T, F>(&self, guard: &mut MutexGuard<'a, T>, mut condition: F)
228    where
229        F: FnMut(&mut T) -> bool,
230    {
231        while condition(guard.deref_mut()) {
232            self.wait(guard);
233        }
234    }
235
236    /// Waits on this condition variable with a timeout while a condition is true
237    ///
238    /// This is a convenience method that waits with a timeout while the condition
239    /// returns true.
240    ///
241    /// # Arguments
242    /// * `guard` - A mutable reference to a MutexGuard
243    /// * `timeout` - The maximum duration to wait
244    /// * `condition` - A closure that returns true while waiting should continue
245    ///
246    /// # Returns
247    /// `true` if the timeout elapsed, `false` if the condition became false
248    ///
249    /// # Example
250    ///
251    /// ```rust
252    /// use deloxide::{Mutex, Condvar};
253    /// use std::sync::Arc;
254    /// use std::time::Duration;
255    ///
256    /// let pair = Arc::new((Mutex::new(true), Condvar::new()));
257    /// let (lock, cvar) = &*pair;
258    ///
259    /// let mut guard = lock.lock();
260    /// let timed_out = cvar.wait_timeout_while(
261    ///     &mut guard,
262    ///     Duration::from_millis(100),
263    ///     |pending| *pending
264    /// );
265    /// ```
266    pub fn wait_timeout_while<'a, T, F>(
267        &self,
268        guard: &mut MutexGuard<'a, T>,
269        timeout: Duration,
270        mut condition: F,
271    ) -> bool
272    where
273        F: FnMut(&mut T) -> bool,
274    {
275        let start = std::time::Instant::now();
276        while condition(guard.deref_mut()) {
277            let elapsed = start.elapsed();
278            if elapsed >= timeout {
279                return true; // Timed out
280            }
281            let remaining = timeout - elapsed;
282            if self.wait_timeout(guard, remaining) {
283                return true; // Timed out in wait_timeout
284            }
285        }
286        false // Condition became false
287    }
288
289    /// Wake up one blocked thread on this condition variable
290    ///
291    /// If there is a blocked thread on this condition variable, then it will be woken up
292    /// from its call to wait or wait_timeout. Calls to notify_one are not buffered in any way.
293    ///
294    /// # Example
295    ///
296    /// ```rust
297    /// use deloxide::{Mutex, Condvar};
298    /// use std::sync::Arc;
299    ///
300    /// let pair = Arc::new((Mutex::new(false), Condvar::new()));
301    /// let (lock, cvar) = &*pair;
302    ///
303    /// // ... some other thread is waiting on cvar ...
304    ///
305    /// let mut guard = lock.lock();
306    /// *guard = true;
307    /// drop(guard); // Release the lock before notifying
308    /// cvar.notify_one();
309    /// ```
310    pub fn notify_one(&self) {
311        let thread_id = get_current_thread_id();
312
313        // Report the notify operation to the detector first (for synthetic mutex attempts)
314        detector::condvar::notify_one(self.id, thread_id);
315
316        // Perform the actual notification
317        self.inner.notify_one();
318    }
319
320    /// Wake up all blocked threads on this condition variable
321    ///
322    /// All threads currently waiting on this condition variable will be woken up from
323    /// their call to wait or wait_timeout. Calls to notify_all are not buffered in any way.
324    ///
325    /// # Example
326    ///
327    /// ```rust
328    /// use deloxide::{Mutex, Condvar};
329    /// use std::sync::Arc;
330    ///
331    /// let pair = Arc::new((Mutex::new(false), Condvar::new()));
332    /// let (lock, cvar) = &*pair;
333    ///
334    /// // ... multiple threads are waiting on cvar ...
335    ///
336    /// let mut guard = lock.lock();
337    /// *guard = true;
338    /// drop(guard); // Release the lock before notifying
339    /// cvar.notify_all();
340    /// ```
341    pub fn notify_all(&self) {
342        let thread_id = get_current_thread_id();
343
344        // Report the notify operation to the detector first (for synthetic mutex attempts)
345        detector::condvar::notify_all(self.id, thread_id);
346
347        // Perform the actual notification
348        self.inner.notify_all();
349    }
350}
351
352impl Default for Condvar {
353    fn default() -> Self {
354        Self::new()
355    }
356}
357
358impl Drop for Condvar {
359    fn drop(&mut self) {
360        // Register the condvar destruction with the detector
361        detector::condvar::destroy_condvar(self.id);
362    }
363}
364
365#[cfg(all(test, not(feature = "lock-order-graph")))]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn condvar_reacquisition_marks_fast_guard_globally_tracked() {
371        let mutex = crate::Mutex::new(());
372        let condvar = Condvar::new();
373        let mut guard = mutex.lock();
374        assert!(!guard.is_tracked_globally());
375
376        assert!(condvar.wait_timeout(&mut guard, Duration::ZERO));
377
378        assert!(
379            guard.is_tracked_globally(),
380            "the guard must release the ownership inserted during reacquisition"
381        );
382        assert_eq!(
383            crate::core::detector::mutex::owner_for_test(mutex.id()),
384            Some(get_current_thread_id())
385        );
386
387        drop(guard);
388
389        assert_eq!(
390            crate::core::detector::mutex::owner_for_test(mutex.id()),
391            None
392        );
393    }
394}