Skip to content

Commit 05eb53f

Browse files
committed
Adds priority-inheritance futexes for mutexex
This uses FUTEX_LOCK_PI and FUTEX_UNLOCK_PI on Linux.
1 parent 8d94e06 commit 05eb53f

File tree

4 files changed

+139
-1
lines changed

4 files changed

+139
-1
lines changed

library/std/src/sys/pal/unix/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod net;
2323
#[cfg(target_os = "l4re")]
2424
pub use self::l4re::net;
2525
pub mod os;
26+
pub mod pi_futex;
2627
pub mod pipe;
2728
pub mod process;
2829
pub mod stack_overflow;
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#![cfg(any(target_os = "linux", target_os = "android"))]
2+
3+
use crate::sync::atomic::AtomicU32;
4+
use crate::sys::cvt;
5+
use crate::{io, ptr};
6+
7+
pub const fn unlocked() -> u32 {
8+
0
9+
}
10+
11+
pub fn locked() -> u32 {
12+
(unsafe { libc::gettid() }) as _
13+
}
14+
15+
pub fn is_contended(futex_val: u32) -> bool {
16+
(futex_val & libc::FUTEX_WAITERS) != 0
17+
}
18+
19+
pub fn is_owned_died(futex_val: u32) -> bool {
20+
(futex_val & libc::FUTEX_OWNER_DIED) != 0
21+
}
22+
23+
pub fn futex_lock(futex: &AtomicU32) -> io::Result<()> {
24+
loop {
25+
match cvt(unsafe {
26+
libc::syscall(
27+
libc::SYS_futex,
28+
ptr::from_ref(futex),
29+
libc::FUTEX_LOCK_PI | libc::FUTEX_PRIVATE_FLAG,
30+
0,
31+
ptr::null::<u32>(),
32+
// remaining args are unused
33+
)
34+
}) {
35+
Ok(_) => return Ok(()),
36+
Err(e) if e.raw_os_error() == Some(libc::EINTR) => continue,
37+
Err(e) => return Err(e),
38+
}
39+
}
40+
}
41+
42+
pub fn futex_unlock(futex: &AtomicU32) -> io::Result<()> {
43+
cvt(unsafe {
44+
libc::syscall(
45+
libc::SYS_futex,
46+
ptr::from_ref(futex),
47+
libc::FUTEX_UNLOCK_PI | libc::FUTEX_PRIVATE_FLAG,
48+
// remaining args are unused
49+
)
50+
})
51+
.map(|_| ())
52+
}

library/std/src/sys/sync/mutex/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
cfg_if::cfg_if! {
22
if #[cfg(any(
3-
all(target_os = "windows", not(target_vendor = "win7")),
43
target_os = "linux",
54
target_os = "android",
5+
))] {
6+
mod pi_futex;
7+
pub use pi_futex::Mutex;
8+
} else if #[cfg(any(
9+
all(target_os = "windows", not(target_vendor = "win7")),
610
target_os = "freebsd",
711
target_os = "openbsd",
812
target_os = "dragonfly",
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::sync::atomic::AtomicU32;
2+
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
3+
use crate::sys::pi_futex as pi;
4+
5+
pub struct Mutex {
6+
futex: AtomicU32,
7+
}
8+
9+
impl Mutex {
10+
#[inline]
11+
pub const fn new() -> Self {
12+
Self { futex: AtomicU32::new(pi::unlocked()) }
13+
}
14+
15+
#[inline]
16+
pub fn try_lock(&self) -> bool {
17+
self.futex.compare_exchange(pi::unlocked(), pi::locked(), Acquire, Relaxed).is_ok()
18+
}
19+
20+
#[inline]
21+
pub fn lock(&self) {
22+
if self.futex.compare_exchange(pi::unlocked(), pi::locked(), Acquire, Relaxed).is_err() {
23+
self.lock_contended();
24+
}
25+
}
26+
27+
#[cold]
28+
fn lock_contended(&self) {
29+
// Spin first to speed things up if the lock is released quickly.
30+
let state = self.spin();
31+
32+
// If it's unlocked now, attempt to take the lock.
33+
if state == pi::unlocked() {
34+
if self.try_lock() {
35+
return;
36+
}
37+
};
38+
39+
pi::futex_lock(&self.futex).expect("failed to lock mutex");
40+
41+
let state = self.futex.load(Relaxed);
42+
if pi::is_owned_died(state) {
43+
panic!("failed to lock mutex because its owner is died");
44+
}
45+
}
46+
47+
fn spin(&self) -> u32 {
48+
let mut spin = 100;
49+
loop {
50+
// We only use `load` (and not `swap` or `compare_exchange`)
51+
// while spinning, to be easier on the caches.
52+
let state = self.futex.load(Relaxed);
53+
54+
// We stop spinning when the mutex is unlocked,
55+
// but also when it's contended.
56+
if state == pi::unlocked() || pi::is_contended(state) || spin == 0 {
57+
return state;
58+
}
59+
60+
crate::hint::spin_loop();
61+
spin -= 1;
62+
}
63+
}
64+
65+
#[inline]
66+
pub unsafe fn unlock(&self) {
67+
if self.futex.compare_exchange(pi::locked(), pi::unlocked(), Release, Relaxed).is_err() {
68+
// We only wake up one thread. When that thread locks the mutex,
69+
// the kernel will mark the mutex as contended automatically
70+
// (futex != pi::locked() in this case),
71+
// which makes sure that any other waiting threads will also be
72+
// woken up eventually.
73+
self.wake();
74+
}
75+
}
76+
77+
#[cold]
78+
fn wake(&self) {
79+
pi::futex_unlock(&self.futex).unwrap();
80+
}
81+
}

0 commit comments

Comments
 (0)