|
52 | 52 | // You'll find a few more details in the implementation, but that's the gist of |
53 | 53 | // it! |
54 | 54 |
|
| 55 | +use crate::cell::Cell; |
55 | 56 | use crate::fmt; |
56 | 57 | use crate::marker; |
57 | 58 | use crate::ptr; |
@@ -132,9 +133,14 @@ const COMPLETE: usize = 0x3; |
132 | 133 | // this is in the RUNNING state. |
133 | 134 | const STATE_MASK: usize = 0x3; |
134 | 135 |
|
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. |
136 | 142 | struct Waiter { |
137 | | - thread: Thread, |
| 143 | + thread: Cell<Option<Thread>>, |
138 | 144 | signaled: AtomicBool, |
139 | 145 | next: *const Waiter, |
140 | 146 | } |
@@ -400,7 +406,7 @@ fn wait(state_and_queue: &AtomicUsize, current_state: usize) { |
400 | 406 | // Create the node for our current thread that we are going to try to slot |
401 | 407 | // in at the head of the linked list. |
402 | 408 | let mut node = Waiter { |
403 | | - thread: thread::current(), |
| 409 | + thread: Cell::new(Some(thread::current())), |
404 | 410 | signaled: AtomicBool::new(false), |
405 | 411 | next: ptr::null(), |
406 | 412 | }; |
@@ -453,18 +459,22 @@ impl Drop for WaiterQueue<'_> { |
453 | 459 | // We should only ever see an old state which was RUNNING. |
454 | 460 | assert_eq!(state_and_queue & STATE_MASK, RUNNING); |
455 | 461 |
|
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). |
460 | 464 | 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. |
461 | 469 | let mut queue = (state_and_queue & !STATE_MASK) as *const Waiter; |
462 | 470 | while !queue.is_null() { |
463 | 471 | let next = (*queue).next; |
464 | | - let thread = (*queue).thread.clone(); |
| 472 | + let thread = (*queue).thread.replace(None).unwrap(); |
465 | 473 | (*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`. |
467 | 476 | queue = next; |
| 477 | + thread.unpark(); |
468 | 478 | } |
469 | 479 | } |
470 | 480 | } |
|
0 commit comments