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 = "ios"))]
9 use core::ptr;
10 use core::{
11     cell::{Cell, UnsafeCell},
12     mem::MaybeUninit,
13 };
14 use instant::Instant;
15 use libc;
16 use 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)]
22 type tv_nsec_t = i64;
23 #[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
24 #[allow(non_camel_case_types)]
25 type tv_nsec_t = libc::c_long;
26 
27 // Helper type for putting a thread to sleep until some other thread wakes it up
28 pub 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 
35 impl super::ThreadParkerT for ThreadParker {
36     type UnparkHandle = UnparkHandle;
37 
38     const IS_CHEAP_TO_CONSTRUCT: bool = false;
39 
40     #[inline]
new() -> ThreadParker41     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]
prepare_park(&self)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]
timed_out(&self) -> bool60     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]
park(&self)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]
park_until(&self, timeout: Instant) -> bool85     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]
unpark_lock(&self) -> UnparkHandle118     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 
128 impl ThreadParker {
129     /// Initializes the condvar to use CLOCK_MONOTONIC instead of CLOCK_REALTIME.
130     #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
131     #[inline]
init(&self)132     unsafe fn init(&self) {}
133 
134     /// Initializes the condvar to use CLOCK_MONOTONIC instead of CLOCK_REALTIME.
135     #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
136     #[inline]
init(&self)137     unsafe fn init(&self) {
138         let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
139         let r = libc::pthread_condattr_init(attr.as_mut_ptr());
140         debug_assert_eq!(r, 0);
141         let r = libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC);
142         debug_assert_eq!(r, 0);
143         let r = libc::pthread_cond_init(self.condvar.get(), attr.as_ptr());
144         debug_assert_eq!(r, 0);
145         let r = libc::pthread_condattr_destroy(attr.as_mut_ptr());
146         debug_assert_eq!(r, 0);
147     }
148 }
149 
150 impl Drop for ThreadParker {
151     #[inline]
drop(&mut self)152     fn drop(&mut self) {
153         // On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
154         // mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
155         // Once it is used (locked/unlocked) or pthread_mutex_init() is called,
156         // this behaviour no longer occurs. The same applies to condvars.
157         unsafe {
158             let r = libc::pthread_mutex_destroy(self.mutex.get());
159             debug_assert!(r == 0 || r == libc::EINVAL);
160             let r = libc::pthread_cond_destroy(self.condvar.get());
161             debug_assert!(r == 0 || r == libc::EINVAL);
162         }
163     }
164 }
165 
166 pub struct UnparkHandle {
167     thread_parker: *const ThreadParker,
168 }
169 
170 impl super::UnparkHandleT for UnparkHandle {
171     #[inline]
unpark(self)172     unsafe fn unpark(self) {
173         (*self.thread_parker).should_park.set(false);
174 
175         // We notify while holding the lock here to avoid races with the target
176         // thread. In particular, the thread could exit after we unlock the
177         // mutex, which would make the condvar access invalid memory.
178         let r = libc::pthread_cond_signal((*self.thread_parker).condvar.get());
179         debug_assert_eq!(r, 0);
180         let r = libc::pthread_mutex_unlock((*self.thread_parker).mutex.get());
181         debug_assert_eq!(r, 0);
182     }
183 }
184 
185 // Returns the current time on the clock used by pthread_cond_t as a timespec.
186 #[cfg(any(target_os = "macos", target_os = "ios"))]
187 #[inline]
timespec_now() -> libc::timespec188 fn timespec_now() -> libc::timespec {
189     let mut now = MaybeUninit::<libc::timeval>::uninit();
190     let r = unsafe { libc::gettimeofday(now.as_mut_ptr(), ptr::null_mut()) };
191     debug_assert_eq!(r, 0);
192     // SAFETY: We know `libc::gettimeofday` has initialized the value.
193     let now = unsafe { now.assume_init() };
194     libc::timespec {
195         tv_sec: now.tv_sec,
196         tv_nsec: now.tv_usec as tv_nsec_t * 1000,
197     }
198 }
199 #[cfg(not(any(target_os = "macos", target_os = "ios")))]
200 #[inline]
timespec_now() -> libc::timespec201 fn timespec_now() -> libc::timespec {
202     let mut now = MaybeUninit::<libc::timespec>::uninit();
203     let clock = if cfg!(target_os = "android") {
204         // Android doesn't support pthread_condattr_setclock, so we need to
205         // specify the timeout in CLOCK_REALTIME.
206         libc::CLOCK_REALTIME
207     } else {
208         libc::CLOCK_MONOTONIC
209     };
210     let r = unsafe { libc::clock_gettime(clock, now.as_mut_ptr()) };
211     debug_assert_eq!(r, 0);
212     // SAFETY: We know `libc::clock_gettime` has initialized the value.
213     unsafe { now.assume_init() }
214 }
215 
216 // Converts a relative timeout into an absolute timeout in the clock used by
217 // pthread_cond_t.
218 #[inline]
timeout_to_timespec(timeout: Duration) -> Option<libc::timespec>219 fn timeout_to_timespec(timeout: Duration) -> Option<libc::timespec> {
220     // Handle overflows early on
221     if timeout.as_secs() > libc::time_t::max_value() as u64 {
222         return None;
223     }
224 
225     let now = timespec_now();
226     let mut nsec = now.tv_nsec + timeout.subsec_nanos() as tv_nsec_t;
227     let mut sec = now.tv_sec.checked_add(timeout.as_secs() as libc::time_t);
228     if nsec >= 1_000_000_000 {
229         nsec -= 1_000_000_000;
230         sec = sec.and_then(|sec| sec.checked_add(1));
231     }
232 
233     sec.map(|sec| libc::timespec {
234         tv_nsec: nsec,
235         tv_sec: sec,
236     })
237 }
238 
239 #[inline]
thread_yield()240 pub fn thread_yield() {
241     thread::yield_now();
242 }
243