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