Skip to main content

ax_task/sync/spin/
mod.rs

1//! Public spin-lock types whose acquisition methods express context policy.
2
3mod base;
4#[cfg(feature = "lockdep")]
5pub(crate) mod lockdep;
6#[cfg(feature = "lock-api")]
7mod raw;
8pub(crate) mod rwlock;
9
10use core::{fmt, ptr};
11
12#[cfg(feature = "lock-api")]
13pub use self::raw::*;
14use self::{
15    base::{BaseSpinLock, BaseSpinLockGuard},
16    rwlock::{BaseSpinRwLock, BaseSpinRwLockReadGuard, BaseSpinRwLockWriteGuard},
17};
18use crate::sync::context::{PreemptIrqSaveState, PreemptState, RawState};
19
20/// A non-sleeping mutual-exclusion lock.
21///
22/// The lock object does not bake in an execution-context policy. Callers
23/// choose the policy at the acquisition site with [`Self::lock`],
24/// [`Self::lock_irqsave`], or [`Self::lock_raw`].
25#[repr(transparent)]
26pub struct SpinLock<T: ?Sized>(BaseSpinLock<RawState, T>);
27
28/// A guard returned by [`SpinLock::lock`].
29///
30/// ```compile_fail
31/// fn require_send<T: Send>() {}
32/// require_send::<ax_task::sync::SpinLockGuard<'static, ()>>();
33/// ```
34pub type SpinLockGuard<'a, T> = BaseSpinLockGuard<'a, PreemptState, T>;
35
36/// A guard returned by [`SpinLock::lock_irqsave`].
37pub type SpinLockIrqSaveGuard<'a, T> = BaseSpinLockGuard<'a, PreemptIrqSaveState, T>;
38
39/// A guard returned by [`SpinLock::lock_raw`].
40pub type RawSpinLockGuard<'a, T> = BaseSpinLockGuard<'a, RawState, T>;
41
42impl<T> SpinLock<T> {
43    /// Creates an unlocked spin lock.
44    #[inline(always)]
45    #[track_caller]
46    pub const fn new(data: T) -> Self {
47        Self(BaseSpinLock::new(data))
48    }
49
50    /// Consumes the lock and returns the protected value.
51    #[inline(always)]
52    pub fn into_inner(self) -> T {
53        self.0.into_inner()
54    }
55}
56
57impl<T: ?Sized> SpinLock<T> {
58    #[inline(always)]
59    fn with_state<G: crate::sync::context::GuardState>(&self) -> &BaseSpinLock<G, T> {
60        // SAFETY: `BaseSpinLock` has a stable C layout, and its guard-state
61        // parameter is represented only by `PhantomData`. The atomic state,
62        // lockdep map, and protected value therefore have identical addresses
63        // for every `G`.
64        unsafe { &*(ptr::from_ref(&self.0) as *const BaseSpinLock<G, T>) }
65    }
66
67    #[inline(always)]
68    fn with_state_mut<G: crate::sync::context::GuardState>(&mut self) -> &mut BaseSpinLock<G, T> {
69        // SAFETY: see `with_state`; the exclusive borrow prevents aliases.
70        unsafe { &mut *(ptr::from_mut(&mut self.0) as *mut BaseSpinLock<G, T>) }
71    }
72
73    /// Acquires the lock after disabling kernel preemption.
74    #[inline(always)]
75    #[track_caller]
76    pub fn lock(&self) -> SpinLockGuard<'_, T> {
77        self.with_state::<PreemptState>().lock()
78    }
79
80    /// Acquires the lock after disabling preemption, using a lockdep subclass.
81    ///
82    /// This is intended for structurally nested acquisitions of different
83    /// locks with the same class. Without `lockdep`, `subclass` has no effect.
84    #[inline(always)]
85    #[track_caller]
86    pub fn lock_nested(&self, subclass: u32) -> SpinLockGuard<'_, T> {
87        self.with_state::<PreemptState>().lock_nested(subclass)
88    }
89
90    /// Attempts to acquire the lock after disabling kernel preemption.
91    #[inline(always)]
92    #[track_caller]
93    pub fn try_lock(&self) -> Option<SpinLockGuard<'_, T>> {
94        self.with_state::<PreemptState>().try_lock()
95    }
96
97    /// Acquires the lock after disabling preemption and saving/disabling IRQs.
98    #[inline(always)]
99    #[track_caller]
100    pub fn lock_irqsave(&self) -> SpinLockIrqSaveGuard<'_, T> {
101        self.with_state::<PreemptIrqSaveState>().lock()
102    }
103
104    /// Acquires the lock after disabling preemption and saving/disabling IRQs,
105    /// using a lockdep subclass.
106    ///
107    /// This is intended for structurally nested acquisitions of different
108    /// locks with the same class. Without `lockdep`, `subclass` has no effect.
109    #[inline(always)]
110    #[track_caller]
111    pub fn lock_irqsave_nested(&self, subclass: u32) -> SpinLockIrqSaveGuard<'_, T> {
112        self.with_state::<PreemptIrqSaveState>()
113            .lock_nested(subclass)
114    }
115
116    /// Attempts to acquire the lock after disabling preemption and IRQs.
117    #[inline(always)]
118    #[track_caller]
119    pub fn try_lock_irqsave(&self) -> Option<SpinLockIrqSaveGuard<'_, T>> {
120        self.with_state::<PreemptIrqSaveState>().try_lock()
121    }
122
123    /// Acquires the lock without changing preemption or interrupt state.
124    ///
125    /// # Safety
126    ///
127    /// The caller must prevent same-CPU re-entry and all concurrent access
128    /// which could violate exclusive access, including on single-core builds
129    /// where the atomic lock word is compiled out.
130    #[inline(always)]
131    #[track_caller]
132    pub unsafe fn lock_raw(&self) -> RawSpinLockGuard<'_, T> {
133        self.with_state::<RawState>().lock()
134    }
135
136    /// Attempts a raw acquisition without changing execution context.
137    ///
138    /// # Safety
139    ///
140    /// The caller must uphold the same exclusion contract as
141    /// [`Self::lock_raw`], even when this function returns `None`.
142    #[inline(always)]
143    #[track_caller]
144    pub unsafe fn try_lock_raw(&self) -> Option<RawSpinLockGuard<'_, T>> {
145        self.with_state::<RawState>().try_lock()
146    }
147
148    /// Returns whether the lock appears held.
149    ///
150    /// This is only a diagnostic snapshot and provides no synchronization.
151    #[inline(always)]
152    pub fn is_locked(&self) -> bool {
153        self.0.is_locked()
154    }
155
156    /// Returns mutable access without locking.
157    #[inline(always)]
158    pub fn get_mut(&mut self) -> &mut T {
159        self.with_state_mut::<RawState>().get_mut()
160    }
161
162    /// Releases a preemption-mode lock without consuming its guard.
163    ///
164    /// # Safety
165    ///
166    /// The caller must own exactly one guard returned by [`Self::lock`] and
167    /// must ensure that guard will never subsequently be dropped.
168    #[doc(hidden)]
169    #[inline(always)]
170    pub unsafe fn force_unlock(&self) {
171        unsafe { self.with_state::<PreemptState>().force_unlock() };
172    }
173}
174
175impl<T: Default> Default for SpinLock<T> {
176    fn default() -> Self {
177        Self::new(T::default())
178    }
179}
180
181impl<T: fmt::Debug> fmt::Debug for SpinLock<T> {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self.try_lock() {
184            Some(guard) => f.debug_struct("SpinLock").field("data", &&*guard).finish(),
185            None => f
186                .debug_struct("SpinLock")
187                .field("data", &"<locked>")
188                .finish(),
189        }
190    }
191}
192
193/// A non-sleeping read-write lock with acquisition-site context policy.
194#[repr(transparent)]
195pub struct SpinRwLock<T: ?Sized>(BaseSpinRwLock<RawState, T>);
196
197/// A read guard returned by [`SpinRwLock::read`].
198///
199/// ```compile_fail
200/// fn require_send<T: Send>() {}
201/// require_send::<ax_task::sync::SpinRwLockReadGuard<'static, ()>>();
202/// ```
203pub type SpinRwLockReadGuard<'a, T> = BaseSpinRwLockReadGuard<'a, PreemptState, T>;
204
205/// A write guard returned by [`SpinRwLock::write`].
206///
207/// ```compile_fail
208/// fn require_send<T: Send>() {}
209/// require_send::<ax_task::sync::SpinRwLockWriteGuard<'static, ()>>();
210/// ```
211pub type SpinRwLockWriteGuard<'a, T> = BaseSpinRwLockWriteGuard<'a, PreemptState, T>;
212
213/// An IRQ-save read guard.
214pub type SpinRwLockIrqSaveReadGuard<'a, T> = BaseSpinRwLockReadGuard<'a, PreemptIrqSaveState, T>;
215
216/// An IRQ-save write guard.
217pub type SpinRwLockIrqSaveWriteGuard<'a, T> = BaseSpinRwLockWriteGuard<'a, PreemptIrqSaveState, T>;
218
219/// A raw read guard.
220pub type RawSpinRwLockReadGuard<'a, T> = BaseSpinRwLockReadGuard<'a, RawState, T>;
221
222/// A raw write guard.
223pub type RawSpinRwLockWriteGuard<'a, T> = BaseSpinRwLockWriteGuard<'a, RawState, T>;
224
225impl<T> SpinRwLock<T> {
226    /// Creates an unlocked spin read-write lock.
227    #[inline(always)]
228    #[track_caller]
229    pub const fn new(data: T) -> Self {
230        Self(BaseSpinRwLock::new(data))
231    }
232
233    /// Consumes the lock and returns the protected value.
234    #[inline(always)]
235    pub fn into_inner(self) -> T {
236        self.0.into_inner()
237    }
238}
239
240impl<T: ?Sized> SpinRwLock<T> {
241    #[inline(always)]
242    fn with_state<G: crate::sync::context::GuardState>(&self) -> &BaseSpinRwLock<G, T> {
243        // SAFETY: identical to `SpinLock::with_state`; the generic parameter
244        // is represented only by `PhantomData` in a stable C layout.
245        unsafe { &*(ptr::from_ref(&self.0) as *const BaseSpinRwLock<G, T>) }
246    }
247
248    #[inline(always)]
249    fn with_state_mut<G: crate::sync::context::GuardState>(&mut self) -> &mut BaseSpinRwLock<G, T> {
250        // SAFETY: see `with_state`; the exclusive borrow prevents aliases.
251        unsafe { &mut *(ptr::from_mut(&mut self.0) as *mut BaseSpinRwLock<G, T>) }
252    }
253
254    /// Acquires a read guard after disabling preemption.
255    #[inline(always)]
256    #[track_caller]
257    pub fn read(&self) -> SpinRwLockReadGuard<'_, T> {
258        self.with_state::<PreemptState>().read()
259    }
260
261    /// Attempts a read acquisition after disabling preemption.
262    #[inline(always)]
263    #[track_caller]
264    pub fn try_read(&self) -> Option<SpinRwLockReadGuard<'_, T>> {
265        self.with_state::<PreemptState>().try_read()
266    }
267
268    /// Acquires a write guard after disabling preemption.
269    #[inline(always)]
270    #[track_caller]
271    pub fn write(&self) -> SpinRwLockWriteGuard<'_, T> {
272        self.with_state::<PreemptState>().write()
273    }
274
275    /// Attempts a write acquisition after disabling preemption.
276    #[inline(always)]
277    #[track_caller]
278    pub fn try_write(&self) -> Option<SpinRwLockWriteGuard<'_, T>> {
279        self.with_state::<PreemptState>().try_write()
280    }
281
282    /// Acquires an IRQ-save read guard.
283    #[inline(always)]
284    #[track_caller]
285    pub fn read_irqsave(&self) -> SpinRwLockIrqSaveReadGuard<'_, T> {
286        self.with_state::<PreemptIrqSaveState>().read()
287    }
288
289    /// Attempts an IRQ-save read acquisition.
290    #[inline(always)]
291    #[track_caller]
292    pub fn try_read_irqsave(&self) -> Option<SpinRwLockIrqSaveReadGuard<'_, T>> {
293        self.with_state::<PreemptIrqSaveState>().try_read()
294    }
295
296    /// Acquires an IRQ-save write guard.
297    #[inline(always)]
298    #[track_caller]
299    pub fn write_irqsave(&self) -> SpinRwLockIrqSaveWriteGuard<'_, T> {
300        self.with_state::<PreemptIrqSaveState>().write()
301    }
302
303    /// Attempts an IRQ-save write acquisition.
304    #[inline(always)]
305    #[track_caller]
306    pub fn try_write_irqsave(&self) -> Option<SpinRwLockIrqSaveWriteGuard<'_, T>> {
307        self.with_state::<PreemptIrqSaveState>().try_write()
308    }
309
310    /// Acquires a raw read guard without changing execution context.
311    ///
312    /// # Safety
313    ///
314    /// The caller must prevent re-entry and uphold the read-side exclusion
315    /// contract, including on single-core builds.
316    #[inline(always)]
317    #[track_caller]
318    pub unsafe fn read_raw(&self) -> RawSpinRwLockReadGuard<'_, T> {
319        self.with_state::<RawState>().read()
320    }
321
322    /// Attempts a raw read acquisition.
323    ///
324    /// # Safety
325    ///
326    /// The caller must uphold the contract of [`Self::read_raw`].
327    #[inline(always)]
328    #[track_caller]
329    pub unsafe fn try_read_raw(&self) -> Option<RawSpinRwLockReadGuard<'_, T>> {
330        self.with_state::<RawState>().try_read()
331    }
332
333    /// Acquires a raw write guard without changing execution context.
334    ///
335    /// # Safety
336    ///
337    /// The caller must prevent re-entry and concurrent readers or writers,
338    /// including on single-core builds.
339    #[inline(always)]
340    #[track_caller]
341    pub unsafe fn write_raw(&self) -> RawSpinRwLockWriteGuard<'_, T> {
342        self.with_state::<RawState>().write()
343    }
344
345    /// Attempts a raw write acquisition.
346    ///
347    /// # Safety
348    ///
349    /// The caller must uphold the contract of [`Self::write_raw`].
350    #[inline(always)]
351    #[track_caller]
352    pub unsafe fn try_write_raw(&self) -> Option<RawSpinRwLockWriteGuard<'_, T>> {
353        self.with_state::<RawState>().try_write()
354    }
355
356    /// Returns mutable access without locking.
357    #[inline(always)]
358    pub fn get_mut(&mut self) -> &mut T {
359        self.with_state_mut::<RawState>().get_mut()
360    }
361
362    /// Removes one deliberately leaked raw read guard from the reader count.
363    ///
364    /// # Safety
365    ///
366    /// The caller must own a deliberately forgotten guard returned by
367    /// [`Self::read_raw`] and must prove that no live reference from it remains.
368    #[doc(hidden)]
369    #[inline(always)]
370    pub unsafe fn force_read_decrement_raw(&self) {
371        unsafe {
372            self.with_state::<RawState>().force_read_decrement();
373        }
374    }
375}
376
377impl<T: Default> Default for SpinRwLock<T> {
378    fn default() -> Self {
379        Self::new(T::default())
380    }
381}
382
383impl<T> From<T> for SpinRwLock<T> {
384    fn from(value: T) -> Self {
385        Self::new(value)
386    }
387}
388
389impl<T: fmt::Debug> fmt::Debug for SpinRwLock<T> {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        match self.try_read() {
392            Some(guard) => f
393                .debug_struct("SpinRwLock")
394                .field("data", &&*guard)
395                .finish(),
396            None => f
397                .debug_struct("SpinRwLock")
398                .field("data", &"<write locked>")
399                .finish(),
400        }
401    }
402}
403
404#[cfg(all(test, feature = "host-test", not(target_os = "none")))]
405mod tests {
406    use std::{
407        sync::{Arc, mpsc},
408        thread,
409    };
410
411    use super::{SpinLock, SpinRwLock};
412    use crate::sync::context::host_context_snapshot;
413
414    #[test]
415    fn spin_lock_acquisition_method_selects_context_policy() {
416        let lock = SpinLock::new(());
417        assert_eq!(host_context_snapshot(), (0, true));
418
419        let guard = lock.lock();
420        assert_eq!(host_context_snapshot(), (1, true));
421        drop(guard);
422        assert_eq!(host_context_snapshot(), (0, true));
423
424        let guard = lock.lock_irqsave();
425        assert_eq!(host_context_snapshot(), (1, false));
426        drop(guard);
427        assert_eq!(host_context_snapshot(), (0, true));
428
429        let guard = lock.lock_irqsave_nested(1);
430        assert_eq!(host_context_snapshot(), (1, false));
431        drop(guard);
432        assert_eq!(host_context_snapshot(), (0, true));
433    }
434
435    #[test]
436    fn spin_rwlock_acquisition_method_selects_context_policy() {
437        let lock = SpinRwLock::new(());
438
439        let reader = lock.read();
440        assert_eq!(host_context_snapshot(), (1, true));
441        drop(reader);
442        assert_eq!(host_context_snapshot(), (0, true));
443
444        let writer = lock.write_irqsave();
445        assert_eq!(host_context_snapshot(), (1, false));
446        drop(writer);
447        assert_eq!(host_context_snapshot(), (0, true));
448    }
449
450    #[test]
451    fn failed_spin_rwlock_try_modes_restore_context() {
452        let lock = Arc::new(SpinRwLock::new(()));
453        let holder_lock = Arc::clone(&lock);
454        let (held_sender, held_receiver) = mpsc::channel();
455        let (release_sender, release_receiver) = mpsc::channel();
456        let holder = thread::spawn(move || {
457            // SAFETY: this thread owns the raw writer and the channel protocol
458            // keeps it alive until the contending thread finishes its tries.
459            let held = unsafe { holder_lock.write_raw() };
460            held_sender.send(()).unwrap();
461            release_receiver.recv().unwrap();
462            drop(held);
463        });
464        held_receiver.recv().unwrap();
465
466        assert!(lock.try_read().is_none());
467        assert_eq!(host_context_snapshot(), (0, true));
468        assert!(lock.try_write().is_none());
469        assert_eq!(host_context_snapshot(), (0, true));
470        assert!(lock.try_read_irqsave().is_none());
471        assert_eq!(host_context_snapshot(), (0, true));
472        assert!(lock.try_write_irqsave().is_none());
473        assert_eq!(host_context_snapshot(), (0, true));
474        assert!(unsafe { lock.try_read_raw() }.is_none());
475        assert!(unsafe { lock.try_write_raw() }.is_none());
476        assert_eq!(host_context_snapshot(), (0, true));
477
478        release_sender.send(()).unwrap();
479        holder.join().unwrap();
480    }
481}