diff options
| author | Paul Dicker <pitdicker@gmail.com> | 2019-10-23 10:10:36 +0200 |
|---|---|---|
| committer | Paul Dicker <pitdicker@gmail.com> | 2019-10-24 17:57:05 +0200 |
| commit | 88c70edef66b6885dec6aa8f7a4e73eff2b745ef (patch) | |
| tree | df38c657b77707f35eb9240737c9e6ef3176edef /src/libstd/sync | |
| parent | 4b8da9ccd528d46637c88a40f6cdd0d634c0fb22 (diff) | |
| download | rust-88c70edef66b6885dec6aa8f7a4e73eff2b745ef.tar.gz rust-88c70edef66b6885dec6aa8f7a4e73eff2b745ef.zip | |
In Waiter use interior mutability for thread
Diffstat (limited to 'src/libstd/sync')
| -rw-r--r-- | src/libstd/sync/once.rs | 28 |
1 files changed, 19 insertions, 9 deletions
diff --git a/src/libstd/sync/once.rs b/src/libstd/sync/once.rs index 59cc6188045..ef8a95eed27 100644 --- a/src/libstd/sync/once.rs +++ b/src/libstd/sync/once.rs @@ -52,6 +52,7 @@ // You'll find a few more details in the implementation, but that's the gist of // it! +use crate::cell::Cell; use crate::fmt; use crate::marker; use crate::ptr; @@ -132,9 +133,14 @@ const COMPLETE: usize = 0x3; // this is in the RUNNING state. const STATE_MASK: usize = 0x3; -// Representation of a node in the linked list of waiters in the RUNNING state. +// Representation of a node in the linked list of waiters, used while in the +// RUNNING state. +// Note: `Waiter` can't hold a mutable pointer to the next thread, because then +// `wait` would both hand out a mutable reference to its `Waiter` node, and keep +// a shared reference to check `signaled`. Instead we hold shared references and +// use interior mutability. struct Waiter { - thread: Thread, + thread: Cell<Option<Thread>>, signaled: AtomicBool, next: *const Waiter, } @@ -400,7 +406,7 @@ fn wait(state_and_queue: &AtomicUsize, current_state: usize) { // Create the node for our current thread that we are going to try to slot // in at the head of the linked list. let mut node = Waiter { - thread: thread::current(), + thread: Cell::new(Some(thread::current())), signaled: AtomicBool::new(false), next: ptr::null(), }; @@ -453,18 +459,22 @@ impl Drop for WaiterQueue<'_> { // We should only ever see an old state which was RUNNING. assert_eq!(state_and_queue & STATE_MASK, RUNNING); - // Decode the RUNNING to a list of waiters, then walk that entire list - // and wake them up. Note that it is crucial that after we store `true` - // in the node it can be free'd! As a result we load the `thread` to - // signal ahead of time and then unpark it after the store. + // Walk the entire linked list of waiters and wake them up (in lifo + // order, last to register is first to wake up). unsafe { + // Right after setting `node.signaled = true` the other thread may + // free `node` if there happens to be has a spurious wakeup. + // So we have to take out the `thread` field and copy the pointer to + // `next` first. let mut queue = (state_and_queue & !STATE_MASK) as *const Waiter; while !queue.is_null() { let next = (*queue).next; - let thread = (*queue).thread.clone(); + let thread = (*queue).thread.replace(None).unwrap(); (*queue).signaled.store(true, Ordering::SeqCst); - thread.unpark(); + // ^- FIXME (maybe): This is another case of issue #55005 + // `store()` has a potentially dangling ref to `signaled`. queue = next; + thread.unpark(); } } } |
