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