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 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}