axstd/sync/mutex.rs
1//! A naïve sleeping mutex.
2
3use core::cell::UnsafeCell;
4use core::fmt;
5use core::ops::{Deref, DerefMut};
6use core::sync::atomic::{AtomicU64, Ordering};
7
8use arceos_api::task::{self as api, AxWaitQueueHandle};
9
10/// A mutual exclusion primitive useful for protecting shared data, similar to
11/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html).
12///
13/// When the mutex is locked, the current task will block and be put into the
14/// wait queue. When the mutex is unlocked, all tasks waiting on the queue
15/// will be woken up.
16pub struct Mutex<T: ?Sized> {
17 wq: AxWaitQueueHandle,
18 owner_id: AtomicU64,
19 data: UnsafeCell<T>,
20}
21
22/// A guard that provides mutable data access.
23///
24/// When the guard falls out of scope it will release the lock.
25pub struct MutexGuard<'a, T: ?Sized + 'a> {
26 lock: &'a Mutex<T>,
27 data: *mut T,
28}
29
30// Same unsafe impls as `std::sync::Mutex`
31unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
32unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
33
34impl<T> Mutex<T> {
35 /// Creates a new [`Mutex`] wrapping the supplied data.
36 #[inline(always)]
37 pub const fn new(data: T) -> Self {
38 Self {
39 wq: AxWaitQueueHandle::new(),
40 owner_id: AtomicU64::new(0),
41 data: UnsafeCell::new(data),
42 }
43 }
44
45 /// Consumes this [`Mutex`] and unwraps the underlying data.
46 #[inline(always)]
47 pub fn into_inner(self) -> T {
48 // We know statically that there are no outstanding references to
49 // `self` so there's no need to lock.
50 let Mutex { data, .. } = self;
51 data.into_inner()
52 }
53}
54
55impl<T: ?Sized> Mutex<T> {
56 /// Returns `true` if the lock is currently held.
57 ///
58 /// # Safety
59 ///
60 /// This function provides no synchronization guarantees and so its result should be considered 'out of date'
61 /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic.
62 #[inline(always)]
63 pub fn is_locked(&self) -> bool {
64 self.owner_id.load(Ordering::Relaxed) != 0
65 }
66
67 /// Locks the [`Mutex`] and returns a guard that permits access to the inner data.
68 ///
69 /// The returned value may be dereferenced for data access
70 /// and the lock will be dropped when the guard falls out of scope.
71 pub fn lock(&self) -> MutexGuard<T> {
72 let current_id = api::ax_current_task_id();
73 loop {
74 // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock`
75 // when called in a loop.
76 match self.owner_id.compare_exchange_weak(
77 0,
78 current_id,
79 Ordering::Acquire,
80 Ordering::Relaxed,
81 ) {
82 Ok(_) => break,
83 Err(owner_id) => {
84 assert_ne!(
85 owner_id, current_id,
86 "Thread({current_id}) tried to acquire mutex it already owns.",
87 );
88 // Wait until the lock looks unlocked before retrying
89 api::ax_wait_queue_wait_until(&self.wq, || !self.is_locked(), None);
90 }
91 }
92 }
93 MutexGuard {
94 lock: self,
95 data: unsafe { &mut *self.data.get() },
96 }
97 }
98
99 /// Try to lock this [`Mutex`], returning a lock guard if successful.
100 #[inline(always)]
101 pub fn try_lock(&self) -> Option<MutexGuard<T>> {
102 let current_id = api::ax_current_task_id();
103 // The reason for using a strong compare_exchange is explained here:
104 // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107
105 if self
106 .owner_id
107 .compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed)
108 .is_ok()
109 {
110 Some(MutexGuard {
111 lock: self,
112 data: unsafe { &mut *self.data.get() },
113 })
114 } else {
115 None
116 }
117 }
118
119 /// Force unlock the [`Mutex`].
120 ///
121 /// # Safety
122 ///
123 /// This is *extremely* unsafe if the lock is not held by the current
124 /// thread. However, this can be useful in some instances for exposing
125 /// the lock to FFI that doesn’t know how to deal with RAII.
126 pub unsafe fn force_unlock(&self) {
127 let owner_id = self.owner_id.swap(0, Ordering::Release);
128 let current_id = api::ax_current_task_id();
129 assert_eq!(
130 owner_id, current_id,
131 "Thread({current_id}) tried to release mutex it doesn't own",
132 );
133 // wake up one waiting thread.
134 api::ax_wait_queue_wake(&self.wq, 1);
135 }
136
137 /// Returns a mutable reference to the underlying data.
138 ///
139 /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in
140 /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As
141 /// such, this is a 'zero-cost' operation.
142 #[inline(always)]
143 pub fn get_mut(&mut self) -> &mut T {
144 // We know statically that there are no other references to `self`, so
145 // there's no need to lock the inner mutex.
146 unsafe { &mut *self.data.get() }
147 }
148}
149
150impl<T: Default> Default for Mutex<T> {
151 #[inline(always)]
152 fn default() -> Self {
153 Self::new(Default::default())
154 }
155}
156
157impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 match self.try_lock() {
160 Some(guard) => write!(f, "Mutex {{ data: ")
161 .and_then(|()| (*guard).fmt(f))
162 .and_then(|()| write!(f, "}}")),
163 None => write!(f, "Mutex {{ <locked> }}"),
164 }
165 }
166}
167
168impl<T: ?Sized> Deref for MutexGuard<'_, T> {
169 type Target = T;
170 #[inline(always)]
171 fn deref(&self) -> &T {
172 // We know statically that only we are referencing data
173 unsafe { &*self.data }
174 }
175}
176
177impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
178 #[inline(always)]
179 fn deref_mut(&mut self) -> &mut T {
180 // We know statically that only we are referencing data
181 unsafe { &mut *self.data }
182 }
183}
184
185impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
186 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187 fmt::Debug::fmt(&**self, f)
188 }
189}
190
191impl<T: ?Sized> Drop for MutexGuard<'_, T> {
192 /// The dropping of the [`MutexGuard`] will release the lock it was created from.
193 fn drop(&mut self) {
194 unsafe { self.lock.force_unlock() }
195 }
196}