abyss/spinlock.rs
1//! SMP-supported spinlock.
2
3use core::{
4 cell::UnsafeCell,
5 ops::{Deref, DerefMut},
6 sync::atomic::{AtomicBool, Ordering},
7};
8
9/// The lock could not be acquired at this time because the operation would
10/// otherwise block.
11pub struct WouldBlock;
12
13/// A mutual exclusion primitive useful for protecting shared data
14///
15/// This spinlock will block threads waiting for the lock to become available.
16/// The spinlock can be created via a [`new`] constructor. Each spinlock has a
17/// type parameter which represents the data that it is protecting. The data can
18/// only be accessed through the guards returned from [`lock`] and
19/// [`try_lock`], which guarantees that the data is only ever accessed when the
20/// spinlock is locked.
21///
22/// [`new`]: Self::new
23/// [`lock`]: Self::lock
24/// [`try_lock`]: Self::try_lock
25/// [`unwrap()`]: Result::unwrap
26///
27/// # Examples
28///
29/// ```
30/// use alloc::sync::Arc;
31/// use keos::sync::SpinLock;
32/// use keos::thread;
33///
34/// const N: usize = 10;
35///
36/// // Spawn a few threads to increment a shared variable (non-atomically), and
37/// // let the main thread know once all increments are done.
38/// //
39/// // Here we're using an Arc to share memory among threads, and the data inside
40/// // the Arc is protected with a spinlock.
41/// let data = Arc::new(SpinLock::new(0));
42///
43/// for _ in 0..N {
44/// let data = Arc::clone(&data);
45/// thread::ThreadBuilder::new("work").spawn(move || {
46/// // The shared state can only be accessed once the lock is held.
47/// // Our non-atomic increment is safe because we're the only thread
48/// // which can access the shared state when the lock is held.
49/// //
50/// // We unwrap() the return value to assert that we are not expecting
51/// // threads to ever fail while holding the lock.
52/// let mut guard = data.lock();
53/// guard += 1;
54/// // the lock must be "explicitly" unlocked before `guard` goes out of scope.
55/// guard.unlock();
56/// });
57/// }
58pub struct SpinLock<T: ?Sized> {
59 locked: AtomicBool,
60 _pad: [u8; 15],
61 data: UnsafeCell<T>,
62}
63
64unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
65unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}
66
67impl<T> SpinLock<T> {
68 /// Creates a new spinlock in an unlocked state ready for use.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// use keos::sync::SpinLock;
74 ///
75 /// let spinlock = SpinLock::new(0);
76 /// ```
77 #[inline]
78 pub const fn new(t: T) -> SpinLock<T> {
79 SpinLock {
80 data: UnsafeCell::new(t),
81 _pad: [0u8; 15],
82 locked: AtomicBool::new(false),
83 }
84 }
85}
86
87impl<T: ?Sized> SpinLock<T> {
88 /// Acquires a spinlock, blocking the current thread until it is able to do
89 /// so.
90 ///
91 /// This function will block the local thread until it is available to
92 /// acquire the spinlock. Upon returning, the thread is the only thread
93 /// with the lock held. An guard is returned to allow scoped access
94 /// of the lock. When the guard goes out of scope without
95 /// [`SpinLockGuard::unlock`], panic occurs.
96 ///
97 /// The exact behavior on locking a spinlock in the thread which already
98 /// holds the lock is left unspecified. However, this function will not
99 /// return on the second call (it might panic or deadlock, for example).
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use alloc::sync::Arc;
105 /// use keos::sync::SpinLock;
106 /// use keos::thread;
107 ///
108 /// let spinlock = Arc::new(SpinLock::new(0));
109 /// let c_spinlock = Arc::clone(&spinlock);
110 ///
111 /// thread::spawn(move || {
112 /// let mut guard = c_spinlock.lock();
113 /// *guard = 10;
114 /// guard.unlock();
115 /// }).join().expect("thread::spawn failed");
116 /// let guard = spinlock.lock();
117 /// assert_eq!(*guard, 10);
118 /// guard.unlock();
119 /// ```
120 #[track_caller]
121 pub fn lock(&self) -> SpinLockGuard<'_, T> {
122 let guard = loop {
123 let guard = crate::interrupt::InterruptGuard::new();
124
125 core::hint::spin_loop();
126 if !self.locked.fetch_or(true, Ordering::SeqCst) {
127 break guard;
128 }
129
130 drop(guard);
131 };
132
133 SpinLockGuard {
134 caller: core::panic::Location::caller(),
135 lock: self,
136 guard: Some(guard),
137 }
138 }
139 /// Attempts to acquire this lock.
140 ///
141 /// If the lock could not be acquired at this time, then [`Err`] is
142 /// returned. Otherwise, an guard is returned. The lock will be
143 /// unlocked when the guard is dropped.
144 ///
145 /// This function does not block.
146 ///
147 /// # Errors
148 ///
149 /// If the spinlock could not be acquired because it is already locked, then
150 /// this call will return the [`WouldBlock`] error.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use keos::sync::SpinLock;
156 /// use alloc::sync::Arc;
157 /// use keos::thread;
158 ///
159 /// let spinlock = Arc::new(SpinLock::new(0));
160 /// let c_spinlock = Arc::clone(&spinlock);
161 ///
162 /// thread::spawn(move || {
163 /// let mut lock = c_spinlock.try_lock();
164 /// if let Ok(ref mut spinlock) = lock {
165 /// **spinlock = 10;
166 /// } else {
167 /// println!("try_lock failed");
168 /// }
169 /// }).join().expect("thread::spawn failed");
170 /// let guard = spinlock.lock();
171 /// assert_eq!(*guard, 10);
172 /// guard.unlock();
173 /// ```
174 #[track_caller]
175 pub fn try_lock(&self) -> Result<SpinLockGuard<'_, T>, WouldBlock> {
176 let guard = crate::interrupt::InterruptGuard::new();
177 let acquired = !self.locked.fetch_or(true, Ordering::SeqCst);
178 if acquired {
179 Ok(SpinLockGuard {
180 guard: Some(guard),
181 caller: core::panic::Location::caller(),
182 lock: self,
183 })
184 } else {
185 Err(WouldBlock)
186 }
187 }
188
189 /// Consumes this spinlock, returning the underlying data.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use keos::sync::SpinLock;
195 ///
196 /// let spinlock = SpinLock::new(0);
197 /// assert_eq!(spinlock.into_inner().unwrap(), 0);
198 /// ```
199 pub fn into_inner(self) -> T
200 where
201 T: Sized,
202 {
203 self.data.into_inner()
204 }
205}
206
207impl<T: Default> Default for SpinLock<T> {
208 /// Creates a `SpinLock<T>`, with the `Default` value for T.
209 fn default() -> SpinLock<T> {
210 SpinLock::new(Default::default())
211 }
212}
213
214impl<T: ?Sized> Deref for SpinLockGuard<'_, T> {
215 type Target = T;
216
217 fn deref(&self) -> &T {
218 unsafe { &*self.lock.data.get() }
219 }
220}
221
222impl<T: ?Sized> DerefMut for SpinLockGuard<'_, T> {
223 fn deref_mut(&mut self) -> &mut T {
224 unsafe { &mut *self.lock.data.get() }
225 }
226}
227
228/// An implementation of a "scoped lock" of a spinlock. When this structure
229/// is dropped (falls out of scope) without unlock, panic occurs.
230///
231/// The lock must be explicitly unlocked by [`unlock`] method.
232///
233/// The data protected by the mutex can be accessed through this guard.
234///
235/// This structure is created by the [`lock`] and [`try_lock`] methods on
236/// [`SpinLock`].
237///
238/// [`lock`]: SpinLock::lock
239/// [`try_lock`]: SpinLock::try_lock
240/// [`unlock`]: Self::unlock
241pub struct SpinLockGuard<'a, T: ?Sized + 'a> {
242 caller: &'static core::panic::Location<'static>,
243 lock: &'a SpinLock<T>,
244 guard: Option<crate::interrupt::InterruptGuard>,
245}
246
247impl<T: ?Sized> !Send for SpinLockGuard<'_, T> {}
248unsafe impl<T: ?Sized + Sync> Sync for SpinLockGuard<'_, T> {}
249
250impl<T: ?Sized> SpinLockGuard<'_, T> {
251 /// Releases the underlying [`SpinLock`].
252 ///
253 /// As the guard does **not** automatically release the lock on drop,
254 /// the caller must explicitly invoke [`unlock`] to mark the lock
255 /// as available again.
256 ///
257 /// # Example
258 /// ```
259 /// let lock = SpinLock::new(123);
260 /// let guard = lock.lock();
261 ///
262 /// // Work with the locked data...
263 ///
264 /// // Explicitly release the lock.
265 /// guard.unlock();
266 /// ```
267 pub fn unlock(mut self) {
268 self.lock.locked.store(false, Ordering::SeqCst);
269 self.guard.take();
270 core::mem::forget(self);
271 }
272}
273
274impl<T: ?Sized> Drop for SpinLockGuard<'_, T> {
275 fn drop(&mut self) {
276 panic!(
277 "`.unlock()` must be explicitly called before dropping SpinLockGuard.
278The lock is held at {:?}.",
279 self.caller
280 );
281 }
282}