Skip to main content

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}