Skip to content

Commit 82fa41a

Browse files
committed
std: use futex for thread parking and Once on Apple platforms
1 parent 0398215 commit 82fa41a

File tree

5 files changed

+197
-142
lines changed

5 files changed

+197
-142
lines changed

library/std/src/sys/pal/unix/futex.rs

+184
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
target_os = "openbsd",
77
target_os = "dragonfly",
88
target_os = "fuchsia",
9+
target_os = "macos",
10+
target_os = "ios",
11+
target_os = "tvos",
12+
target_os = "watchos",
913
))]
1014

1115
use crate::sync::atomic::AtomicU32;
@@ -135,6 +139,186 @@ pub fn futex_wake_all(futex: &AtomicU32) {
135139
};
136140
}
137141

142+
/// With macOS version 14.4, Apple introduced a public futex API. Unfortunately,
143+
/// our minimum supported version is 10.12, so we need a fallback API. Luckily
144+
/// for us, the underlying syscalls have been available since exactly that
145+
/// version, so we just use those when needed. This is private API however,
146+
/// which means we need to take care to avoid breakage if the syscall is removed
147+
/// and to avoid apps being rejected from the App Store. To do this, we use weak
148+
/// linkage emulation for both the public and the private API.
149+
///
150+
/// See os/os_sync_wait_on_address.h (Apple has failed to upload the documentation
151+
/// to their website) for documentation of the public API and
152+
/// https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/bsd/sys/ulock.h#L69
153+
/// for the header file of the private API, along with its usage in libpthread
154+
/// https://github.com/apple-oss-distributions/libpthread/blob/d8c4e3c212553d3e0f5d76bb7d45a8acd61302dc/src/pthread_cond.c#L463
155+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos",))]
156+
mod apple {
157+
use crate::ffi::{c_int, c_void};
158+
use crate::sys::pal::weak::weak;
159+
160+
pub const OS_CLOCK_MACH_ABSOLUTE_TIME: u32 = 32;
161+
pub const OS_SYNC_WAIT_ON_ADDRESS_NONE: u32 = 0;
162+
pub const OS_SYNC_WAKE_BY_ADDRESS_NONE: u32 = 0;
163+
164+
pub const UL_COMPARE_AND_WAIT: u32 = 1;
165+
pub const ULF_WAKE_ALL: u32 = 0x100;
166+
// The syscalls support directly returning errors instead of going through errno.
167+
pub const ULF_NO_ERRNO: u32 = 0x1000000;
168+
169+
// These functions appeared with macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, visionOS 1.1.
170+
weak! {
171+
pub fn os_sync_wait_on_address(*mut c_void, u64, usize, u32) -> c_int
172+
}
173+
174+
weak! {
175+
pub fn os_sync_wait_on_address_with_timeout(*mut c_void, u64, usize, u32, u32, u64) -> c_int
176+
}
177+
178+
weak! {
179+
pub fn os_sync_wake_by_address_any(*mut c_void, usize, u32) -> c_int
180+
}
181+
182+
weak! {
183+
pub fn os_sync_wake_by_address_all(*mut c_void, usize, u32) -> c_int
184+
}
185+
186+
// This syscall appeared with macOS 11.0.
187+
// It is used to support nanosecond precision for timeouts, among other features.
188+
weak! {
189+
pub fn __ulock_wait2(u32, *mut c_void, u64, u64, u64) -> c_int
190+
}
191+
192+
// These syscalls appeared with macOS 10.12.
193+
weak! {
194+
pub fn __ulock_wait(u32, *mut c_void, u64, u32) -> c_int
195+
}
196+
197+
weak! {
198+
pub fn __ulock_wake(u32, *mut c_void, u64) -> c_int
199+
}
200+
}
201+
202+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos",))]
203+
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
204+
use crate::mem::size_of;
205+
use apple::*;
206+
207+
let addr = futex.as_ptr().cast();
208+
let value = expected as u64;
209+
let size = size_of::<u32>();
210+
if let Some(timeout) = timeout {
211+
let timeout_ns = timeout.as_nanos().clamp(1, u64::MAX as u128) as u64;
212+
let timeout_ms = timeout.as_micros().clamp(1, u32::MAX as u128) as u32;
213+
214+
if let Some(wait) = os_sync_wait_on_address_with_timeout.get() {
215+
let r = unsafe {
216+
wait(
217+
addr,
218+
value,
219+
size,
220+
OS_SYNC_WAIT_ON_ADDRESS_NONE,
221+
OS_CLOCK_MACH_ABSOLUTE_TIME,
222+
timeout_ns,
223+
)
224+
};
225+
226+
// We promote spurious wakeups (reported as EINTR) to normal ones for
227+
// simplicity and because Apple's documentation is unclear as to what the
228+
// unit for the deadline of `os_sync_wait_on_address_with_deadline` is,
229+
// making it hard to support timeouts in a fashion similar to the Linux
230+
// futex implementation.
231+
r != -1 || super::os::errno() != libc::ETIMEDOUT
232+
} else if let Some(wait) = __ulock_wait2.get() {
233+
unsafe {
234+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ns, 0)
235+
!= -libc::ETIMEDOUT
236+
}
237+
} else if let Some(wait) = __ulock_wait.get() {
238+
unsafe {
239+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, timeout_ms)
240+
!= -libc::ETIMEDOUT
241+
}
242+
} else {
243+
panic!("your system is below the minimum supported version of Rust");
244+
}
245+
} else {
246+
if let Some(wait) = os_sync_wait_on_address.get() {
247+
unsafe {
248+
wait(addr, value, size, OS_SYNC_WAIT_ON_ADDRESS_NONE);
249+
}
250+
} else if let Some(wait) = __ulock_wait.get() {
251+
unsafe {
252+
wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, value, 0);
253+
}
254+
} else {
255+
panic!("your system is below the minimum supported version of Rust");
256+
}
257+
true
258+
}
259+
}
260+
261+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos",))]
262+
pub fn futex_wake(futex: &AtomicU32) -> bool {
263+
use crate::io::Error;
264+
use crate::mem::size_of;
265+
use apple::*;
266+
267+
let addr = futex.as_ptr().cast();
268+
if let Some(wake) = os_sync_wake_by_address_any.get() {
269+
unsafe { wake(addr, size_of::<u32>(), OS_SYNC_WAKE_BY_ADDRESS_NONE) == 0 }
270+
} else if let Some(wake) = __ulock_wake.get() {
271+
// __ulock_wake can get interrupted, so retry until either waking up a
272+
// waiter or failing because there are no waiters (ENOENT).
273+
loop {
274+
let r = unsafe { wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, addr, 0) };
275+
276+
if r >= 0 {
277+
return true;
278+
} else {
279+
match -r {
280+
libc::ENOENT => return false,
281+
libc::EINTR => continue,
282+
err => panic!("__ulock_wake failed: {}", Error::from_raw_os_error(err)),
283+
}
284+
}
285+
}
286+
} else {
287+
panic!("your system is below the minimum supported version of Rust");
288+
}
289+
}
290+
291+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos",))]
292+
pub fn futex_wake_all(futex: &AtomicU32) {
293+
use crate::io::Error;
294+
use crate::mem::size_of;
295+
use apple::*;
296+
297+
let addr = futex.as_ptr().cast();
298+
299+
if let Some(wake) = os_sync_wake_by_address_all.get() {
300+
unsafe {
301+
wake(addr, size_of::<u32>(), OS_SYNC_WAKE_BY_ADDRESS_NONE);
302+
}
303+
} else if let Some(wake) = __ulock_wake.get() {
304+
loop {
305+
let r = unsafe { wake(UL_COMPARE_AND_WAIT | ULF_WAKE_ALL | ULF_NO_ERRNO, addr, 0) };
306+
307+
if r >= 0 {
308+
return;
309+
} else {
310+
match -r {
311+
libc::ENOENT => return,
312+
libc::EINTR => continue,
313+
err => panic!("__ulock_wake failed: {}", Error::from_raw_os_error(err)),
314+
}
315+
}
316+
}
317+
} else {
318+
panic!("your system is below the minimum supported version of Rust");
319+
}
320+
}
321+
138322
#[cfg(target_os = "openbsd")]
139323
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
140324
use super::time::Timespec;

