Use futex-based locks and thread parker on FreeBSD.

This commit is contained in:
Mara Bos 2022-04-28 12:22:14 +02:00
parent 69f0bcb26d
commit 04b0bc97bb
4 changed files with 60 additions and 4 deletions

View File

@ -2,6 +2,7 @@
target_os = "linux",
target_os = "android",
all(target_os = "emscripten", target_feature = "atomics"),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",
@ -18,7 +19,12 @@ pub const SYS___futex: i32 = 166;
/// Returns directly if the futex doesn't hold the expected value.
///
/// Returns false on timeout, and true in all other cases.
#[cfg(any(target_os = "linux", target_os = "android", target_os = "netbsd"))]
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd"
))]
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
use super::time::Timespec;
use crate::ptr::null;
@ -40,7 +46,26 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
// absolute time rather than a relative time.
let r = unsafe {
cfg_if::cfg_if! {
if #[cfg(target_os = "netbsd")] {
if #[cfg(target_os = "freebsd")] {
// FreeBSD doesn't have futex(), but it has
// _umtx_op(UMTX_OP_WAIT_UINT_PRIVATE), which is nearly
// identical. It supports absolute timeouts through a flag
// in the _umtx_time struct.
let umtx_timeout = timespec.map(|t| libc::_umtx_time {
_timeout: t.t,
_flags: libc::UMTX_ABSTIME,
_clockid: libc::CLOCK_MONOTONIC as u32,
});
let umtx_timeout_ptr = umtx_timeout.as_ref().map_or(null(), |t| t as *const _);
let umtx_timeout_size = umtx_timeout.as_ref().map_or(0, |t| crate::mem::size_of_val(t));
libc::_umtx_op(
futex as *const AtomicU32 as *mut _,
libc::UMTX_OP_WAIT_UINT_PRIVATE,
expected as libc::c_ulong,
crate::ptr::invalid_mut(umtx_timeout_size),
umtx_timeout_ptr as *mut _,
)
} else if #[cfg(target_os = "netbsd")] {
// Netbsd's futex syscall takes addr2 and val2 as separate arguments.
// (Both are unused for FUTEX_WAIT[_BITSET].)
libc::syscall(
@ -110,6 +135,35 @@ pub fn futex_wake_all(futex: &AtomicU32) {
}
}
// FreeBSD doesn't tell us how many threads are woken up, so this doesn't return a bool.
#[cfg(target_os = "freebsd")]
pub fn futex_wake(futex: &AtomicU32) {
use crate::ptr::null_mut;
unsafe {
libc::_umtx_op(
futex as *const AtomicU32 as *mut _,
libc::UMTX_OP_WAKE_PRIVATE,
1,
null_mut(),
null_mut(),
)
};
}
#[cfg(target_os = "freebsd")]
pub fn futex_wake_all(futex: &AtomicU32) {
use crate::ptr::null_mut;
unsafe {
libc::_umtx_op(
futex as *const AtomicU32 as *mut _,
libc::UMTX_OP_WAKE_PRIVATE,
i32::MAX as libc::c_ulong,
null_mut(),
null_mut(),
)
};
}
#[cfg(target_os = "openbsd")]
pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -> bool {
use crate::convert::TryInto;

View File

@ -284,8 +284,8 @@ impl RwLock {
fn wake_writer(&self) -> bool {
self.writer_notify.fetch_add(1, Release);
cfg_if::cfg_if! {
if #[cfg(target_os = "dragonfly")] {
// DragonFlyBSD doesn't tell us whether it woke up any threads or not.
if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] {
// FreeBSD and DragonFlyBSD don't tell us whether they woke up any threads or not.
// So, we always return `false` here, as that still results in correct behaviour.
// The downside is an extra syscall in case both readers and writers were waiting,
// and unnecessarily waking up readers when a writer is about to attempt to lock the lock.

View File

@ -3,6 +3,7 @@ cfg_if::cfg_if! {
target_os = "linux",
target_os = "android",
all(target_os = "emscripten", target_feature = "atomics"),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",

View File

@ -3,6 +3,7 @@ cfg_if::cfg_if! {
target_os = "linux",
target_os = "android",
all(target_arch = "wasm32", target_feature = "atomics"),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly",