]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/futex.rs
Rollup merge of #95211 - terrarier2111:improve-parser, r=compiler-errors
[rust.git] / library / std / src / sys / unix / futex.rs
1 #![cfg(any(
2     target_os = "linux",
3     target_os = "android",
4     all(target_os = "emscripten", target_feature = "atomics"),
5     target_os = "freebsd",
6     target_os = "openbsd",
7     target_os = "dragonfly",
8 ))]
9
10 use crate::sync::atomic::AtomicU32;
11 use crate::time::Duration;
12
13 /// Wait for a futex_wake operation to wake us.
14 ///
15 /// Returns directly if the futex doesn't hold the expected value.
16 ///
17 /// Returns false on timeout, and true in all other cases.
18 #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
19 pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
20     use super::time::Timespec;
21     use crate::ptr::null;
22     use crate::sync::atomic::Ordering::Relaxed;
23
24     // Calculate the timeout as an absolute timespec.
25     //
26     // Overflows are rounded up to an infinite timeout (None).
27     let timespec = timeout
28         .and_then(|d| Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d))
29         .and_then(|t| t.to_timespec());
30
31     loop {
32         // No need to wait if the value already changed.
33         if futex.load(Relaxed) != expected {
34             return true;
35         }
36
37         let r = unsafe {
38             cfg_if::cfg_if! {
39                 if #[cfg(target_os = "freebsd")] {
40                     // FreeBSD doesn't have futex(), but it has
41                     // _umtx_op(UMTX_OP_WAIT_UINT_PRIVATE), which is nearly
42                     // identical. It supports absolute timeouts through a flag
43                     // in the _umtx_time struct.
44                     let umtx_timeout = timespec.map(|t| libc::_umtx_time {
45                         _timeout: t,
46                         _flags: libc::UMTX_ABSTIME,
47                         _clockid: libc::CLOCK_MONOTONIC as u32,
48                     });
49                     let umtx_timeout_ptr = umtx_timeout.as_ref().map_or(null(), |t| t as *const _);
50                     let umtx_timeout_size = umtx_timeout.as_ref().map_or(0, |t| crate::mem::size_of_val(t));
51                     libc::_umtx_op(
52                         futex as *const AtomicU32 as *mut _,
53                         libc::UMTX_OP_WAIT_UINT_PRIVATE,
54                         expected as libc::c_ulong,
55                         crate::ptr::invalid_mut(umtx_timeout_size),
56                         umtx_timeout_ptr as *mut _,
57                     )
58                 } else if #[cfg(any(target_os = "linux", target_os = "android"))] {
59                     // Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an
60                     // absolute time rather than a relative time.
61                     libc::syscall(
62                         libc::SYS_futex,
63                         futex as *const AtomicU32,
64                         libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,
65                         expected,
66                         timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
67                         null::<u32>(), // This argument is unused for FUTEX_WAIT_BITSET.
68                         !0u32,         // A full bitmask, to make it behave like a regular FUTEX_WAIT.
69                     )
70                 } else {
71                     compile_error!("unknown target_os");
72                 }
73             }
74         };
75
76         match (r < 0).then(super::os::errno) {
77             Some(libc::ETIMEDOUT) => return false,
78             Some(libc::EINTR) => continue,
79             _ => return true,
80         }
81     }
82 }
83
84 /// Wake up one thread that's blocked on futex_wait on this futex.
85 ///
86 /// Returns true if this actually woke up such a thread,
87 /// or false if no thread was waiting on this futex.
88 ///
89 /// On some platforms, this always returns false.
90 #[cfg(any(target_os = "linux", target_os = "android"))]
91 pub fn futex_wake(futex: &AtomicU32) -> bool {
92     let ptr = futex as *const AtomicU32;
93     let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
94     unsafe { libc::syscall(libc::SYS_futex, ptr, op, 1) > 0 }
95 }
96
97 /// Wake up all threads that are waiting on futex_wait on this futex.
98 #[cfg(any(target_os = "linux", target_os = "android"))]
99 pub fn futex_wake_all(futex: &AtomicU32) {
100     let ptr = futex as *const AtomicU32;
101     let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
102     unsafe {
103         libc::syscall(libc::SYS_futex, ptr, op, i32::MAX);
104     }
105 }
106
107 // FreeBSD doesn't tell us how many threads are woken up, so this always returns false.
108 #[cfg(target_os = "freebsd")]
109 pub fn futex_wake(futex: &AtomicU32) -> bool {
110     use crate::ptr::null_mut;
111     unsafe {
112         libc::_umtx_op(
113             futex as *const AtomicU32 as *mut _,
114             libc::UMTX_OP_WAKE_PRIVATE,
115             1,
116             null_mut(),
117             null_mut(),
118         )
119     };
120     false
121 }
122
123 #[cfg(target_os = "freebsd")]
124 pub fn futex_wake_all(futex: &AtomicU32) {
125     use crate::ptr::null_mut;
126     unsafe {
127         libc::_umtx_op(
128             futex as *const AtomicU32 as *mut _,
129             libc::UMTX_OP_WAKE_PRIVATE,
130             i32::MAX as libc::c_ulong,
131             null_mut(),
132             null_mut(),
133         )
134     };
135 }
136
137 #[cfg(target_os = "openbsd")]
138 pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
139     use super::time::Timespec;
140     use crate::ptr::{null, null_mut};
141
142     // Overflows are rounded up to an infinite timeout (None).
143     let timespec = timeout
144         .and_then(|d| Timespec::zero().checked_add_duration(&d))
145         .and_then(|t| t.to_timespec());
146
147     let r = unsafe {
148         libc::futex(
149             futex as *const AtomicU32 as *mut u32,
150             libc::FUTEX_WAIT,
151             expected as i32,
152             timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
153             null_mut(),
154         )
155     };
156
157     r == 0 || super::os::errno() != libc::ETIMEDOUT
158 }
159
160 #[cfg(target_os = "openbsd")]
161 pub fn futex_wake(futex: &AtomicU32) -> bool {
162     use crate::ptr::{null, null_mut};
163     unsafe {
164         libc::futex(futex as *const AtomicU32 as *mut u32, libc::FUTEX_WAKE, 1, null(), null_mut())
165             > 0
166     }
167 }
168
169 #[cfg(target_os = "openbsd")]
170 pub fn futex_wake_all(futex: &AtomicU32) {
171     use crate::ptr::{null, null_mut};
172     unsafe {
173         libc::futex(
174             futex as *const AtomicU32 as *mut u32,
175             libc::FUTEX_WAKE,
176             i32::MAX,
177             null(),
178             null_mut(),
179         );
180     }
181 }
182
183 #[cfg(target_os = "dragonfly")]
184 pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
185     // A timeout of 0 means infinite.
186     // We round smaller timeouts up to 1 millisecond.
187     // Overflows are rounded up to an infinite timeout.
188     let timeout_ms =
189         timeout.and_then(|d| Some(i32::try_from(d.as_millis()).ok()?.max(1))).unwrap_or(0);
190
191     let r = unsafe {
192         libc::umtx_sleep(futex as *const AtomicU32 as *const i32, expected as i32, timeout_ms)
193     };
194
195     r == 0 || super::os::errno() != libc::ETIMEDOUT
196 }
197
198 // DragonflyBSD doesn't tell us how many threads are woken up, so this always returns false.
199 #[cfg(target_os = "dragonfly")]
200 pub fn futex_wake(futex: &AtomicU32) -> bool {
201     unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, 1) };
202     false
203 }
204
205 #[cfg(target_os = "dragonfly")]
206 pub fn futex_wake_all(futex: &AtomicU32) {
207     unsafe { libc::umtx_wakeup(futex as *const AtomicU32 as *const i32, i32::MAX) };
208 }
209
210 #[cfg(target_os = "emscripten")]
211 extern "C" {
212     fn emscripten_futex_wake(addr: *const AtomicU32, count: libc::c_int) -> libc::c_int;
213     fn emscripten_futex_wait(
214         addr: *const AtomicU32,
215         val: libc::c_uint,
216         max_wait_ms: libc::c_double,
217     ) -> libc::c_int;
218 }
219
220 #[cfg(target_os = "emscripten")]
221 pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
222     unsafe {
223         emscripten_futex_wait(
224             futex,
225             expected,
226             timeout.map_or(f64::INFINITY, |d| d.as_secs_f64() * 1000.0),
227         ) != -libc::ETIMEDOUT
228     }
229 }
230
231 #[cfg(target_os = "emscripten")]
232 pub fn futex_wake(futex: &AtomicU32) -> bool {
233     unsafe { emscripten_futex_wake(futex, 1) > 0 }
234 }
235
236 #[cfg(target_os = "emscripten")]
237 pub fn futex_wake_all(futex: &AtomicU32) {
238     unsafe { emscripten_futex_wake(futex, i32::MAX) };
239 }