axsync/mutex.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
//! A naïve sleeping mutex.
use core::sync::atomic::{AtomicU64, Ordering};
use axtask::{WaitQueue, current};
/// A [`lock_api::RawMutex`] implementation.
///
/// When the mutex is locked, the current task will block and be put into the
/// wait queue. When the mutex is unlocked, all tasks waiting on the queue
/// will be woken up.
pub struct RawMutex {
wq: WaitQueue,
owner_id: AtomicU64,
}
impl RawMutex {
/// Creates a [`RawMutex`].
#[inline(always)]
pub const fn new() -> Self {
Self {
wq: WaitQueue::new(),
owner_id: AtomicU64::new(0),
}
}
}
unsafe impl lock_api::RawMutex for RawMutex {
const INIT: Self = RawMutex::new();
type GuardMarker = lock_api::GuardSend;
fn lock(&self) {
let current_id = current().id().as_u64();
loop {
// Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock`
// when called in a loop.
match self.owner_id.compare_exchange_weak(
0,
current_id,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(owner_id) => {
assert_ne!(
owner_id,
current_id,
"{} tried to acquire mutex it already owns.",
current().id_name()
);
// Wait until the lock looks unlocked before retrying
self.wq.wait_until(|| !self.is_locked());
}
}
}
}
fn try_lock(&self) -> bool {
let current_id = current().id().as_u64();
// The reason for using a strong compare_exchange is explained here:
// https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107
self.owner_id
.compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
unsafe fn unlock(&self) {
let owner_id = self.owner_id.swap(0, Ordering::Release);
assert_eq!(
owner_id,
current().id().as_u64(),
"{} tried to release mutex it doesn't own",
current().id_name()
);
self.wq.notify_one(true);
}
fn is_locked(&self) -> bool {
self.owner_id.load(Ordering::Relaxed) != 0
}
}
/// An alias of [`lock_api::Mutex`].
pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
/// An alias of [`lock_api::MutexGuard`].
pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
#[cfg(test)]
mod tests {
use crate::Mutex;
use axtask as thread;
use std::sync::Once;
static INIT: Once = Once::new();
fn may_interrupt() {
// simulate interrupts
if rand::random::<u32>() % 3 == 0 {
thread::yield_now();
}
}
#[test]
fn lots_and_lots() {
INIT.call_once(thread::init_scheduler);
const NUM_TASKS: u32 = 10;
const NUM_ITERS: u32 = 10_000;
static M: Mutex<u32> = Mutex::new(0);
fn inc(delta: u32) {
for _ in 0..NUM_ITERS {
let mut val = M.lock();
*val += delta;
may_interrupt();
drop(val);
may_interrupt();
}
}
for _ in 0..NUM_TASKS {
thread::spawn(|| inc(1));
thread::spawn(|| inc(2));
}
println!("spawn OK");
loop {
let val = M.lock();
if *val == NUM_ITERS * NUM_TASKS * 3 {
break;
}
may_interrupt();
drop(val);
may_interrupt();
}
assert_eq!(*M.lock(), NUM_ITERS * NUM_TASKS * 3);
println!("Mutex test OK");
}
}