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}