Skip to main content

osiris/sync/
spinlock.rs

1//! Synchronization primitives.
2#![allow(dead_code)]
3
4use crate::hal;
5
6use core::cell::UnsafeCell;
7use core::ptr::NonNull;
8use core::sync::atomic::AtomicBool;
9use core::sync::atomic::AtomicIsize;
10use core::sync::atomic::Ordering;
11
12/// A busy-waiting reader-writer lock.
13pub struct RwSpinLock {
14    lock: AtomicIsize,
15}
16
17impl RwSpinLock {
18    /// Creates a new unlocked RwSpinLock.
19    pub const fn new() -> Self {
20        RwSpinLock {
21            lock: AtomicIsize::new(0),
22        }
23    }
24
25    /// Waits until a read lock can be acquired.
26    pub fn read_lock(&self) {
27        loop {
28            let count = self.lock.load(Ordering::Acquire);
29            if count >= 0 && count < isize::MAX {
30                if self
31                    .lock
32                    .compare_exchange_weak(count, count + 1, Ordering::SeqCst, Ordering::Relaxed)
33                    .is_ok()
34                {
35                    break;
36                }
37            }
38        }
39    }
40
41    /// Releases one read lock.
42    ///
43    /// # Safety
44    ///
45    /// The caller must hold a read lock acquired from this RwSpinLock.
46    pub unsafe fn read_unlock(&self) {
47        self.lock.fetch_sub(1, Ordering::Release);
48    }
49
50    /// Waits until a write lock can be acquired.
51    pub fn write_lock(&self) {
52        while self
53            .lock
54            .compare_exchange_weak(0, -1, Ordering::SeqCst, Ordering::Relaxed)
55            .is_err()
56        {}
57    }
58
59    /// Releases the write lock.
60    ///
61    /// # Safety
62    ///
63    /// The caller must hold the write lock acquired from this RwSpinLock.
64    pub unsafe fn write_unlock(&self) {
65        self.lock.store(0, Ordering::Release);
66    }
67}
68
69/// A mutual exclusion primitive, facilitating busy-waiting.
70#[proc_macros::fmt]
71pub struct SpinLock {
72    lock: AtomicBool,
73}
74
75impl SpinLock {
76    /// Creates a new SpinLock.
77    pub const fn new() -> Self {
78        SpinLock {
79            lock: AtomicBool::new(false),
80        }
81    }
82
83    /// Waits until the SpinLock can be acquired and lock it.
84    pub fn lock(&self) {
85        let lock = &self.lock;
86
87        if lock.load(Ordering::Relaxed) {
88            hal::asm::nop!();
89        }
90
91        loop {
92            if lock
93                .compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
94                .is_ok()
95            {
96                break;
97            }
98        }
99    }
100
101    /// Tries to lock the SpinLock.
102    /// Returns `true` if the lock was acquired.
103    pub fn try_lock(&self) -> bool {
104        !self.lock.swap(true, Ordering::Acquire)
105    }
106
107    /// Unlocks the SpinLock.
108    /// Returns `true` if the lock was released.
109    ///
110    /// # Safety
111    /// Precondition: The SpinLock must be locked by the current thread.
112    /// Postcondition: The SpinLock is unlocked.
113    pub unsafe fn unlock(&self) {
114        self.lock.store(false, Ordering::Release)
115    }
116}
117
118/// A guard that releases a read lock when dropped.
119pub struct RwSpinLockReadGuard<'a, T: ?Sized> {
120    lock: &'a RwSpinLock,
121    value: NonNull<T>,
122    marker: core::marker::PhantomData<&'a T>,
123}
124
125impl<T: ?Sized> core::ops::Deref for RwSpinLockReadGuard<'_, T> {
126    type Target = T;
127
128    #[inline]
129    fn deref(&self) -> &T {
130        unsafe { self.value.as_ref() }
131    }
132}
133
134impl<T: ?Sized> Drop for RwSpinLockReadGuard<'_, T> {
135    fn drop(&mut self) {
136        unsafe {
137            self.lock.read_unlock();
138        }
139    }
140}
141
142/// A guard that releases a write lock when dropped.
143pub struct RwSpinLockWriteGuard<'a, T: ?Sized> {
144    lock: &'a RwSpinLock,
145    value: NonNull<T>,
146    marker: core::marker::PhantomData<&'a mut T>,
147}
148
149impl<T: ?Sized> core::ops::Deref for RwSpinLockWriteGuard<'_, T> {
150    type Target = T;
151
152    #[inline]
153    fn deref(&self) -> &T {
154        unsafe { self.value.as_ref() }
155    }
156}
157
158impl<T: ?Sized> core::ops::DerefMut for RwSpinLockWriteGuard<'_, T> {
159    fn deref_mut(&mut self) -> &mut T {
160        unsafe { self.value.as_mut() }
161    }
162}
163
164impl<T: ?Sized> Drop for RwSpinLockWriteGuard<'_, T> {
165    fn drop(&mut self) {
166        unsafe {
167            self.lock.write_unlock();
168        }
169    }
170}
171
172/// A guard that releases the SpinLock when dropped.
173#[proc_macros::fmt]
174pub struct SpinLockGuard<'a, T: ?Sized> {
175    lock: &'a SpinLock,
176    value: NonNull<T>,
177    marker: core::marker::PhantomData<&'a mut T>,
178}
179
180impl<T: ?Sized> core::ops::Deref for SpinLockGuard<'_, T> {
181    type Target = T;
182
183    #[inline]
184    fn deref(&self) -> &T {
185        unsafe { self.value.as_ref() }
186    }
187}
188
189impl<T: ?Sized> core::ops::DerefMut for SpinLockGuard<'_, T> {
190    fn deref_mut(&mut self) -> &mut T {
191        unsafe { self.value.as_mut() }
192    }
193}
194
195impl<T: ?Sized> Drop for SpinLockGuard<'_, T> {
196    fn drop(&mut self) {
197        unsafe {
198            self.lock.unlock();
199        }
200    }
201}
202
203/// Protects a value with a busy-waiting reader-writer lock.
204///
205/// Multiple readers may access the value concurrently, while a writer has
206/// exclusive access. This lock is not reentrant and does not provide writer
207/// priority.
208pub struct RwSpinLocked<T> {
209    lock: RwSpinLock,
210    value: UnsafeCell<T>,
211}
212
213// Safety: access to `value` is synchronized by `lock`. `T` must be `Sync`
214// because read guards expose `&T`, and `Send` because write guards expose
215// exclusive access from a shared lock reference.
216unsafe impl<T: Send> Send for RwSpinLocked<T> {}
217unsafe impl<T: Send + Sync> Sync for RwSpinLocked<T> {}
218
219impl<T> RwSpinLocked<T> {
220    /// Creates a new RwSpinLocked.
221    pub const fn new(value: T) -> Self {
222        RwSpinLocked {
223            lock: RwSpinLock::new(),
224            value: UnsafeCell::new(value),
225        }
226    }
227
228    /// Locks the value for shared read access.
229    pub fn read_lock(&self) -> RwSpinLockReadGuard<'_, T> {
230        self.lock.read_lock();
231        RwSpinLockReadGuard {
232            lock: &self.lock,
233            value: unsafe { NonNull::new_unchecked(self.value.get()) },
234            marker: core::marker::PhantomData,
235        }
236    }
237
238    /// Locks the value for exclusive write access.
239    pub fn write_lock(&self) -> RwSpinLockWriteGuard<'_, T> {
240        self.lock.write_lock();
241        RwSpinLockWriteGuard {
242            lock: &self.lock,
243            value: unsafe { NonNull::new_unchecked(self.value.get()) },
244            marker: core::marker::PhantomData,
245        }
246    }
247}
248
249/// A mutual exclusion primitive that allows at most one thread to access a resource at a time.
250pub struct SpinLocked<T> {
251    lock: SpinLock,
252    value: UnsafeCell<T>,
253}
254
255// Safety: access to `value` is synchronized by `lock`. `T` must be `Sync`
256// because guards expose `&T`, and `Send` because guards expose exclusive access from a shared lock reference.
257unsafe impl<T: Send> Send for SpinLocked<T> {}
258unsafe impl<T: Sync> Sync for SpinLocked<T> {}
259
260/// Test
261impl<T> SpinLocked<T> {
262    /// Creates a new SpinLocked.
263    pub const fn new(value: T) -> Self {
264        SpinLocked {
265            lock: SpinLock::new(),
266            value: UnsafeCell::new(value),
267        }
268    }
269
270    /// Locks the SpinLocked and returns a guard that releases the lock when dropped.
271    pub fn lock(&self) -> SpinLockGuard<'_, T> {
272        self.lock.lock();
273        SpinLockGuard {
274            lock: &self.lock,
275            value: unsafe { NonNull::new_unchecked(self.value.get()) },
276            marker: core::marker::PhantomData,
277        }
278    }
279
280    /// Tries to lock the SpinLocked and returns a guard that releases the lock when dropped.
281    pub fn try_lock(&self) -> Option<SpinLockGuard<'_, T>> {
282        if self.lock.try_lock() {
283            Some(SpinLockGuard {
284                lock: &self.lock,
285                value: unsafe { NonNull::new_unchecked(self.value.get()) },
286                marker: core::marker::PhantomData,
287            })
288        } else {
289            None
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn rw_spin_lock_tracks_multiple_readers() {
300        let lock = RwSpinLock::new();
301
302        lock.read_lock();
303        lock.read_lock();
304
305        assert_eq!(lock.lock.load(Ordering::Acquire), 2);
306
307        unsafe {
308            lock.read_unlock();
309        }
310
311        assert_eq!(lock.lock.load(Ordering::Acquire), 1);
312
313        unsafe {
314            lock.read_unlock();
315        }
316
317        assert_eq!(lock.lock.load(Ordering::Acquire), 0);
318    }
319
320    #[test]
321    fn rw_spin_lock_tracks_writer() {
322        let lock = RwSpinLock::new();
323
324        lock.write_lock();
325
326        assert_eq!(lock.lock.load(Ordering::Acquire), -1);
327
328        unsafe {
329            lock.write_unlock();
330        }
331
332        assert_eq!(lock.lock.load(Ordering::Acquire), 0);
333    }
334
335    #[test]
336    fn rw_spin_locked_read_guards_allow_shared_access() {
337        let value = RwSpinLocked::new(7usize);
338        let first = value.read_lock();
339        let second = value.read_lock();
340
341        assert_eq!(*first, 7);
342        assert_eq!(*second, 7);
343        assert_eq!(value.lock.lock.load(Ordering::Acquire), 2);
344    }
345
346    #[test]
347    fn rw_spin_locked_read_guard_releases_on_drop() {
348        let value = RwSpinLocked::new(7usize);
349
350        {
351            let _guard = value.read_lock();
352
353            assert_eq!(value.lock.lock.load(Ordering::Acquire), 1);
354        }
355
356        assert_eq!(value.lock.lock.load(Ordering::Acquire), 0);
357    }
358
359    #[test]
360    fn rw_spin_locked_write_guard_updates_value() {
361        let value = RwSpinLocked::new(7usize);
362
363        {
364            let mut guard = value.write_lock();
365            *guard = 11;
366
367            assert_eq!(*guard, 11);
368            assert_eq!(value.lock.lock.load(Ordering::Acquire), -1);
369        }
370
371        assert_eq!(value.lock.lock.load(Ordering::Acquire), 0);
372        assert_eq!(*value.read_lock(), 11);
373    }
374
375    #[test]
376    fn spin_lock_try_lock_reports_state() {
377        let lock = SpinLock::new();
378
379        assert!(lock.try_lock());
380        assert!(!lock.try_lock());
381
382        unsafe {
383            lock.unlock();
384        }
385
386        assert!(lock.try_lock());
387
388        unsafe {
389            lock.unlock();
390        }
391    }
392
393    #[test]
394    fn spin_lock_lock_and_unlock_update_state() {
395        let lock = SpinLock::new();
396
397        lock.lock();
398
399        assert!(lock.lock.load(Ordering::Acquire));
400
401        unsafe {
402            lock.unlock();
403        }
404
405        assert!(!lock.lock.load(Ordering::Acquire));
406    }
407
408    #[test]
409    fn spin_locked_guard_updates_value() {
410        let value = SpinLocked::new(7usize);
411
412        {
413            let mut guard = value.lock();
414            *guard = 11;
415
416            assert_eq!(*guard, 11);
417        }
418
419        assert_eq!(*value.lock(), 11);
420    }
421
422    #[test]
423    fn spin_locked_try_lock_returns_guard_when_unlocked() {
424        let value = SpinLocked::new(7usize);
425
426        {
427            let mut guard = value.try_lock().expect("lock should be available");
428            *guard = 11;
429
430            assert!(value.try_lock().is_none());
431        }
432
433        assert_eq!(*value.try_lock().expect("lock should be available"), 11);
434    }
435
436    #[test]
437    fn locked_types_are_sync_for_shareable_values() {
438        fn assert_sync<T: Sync>() {}
439
440        assert_sync::<RwSpinLocked<usize>>();
441        assert_sync::<SpinLocked<usize>>();
442    }
443}