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