-
Notifications
You must be signed in to change notification settings - Fork 12.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of #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 #93740). Therefore, the generic `Parker` needs to be replaced on all platforms where the new lock implementation will be used, which, after #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?
- Loading branch information
Showing
5 changed files
with
225 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use crate::mem::MaybeUninit; | ||
use crate::time::Duration; | ||
|
||
use super::{ | ||
abi, | ||
error::{expect_success, fail}, | ||
time::with_tmos, | ||
}; | ||
|
||
const CLEAR: abi::FLGPTN = 0; | ||
const RAISED: abi::FLGPTN = 1; | ||
|
||
/// A thread parking primitive that is not susceptible to race conditions, | ||
/// but provides no atomic ordering guarantees and allows only one `raise` per wait. | ||
pub struct WaitFlag { | ||
flag: abi::ID, | ||
} | ||
|
||
impl WaitFlag { | ||
/// Creates a new wait flag. | ||
pub fn new() -> WaitFlag { | ||
let flag = expect_success( | ||
unsafe { | ||
abi::acre_flg(&abi::T_CFLG { | ||
flgatr: abi::TA_FIFO | abi::TA_WSGL | abi::TA_CLR, | ||
iflgptn: CLEAR, | ||
}) | ||
}, | ||
&"acre_flg", | ||
); | ||
|
||
WaitFlag { flag } | ||
} | ||
|
||
/// Wait for the wait flag to be raised. | ||
pub fn wait(&self) { | ||
let mut token = MaybeUninit::uninit(); | ||
expect_success( | ||
unsafe { abi::wai_flg(self.flag, RAISED, abi::TWF_ORW, token.as_mut_ptr()) }, | ||
&"wai_flg", | ||
); | ||
} | ||
|
||
/// Wait for the wait flag to be raised or the timeout to occur. | ||
/// | ||
/// Returns whether the flag was raised (`true`) or the operation timed out (`false`). | ||
pub fn wait_timeout(&self, dur: Duration) -> bool { | ||
let mut token = MaybeUninit::uninit(); | ||
let res = with_tmos(dur, |tmout| unsafe { | ||
abi::twai_flg(self.flag, RAISED, abi::TWF_ORW, token.as_mut_ptr(), tmout) | ||
}); | ||
|
||
match res { | ||
abi::E_OK => true, | ||
abi::E_TMOUT => false, | ||
error => fail(error, &"twai_flg"), | ||
} | ||
} | ||
|
||
/// Raise the wait flag. | ||
/// | ||
/// Calls to this function should be balanced with the number of successful waits. | ||
pub fn raise(&self) { | ||
expect_success(unsafe { abi::set_flg(self.flag, RAISED) }, &"set_flg"); | ||
} | ||
} | ||
|
||
impl Drop for WaitFlag { | ||
fn drop(&mut self) { | ||
expect_success(unsafe { abi::del_flg(self.flag) }, &"del_flg"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
//! A wait-flag-based thread parker. | ||
//! | ||
//! Some operating systems provide low-level parking primitives like wait counts, | ||
//! event flags or semaphores which are not susceptible to race conditions (meaning | ||
//! the wakeup can occur before the wait operation). To implement the `std` thread | ||
//! parker on top of these primitives, we only have to ensure that parking is fast | ||
//! when the thread token is available, the atomic ordering guarantees are maintained | ||
//! and spurious wakeups are minimized. | ||
//! | ||
//! To achieve this, this parker uses an atomic variable with three states: `EMPTY`, | ||
//! `PARKED` and `NOTIFIED`: | ||
//! * `EMPTY` means the token has not been made available, but the thread is not | ||
//! currently waiting on it. | ||
//! * `PARKED` means the token is not available and the thread is parked. | ||
//! * `NOTIFIED` means the token is available. | ||
//! | ||
//! `park` and `park_timeout` change the state from `EMPTY` to `PARKED` and from | ||
//! `NOTIFIED` to `EMPTY`. If the state was `NOTIFIED`, the thread was unparked and | ||
//! execution can continue without calling into the OS. If the state was `EMPTY`, | ||
//! the token is not available and the thread waits on the primitive (here called | ||
//! "wait flag"). | ||
//! | ||
//! `unpark` changes the state to `NOTIFIED`. If the state was `PARKED`, the thread | ||
//! is or will be sleeping on the wait flag, so we raise it. | ||
|
||
use crate::pin::Pin; | ||
use crate::sync::atomic::AtomicI8; | ||
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; | ||
use crate::sys::wait_flag::WaitFlag; | ||
use crate::time::Duration; | ||
|
||
const EMPTY: i8 = 0; | ||
const PARKED: i8 = -1; | ||
const NOTIFIED: i8 = 1; | ||
|
||
pub struct Parker { | ||
state: AtomicI8, | ||
wait_flag: WaitFlag, | ||
} | ||
|
||
impl Parker { | ||
/// Construct a parker for the current thread. The UNIX parker | ||
/// implementation requires this to happen in-place. | ||
pub unsafe fn new(parker: *mut Parker) { | ||
parker.write(Parker { state: AtomicI8::new(EMPTY), wait_flag: WaitFlag::new() }) | ||
} | ||
|
||
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do. | ||
pub unsafe fn park(self: Pin<&Self>) { | ||
match self.state.fetch_sub(1, Acquire) { | ||
// NOTIFIED => EMPTY | ||
NOTIFIED => return, | ||
// EMPTY => PARKED | ||
EMPTY => (), | ||
_ => panic!("inconsistent park state"), | ||
} | ||
|
||
// Avoid waking up from spurious wakeups (these are quite likely, see below). | ||
loop { | ||
self.wait_flag.wait(); | ||
|
||
match self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed) { | ||
Ok(_) => return, | ||
Err(PARKED) => (), | ||
Err(_) => panic!("inconsistent park state"), | ||
} | ||
} | ||
} | ||
|
||
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do. | ||
pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) { | ||
match self.state.fetch_sub(1, Acquire) { | ||
NOTIFIED => return, | ||
EMPTY => (), | ||
_ => panic!("inconsistent park state"), | ||
} | ||
|
||
self.wait_flag.wait_timeout(dur); | ||
|
||
// Either a wakeup or a timeout occurred. Wakeups may be spurious, as there can be | ||
// a race condition when `unpark` is performed between receiving the timeout and | ||
// resetting the state, resulting in the eventflag being set unnecessarily. `park` | ||
// is protected against this by looping until the token is actually given, but | ||
// here we cannot easily tell. | ||
|
||
// Use `swap` to provide acquire ordering. | ||
match self.state.swap(EMPTY, Acquire) { | ||
NOTIFIED => (), | ||
PARKED => (), | ||
_ => panic!("inconsistent park state"), | ||
} | ||
} | ||
|
||
// This implementation doesn't require `Pin`, but other implementations do. | ||
pub fn unpark(self: Pin<&Self>) { | ||
let state = self.state.swap(NOTIFIED, Release); | ||
|
||
if state == PARKED { | ||
self.wait_flag.raise(); | ||
} | ||
} | ||
} |