Skip to content

Commit 81b11ed

Browse files
committed
std: optimize thread parking on NetBSD
1 parent 098cf88 commit 81b11ed

File tree

3 files changed

+136
-12
lines changed

3 files changed

+136
-12
lines changed
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+
}
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+
}

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)