Skip to content

Commit 56b625b

Browse files
committed
Auto merge of #101482 - joboet:netbsd_parker, r=sanxiyn
Optimize thread parking on NetBSD As the futex syscall is not present in the latest stable release, NetBSD cannot use the efficient thread parker and locks Linux uses. Currently, it therefore relies on a pthread-based parker, consisting of a mutex and semaphore which protect a state variable. NetBSD however has more efficient syscalls available: [`_lwp_park`](https://man.netbsd.org/_lwp_park.2) and [`_lwp_unpark`](https://man.netbsd.org/_lwp_unpark.2). These already provide the exact semantics of `thread::park` and `Thread::unpark`, but work with thread ids. In `std`, this ID is here stored in an atomic state variable, which is also used to optimize cases were the parking token is already available at the time `thread::park` is called. r? `@m-ou-se`
2 parents abd4d2e + 81b11ed commit 56b625b

File tree

3 files changed

+136
-12
lines changed

3 files changed

+136
-12
lines changed

Diff for: library/std/src/sys/unix/thread_parker/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Thread parking on systems without futex support.
2+
3+
#![cfg(not(any(
4+
target_os = "linux",
5+
target_os = "android",
6+
all(target_os = "emscripten", target_feature = "atomics"),
7+
target_os = "freebsd",
8+
target_os = "openbsd",
9+
target_os = "dragonfly",
10+
target_os = "fuchsia",
11+
)))]
12+
13+
cfg_if::cfg_if! {
14+
if #[cfg(target_os = "netbsd")] {
15+
mod netbsd;
16+
pub use netbsd::Parker;
17+
} else {
18+
mod pthread;
19+
pub use pthread::Parker;
20+
}
21+
}