library/std/src/sys/pal/unix/thread_parking/darwin.rs

-130
This file was deleted.

library/std/src/sys/pal/unix/thread_parking/mod.rs

+5-12
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,14 @@
88
target_os = "openbsd",
99
target_os = "dragonfly",
1010
target_os = "fuchsia",
11+
target_os = "macos",
12+
target_os = "ios",
13+
target_os = "tvos",
14+
target_os = "watchos",
1115
)))]
1216

1317
cfg_if::cfg_if! {
14-
if #[cfg(all(
15-
any(
16-
target_os = "macos",
17-
target_os = "ios",
18-
target_os = "watchos",
19-
target_os = "tvos",
20-
),
21-
not(miri),
22-
))] {
23-
mod darwin;
24-
pub use darwin::Parker;
25-
} else if #[cfg(target_os = "netbsd")] {
18+
if #[cfg(target_os = "netbsd")] {
2619
mod netbsd;
2720
pub use netbsd::{current, park, park_timeout, unpark, ThreadId};
2821
} else {

library/std/src/sys_common/once/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ cfg_if::cfg_if! {
1717
target_os = "dragonfly",
1818
target_os = "fuchsia",
1919
target_os = "hermit",
20+
target_os = "macos",
21+
target_os = "ios",
22+
target_os = "tvos",
23+
target_os = "watchos",
2024
))] {
2125
mod futex;
2226
pub use futex::{Once, OnceState};

library/std/src/sys_common/thread_parking/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ cfg_if::cfg_if! {
88
target_os = "dragonfly",
99
target_os = "fuchsia",
1010
target_os = "hermit",
11+
target_os = "macos",
12+
target_os = "ios",
13+
target_os = "tvos",
14+
target_os = "watchos",
1115
))] {
1216
mod futex;
1317
pub use futex::Parker;

0 commit comments

Comments
 (0)