]>
Commit | Line | Data |
---|---|---|
ba9703b0 XL |
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 | }; | |
f035d41b | 14 | use instant::Instant; |
ba9703b0 | 15 | use libc; |
f035d41b | 16 | use std::{thread, time::Duration}; |
ba9703b0 XL |
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] | |
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 | ||
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] | |
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] | |
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] | |
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()); | |
5869c6ff | 159 | debug_assert!(r == 0 || r == libc::EINVAL); |
ba9703b0 | 160 | let r = libc::pthread_cond_destroy(self.condvar.get()); |
5869c6ff | 161 | debug_assert!(r == 0 || r == libc::EINVAL); |
ba9703b0 XL |
162 | } |
163 | } | |
164 | } | |
165 | ||
166 | pub struct UnparkHandle { | |
167 | thread_parker: *const ThreadParker, | |
168 | } | |
169 | ||
170 | impl super::UnparkHandleT for UnparkHandle { | |
171 | #[inline] | |
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] | |
188 | 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] | |
201 | 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] | |
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] | |
240 | pub fn thread_yield() { | |
241 | thread::yield_now(); | |
242 | } |