Fix Waiter-related memory orders

This commit is contained in:
Ruihan Li
2024-05-31 10:39:33 +08:00
committed by Tate, Hongliang Tian
parent 15603e4aad
commit 414a3a389e

View File

@ -6,6 +6,34 @@ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use super::SpinLock; use super::SpinLock;
use crate::task::{add_task, current_task, schedule, Task, TaskStatus}; use crate::task::{add_task, current_task, schedule, Task, TaskStatus};
// # Explanation on the memory orders
//
// ```
// [CPU 1 (the waker)] [CPU 2 (the waiter)]
// cond = true;
// wake_up();
// wait();
// if cond { /* .. */ }
// ```
//
// As soon as the waiter is woken up by the waker, it must see the true condition. This is
// trivially satisfied if `wake_up()` and `wait()` synchronize with a lock. But if they synchronize
// with an atomic variable, `wake_up()` must access the variable with `Ordering::Release` and
// `wait()` must access the variable with `Ordering::Acquire`.
//
// Examples of `wake_up()`:
// - `WaitQueue::wake_one()`
// - `WaitQueue::wake_all()`
// - `Waker::wake_up()`
//
// Examples of `wait()`:
// - `WaitQueue::wait_until()`
// - `Waiter::wait()`
// - `Waiter::drop()`
//
// Note that dropping a waiter must be treated as a `wait()` with zero timeout, because we need to
// make sure that the wake event isn't lost in this case.
/// A wait queue. /// A wait queue.
/// ///
/// One may wait on a wait queue to put its executing thread to sleep. /// One may wait on a wait queue to put its executing thread to sleep.
@ -136,13 +164,16 @@ impl WaitQueue {
} }
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.num_wakers.load(Ordering::Acquire) == 0 // On x86-64, this generates `mfence; mov`, which is exactly the right way to implement
// atomic loading with `Ordering::Release`. It performs much better than naively
// translating `fetch_add(0)` to `lock; xadd`.
self.num_wakers.fetch_add(0, Ordering::Release) == 0
} }
fn enqueue(&self, waker: Arc<Waker>) { fn enqueue(&self, waker: Arc<Waker>) {
let mut wakers = self.wakers.lock_irq_disabled(); let mut wakers = self.wakers.lock_irq_disabled();
wakers.push_back(waker); wakers.push_back(waker);
self.num_wakers.fetch_add(1, Ordering::Release); self.num_wakers.fetch_add(1, Ordering::Acquire);
} }
} }
@ -221,7 +252,7 @@ impl Waker {
/// delivered, _or_ that the waiter will be dropped after being woken. It's up to the caller to /// delivered, _or_ that the waiter will be dropped after being woken. It's up to the caller to
/// handle the latter case properly to avoid missing the wake event. /// handle the latter case properly to avoid missing the wake event.
pub fn wake_up(&self) -> bool { pub fn wake_up(&self) -> bool {
if self.has_woken.swap(true, Ordering::AcqRel) { if self.has_woken.swap(true, Ordering::Release) {
return false; return false;
} }
@ -244,10 +275,10 @@ impl Waker {
} }
fn do_wait(&self) { fn do_wait(&self) {
while !self.has_woken.load(Ordering::Acquire) { while !self.has_woken.swap(false, Ordering::Acquire) {
let mut task = self.task.inner_exclusive_access(); let mut task = self.task.inner_exclusive_access();
// After holding the lock, check again to avoid races // After holding the lock, check again to avoid races
if self.has_woken.load(Ordering::Acquire) { if self.has_woken.swap(false, Ordering::Acquire) {
break; break;
} }
task.task_status = TaskStatus::Sleepy; task.task_status = TaskStatus::Sleepy;
@ -255,12 +286,12 @@ impl Waker {
schedule(); schedule();
} }
self.has_woken.store(false, Ordering::Release);
} }
fn close(&self) { fn close(&self) {
self.has_woken.store(true, Ordering::Release); // This must use `Ordering::Acquire`, although we do not care about the return value. See
// the memory order explanation at the top of the file for details.
let _ = self.has_woken.swap(true, Ordering::Acquire);
} }
} }