priority_inheriting_lock/
lib.rs

1//! This library provides a futex-based priority-inheriting lock implementation.
2//!
3//! It uses [@m-ou-se](https://github.com/m-ou-se/)'s [`linux-futex`](https://docs.rs/linux-futex/latest/linux_futex/) crate
4//! to implement [@Amanieu](https://github.com/Amanieu/)'s [`lock_api`](https://docs.rs/lock_api/latest/lock_api/), providing
5//! a priority-inheriting mutex on Linux.
6//!
7//! In general, you should consider using the lock implementations provided by `std` or `parking_lot`, unless your application
8//! is intended to run on a real-time system where [priority inversions](https://en.wikipedia.org/wiki/Priority_inversion) must be avoided.
9
10use std::sync::atomic::Ordering;
11
12thread_local! {
13    static TID: libc::pid_t = gettid();
14}
15
16/// A priority-inheriting lock implementation.
17///
18/// Consider using [`PriorityInheritingLock`] or [`SharedPriorityInheritingLock`] as higher level
19/// abstractions around this type.
20#[repr(transparent)]
21pub struct RawPriorityInheritingLock<S>(linux_futex::PiFutex<S>);
22
23impl<S> RawPriorityInheritingLock<S> {
24    /// Create a new, unlocked `RawPriorityInheritingLock`.
25    #[must_use]
26    pub const fn new() -> Self {
27        Self(linux_futex::PiFutex::new(0))
28    }
29}
30
31impl<S> Default for RawPriorityInheritingLock<S> {
32    /// Create a new, unlocked `RawPriorityInheritingLock`.
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38unsafe impl<S: linux_futex::Scope> lock_api::RawMutex for RawPriorityInheritingLock<S> {
39    // Following lock_api's design
40    #[allow(clippy::declare_interior_mutable_const)]
41    const INIT: RawPriorityInheritingLock<S> = RawPriorityInheritingLock::new();
42
43    type GuardMarker = lock_api::GuardNoSend;
44
45    /// Acquires this mutex, blocking the current thread until it is able to do so.
46    /// If this call blocks and the thread holding the lock has a lower priority,
47    /// the priority of the thread holding the lock will be temporarily boosted
48    /// to match the calling thread's priority.
49    fn lock(&self) {
50        let tid = get_cached_tid();
51        let fast_locked =
52            self.0
53                .value
54                .compare_exchange(0, tid as u32, Ordering::SeqCst, Ordering::SeqCst);
55
56        if fast_locked.is_err() {
57            while self.0.lock_pi().is_err() {}
58        }
59    }
60
61    fn try_lock(&self) -> bool {
62        let tid = get_cached_tid();
63        let fast_locked =
64            self.0
65                .value
66                .compare_exchange(0, tid as u32, Ordering::SeqCst, Ordering::SeqCst);
67
68        match fast_locked {
69            Ok(_) => true,
70            Err(_) => self.0.trylock_pi().is_ok(),
71        }
72    }
73
74    unsafe fn unlock(&self) {
75        let tid = get_cached_tid();
76        let fast_unlocked =
77            self.0
78                .value
79                .compare_exchange(tid as u32, 0, Ordering::SeqCst, Ordering::SeqCst);
80
81        if fast_unlocked.is_err() {
82            self.0.unlock_pi();
83        }
84    }
85}
86
87unsafe impl<S: linux_futex::Scope> lock_api::RawMutexTimed for RawPriorityInheritingLock<S> {
88    type Duration = std::time::Duration;
89
90    type Instant = std::time::Instant;
91
92    fn try_lock_for(&self, timeout: Self::Duration) -> bool {
93        self.try_lock_until(Self::Instant::now() + timeout)
94    }
95
96    fn try_lock_until(&self, timeout: Self::Instant) -> bool {
97        let tid = get_cached_tid();
98        let fast_locked =
99            self.0
100                .value
101                .compare_exchange(0, tid as u32, Ordering::SeqCst, Ordering::SeqCst);
102
103        if fast_locked.is_ok() {
104            return true;
105        }
106
107        loop {
108            match self.0.lock_pi_until(timeout) {
109                Ok(_) => return true,
110                Err(linux_futex::TimedLockError::TryAgain) => (),
111                Err(linux_futex::TimedLockError::TimedOut) => return false,
112            }
113        }
114    }
115}
116
117/// A priority-inheriting lock implementation, for use within a single process.
118///
119/// See also: [`PriorityInheritingLockGuard`], [`SharedPriorityInheritingLock`]
120pub type PriorityInheritingLock<T> =
121    lock_api::Mutex<RawPriorityInheritingLock<linux_futex::Private>, T>;
122
123/// An RAII implementation of a "scoped lock" for [`PriorityInheritingLock`]. When this structure is
124/// dropped (falls out of scope), the lock will be unlocked.
125///
126/// The data protected by the mutex can be accessed through this guard via its
127/// `Deref` and `DerefMut` implementations.
128pub type PriorityInheritingLockGuard<'a, T> =
129    lock_api::MutexGuard<'a, RawPriorityInheritingLock<linux_futex::Private>, T>;
130
131/// A priority-inheriting lock implementation, for use across processes.
132///
133/// See also: [`SharedPriorityInheritingLockGuard`], [`PriorityInheritingLock`]
134pub type SharedPriorityInheritingLock<T> =
135    lock_api::Mutex<RawPriorityInheritingLock<linux_futex::Shared>, T>;
136
137/// An RAII implementation of a "scoped lock" for [`SharedPriorityInheritingLock`]. When this structure is
138/// dropped (falls out of scope), the lock will be unlocked.
139///
140/// The data protected by the mutex can be accessed through this guard via its
141/// `Deref` and `DerefMut` implementations.
142pub type SharedPriorityInheritingLockGuard<'a, T> =
143    lock_api::MutexGuard<'a, RawPriorityInheritingLock<linux_futex::Shared>, T>;
144
145/// Safe wrapper around `gettid`.
146///
147/// `gettid` is always successful on Linux.
148#[must_use]
149pub fn gettid() -> libc::pid_t {
150    unsafe { libc::gettid() }
151}
152
153#[inline]
154fn get_cached_tid() -> libc::pid_t {
155    let mut tid = 0;
156    TID.with(|it| {
157        tid = *it;
158    });
159
160    tid
161}