Skip to content

Commit e76c2bc

Browse files
committed
Bring back generic thread parker to use for rust9x targets.
The windows-specific thread parker can only work on Windows XP and higher.
1 parent ec9cbe7 commit e76c2bc

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

library/std/src/sys/windows/thread_parking.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(dead_code)]
2+
13
// Thread parker implementation for Windows.
24
//
35
// This uses WaitOnAddress and WakeByAddressSingle if available (Windows 8+).
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! Parker implementation based on a Mutex and Condvar.
2+
3+
use crate::pin::Pin;
4+
use crate::sync::atomic::AtomicUsize;
5+
use crate::sync::atomic::Ordering::SeqCst;
6+
use crate::sync::{Condvar, Mutex};
7+
use crate::time::Duration;
8+
9+
const EMPTY: usize = 0;
10+
const PARKED: usize = 1;
11+
const NOTIFIED: usize = 2;
12+
13+
pub struct Parker {
14+
state: AtomicUsize,
15+
lock: Mutex<()>,
16+
cvar: Condvar,
17+
}
18+
19+
impl Parker {
20+
/// Construct the generic parker. The UNIX parker implementation
21+
/// requires this to happen in-place.
22+
pub unsafe fn new_in_place(parker: *mut Parker) {
23+
parker.write(Parker {
24+
state: AtomicUsize::new(EMPTY),
25+
lock: Mutex::new(()),
26+
cvar: Condvar::new(),
27+
});
28+
}
29+
30+
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
31+
pub unsafe fn park(self: Pin<&Self>) {
32+
// If we were previously notified then we consume this notification and
33+
// return quickly.
34+
if self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
35+
return;
36+
}
37+
38+
// Otherwise we need to coordinate going to sleep
39+
let mut m = self.lock.lock().unwrap();
40+
match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
41+
Ok(_) => {}
42+
Err(NOTIFIED) => {
43+
// We must read here, even though we know it will be `NOTIFIED`.
44+
// This is because `unpark` may have been called again since we read
45+
// `NOTIFIED` in the `compare_exchange` above. We must perform an
46+
// acquire operation that synchronizes with that `unpark` to observe
47+
// any writes it made before the call to unpark. To do that we must
48+
// read from the write it made to `state`.
49+
let old = self.state.swap(EMPTY, SeqCst);
50+
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
51+
return;
52+
} // should consume this notification, so prohibit spurious wakeups in next park.
53+
Err(_) => panic!("inconsistent park state"),
54+
}
55+
loop {
56+
m = self.cvar.wait(m).unwrap();
57+
match self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst) {
58+
Ok(_) => return, // got a notification
59+
Err(_) => {} // spurious wakeup, go back to sleep
60+
}
61+
}
62+
}
63+
64+
// This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
65+
pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
66+
// Like `park` above we have a fast path for an already-notified thread, and
67+
// afterwards we start coordinating for a sleep.
68+
// return quickly.
69+
if self.state.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst).is_ok() {
70+
return;
71+
}
72+
let m = self.lock.lock().unwrap();
73+
match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
74+
Ok(_) => {}
75+
Err(NOTIFIED) => {
76+
// We must read again here, see `park`.
77+
let old = self.state.swap(EMPTY, SeqCst);
78+
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
79+
return;
80+
} // should consume this notification, so prohibit spurious wakeups in next park.
81+
Err(_) => panic!("inconsistent park_timeout state"),
82+
}
83+
84+
// Wait with a timeout, and if we spuriously wake up or otherwise wake up
85+
// from a notification we just want to unconditionally set the state back to
86+
// empty, either consuming a notification or un-flagging ourselves as
87+
// parked.
88+
let (_m, _result) = self.cvar.wait_timeout(m, dur).unwrap();
89+
match self.state.swap(EMPTY, SeqCst) {
90+
NOTIFIED => {} // got a notification, hurray!
91+
PARKED => {} // no notification, alas
92+
n => panic!("inconsistent park_timeout state: {n}"),
93+
}
94+
}
95+
96+
// This implementation doesn't require `Pin`, but other implementations do.
97+
pub fn unpark(self: Pin<&Self>) {
98+
// To ensure the unparked thread will observe any writes we made
99+
// before this call, we must perform a release operation that `park`
100+
// can synchronize with. To do that we must write `NOTIFIED` even if
101+
// `state` is already `NOTIFIED`. That is why this must be a swap
102+
// rather than a compare-and-swap that returns if it reads `NOTIFIED`
103+
// on failure.
104+
match self.state.swap(NOTIFIED, SeqCst) {
105+
EMPTY => return, // no one was waiting
106+
NOTIFIED => return, // already unparked
107+
PARKED => {} // gotta go wake someone up
108+
_ => panic!("inconsistent state in unpark"),
109+
}
110+
111+
// There is a period between when the parked thread sets `state` to
112+
// `PARKED` (or last checked `state` in the case of a spurious wake
113+
// up) and when it actually waits on `cvar`. If we were to notify
114+
// during this period it would be ignored and then when the parked
115+
// thread went to sleep it would never wake up. Fortunately, it has
116+
// `lock` locked at this stage so we can acquire `lock` to wait until
117+
// it is ready to receive the notification.
118+
//
119+
// Releasing `lock` before the call to `notify_one` means that when the
120+
// parked thread wakes it doesn't get woken only to have to wait for us
121+
// to release `lock`.
122+
drop(self.lock.lock().unwrap());
123+
self.cvar.notify_one()
124+
}
125+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(unexpected_cfgs)]
2+
13
cfg_if::cfg_if! {
24
if #[cfg(any(
35
target_os = "linux",
@@ -18,6 +20,9 @@ cfg_if::cfg_if! {
1820
))] {
1921
mod id;
2022
pub use id::Parker;
23+
} else if #[cfg(all(target_os = "windows", target_vendor = "rust9x"))] {
24+
mod generic;
25+
pub use generic::Parker;
2126
} else {
2227
pub use crate::sys::thread_parking::Parker;
2328
}

0 commit comments

Comments
 (0)