|
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