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 core::{
9 ptr,
10 sync::atomic::{AtomicI32, Ordering},
11};
12use libc;
13use std::thread;
14use std::time::Instant;
15
16// x32 Linux uses a non-standard type for tv_nsec in timespec.
17// See https://sourceware.org/bugzilla/show_bug.cgi?id=16437
18#[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
19#[allow(non_camel_case_types)]
20type tv_nsec_t = i64;
21#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
22#[allow(non_camel_case_types)]
23type tv_nsec_t = libc::c_long;
24
25fn errno() -> libc::c_int {
26 #[cfg(target_os = "linux")]
27 unsafe {
28 *libc::__errno_location()
29 }
30 #[cfg(target_os = "android")]
31 unsafe {
32 *libc::__errno()
33 }
34}
35
36// Helper type for putting a thread to sleep until some other thread wakes it up
37pub struct ThreadParker {
38 futex: AtomicI32,
39}
40
41impl super::ThreadParkerT for ThreadParker {
42 type UnparkHandle = UnparkHandle;
43
44 const IS_CHEAP_TO_CONSTRUCT: bool = true;
45
46 #[inline]
47 fn new() -> ThreadParker {
48 ThreadParker {
49 futex: AtomicI32::new(0),
50 }
51 }
52
53 #[inline]
54 unsafe fn prepare_park(&self) {
55 self.futex.store(1, Ordering::Relaxed);
56 }
57
58 #[inline]
59 unsafe fn timed_out(&self) -> bool {
60 self.futex.load(Ordering::Relaxed) != 0
61 }
62
63 #[inline]
64 unsafe fn park(&self) {
65 while self.futex.load(Ordering::Acquire) != 0 {
66 self.futex_wait(None);
67 }
68 }
69
70 #[inline]
71 unsafe fn park_until(&self, timeout: Instant) -> bool {
72 while self.futex.load(Ordering::Acquire) != 0 {
73 let now = Instant::now();
74 if timeout <= now {
75 return false;
76 }
77 let diff = timeout - now;
78 if diff.as_secs() as libc::time_t as u64 != diff.as_secs() {
79 // Timeout overflowed, just sleep indefinitely
80 self.park();
81 return true;
82 }
83 // SAFETY: libc::timespec is zero initializable.
84 let mut ts: libc::timespec = std::mem::zeroed();
85 ts.tv_sec = diff.as_secs() as libc::time_t;
86 ts.tv_nsec = diff.subsec_nanos() as tv_nsec_t;
87 self.futex_wait(Some(ts));
88 }
89 true
90 }
91
92 // Locks the parker to prevent the target thread from exiting. This is
93 // necessary to ensure that thread-local ThreadData objects remain valid.
94 // This should be called while holding the queue lock.
95 #[inline]
96 unsafe fn unpark_lock(&self) -> UnparkHandle {
97 // We don't need to lock anything, just clear the state
98 self.futex.store(0, Ordering::Release);
99
100 UnparkHandle { futex: &self.futex }
101 }
102}
103
104impl ThreadParker {
105 #[inline]
106 fn futex_wait(&self, ts: Option<libc::timespec>) {
107 let ts_ptr = ts
108 .as_ref()
109 .map(|ts_ref| ts_ref as *const _)
110 .unwrap_or(ptr::null());
111 let r = unsafe {
112 libc::syscall(
113 libc::SYS_futex,
114 &self.futex,
115 libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG,
116 1,
117 ts_ptr,
118 )
119 };
120 debug_assert!(r == 0 || r == -1);
121 if r == -1 {
122 debug_assert!(
123 errno() == libc::EINTR
124 || errno() == libc::EAGAIN
125 || (ts.is_some() && errno() == libc::ETIMEDOUT)
126 );
127 }
128 }
129}
130
131pub struct UnparkHandle {
132 futex: *const AtomicI32,
133}
134
135impl super::UnparkHandleT for UnparkHandle {
136 #[inline]
137 unsafe fn unpark(self) {
138 // The thread data may have been freed at this point, but it doesn't
139 // matter since the syscall will just return EFAULT in that case.
140 let r = libc::syscall(
141 libc::SYS_futex,
142 self.futex,
143 libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,
144 1,
145 );
146 debug_assert!(r == 0 || r == 1 || r == -1);
147 if r == -1 {
148 debug_assert_eq!(errno(), libc::EFAULT);
149 }
150 }
151}
152
153#[inline]
154pub fn thread_yield() {
155 thread::yield_now();
156}
157