Diff for: library/std/src/sys/unix/thread_parker/netbsd.rs

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use crate::ffi::{c_int, c_void};
2+
use crate::pin::Pin;
3+
use crate::ptr::{null, null_mut};
4+
use crate::sync::atomic::{
5+
AtomicU64,
6+
Ordering::{Acquire, Relaxed, Release},
7+
};
8+
use crate::time::Duration;
9+
use libc::{_lwp_self, clockid_t, lwpid_t, time_t, timespec, CLOCK_MONOTONIC};
10+
11+
extern "C" {
12+
fn ___lwp_park60(
13+
clock_id: clockid_t,
14+
flags: c_int,
15+
ts: *mut timespec,
16+
unpark: lwpid_t,
17+
hint: *const c_void,
18+
unparkhint: *const c_void,
19+
) -> c_int;
20+
fn _lwp_unpark(lwp: lwpid_t, hint: *const c_void) -> c_int;
21+
}
22+
23+
/// The thread is not parked and the token is not available.
24+
///
25+
/// Zero cannot be a valid LWP id, since it is used as empty value for the unpark
26+
/// argument in _lwp_park.
27+
const EMPTY: u64 = 0;
28+
/// The token is available. Do not park anymore.
29+
const NOTIFIED: u64 = u64::MAX;
30+
31+
pub struct Parker {
32+
/// The parker state. Contains either one of the two state values above or the LWP
33+
/// id of the parked thread.
34+
state: AtomicU64,
35+
}
36+
37+
impl Parker {
38+
pub unsafe fn new(parker: *mut Parker) {
39+
parker.write(Parker { state: AtomicU64::new(EMPTY) })
40+
}
41+
42+
// Does not actually need `unsafe` or `Pin`, but the pthread implementation does.
43+
pub unsafe fn park(self: Pin<&Self>) {
44+
// If the token has already been made available, we can skip
45+
// a bit of work, so check for it here.
46+
if self.state.load(Acquire) != NOTIFIED {
47+
let parked = _lwp_self() as u64;
48+
let hint = self.state.as_mut_ptr().cast();
49+
if self.state.compare_exchange(EMPTY, parked, Relaxed, Acquire).is_ok() {
50+
// Loop to guard against spurious wakeups.
51+
loop {
52+
___lwp_park60(0, 0, null_mut(), 0, hint, null());
53+
if self.state.load(Acquire) == NOTIFIED {
54+
break;
55+
}
56+
}
57+
}
58+
}
59+
60+
// At this point, the change to NOTIFIED has always been observed with acquire
61+
// ordering, so we can just use a relaxed store here (instead of a swap).
62+
self.state.store(EMPTY, Relaxed);
63+
}
64+
65+
// Does not actually need `unsafe` or `Pin`, but the pthread implementation does.
66+
pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
67+
if self.state.load(Acquire) != NOTIFIED {
68+
let parked = _lwp_self() as u64;
69+
let hint = self.state.as_mut_ptr().cast();
70+
let mut timeout = timespec {
71+
// Saturate so that the operation will definitely time out
72+
// (even if it is after the heat death of the universe).
73+
tv_sec: dur.as_secs().try_into().ok().unwrap_or(time_t::MAX),
74+
tv_nsec: dur.subsec_nanos().into(),
75+
};
76+
77+
if self.state.compare_exchange(EMPTY, parked, Relaxed, Acquire).is_ok() {
78+
// Timeout needs to be mutable since it is modified on NetBSD 9.0 and
79+
// above.
80+
___lwp_park60(CLOCK_MONOTONIC, 0, &mut timeout, 0, hint, null());
81+
// Use a swap to get acquire ordering even if the token was set after
82+
// the timeout occurred.
83+
self.state.swap(EMPTY, Acquire);
84+
return;
85+
}
86+
}
87+
88+
self.state.store(EMPTY, Relaxed);
89+
}
90+
91+
// Does not actually need `Pin`, but the pthread implementation does.
92+
pub fn unpark(self: Pin<&Self>) {
93+
let state = self.state.swap(NOTIFIED, Release);
94+
if !matches!(state, EMPTY | NOTIFIED) {
95+
let lwp = state as lwpid_t;
96+
let hint = self.state.as_mut_ptr().cast();
97+
98+
// If the parking thread terminated and did not actually park, this will
99+
// probably return an error, which is OK. In the worst case, another
100+
// thread has received the same LWP id. It will then receive a spurious
101+
// wakeup, but those are allowable per the API contract. The same reasoning
102+
// applies if a timeout occurred before this call, but the state was not
103+
// yet reset.
104+
105+
// SAFETY:
106+
// The syscall has no invariants to hold. Only unsafe because it is an
107+
// extern function.
108+
unsafe {
109+
_lwp_unpark(lwp, hint);
110+
}
111+
}
112+
}
113+
}

Diff for: library/std/src/sys/unix/thread_parker.rs renamed to library/std/src/sys/unix/thread_parker/pthread.rs

+2-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
//! Thread parking without `futex` using the `pthread` synchronization primitives.
22
3-
#![cfg(not(any(
4-
target_os = "linux",
5-
target_os = "android",
6-
all(target_os = "emscripten", target_feature = "atomics"),
7-
target_os = "freebsd",
8-
target_os = "openbsd",
9-
target_os = "dragonfly",
10-
target_os = "fuchsia",
11-
)))]
12-
133
use crate::cell::UnsafeCell;
144
use crate::marker::PhantomPinned;
155
use crate::pin::Pin;
@@ -59,8 +49,8 @@ unsafe fn wait_timeout(
5949
target_os = "espidf"
6050
))]
6151
let (now, dur) = {
62-
use super::time::SystemTime;
6352
use crate::cmp::min;
53+
use crate::sys::time::SystemTime;
6454

6555
// OSX implementation of `pthread_cond_timedwait` is buggy
6656
// with super long durations. When duration is greater than
@@ -85,7 +75,7 @@ unsafe fn wait_timeout(
8575
target_os = "espidf"
8676
)))]
8777
let (now, dur) = {
88-
use super::time::Timespec;
78+
use crate::sys::time::Timespec;
8979

9080
(Timespec::now(libc::CLOCK_MONOTONIC), dur)
9181
};

0 commit comments

Comments
 (0)