Skip to content

Commit 49cd298

Browse files
authored
Unrolled build for #145895
Rollup merge of #145895 - RalfJung:unpark, r=joboet thread parking: fix docs and examples Fixes #145816 r? ```@joboet``` Cc ```@m-ou-se``` ```@Amanieu```
2 parents ac4495a + 39d51d0 commit 49cd298

File tree

4 files changed

+60
-16
lines changed

4 files changed

+60
-16
lines changed

library/std/src/sys/sync/once/queue.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ fn wait(
276276
// If the managing thread happens to signal and unpark us before we
277277
// can park ourselves, the result could be this thread never gets
278278
// unparked. Luckily `park` comes with the guarantee that if it got
279-
// an `unpark` just before on an unparked thread it does not park.
279+
// an `unpark` just before on an unparked thread it does not park. Crucially, we know
280+
// the `unpark` must have happened between the `compare_exchange_weak` above and here,
281+
// and there's no other `park` in that code that could steal our token.
280282
// SAFETY: we retrieved this handle on the current thread above.
281283
unsafe { node.thread.park() }
282284
}

library/std/src/thread/mod.rs

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,13 +1021,23 @@ impl Drop for PanicGuard {
10211021
/// specifying a maximum time to block the thread for.
10221022
///
10231023
/// * The [`unpark`] method on a [`Thread`] atomically makes the token available
1024-
/// if it wasn't already. Because the token is initially absent, [`unpark`]
1025-
/// followed by [`park`] will result in the second call returning immediately.
1026-
///
1027-
/// The API is typically used by acquiring a handle to the current thread,
1028-
/// placing that handle in a shared data structure so that other threads can
1029-
/// find it, and then `park`ing in a loop. When some desired condition is met, another
1030-
/// thread calls [`unpark`] on the handle.
1024+
/// if it wasn't already. Because the token can be held by a thread even if it is currently not
1025+
/// parked, [`unpark`] followed by [`park`] will result in the second call returning immediately.
1026+
/// However, note that to rely on this guarantee, you need to make sure that your `unpark` happens
1027+
/// after all `park` that may be done by other data structures!
1028+
///
1029+
/// The API is typically used by acquiring a handle to the current thread, placing that handle in a
1030+
/// shared data structure so that other threads can find it, and then `park`ing in a loop. When some
1031+
/// desired condition is met, another thread calls [`unpark`] on the handle. The last bullet point
1032+
/// above guarantees that even if the `unpark` occurs before the thread is finished `park`ing, it
1033+
/// will be woken up properly.
1034+
///
1035+
/// Note that the coordination via the shared data structure is crucial: If you `unpark` a thread
1036+
/// without first establishing that it is about to be `park`ing within your code, that `unpark` may
1037+
/// get consumed by a *different* `park` in the same thread, leading to a deadlock. This also means
1038+
/// you must not call unknown code between setting up for parking and calling `park`; for instance,
1039+
/// if you invoke `println!`, that may itself call `park` and thus consume your `unpark` and cause a
1040+
/// deadlock.
10311041
///
10321042
/// The motivation for this design is twofold:
10331043
///
@@ -1058,33 +1068,47 @@ impl Drop for PanicGuard {
10581068
///
10591069
/// ```
10601070
/// use std::thread;
1061-
/// use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
1071+
/// use std::sync::atomic::{Ordering, AtomicBool};
10621072
/// use std::time::Duration;
10631073
///
1064-
/// let flag = Arc::new(AtomicBool::new(false));
1065-
/// let flag2 = Arc::clone(&flag);
1074+
/// static QUEUED: AtomicBool = AtomicBool::new(false);
1075+
/// static FLAG: AtomicBool = AtomicBool::new(false);
10661076
///
10671077
/// let parked_thread = thread::spawn(move || {
1078+
/// println!("Thread spawned");
1079+
/// // Signal that we are going to `park`. Between this store and our `park`, there may
1080+
/// // be no other `park`, or else that `park` could consume our `unpark` token!
1081+
/// QUEUED.store(true, Ordering::Release);
10681082
/// // We want to wait until the flag is set. We *could* just spin, but using
10691083
/// // park/unpark is more efficient.
1070-
/// while !flag2.load(Ordering::Relaxed) {
1071-
/// println!("Parking thread");
1084+
/// while !FLAG.load(Ordering::Acquire) {
1085+
/// // We can *not* use `println!` here since that could use thread parking internally.
10721086
/// thread::park();
10731087
/// // We *could* get here spuriously, i.e., way before the 10ms below are over!
10741088
/// // But that is no problem, we are in a loop until the flag is set anyway.
1075-
/// println!("Thread unparked");
10761089
/// }
10771090
/// println!("Flag received");
10781091
/// });
10791092
///
10801093
/// // Let some time pass for the thread to be spawned.
10811094
/// thread::sleep(Duration::from_millis(10));
10821095
///
1096+
/// // Ensure the thread is about to park.
1097+
/// // This is crucial! It guarantees that the `unpark` below is not consumed
1098+
/// // by some other code in the parked thread (e.g. inside `println!`).
1099+
/// while !QUEUED.load(Ordering::Acquire) {
1100+
/// // Spinning is of course inefficient; in practice, this would more likely be
1101+
/// // a dequeue where we have no work to do if there's nobody queued.
1102+
/// std::hint::spin_loop();
1103+
/// }
1104+
///
10831105
/// // Set the flag, and let the thread wake up.
1084-
/// // There is no race condition here, if `unpark`
1106+
/// // There is no race condition here: if `unpark`
10851107
/// // happens first, `park` will return immediately.
1108+
/// // There is also no other `park` that could consume this token,
1109+
/// // since we waited until the other thread got queued.
10861110
/// // Hence there is no risk of a deadlock.
1087-
/// flag.store(true, Ordering::Relaxed);
1111+
/// FLAG.store(true, Ordering::Release);
10881112
/// println!("Unpark the thread");
10891113
/// parked_thread.thread().unpark();
10901114
///
@@ -1494,10 +1518,14 @@ impl Thread {
14941518
/// ```
14951519
/// use std::thread;
14961520
/// use std::time::Duration;
1521+
/// use std::sync::atomic::{AtomicBool, Ordering};
1522+
///
1523+
/// static QUEUED: AtomicBool = AtomicBool::new(false);
14971524
///
14981525
/// let parked_thread = thread::Builder::new()
14991526
/// .spawn(|| {
15001527
/// println!("Parking thread");
1528+
/// QUEUED.store(true, Ordering::Release);
15011529
/// thread::park();
15021530
/// println!("Thread unparked");
15031531
/// })
@@ -1506,6 +1534,15 @@ impl Thread {
15061534
/// // Let some time pass for the thread to be spawned.
15071535
/// thread::sleep(Duration::from_millis(10));
15081536
///
1537+
/// // Wait until the other thread is queued.
1538+
/// // This is crucial! It guarantees that the `unpark` below is not consumed
1539+
/// // by some other code in the parked thread (e.g. inside `println!`).
1540+
/// while !QUEUED.load(Ordering::Acquire) {
1541+
/// // Spinning is of course inefficient; in practice, this would more likely be
1542+
/// // a dequeue where we have no work to do if there's nobody queued.
1543+
/// std::hint::spin_loop();
1544+
/// }
1545+
///
15091546
/// println!("Unpark the thread");
15101547
/// parked_thread.thread().unpark();
15111548
///

library/std/src/thread/tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ fn test_park_unpark_called_other_thread() {
287287
for _ in 0..10 {
288288
let th = thread::current();
289289

290+
// Here we rely on `thread::spawn` (specifically the part that runs after spawning
291+
// the thread) to not consume the parking token.
290292
let _guard = thread::spawn(move || {
291293
super::sleep(Duration::from_millis(50));
292294
th.unpark();
@@ -316,6 +318,8 @@ fn test_park_timeout_unpark_called_other_thread() {
316318
for _ in 0..10 {
317319
let th = thread::current();
318320

321+
// Here we rely on `thread::spawn` (specifically the part that runs after spawning
322+
// the thread) to not consume the parking token.
319323
let _guard = thread::spawn(move || {
320324
super::sleep(Duration::from_millis(50));
321325
th.unpark();

src/tools/miri/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ Definite bugs found:
606606
* [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo)
607607
* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
608608
* [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248)
609+
* [Rare Deadlock in the thread (un)parking example code](https://github.com/rust-lang/rust/issues/145816)
609610

610611
Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
611612

0 commit comments

Comments
 (0)