parking_lot_core/thread_parker/
unix.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
8#[cfg(any(target_os = "macos", target_os = "tvos", target_os = "ios", target_os = "watchos"))]
9use core::ptr;
10use core::{
11    cell::{Cell, UnsafeCell},
12    mem::MaybeUninit,
13};
14use libc;
15use std::time::Instant;
16use std::{thread, time::Duration};
17
18// x32 Linux uses a non-standard type for tv_nsec in timespec.
19// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437
20#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
21#[allow(non_camel_case_types)]
22type tv_nsec_t = i64;
23#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
24#[allow(non_camel_case_types)]
25type tv_nsec_t = libc::c_long;
26
27// Helper type for putting a thread to sleep until some other thread wakes it up
28pub struct ThreadParker {
29    should_park: Cell<bool>,
30    mutex: UnsafeCell<libc::pthread_mutex_t>,
31    condvar: UnsafeCell<libc::pthread_cond_t>,
32    initialized: Cell<bool>,
33}
34
35impl super::ThreadParkerT for ThreadParker {
36    type UnparkHandle = UnparkHandle;
37
38    const IS_CHEAP_TO_CONSTRUCT: bool = false;
39
40    #[inline]
41    fn new() -> ThreadParker {
42        ThreadParker {
43            should_park: Cell::new(false),
44            mutex: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER),
45            condvar: UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER),
46            initialized: Cell::new(false),
47        }
48    }
49
50    #[inline]
51    unsafe fn prepare_park(&self) {
52        self.should_park.set(true);
53        if !self.initialized.get() {
54            self.init();
55            self.initialized.set(true);
56        }
57    }
58
59    #[inline]
60    unsafe fn timed_out(&self) -> bool {
61        // We need to grab the mutex here because another thread may be
62        // concurrently executing UnparkHandle::unpark, which is done without
63        // holding the queue lock.
64        let r = libc::pthread_mutex_lock(self.mutex.get());
65        debug_assert_eq!(r, 0);
66        let should_park = self.should_park.get();
67        let r = libc::pthread_mutex_unlock(self.mutex.get());
68        debug_assert_eq!(r, 0);
69        should_park
70    }
71
72    #[inline]
73    unsafe fn park(&self) {
74        let r = libc::pthread_mutex_lock(self.mutex.get());
75        debug_assert_eq!(r, 0);
76        while self.should_park.get() {
77            let r = libc::pthread_cond_wait(self.condvar.get(), self.mutex.get());
78            debug_assert_eq!(r, 0);
79        }
80        let r = libc::pthread_mutex_unlock(self.mutex.get());
81        debug_assert_eq!(r, 0);
82    }
83
84    #[inline]
85    unsafe fn park_until(&self, timeout: Instant) -> bool {
86        let r = libc::pthread_mutex_lock(self.mutex.get());
87        debug_assert_eq!(r, 0);
88        while self.should_park.get() {
89            let now = Instant::now();
90            if timeout <= now {
91                let r = libc::pthread_mutex_unlock(self.mutex.get());
92                debug_assert_eq!(r, 0);
93                return false;
94            }
95
96            if let Some(ts) = timeout_to_timespec(timeout - now) {
97                let r = libc::pthread_cond_timedwait(self.condvar.get(), self.mutex.get(), &ts);
98                if ts.tv_sec < 0 {
99                    // On some systems, negative timeouts will return EINVAL. In
100                    // that case we won't sleep and will just busy loop instead,
101                    // which is the best we can do.
102                    debug_assert!(r == 0 || r == libc::ETIMEDOUT || r == libc::EINVAL);
103                } else {
104                    debug_assert!(r == 0 || r == libc::ETIMEDOUT);
105                }
106            } else {
107                // Timeout calculation overflowed, just sleep indefinitely
108                let r = libc::pthread_cond_wait(self.condvar.get(), self.mutex.get());
109                debug_assert_eq!(r, 0);
110            }
111        }
112        let r = libc::pthread_mutex_unlock(self.mutex.get());
113        debug_assert_eq!(r, 0);
114        true
115    }
116
117    #[inline]
118    unsafe fn unpark_lock(&self) -> UnparkHandle {
119        let r = libc::pthread_mutex_lock(self.mutex.get());
120        debug_assert_eq!(r, 0);
121
122        UnparkHandle {
123            thread_parker: self,
124        }
125    }
126}
127
128impl ThreadParker {
129    /// Initializes the condvar to use CLOCK_MONOTONIC instead of CLOCK_REALTIME.
130    #[cfg(any(
131        target_os = "macos",
132        target_os = "ios",
133        target_os = "tvos",
134        target_os = "watchos",
135        target_os = "android",
136        target_os = "espidf"
137    ))]
138    #[inline]
139    unsafe fn init(&self) {}
140
141    /// Initializes the condvar to use CLOCK_MONOTONIC instead of CLOCK_REALTIME.
142    #[cfg(not(any(
143        target_os = "macos",
144        target_os = "ios",
145        target_os = "tvos",
146        target_os = "watchos",
147        target_os = "android",
148        target_os = "espidf"
149    )))]
150    #[inline]
151    unsafe fn init(&self) {
152        let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
153        let r = libc::pthread_condattr_init(attr.as_mut_ptr());
154        debug_assert_eq!(r, 0);
155        let r = libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC);
156        debug_assert_eq!(r, 0);
157        let r = libc::pthread_cond_init(self.condvar.get(), attr.as_ptr());
158        debug_assert_eq!(r, 0);
159        let r = libc::pthread_condattr_destroy(attr.as_mut_ptr());
160        debug_assert_eq!(r, 0);
161    }
162}
163
164impl Drop for ThreadParker {
165    #[inline]
166    fn drop(&mut self) {
167        // On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
168        // mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
169        // Once it is used (locked/unlocked) or pthread_mutex_init() is called,
170        // this behaviour no longer occurs. The same applies to condvars.
171        unsafe {
172            let r = libc::pthread_mutex_destroy(self.mutex.get());
173            debug_assert!(r == 0 || r == libc::EINVAL);
174            let r = libc::pthread_cond_destroy(self.condvar.get());
175            debug_assert!(r == 0 || r == libc::EINVAL);
176        }
177    }
178}
179
180pub struct UnparkHandle {
181    thread_parker: *const ThreadParker,
182}
183
184impl super::UnparkHandleT for UnparkHandle {
185    #[inline]
186    unsafe fn unpark(self) {
187        (*self.thread_parker).should_park.set(false);
188
189        // We notify while holding the lock here to avoid races with the target
190        // thread. In particular, the thread could exit after we unlock the
191        // mutex, which would make the condvar access invalid memory.
192        let r = libc::pthread_cond_signal((*self.thread_parker).condvar.get());
193        debug_assert_eq!(r, 0);
194        let r = libc::pthread_mutex_unlock((*self.thread_parker).mutex.get());
195        debug_assert_eq!(r, 0);
196    }
197}
198
199// Returns the current time on the clock used by pthread_cond_t as a timespec.
200#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
201#[inline]
202fn timespec_now() -> libc::timespec {
203    let mut now = MaybeUninit::<libc::timeval>::uninit();
204    let r = unsafe { libc::gettimeofday(now.as_mut_ptr(), ptr::null_mut()) };
205    debug_assert_eq!(r, 0);
206    // SAFETY: We know `libc::gettimeofday` has initialized the value.
207    let now = unsafe { now.assume_init() };
208    libc::timespec {
209        tv_sec: now.tv_sec,
210        tv_nsec: now.tv_usec as tv_nsec_t * 1000,
211    }
212}
213#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos")))]
214#[inline]
215fn timespec_now() -> libc::timespec {
216    let mut now = MaybeUninit::<libc::timespec>::uninit();
217    let clock = if cfg!(target_os = "android") {
218        // Android doesn't support pthread_condattr_setclock, so we need to
219        // specify the timeout in CLOCK_REALTIME.
220        libc::CLOCK_REALTIME
221    } else {
222        libc::CLOCK_MONOTONIC
223    };
224    let r = unsafe { libc::clock_gettime(clock, now.as_mut_ptr()) };
225    debug_assert_eq!(r, 0);
226    // SAFETY: We know `libc::clock_gettime` has initialized the value.
227    unsafe { now.assume_init() }
228}
229
230// Converts a relative timeout into an absolute timeout in the clock used by
231// pthread_cond_t.
232#[inline]
233fn timeout_to_timespec(timeout: Duration) -> Option<libc::timespec> {
234    // Handle overflows early on
235    if timeout.as_secs() > libc::time_t::max_value() as u64 {
236        return None;
237    }
238
239    let now = timespec_now();
240    let mut nsec = now.tv_nsec + timeout.subsec_nanos() as tv_nsec_t;
241    let mut sec = now.tv_sec.checked_add(timeout.as_secs() as libc::time_t);
242    if nsec >= 1_000_000_000 {
243        nsec -= 1_000_000_000;
244        sec = sec.and_then(|sec| sec.checked_add(1));
245    }
246
247    sec.map(|sec| libc::timespec {
248        tv_nsec: nsec,
249        tv_sec: sec,
250    })
251}
252
253#[inline]
254pub fn thread_yield() {
255    thread::yield_now();
256}