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}