Skip to content

Commit 20dc0c5

Browse files
committed
Auto merge of #54174 - parched:park, r=alexcrichton
Fix `thread` `park`/`unpark` synchronization Previously the code below would not be guaranteed to exit when the second unpark took the `return, // already unparked` path because there was no write to synchronize with a read in `park`. EDIT: doesn't actually require third thread ``` use std::sync::atomic::{AtomicBool, Ordering}; use std::thread::{current, spawn, park}; static FLAG: AtomicBool = AtomicBool::new(false); fn main() { let thread_0 = current(); spawn(move || { thread_0.unpark(); FLAG.store(true, Ordering::Relaxed); thread_0.unpark(); }); while !FLAG.load(Ordering::Relaxed) { park(); } } ``` I have some other ideas on how to improve the performance of `park` and `unpark` using fences, avoiding any atomic RMW when the state is already `NOTIFIED`, and also how to avoid calling `notify_one` without the mutex locked. But I need to write some micro benchmarks first, so I'll submit those changes at a later date if they prove to be faster. Fixes #53366 I hope.
2 parents 4f3ff5a + a3b8705 commit 20dc0c5

File tree

1 file changed

+26
-18
lines changed

1 file changed

+26
-18
lines changed

src/libstd/thread/mod.rs

+26-18
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,14 @@ pub fn park() {
800800
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
801801
Ok(_) => {}
802802
Err(NOTIFIED) => {
803-
thread.inner.state.store(EMPTY, SeqCst);
803+
// We must read here, even though we know it will be `NOTIFIED`.
804+
// This is because `unpark` may have been called again since we read
805+
// `NOTIFIED` in the `compare_exchange` above. We must perform an
806+
// acquire operation that synchronizes with that `unpark` to observe
807+
// any writes it made before the call to unpark. To do that we must
808+
// read from the write it made to `state`.
809+
let old = thread.inner.state.swap(EMPTY, SeqCst);
810+
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
804811
return;
805812
} // should consume this notification, so prohibit spurious wakeups in next park.
806813
Err(_) => panic!("inconsistent park state"),
@@ -889,7 +896,9 @@ pub fn park_timeout(dur: Duration) {
889896
match thread.inner.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
890897
Ok(_) => {}
891898
Err(NOTIFIED) => {
892-
thread.inner.state.store(EMPTY, SeqCst);
899+
// We must read again here, see `park`.
900+
let old = thread.inner.state.swap(EMPTY, SeqCst);
901+
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
893902
return;
894903
} // should consume this notification, so prohibit spurious wakeups in next park.
895904
Err(_) => panic!("inconsistent park_timeout state"),
@@ -1058,23 +1067,22 @@ impl Thread {
10581067
/// [park]: fn.park.html
10591068
#[stable(feature = "rust1", since = "1.0.0")]
10601069
pub fn unpark(&self) {
1061-
loop {
1062-
match self.inner.state.compare_exchange(EMPTY, NOTIFIED, SeqCst, SeqCst) {
1063-
Ok(_) => return, // no one was waiting
1064-
Err(NOTIFIED) => return, // already unparked
1065-
Err(PARKED) => {} // gotta go wake someone up
1066-
_ => panic!("inconsistent state in unpark"),
1067-
}
1068-
1069-
// Coordinate wakeup through the mutex and a condvar notification
1070-
let _lock = self.inner.lock.lock().unwrap();
1071-
match self.inner.state.compare_exchange(PARKED, NOTIFIED, SeqCst, SeqCst) {
1072-
Ok(_) => return self.inner.cvar.notify_one(),
1073-
Err(NOTIFIED) => return, // a different thread unparked
1074-
Err(EMPTY) => {} // parked thread went away, try again
1075-
_ => panic!("inconsistent state in unpark"),
1076-
}
1070+
// To ensure the unparked thread will observe any writes we made
1071+
// before this call, we must perform a release operation that `park`
1072+
// can synchronize with. To do that we must write `NOTIFIED` even if
1073+
// `state` is already `NOTIFIED`. That is why this must be a swap
1074+
// rather than a compare-and-swap that returns if it reads `NOTIFIED`
1075+
// on failure.
1076+
match self.inner.state.swap(NOTIFIED, SeqCst) {
1077+
EMPTY => return, // no one was waiting
1078+
NOTIFIED => return, // already unparked
1079+
PARKED => {} // gotta go wake someone up
1080+
_ => panic!("inconsistent state in unpark"),
10771081
}
1082+
1083+
// Coordinate wakeup through the mutex and a condvar notification
1084+
let _lock = self.inner.lock.lock().unwrap();
1085+
self.inner.cvar.notify_one()
10781086
}
10791087

10801088
/// Gets the thread's unique identifier.

0 commit comments

Comments
 (0)