Skip to main content

parking_lot/
raw_condvar.rs

1use crate::raw_mutex::{RawMutex, TOKEN_HANDOFF, TOKEN_NORMAL};
2use crate::{deadlock, util};
3use core::{
4    ptr,
5    sync::atomic::{AtomicPtr, Ordering},
6};
7use lock_api::RawMutex as RawMutex_;
8use parking_lot_core::{self, DEFAULT_PARK_TOKEN, ParkResult, RequeueOp, UnparkResult};
9use std::time::{Duration, Instant};
10
11/// A raw condition variable.
12///
13/// Waits never return spuriously. [`notify_one`](lock_api::RawCondvar::notify_one)
14/// returns exactly whether a waiting thread was selected for notification, and
15/// [`notify_all`](lock_api::RawCondvar::notify_all) returns the exact number of
16/// waiting threads selected. A thread requeued directly onto the associated
17/// mutex is considered notified.
18pub struct RawCondvar {
19    state: AtomicPtr<RawMutex>,
20}
21
22// SAFETY: Waiting atomically releases and re-acquires the mutex, does not wake
23// spuriously, and safely panics if used with distinct mutexes simultaneously.
24unsafe impl lock_api::RawCondvar for RawCondvar {
25    const INIT: Self = RawCondvar {
26        state: AtomicPtr::new(ptr::null_mut()),
27    };
28
29    type RawMutex = RawMutex;
30
31    unsafe fn wait(&self, mutex: &RawMutex, _guard: &mut crate::GuardMarker) {
32        self.wait_until_internal(mutex, None);
33    }
34
35    #[inline]
36    fn notify_one(&self) -> bool {
37        // Nothing to do if there are no waiting threads
38        let state = self.state.load(Ordering::Relaxed);
39        if state.is_null() {
40            return false;
41        }
42
43        self.notify_one_slow(state)
44    }
45
46    #[inline]
47    fn notify_all(&self) -> usize {
48        // Nothing to do if there are no waiting threads
49        let state = self.state.load(Ordering::Relaxed);
50        if state.is_null() {
51            return 0;
52        }
53
54        self.notify_all_slow(state)
55    }
56}
57
58// SAFETY: The timed waits uphold the same requirements as the untimed wait and
59// accurately distinguish notification from timeout.
60unsafe impl lock_api::RawCondvarTimed for RawCondvar {
61    fn checked_duration_to_instant(timeout: &Duration) -> Option<Instant> {
62        util::to_deadline(*timeout)
63    }
64
65    unsafe fn wait_for(
66        &self,
67        mutex: &RawMutex,
68        _guard: &mut crate::GuardMarker,
69        timeout: &Duration,
70    ) -> bool {
71        let deadline = util::to_deadline(*timeout);
72        self.wait_until_internal(mutex, deadline)
73    }
74
75    unsafe fn wait_until(
76        &self,
77        mutex: &RawMutex,
78        _guard: &mut crate::GuardMarker,
79        timeout: &Instant,
80    ) -> bool {
81        self.wait_until_internal(mutex, Some(*timeout))
82    }
83}
84
85impl RawCondvar {
86    #[cold]
87    fn notify_one_slow(&self, mutex: *mut RawMutex) -> bool {
88        // Unpark one thread or requeue it onto the mutex
89        let from = ptr::from_ref(self).addr();
90        let to = mutex.addr();
91        let validate = || {
92            // Make sure that our atomic state still points to the same
93            // mutex. If not then it means that all threads on the current
94            // mutex were woken up and a new waiting thread switched to a
95            // different mutex. In that case we can get away with doing
96            // nothing.
97            if self.state.load(Ordering::Relaxed) != mutex {
98                return RequeueOp::Abort;
99            }
100
101            // Unpark one thread if the mutex is unlocked, otherwise
102            // requeue it onto the mutex. This is safe to do here
103            // since unlocking the mutex when the parked bit is set requires
104            // locking the queue. There is the possibility of a race if the
105            // mutex gets locked after we check, but that doesn't matter in
106            // this case.
107            if unsafe { (*mutex).mark_parked_if_locked() } {
108                RequeueOp::RequeueOne
109            } else {
110                RequeueOp::UnparkOne
111            }
112        };
113        let callback = |_op, result: UnparkResult| {
114            // Clear our state if there are no more waiting threads
115            if !result.have_more_threads {
116                self.state.store(ptr::null_mut(), Ordering::Relaxed);
117            }
118            TOKEN_NORMAL
119        };
120        let res = unsafe { parking_lot_core::unpark_requeue(from, to, validate, callback) };
121
122        res.unparked_threads + res.requeued_threads != 0
123    }
124
125    #[cold]
126    fn notify_all_slow(&self, mutex: *mut RawMutex) -> usize {
127        // Unpark one thread and requeue the rest onto the mutex
128        let from = ptr::from_ref(self).addr();
129        let to = mutex.addr();
130        let validate = || {
131            // Make sure that our atomic state still points to the same
132            // mutex. If not then it means that all threads on the current
133            // mutex were woken up and a new waiting thread switched to a
134            // different mutex. In that case we can get away with doing
135            // nothing.
136            if self.state.load(Ordering::Relaxed) != mutex {
137                return RequeueOp::Abort;
138            }
139
140            // Clear our state since we are going to unpark or requeue all
141            // threads.
142            self.state.store(ptr::null_mut(), Ordering::Relaxed);
143
144            // Unpark one thread if the mutex is unlocked, otherwise just
145            // requeue everything to the mutex. This is safe to do here
146            // since unlocking the mutex when the parked bit is set requires
147            // locking the queue. There is the possibility of a race if the
148            // mutex gets locked after we check, but that doesn't matter in
149            // this case.
150            if unsafe { (*mutex).mark_parked_if_locked() } {
151                RequeueOp::RequeueAll
152            } else {
153                RequeueOp::UnparkOneRequeueRest
154            }
155        };
156        let callback = |op, result: UnparkResult| {
157            // If we requeued threads to the mutex, mark it as having
158            // parked threads. The RequeueAll case is already handled above.
159            if op == RequeueOp::UnparkOneRequeueRest && result.requeued_threads != 0 {
160                unsafe { (*mutex).mark_parked() };
161            }
162            TOKEN_NORMAL
163        };
164        let res = unsafe { parking_lot_core::unpark_requeue(from, to, validate, callback) };
165
166        res.unparked_threads + res.requeued_threads
167    }
168
169    // This is a non-generic function to reduce the monomorphization cost of
170    // using `wait_until`.
171    fn wait_until_internal(&self, mutex: &RawMutex, timeout: Option<Instant>) -> bool {
172        let result;
173        let mut requeued = false;
174
175        // Stop recording ownership before ThreadData is transferred to the
176        // parking lot. The physical unlock remains in `before_sleep` so that
177        // unlocking and parking are atomic with respect to notification.
178        let mutex_addr = ptr::from_ref(mutex).addr();
179        unsafe { deadlock::release_resource(mutex_addr) };
180
181        {
182            let addr = ptr::from_ref(self).addr();
183            let lock_addr = mutex as *const _ as *mut _;
184            let validate = || {
185                // Ensure we don't use two different mutexes with the same
186                // Condvar at the same time. This is done while locked to
187                // avoid races with notify_one
188                let state = self.state.load(Ordering::Relaxed);
189                if state.is_null() {
190                    self.state.store(lock_addr, Ordering::Relaxed);
191                } else if state != lock_addr {
192                    return false;
193                }
194                true
195            };
196            let before_sleep = || {
197                // Unlock the mutex before sleeping...
198                unsafe { mutex.unlock_inner() };
199            };
200            let timed_out = |k, was_last_thread| {
201                // If we were requeued to a mutex, then we did not time out.
202                // We'll just park ourselves on the mutex again when we try
203                // to lock it later.
204                requeued = k != addr;
205
206                // If we were the last thread on the queue then we need to
207                // clear our state. This is normally done by the
208                // notify_{one,all} functions when not timing out.
209                if !requeued && was_last_thread {
210                    self.state.store(ptr::null_mut(), Ordering::Relaxed);
211                }
212            };
213            result = unsafe {
214                parking_lot_core::park(
215                    addr,
216                    validate,
217                    before_sleep,
218                    timed_out,
219                    DEFAULT_PARK_TOKEN,
220                    timeout,
221                )
222            };
223        }
224
225        // Panic if we tried to use multiple mutexes with a Condvar. Note
226        // that at this point the MutexGuard is still locked. It will be
227        // unlocked by the unwinding logic.
228        if result == ParkResult::Invalid {
229            // We never got around to unlocking the mutex.
230            unsafe { deadlock::acquire_resource(mutex_addr) };
231            {
    ::core::panicking::panic_fmt(format_args!("attempted to use a condition variable with more than one mutex"));
};panic!("attempted to use a condition variable with more than one mutex");
232        }
233
234        // ... and re-lock it once we are done sleeping
235        if result == ParkResult::Unparked(TOKEN_HANDOFF) {
236            unsafe { deadlock::acquire_resource(mutex_addr) };
237        } else {
238            mutex.lock();
239        }
240
241        !(result.is_unparked() || requeued)
242    }
243}