Skip to content

Commit 88c70ed

Browse files
committed
In Waiter use interior mutability for thread
1 parent 4b8da9c commit 88c70ed

File tree

1 file changed

+19
-9
lines changed

1 file changed

+19
-9
lines changed

src/libstd/sync/once.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
// You'll find a few more details in the implementation, but that's the gist of
5353
// it!
5454

55+
use crate::cell::Cell;
5556
use crate::fmt;
5657
use crate::marker;
5758
use crate::ptr;
@@ -132,9 +133,14 @@ const COMPLETE: usize = 0x3;
132133
// this is in the RUNNING state.
133134
const STATE_MASK: usize = 0x3;
134135

135-
// Representation of a node in the linked list of waiters in the RUNNING state.
136+
// Representation of a node in the linked list of waiters, used while in the
137+
// RUNNING state.
138+
// Note: `Waiter` can't hold a mutable pointer to the next thread, because then
139+
// `wait` would both hand out a mutable reference to its `Waiter` node, and keep
140+
// a shared reference to check `signaled`. Instead we hold shared references and
141+
// use interior mutability.
136142
struct Waiter {
137-
thread: Thread,
143+
thread: Cell<Option<Thread>>,
138144
signaled: AtomicBool,
139145
next: *const Waiter,
140146
}
@@ -400,7 +406,7 @@ fn wait(state_and_queue: &AtomicUsize, current_state: usize) {
400406
// Create the node for our current thread that we are going to try to slot
401407
// in at the head of the linked list.
402408
let mut node = Waiter {
403-
thread: thread::current(),
409+
thread: Cell::new(Some(thread::current())),
404410
signaled: AtomicBool::new(false),
405411
next: ptr::null(),
406412
};
@@ -453,18 +459,22 @@ impl Drop for WaiterQueue<'_> {
453459
// We should only ever see an old state which was RUNNING.
454460
assert_eq!(state_and_queue & STATE_MASK, RUNNING);
455461

456-
// Decode the RUNNING to a list of waiters, then walk that entire list
457-
// and wake them up. Note that it is crucial that after we store `true`
458-
// in the node it can be free'd! As a result we load the `thread` to
459-
// signal ahead of time and then unpark it after the store.
462+
// Walk the entire linked list of waiters and wake them up (in lifo
463+
// order, last to register is first to wake up).
460464
unsafe {
465+
// Right after setting `node.signaled = true` the other thread may
466+
// free `node` if there happens to be has a spurious wakeup.
467+
// So we have to take out the `thread` field and copy the pointer to
468+
// `next` first.
461469
let mut queue = (state_and_queue & !STATE_MASK) as *const Waiter;
462470
while !queue.is_null() {
463471
let next = (*queue).next;
464-
let thread = (*queue).thread.clone();
472+
let thread = (*queue).thread.replace(None).unwrap();
465473
(*queue).signaled.store(true, Ordering::SeqCst);
466-
thread.unpark();
474+
// ^- FIXME (maybe): This is another case of issue #55005
475+
// `store()` has a potentially dangling ref to `signaled`.
467476
queue = next;
477+
thread.unpark();
468478
}
469479
}
470480
}

0 commit comments

Comments
 (0)