Skip to main content

ax_task/sync/mutex/
mod.rs

1//! Priority-inheritance sleeping mutex.
2
3use core::sync::atomic::{AtomicU64, Ordering};
4
5mod entry;
6#[cfg(feature = "lockdep")]
7pub(in crate::sync) mod lockdep;
8mod pi_core;
9
10use self::entry::{
11    FastLockAttempt, LockEntry, capture_current_and_prepare_slow, owner_spin_eligible,
12    owner_spin_progress_gates,
13};
14pub use self::pi_core::*;
15
16/// A non-recursive, urgency-ordered PI mutex implementing `lock_api::RawMutex`.
17///
18/// The uncontended path uses a Linux rtmutex-style atomic owner word. Its high
19/// bit forces contenders through the metadata lock while waiter publication,
20/// donation registration, and handoff are in progress. Blocking and targeted
21/// wake happen after that metadata guard has been released.
22pub struct RawMutex {
23    core: PiMutexCore,
24    next_waiter_sequence: AtomicU64,
25    #[cfg(feature = "lockdep")]
26    pub(crate) lockdep: super::lockdep::LockdepMap,
27}
28
29/// Borrowed execution state for the unique native PI-mutex algorithm.
30pub(in crate::sync) struct PiMutexAlgorithm<'lock> {
31    core: PiMutexCoreView<'lock>,
32    next_waiter_sequence: &'lock AtomicU64,
33}
34
35/// Interruption observed while waiting for a PI mutex.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct PiMutexLockInterrupted;
38
39impl core::fmt::Display for PiMutexLockInterrupted {
40    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        formatter.write_str("PI mutex wait interrupted")
42    }
43}
44
45impl core::error::Error for PiMutexLockInterrupted {}
46
47/// Linux rtmutex-style interruptible acquisition for a PI mutex.
48pub trait InterruptibleMutexExt<T: ?Sized> {
49    /// Acquires this mutex unless `should_interrupt` becomes true while the
50    /// caller remains queued.
51    ///
52    /// A published ownerless handoff wins over interruption. The returned
53    /// guard therefore has the same acquire-before-signal ordering as Linux
54    /// `mutex_lock_interruptible()` under PREEMPT_RT. The interruption
55    /// publisher must also wake the waiting task, just as Linux
56    /// `signal_wake_up_state()` sets `TIF_SIGPENDING` and wakes
57    /// `TASK_INTERRUPTIBLE`; this predicate is not polled while the task is
58    /// asleep.
59    fn lock_interruptible<F>(
60        &self,
61        should_interrupt: F,
62    ) -> Result<MutexGuard<'_, T>, PiMutexLockInterrupted>
63    where
64        F: FnMut() -> bool;
65}
66
67#[cfg(not(feature = "lockdep"))]
68/// A lockdep subclass identifier when lockdep is disabled.
69pub type LockSubclass = u32;
70#[cfg(feature = "lockdep")]
71pub type LockSubclass = super::lockdep::LockSubclass;
72
73/// Adds lockdep subclass acquisition to a sleeping [`Mutex`].
74pub trait LockdepMutexExt<T: ?Sized> {
75    /// Acquires the mutex using `subclass` for lock-order validation.
76    fn lock_nested(&self, subclass: LockSubclass) -> MutexGuard<'_, T>;
77}
78
79impl<T: ?Sized> LockdepMutexExt<T> for Mutex<T> {
80    #[inline(always)]
81    #[track_caller]
82    fn lock_nested(&self, subclass: LockSubclass) -> MutexGuard<'_, T> {
83        #[cfg(not(feature = "lockdep"))]
84        {
85            let _ = subclass;
86            self.lock()
87        }
88
89        #[cfg(feature = "lockdep")]
90        {
91            // SAFETY: the raw reference is used for the matching acquisition.
92            let raw = unsafe { self.raw() };
93            raw.lock_nested(subclass);
94            // SAFETY: `lock_nested` acquired this mutex.
95            unsafe { self.make_guard_unchecked() }
96        }
97    }
98}
99
100impl RawMutex {
101    /// Creates an unlocked PI mutex.
102    pub const fn new() -> Self {
103        Self {
104            core: PiMutexCore::new(),
105            next_waiter_sequence: AtomicU64::new(0),
106            #[cfg(feature = "lockdep")]
107            lockdep: super::lockdep::LockdepMap::new(),
108        }
109    }
110
111    const fn algorithm(&self) -> PiMutexAlgorithm<'_> {
112        PiMutexAlgorithm::new(self.core.view(), &self.next_waiter_sequence)
113    }
114
115    /// Returns whether the current thread owns this mutex.
116    pub fn is_owned_by_current(&self) -> bool {
117        self.algorithm().is_owned_by_current()
118    }
119}
120
121impl<'lock> PiMutexAlgorithm<'lock> {
122    pub(in crate::sync) const fn new(
123        core: PiMutexCoreView<'lock>,
124        next_waiter_sequence: &'lock AtomicU64,
125    ) -> Self {
126        Self {
127            core,
128            next_waiter_sequence,
129        }
130    }
131
132    pub(in crate::sync) fn is_owned_by_current(&self) -> bool {
133        Self::core_is_owned_by_current(self.core)
134    }
135
136    pub(in crate::sync) fn core_is_owned_by_current(core: PiMutexCoreView<'_>) -> bool {
137        core.is_owned_by(Self::current_task_id())
138    }
139
140    #[inline(always)]
141    fn current_task_id() -> PiTaskId {
142        task_result(
143            crate::thread::current::current_thread_id(),
144            "capture current PI mutex task",
145        )
146        .into()
147    }
148
149    pub(in crate::sync) fn lock_pi(&self) {
150        #[cfg(feature = "qperf-metrics")]
151        crate::diagnostics::counters::record_pi_mutex_lock_attempt();
152        match capture_current_and_prepare_slow(
153            || {
154                task_result(
155                    crate::thread::current::current_thread_token(),
156                    "capture current PI mutex task",
157                )
158            },
159            |current| self.try_or_observe_current_token(current.id().into()),
160            || {
161                // The uncontended path neither publishes a waiter nor
162                // schedules and must remain usable during single-threaded
163                // boot.
164                task_result(
165                    crate::thread::current::validate_blocking_context(),
166                    "validate PI mutex blocking context",
167                );
168            },
169        ) {
170            LockEntry::Acquired => {
171                #[cfg(feature = "qperf-metrics")]
172                crate::diagnostics::counters::record_pi_mutex_fast_acquisition();
173            }
174            LockEntry::Contended(current) => {
175                #[cfg(feature = "qperf-metrics")]
176                crate::diagnostics::counters::record_pi_mutex_slow_entry();
177                self.lock_contended(current);
178            }
179        }
180    }
181
182    fn lock_pi_interruptible(
183        &self,
184        mut should_interrupt: impl FnMut() -> bool,
185    ) -> Result<(), PiMutexLockInterrupted> {
186        match capture_current_and_prepare_slow(
187            || {
188                task_result(
189                    crate::thread::current::current_thread_token(),
190                    "capture current PI mutex task",
191                )
192            },
193            |current| self.try_or_observe_current_token(current.id().into()),
194            || {
195                task_result(
196                    crate::thread::current::validate_blocking_context(),
197                    "validate PI mutex blocking context",
198                );
199            },
200        ) {
201            LockEntry::Acquired => Ok(()),
202            LockEntry::Contended(current) => {
203                self.lock_contended_interruptible(current, &mut should_interrupt)
204            }
205        }
206    }
207
208    #[cold]
209    #[inline(never)]
210    fn lock_contended(&self, current: crate::thread::CurrentThreadToken) {
211        let current_id = current.id().into();
212        let sequence = self.next_waiter_sequence.fetch_add(1, Ordering::Relaxed);
213        let lock = core_result(self.core.mutex_ref(), "borrow PI mutex identity");
214        let token = match task_result(
215            crate::runtime::sync::pi_mutex_lock_slow(lock, &current, sequence),
216            "register PI mutex waiter",
217        ) {
218            PiMutexLockResult::Acquired => {
219                #[cfg(feature = "qperf-metrics")]
220                crate::diagnostics::counters::record_pi_mutex_slow_race_acquisition();
221                return;
222            }
223            PiMutexLockResult::Waiting(token) => {
224                #[cfg(feature = "qperf-metrics")]
225                crate::diagnostics::counters::record_pi_mutex_waiter_registration();
226                token
227            }
228        };
229        debug_assert_eq!(token.thread_id(), current_id);
230        if self.try_claim_waiter(&token, &current) {
231            return;
232        }
233        self.wait_for_handoff(token, &current);
234    }
235
236    #[cold]
237    #[inline(never)]
238    fn lock_contended_interruptible(
239        &self,
240        current: crate::thread::CurrentThreadToken,
241        should_interrupt: &mut impl FnMut() -> bool,
242    ) -> Result<(), PiMutexLockInterrupted> {
243        let current_id = current.id().into();
244        let sequence = self.next_waiter_sequence.fetch_add(1, Ordering::Relaxed);
245        let lock = core_result(self.core.mutex_ref(), "borrow PI mutex identity");
246        let token = match task_result(
247            crate::runtime::sync::pi_mutex_lock_slow(lock, &current, sequence),
248            "register interruptible PI mutex waiter",
249        ) {
250            PiMutexLockResult::Acquired => return Ok(()),
251            PiMutexLockResult::Waiting(token) => token,
252        };
253        debug_assert_eq!(token.thread_id(), current_id);
254
255        loop {
256            if self.try_claim_waiter(&token, &current) {
257                return Ok(());
258            }
259            if should_interrupt() {
260                match task_result(
261                    crate::runtime::sync::pi_wait_try_cancel(&token),
262                    "cancel interruptible PI mutex waiter",
263                ) {
264                    PiWaitCancelOutcome::Cancelled => {
265                        task_result(
266                            crate::runtime::sync::pi::cancel_prepared_pi_park(&token),
267                            "cancel prepared interruptible PI mutex park",
268                        );
269                        return Err(PiMutexLockInterrupted);
270                    }
271                    PiWaitCancelOutcome::HandoffPending => continue,
272                }
273            }
274            if !token.can_claim() && !self.spin_on_owner(&token) {
275                task_result(
276                    crate::runtime::sync::pi_park_current_once(&token),
277                    "park interruptible PI mutex waiter",
278                );
279            }
280        }
281    }
282
283    fn wait_for_handoff(&self, token: PiWaitToken, current: &crate::thread::CurrentThreadToken) {
284        loop {
285            if self.try_claim_waiter(&token, current) {
286                break;
287            }
288            if !token.can_claim() && !self.spin_on_owner(&token) {
289                #[cfg(feature = "qperf-metrics")]
290                crate::diagnostics::counters::record_pi_mutex_waiter_park();
291                task_result(
292                    crate::runtime::sync::pi_park_current_once(&token),
293                    "park PI mutex waiter",
294                );
295            }
296        }
297        assert!(
298            self.core.is_owned_by(token.thread_id()),
299            "PI core owner must name the granted waiter"
300        );
301    }
302
303    /// Spins only while the registered waiter can make progress under the same
304    /// gates as Linux `rtmutex_spin_on_owner`: the observed owner is unchanged
305    /// and executing, this waiter remains most urgent, and the current CPU has
306    /// no pending reschedule request. The architecture current-state query is
307    /// advisory and leaves owner spinning preemptible.
308    fn spin_on_owner(&self, token: &PiWaitToken) -> bool {
309        let Some(owner) = token.initial_owner() else {
310            return token.can_claim() || token.is_granted();
311        };
312        let cpu_count = task_result(
313            crate::sched::cpu_topology_len(),
314            "capture PI mutex CPU topology",
315        );
316
317        loop {
318            if token.can_claim() || token.is_granted() {
319                return true;
320            }
321
322            let may_spin = owner_spin_eligible(cpu_count, || {
323                owner_spin_progress_gates(
324                    self.core.is_owned_by(owner),
325                    token.initial_owner_is_on_cpu(),
326                    token.is_top_waiter(),
327                    crate::runtime::task_runtime::current_preemption_pending(),
328                )
329            });
330            if !may_spin {
331                return token.can_claim() || token.is_granted();
332            }
333
334            core::hint::spin_loop();
335        }
336    }
337
338    fn try_or_observe_current_token(&self, current: PiTaskId) -> FastLockAttempt {
339        match core_result(self.core.try_acquire(current), "try PI mutex acquisition") {
340            PiMutexAcquire::Acquired => FastLockAttempt::Acquired,
341            PiMutexAcquire::Contended => FastLockAttempt::Contended,
342        }
343    }
344
345    pub(in crate::sync) fn try_lock_pi(&self) -> bool {
346        let current = Self::current_task_id();
347        match self.core.try_acquire(current) {
348            Ok(PiMutexAcquire::Acquired) => true,
349            Ok(PiMutexAcquire::Contended) | Err(PiMutexStateError::WaiterOwnsLock) => false,
350            Err(error) => panic!("try PI mutex failed: {error}"),
351        }
352    }
353
354    fn try_claim_waiter(
355        &self,
356        token: &PiWaitToken,
357        current: &crate::thread::CurrentThreadToken,
358    ) -> bool {
359        if token.is_granted() {
360            task_result(
361                crate::runtime::sync::pi::cancel_prepared_pi_park(token),
362                "cancel prepared PI mutex park after handoff",
363            );
364            return true;
365        }
366        if !token.can_claim() {
367            return false;
368        }
369        let claimed = match task_result(
370            crate::runtime::sync::pi_mutex_claim(token, current),
371            "claim ownerless PI mutex handoff",
372        ) {
373            PiMutexClaimOutcome::Claimed => true,
374            PiMutexClaimOutcome::Retry => false,
375        };
376        if claimed {
377            task_result(
378                crate::runtime::sync::pi::cancel_prepared_pi_park(token),
379                "cancel prepared PI mutex park after claim",
380            );
381        }
382        claimed
383    }
384
385    pub(in crate::sync) unsafe fn unlock_pi(&self) {
386        // SAFETY: forwarded from this method's raw-mutex ownership contract.
387        unsafe { Self::unlock_core(self.core) };
388    }
389
390    pub(in crate::sync) unsafe fn unlock_core(core: PiMutexCoreView<'_>) {
391        let current = Self::current_task_id();
392        // SAFETY: the caller is the lock_api raw-mutex owner and retains that
393        // exclusive authority through this complete release transaction;
394        // `current` is the executing scheduler identity named by that owner
395        // contract.
396        match core_result(
397            unsafe { core.try_release_owned(current) },
398            "try PI mutex release",
399        ) {
400            PiMutexOwnedRelease::Released => {}
401            PiMutexOwnedRelease::Contended(owner) => {
402                #[cfg(feature = "qperf-metrics")]
403                crate::diagnostics::counters::record_pi_mutex_contended_release();
404                // SAFETY: `owner` came from this core's owner-authorized release
405                // result and the raw-mutex contract remains active.
406                unsafe { Self::unlock_contended(core, owner) };
407            }
408        }
409    }
410
411    unsafe fn unlock_contended(core: PiMutexCoreView<'_>, owner: PiTaskId) {
412        let lock = core_result(core.mutex_ref(), "borrow PI mutex release identity");
413        task_result(
414            unsafe {
415                // SAFETY: `owner` came from this core's owner-authorized
416                // release transition, and the raw-mutex contract remains held.
417                crate::runtime::sync::pi_mutex_release_owned(lock, owner.into())
418            },
419            "release contended PI mutex",
420        );
421    }
422
423    pub(in crate::sync) fn is_locked(&self) -> bool {
424        Self::core_is_locked(self.core)
425    }
426
427    pub(in crate::sync) fn core_is_locked(core: PiMutexCoreView<'_>) -> bool {
428        core.is_locked()
429    }
430}
431
432impl RawMutex {
433    fn lock_pi(&self) {
434        self.algorithm().lock_pi();
435    }
436
437    fn lock_pi_interruptible(
438        &self,
439        should_interrupt: impl FnMut() -> bool,
440    ) -> Result<(), PiMutexLockInterrupted> {
441        self.algorithm().lock_pi_interruptible(should_interrupt)
442    }
443
444    fn try_lock_pi(&self) -> bool {
445        self.algorithm().try_lock_pi()
446    }
447
448    unsafe fn unlock_pi(&self) {
449        // SAFETY: forwarded from the caller's raw-mutex ownership contract.
450        unsafe { self.algorithm().unlock_pi() };
451    }
452
453    #[cfg(feature = "lockdep")]
454    #[track_caller]
455    fn lock_nested(&self, subclass: LockSubclass) {
456        let lockdep = lockdep::LockdepAcquire::prepare_nested(self, false, subclass);
457        self.lock_pi();
458        lockdep.finish(true);
459    }
460
461    #[cfg(feature = "lockdep")]
462    #[track_caller]
463    fn lock_interruptible_nested(
464        &self,
465        subclass: LockSubclass,
466        should_interrupt: impl FnMut() -> bool,
467    ) -> Result<(), PiMutexLockInterrupted> {
468        let lockdep = lockdep::LockdepAcquire::prepare_nested(self, false, subclass);
469        let result = self.lock_pi_interruptible(should_interrupt);
470        lockdep.finish(result.is_ok());
471        result
472    }
473
474    #[cfg(feature = "lockdep")]
475    #[track_caller]
476    fn try_lock_nested(&self, subclass: LockSubclass) -> bool {
477        let lockdep = lockdep::LockdepAcquire::prepare_nested(self, true, subclass);
478        let acquired = self.try_lock_pi();
479        lockdep.finish(acquired);
480        acquired
481    }
482}
483
484impl Default for RawMutex {
485    fn default() -> Self {
486        Self::new()
487    }
488}
489
490// SAFETY: task-context metadata transitions are serialized by a preemption-safe
491// gate. Hard IRQ paths never access this state. A lock_api guard is created only
492// after scheduler ownership registration or an explicit PI handoff grants the
493// calling thread.
494unsafe impl lock_api::RawMutex for RawMutex {
495    type GuardMarker = lock_api::GuardNoSend;
496
497    const INIT: Self = Self::new();
498
499    #[inline(always)]
500    #[track_caller]
501    fn lock(&self) {
502        #[cfg(feature = "lockdep")]
503        self.lock_nested(super::lockdep::DEFAULT_LOCK_SUBCLASS);
504
505        #[cfg(not(feature = "lockdep"))]
506        self.lock_pi();
507    }
508
509    #[inline(always)]
510    #[track_caller]
511    fn try_lock(&self) -> bool {
512        #[cfg(feature = "lockdep")]
513        {
514            self.try_lock_nested(super::lockdep::DEFAULT_LOCK_SUBCLASS)
515        }
516
517        #[cfg(not(feature = "lockdep"))]
518        {
519            self.try_lock_pi()
520        }
521    }
522
523    #[inline(always)]
524    unsafe fn unlock(&self) {
525        #[cfg(feature = "lockdep")]
526        lockdep::release(self);
527        // SAFETY: lock_api calls `unlock` only for the execution context that
528        // owns this raw mutex, and this method consumes that ownership once.
529        unsafe { self.unlock_pi() };
530    }
531
532    #[inline(always)]
533    fn is_locked(&self) -> bool {
534        self.algorithm().is_locked()
535    }
536}
537
538#[track_caller]
539fn core_result<T>(result: Result<T, PiMutexStateError>, operation: &'static str) -> T {
540    result.unwrap_or_else(|error| panic!("{operation} failed: {error}"))
541}
542
543#[track_caller]
544pub(super) fn task_result<T, E>(result: Result<T, E>, operation: &'static str) -> T
545where
546    E: core::fmt::Display,
547{
548    result.unwrap_or_else(|error| panic!("{operation} failed: {error}"))
549}
550
551/// A safe PI mutex using [`RawMutex`].
552pub type Mutex<T> = lock_api::Mutex<RawMutex, T>;
553/// A non-send guard returned by [`Mutex`].
554pub type MutexGuard<'a, T> = lock_api::MutexGuard<'a, RawMutex, T>;
555
556impl<T: ?Sized> InterruptibleMutexExt<T> for Mutex<T> {
557    #[track_caller]
558    fn lock_interruptible<F>(
559        &self,
560        should_interrupt: F,
561    ) -> Result<MutexGuard<'_, T>, PiMutexLockInterrupted>
562    where
563        F: FnMut() -> bool,
564    {
565        // SAFETY: this reference is used only for the matching acquisition;
566        // the returned guard retains the safe mutex borrow.
567        let raw = unsafe { self.raw() };
568        #[cfg(feature = "lockdep")]
569        raw.lock_interruptible_nested(super::lockdep::DEFAULT_LOCK_SUBCLASS, should_interrupt)?;
570        #[cfg(not(feature = "lockdep"))]
571        raw.lock_pi_interruptible(should_interrupt)?;
572
573        // SAFETY: the raw acquisition above established current as owner.
574        Ok(unsafe { self.make_guard_unchecked() })
575    }
576}