Skip to content

Commit 44d5c47

Browse files
authored
Rollup merge of rust-lang#97140 - joboet:solid_parker, r=m-ou-se
std: use an event-flag-based thread parker on SOLID `Mutex` and `Condvar` are being replaced by more efficient implementations, which need thread parking themselves (see rust-lang#93740). Therefore, the generic `Parker` needs to be replaced on all platforms where the new lock implementation will be used, which, after rust-lang#96393, are SOLID, SGX and Hermit (more PRs coming soon). SOLID, conforming to the [μITRON specification](http://www.ertl.jp/ITRON/SPEC/FILE/mitron-400e.pdf), has event flags, which are a thread parking primitive very similar to `Parker`. However, they do not make any atomic ordering guarantees (even though those can probably be assumed) and necessitate a system call even when the thread token is already available. Hence, this `Parker`, like the Windows parker, uses an extra atomic state variable. I future-proofed the code by wrapping the event flag in a `WaitFlag` structure, as both SGX and Hermit can share the Parker implementation, they just have slightly different primitives (SGX uses signals and Hermit has a thread blocking API). ``@kawadakk`` I assume you are the target maintainer? Could you test this for me?
2 parents 0e1a6fb + caff723 commit 44d5c47

File tree

5 files changed

+225
-6
lines changed

5 files changed

+225
-6
lines changed

library/std/src/sys/itron/abi.rs

+45-3
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,32 @@ pub type ER = int_t;
3030
/// Error code type, `ID` on success
3131
pub type ER_ID = int_t;
3232

33+
/// Service call operational mode
34+
pub type MODE = uint_t;
35+
36+
/// OR waiting condition for an eventflag
37+
pub const TWF_ORW: MODE = 0x01;
38+
39+
/// Object attributes
40+
pub type ATR = uint_t;
41+
42+
/// FIFO wait order
43+
pub const TA_FIFO: ATR = 0;
44+
/// Only one task is allowed to be in the waiting state for the eventflag
45+
pub const TA_WSGL: ATR = 0;
46+
/// The eventflag’s bit pattern is cleared when a task is released from the
47+
/// waiting state for that eventflag.
48+
pub const TA_CLR: ATR = 0x04;
49+
50+
/// Bit pattern of an eventflag
51+
pub type FLGPTN = uint_t;
52+
3353
/// Task or interrupt priority
3454
pub type PRI = int_t;
3555

3656
/// The special value of `PRI` representing the current task's priority.
3757
pub const TPRI_SELF: PRI = 0;
3858

39-
/// Object attributes
40-
pub type ATR = uint_t;
41-
4259
/// Use the priority inheritance protocol
4360
#[cfg(target_os = "solid_asp3")]
4461
pub const TA_INHERIT: ATR = 0x02;
@@ -90,6 +107,13 @@ pub struct T_CSEM {
90107
pub maxsem: uint_t,
91108
}
92109

110+
#[derive(Clone, Copy)]
111+
#[repr(C)]
112+
pub struct T_CFLG {
113+
pub flgatr: ATR,
114+
pub iflgptn: FLGPTN,
115+
}
116+
93117
#[derive(Clone, Copy)]
94118
#[repr(C)]
95119
pub struct T_CMTX {
@@ -139,6 +163,24 @@ extern "C" {
139163
pub fn sns_dsp() -> bool_t;
140164
#[link_name = "__asp3_get_tim"]
141165
pub fn get_tim(p_systim: *mut SYSTIM) -> ER;
166+
#[link_name = "__asp3_acre_flg"]
167+
pub fn acre_flg(pk_cflg: *const T_CFLG) -> ER_ID;
168+
#[link_name = "__asp3_del_flg"]
169+
pub fn del_flg(flgid: ID) -> ER;
170+
#[link_name = "__asp3_set_flg"]
171+
pub fn set_flg(flgid: ID, setptn: FLGPTN) -> ER;
172+
#[link_name = "__asp3_clr_flg"]
173+
pub fn clr_flg(flgid: ID, clrptn: FLGPTN) -> ER;
174+
#[link_name = "__asp3_wai_flg"]
175+
pub fn wai_flg(flgid: ID, waiptn: FLGPTN, wfmode: MODE, p_flgptn: *mut FLGPTN) -> ER;
176+
#[link_name = "__asp3_twai_flg"]
177+
pub fn twai_flg(
178+
flgid: ID,
179+
waiptn: FLGPTN,
180+
wfmode: MODE,
181+
p_flgptn: *mut FLGPTN,
182+
tmout: TMO,
183+
) -> ER;
142184
#[link_name = "__asp3_acre_mtx"]
143185
pub fn acre_mtx(pk_cmtx: *const T_CMTX) -> ER_ID;
144186
#[link_name = "__asp3_del_mtx"]
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::mem::MaybeUninit;
2+
use crate::time::Duration;
3+
4+
use super::{
5+
abi,
6+
error::{expect_success, fail},
7+
time::with_tmos,
8+
};
9+
10+
const CLEAR: abi::FLGPTN = 0;
11+
const RAISED: abi::FLGPTN = 1;
12+
13+
/// A thread parking primitive that is not susceptible to race conditions,
14+
/// but provides no atomic ordering guarantees and allows only one `raise` per wait.
15+
pub struct WaitFlag {
16+
flag: abi::ID,
17+
}
18+
19+
impl WaitFlag {
20+
/// Creates a new wait flag.
21+
pub fn new() -> WaitFlag {
22+
let flag = expect_success(
23+
unsafe {
24+
abi::acre_flg(&abi::T_CFLG {
25+
flgatr: abi::TA_FIFO | abi::TA_WSGL | abi::TA_CLR,
26+
iflgptn: CLEAR,
27+
})
28+
},
29+
&"acre_flg",
30+
);
31+
32+
WaitFlag { flag }
33+
}
34+
35+
/// Wait for the wait flag to be raised.
36+
pub fn wait(&self) {
37+
let mut token = MaybeUninit::uninit();
38+
expect_success(
39+
unsafe { abi::wai_flg(self.flag, RAISED, abi::TWF_ORW, token.as_mut_ptr()) },
40+
&"wai_flg",
41+
);
42+
}
43+
44+
/// Wait for the wait flag to be raised or the timeout to occur.
45+
///
46+
/// Returns whether the flag was raised (`true`) or the operation timed out (`false`).
47+
pub fn wait_timeout(&self, dur: Duration) -> bool {
48+
let mut token = MaybeUninit::uninit();
49+
let res = with_tmos(dur, |tmout| unsafe {
50+
abi::twai_flg(self.flag, RAISED, abi::TWF_ORW, token.as_mut_ptr(), tmout)
51+
});
52+
53+
match res {
54+
abi::E_OK => true,
55+
abi::E_TMOUT => false,
56+
error => fail(error, &"twai_flg"),
57+
}
58+
}
59+
60+
/// Raise the wait flag.
61+
///
62+
/// Calls to this function should be balanced with the number of successful waits.
63+
pub fn raise(&self) {
64+
expect_success(unsafe { abi::set_flg(self.flag, RAISED) }, &"set_flg");
65+
}
66+
}
67+
68+
impl Drop for WaitFlag {
69+
fn drop(&mut self) {
70+
expect_success(unsafe { abi::del_flg(self.flag) }, &"del_flg");
71+
}
72+
}

library/std/src/sys/solid/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod itron {
1515
pub mod thread;
1616
pub(super) mod time;
1717
use super::unsupported;
18+
pub mod wait_flag;
1819
}
1920

2021
pub mod alloc;
@@ -43,6 +44,7 @@ pub mod memchr;
4344
pub mod thread_local_dtor;
4445
pub mod thread_local_key;
4546
pub mod time;
47+
pub use self::itron::wait_flag;
4648

4749
mod rwlock;
4850

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ cfg_if::cfg_if! {
1010
))] {
1111
mod futex;
1212
pub use futex::Parker;
13-
} else if #[cfg(windows)] {
14-
pub use crate::sys::thread_parker::Parker;
15-
} else if #[cfg(target_family = "unix")] {
13+
} else if #[cfg(target_os = "solid_asp3")] {
14+
mod wait_flag;
15+
pub use wait_flag::Parker;
16+
} else if #[cfg(any(windows, target_family = "unix"))] {
1617
pub use crate::sys::thread_parker::Parker;
1718
} else {
1819
mod generic;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! A wait-flag-based thread parker.
2+
//!
3+
//! Some operating systems provide low-level parking primitives like wait counts,
4+
//! event flags or semaphores which are not susceptible to race conditions (meaning
5+
//! the wakeup can occur before the wait operation). To implement the `std` thread
6+
//! parker on top of these primitives, we only have to ensure that parking is fast
7+
//! when the thread token is available, the atomic ordering guarantees are maintained
8+
//! and spurious wakeups are minimized.
9+
//!
10+
//! To achieve this, this parker uses an atomic variable with three states: `EMPTY`,
11+
//! `PARKED` and `NOTIFIED`:
12+
//! * `EMPTY` means the token has not been made available, but the thread is not
13+
//! currently waiting on it.
14+
//! * `PARKED` means the token is not available and the thread is parked.
15+
//! * `NOTIFIED` means the token is available.
16+
//!
17+
//! `park` and `park_timeout` change the state from `EMPTY` to `PARKED` and from
18+
//! `NOTIFIED` to `EMPTY`. If the state was `NOTIFIED`, the thread was unparked and
19+
//! execution can continue without calling into the OS. If the state was `EMPTY`,
20+
//! the token is not available and the thread waits on the primitive (here called
21+
//! "wait flag").
22+
//!
23+
//! `unpark` changes the state to `NOTIFIED`. If the state was `PARKED`, the thread
24+
//! is or will be sleeping on the wait flag, so we raise it.
25+
26+
use crate::pin::Pin;
27+
use crate::sync::atomic::AtomicI8;
28+
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
29+
use crate::sys::wait_flag::WaitFlag;
30+
use crate::time::Duration;
31+
32+
const EMPTY: i8 = 0;
33+
const PARKED: i8 = -1;
34+
const NOTIFIED: i8 = 1;
35+
36+
pub struct Parker {
37+
state: AtomicI8,
38+
wait_flag: WaitFlag,
39+
}
40+
41+
impl Parker {
42+
/// Construct a parker for the current thread. The UNIX parker
43+
/// implementation requires this to happen in-place.
44+
pub unsafe fn new(parker: *mut Parker) {
45+
parker.write(Parker { state: AtomicI8::new(EMPTY), wait_flag: WaitFlag::new() })
46+
}
47+
48+
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
49+
pub unsafe fn park(self: Pin<&Self>) {
50+
match self.state.fetch_sub(1, Acquire) {
51+
// NOTIFIED => EMPTY
52+
NOTIFIED => return,
53+
// EMPTY => PARKED
54+
EMPTY => (),
55+
_ => panic!("inconsistent park state"),
56+
}
57+
58+
// Avoid waking up from spurious wakeups (these are quite likely, see below).
59+
loop {
60+
self.wait_flag.wait();
61+
62+
match self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed) {
63+
Ok(_) => return,
64+
Err(PARKED) => (),
65+
Err(_) => panic!("inconsistent park state"),
66+
}
67+
}
68+
}
69+
70+
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
71+
pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
72+
match self.state.fetch_sub(1, Acquire) {
73+
NOTIFIED => return,
74+
EMPTY => (),
75+
_ => panic!("inconsistent park state"),
76+
}
77+
78+
self.wait_flag.wait_timeout(dur);
79+
80+
// Either a wakeup or a timeout occurred. Wakeups may be spurious, as there can be
81+
// a race condition when `unpark` is performed between receiving the timeout and
82+
// resetting the state, resulting in the eventflag being set unnecessarily. `park`
83+
// is protected against this by looping until the token is actually given, but
84+
// here we cannot easily tell.
85+
86+
// Use `swap` to provide acquire ordering.
87+
match self.state.swap(EMPTY, Acquire) {
88+
NOTIFIED => (),
89+
PARKED => (),
90+
_ => panic!("inconsistent park state"),
91+
}
92+
}
93+
94+
// This implementation doesn't require `Pin`, but other implementations do.
95+
pub fn unpark(self: Pin<&Self>) {
96+
let state = self.state.swap(NOTIFIED, Release);
97+
98+
if state == PARKED {
99+
self.wait_flag.raise();
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)