Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 306ba83

Browse files
committed
Auto merge of #95035 - m-ou-se:futex-locks-on-linux, r=Amanieu
Replace Linux Mutex and Condvar with futex based ones. Tracking issue: #93740
2 parents f262ca1 + 650315e commit 306ba83

File tree

5 files changed

+251
-41
lines changed

5 files changed

+251
-41
lines changed

‎library/std/src/sync/condvar/tests.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ fn wait_timeout_wake() {
191191

192192
#[test]
193193
#[should_panic]
194-
#[cfg_attr(not(unix), ignore)]
194+
#[cfg(all(unix, not(target_os = "linux"), not(target_os = "android")))]
195195
fn two_mutexes() {
196196
let m = Arc::new(Mutex::new(()));
197197
let m2 = m.clone();

‎library/std/src/sys/unix/futex.rs‎

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,46 @@
44
all(target_os = "emscripten", target_feature = "atomics")
55
))]
66

7-
#[cfg(any(target_os = "linux", target_os = "android"))]
8-
use crate::convert::TryInto;
9-
#[cfg(any(target_os = "linux", target_os = "android"))]
10-
use crate::ptr::null;
117
use crate::sync::atomic::AtomicI32;
128
use crate::time::Duration;
139

1410
#[cfg(any(target_os = "linux", target_os = "android"))]
15-
pub fn futex_wait(futex: &AtomicI32, expected: i32, timeout: Option<Duration>) {
16-
let timespec = timeout.and_then(|d| {
17-
Some(libc::timespec {
18-
// Sleep forever if the timeout is longer than fits in a timespec.
19-
tv_sec: d.as_secs().try_into().ok()?,
20-
// This conversion never truncates, as subsec_nanos is always <1e9.
21-
tv_nsec: d.subsec_nanos() as _,
22-
})
23-
});
24-
unsafe {
25-
libc::syscall(
26-
libc::SYS_futex,
27-
futex as *const AtomicI32,
28-
libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG,
29-
expected,
30-
timespec.as_ref().map_or(null(), |d| d as *const libc::timespec),
31-
);
11+
pub fn futex_wait(futex: &AtomicI32, expected: i32, timeout: Option<Duration>) -> bool {
12+
use super::time::Timespec;
13+
use crate::ptr::null;
14+
use crate::sync::atomic::Ordering::Relaxed;
15+
16+
// Calculate the timeout as an absolute timespec.
17+
//
18+
// Overflows are rounded up to an infinite timeout (None).
19+
let timespec =
20+
timeout.and_then(|d| Some(Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d)?));
21+
22+
loop {
23+
// No need to wait if the value already changed.
24+
if futex.load(Relaxed) != expected {
25+
return true;
26+
}
27+
28+
// Use FUTEX_WAIT_BITSET rather than FUTEX_WAIT to be able to give an
29+
// absolute time rather than a relative time.
30+
let r = unsafe {
31+
libc::syscall(
32+
libc::SYS_futex,
33+
futex as *const AtomicI32,
34+
libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,
35+
expected,
36+
timespec.as_ref().map_or(null(), |t| &t.t as *const libc::timespec),
37+
null::<u32>(), // This argument is unused for FUTEX_WAIT_BITSET.
38+
!0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT.
39+
)
40+
};
41+
42+
match (r < 0).then(super::os::errno) {
43+
Some(libc::ETIMEDOUT) => return false,
44+
Some(libc::EINTR) => continue,
45+
_ => return true,
46+
}
3247
}
3348
}
3449

@@ -65,6 +80,18 @@ pub fn futex_wake(futex: &AtomicI32) {
6580
}
6681
}
6782

83+
#[cfg(any(target_os = "linux", target_os = "android"))]
84+
pub fn futex_wake_all(futex: &AtomicI32) {
85+
unsafe {
86+
libc::syscall(
87+
libc::SYS_futex,
88+
futex as *const AtomicI32,
89+
libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG,
90+
i32::MAX,
91+
);
92+
}
93+
}
94+
6895
#[cfg(target_os = "emscripten")]
6996
pub fn futex_wake(futex: &AtomicI32) {
7097
extern "C" {
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::sync::atomic::{
2+
AtomicI32,
3+
Ordering::{Acquire, Relaxed, Release},
4+
};
5+
use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
6+
use crate::time::Duration;
7+
8+
pub type MovableMutex = Mutex;
9+
pub type MovableCondvar = Condvar;
10+
11+
pub struct Mutex {
12+
/// 0: unlocked
13+
/// 1: locked, no other threads waiting
14+
/// 2: locked, and other threads waiting (contended)
15+
futex: AtomicI32,
16+
}
17+
18+
impl Mutex {
19+
#[inline]
20+
pub const fn new() -> Self {
21+
Self { futex: AtomicI32::new(0) }
22+
}
23+
24+
#[inline]
25+
pub unsafe fn init(&mut self) {}
26+
27+
#[inline]
28+
pub unsafe fn destroy(&self) {}
29+
30+
#[inline]
31+
pub unsafe fn try_lock(&self) -> bool {
32+
self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_ok()
33+
}
34+
35+
#[inline]
36+
pub unsafe fn lock(&self) {
37+
if self.futex.compare_exchange(0, 1, Acquire, Relaxed).is_err() {
38+
self.lock_contended();
39+
}
40+
}
41+
42+
#[cold]
43+
fn lock_contended(&self) {
44+
// Spin first to speed things up if the lock is released quickly.
45+
let mut state = self.spin();
46+
47+
// If it's unlocked now, attempt to take the lock
48+
// without marking it as contended.
49+
if state == 0 {
50+
match self.futex.compare_exchange(0, 1, Acquire, Relaxed) {
51+
Ok(_) => return, // Locked!
52+
Err(s) => state = s,
53+
}
54+
}
55+
56+
loop {
57+
// Put the lock in contended state.
58+
// We avoid an unnecessary write if it as already set to 2,
59+
// to be friendlier for the caches.
60+
if state != 2 && self.futex.swap(2, Acquire) == 0 {
61+
// We changed it from 0 to 2, so we just succesfully locked it.
62+
return;
63+
}
64+
65+
// Wait for the futex to change state, assuming it is still 2.
66+
futex_wait(&self.futex, 2, None);
67+
68+
// Spin again after waking up.
69+
state = self.spin();
70+
}
71+
}
72+
73+
fn spin(&self) -> i32 {
74+
let mut spin = 100;
75+
loop {
76+
// We only use `load` (and not `swap` or `compare_exchange`)
77+
// while spinning, to be easier on the caches.
78+
let state = self.futex.load(Relaxed);
79+
80+
// We stop spinning when the mutex is unlocked (0),
81+
// but also when it's contended (2).
82+
if state != 1 || spin == 0 {
83+
return state;
84+
}
85+
86+
crate::hint::spin_loop();
87+
spin -= 1;
88+
}
89+
}
90+
91+
#[inline]
92+
pub unsafe fn unlock(&self) {
93+
if self.futex.swap(0, Release) == 2 {
94+
// We only wake up one thread. When that thread locks the mutex, it
95+
// will mark the mutex as contended (2) (see lock_contended above),
96+
// which makes sure that any other waiting threads will also be
97+
// woken up eventually.
98+
self.wake();
99+
}
100+
}
101+
102+
#[cold]
103+
fn wake(&self) {
104+
futex_wake(&self.futex);
105+
}
106+
}
107+
108+
pub struct Condvar {
109+
// The value of this atomic is simply incremented on every notification.
110+
// This is used by `.wait()` to not miss any notifications after
111+
// unlocking the mutex and before waiting for notifications.
112+
futex: AtomicI32,
113+
}
114+
115+
impl Condvar {
116+
#[inline]
117+
pub const fn new() -> Self {
118+
Self { futex: AtomicI32::new(0) }
119+
}
120+
121+
#[inline]
122+
pub unsafe fn init(&mut self) {}
123+
124+
#[inline]
125+
pub unsafe fn destroy(&self) {}
126+
127+
// All the memory orderings here are `Relaxed`,
128+
// because synchronization is done by unlocking and locking the mutex.
129+
130+
pub unsafe fn notify_one(&self) {
131+
self.futex.fetch_add(1, Relaxed);
132+
futex_wake(&self.futex);
133+
}
134+
135+
pub unsafe fn notify_all(&self) {
136+
self.futex.fetch_add(1, Relaxed);
137+
futex_wake_all(&self.futex);
138+
}
139+
140+
pub unsafe fn wait(&self, mutex: &Mutex) {
141+
self.wait_optional_timeout(mutex, None);
142+
}
143+
144+
pub unsafe fn wait_timeout(&self, mutex: &Mutex, timeout: Duration) -> bool {
145+
self.wait_optional_timeout(mutex, Some(timeout))
146+
}
147+
148+
unsafe fn wait_optional_timeout(&self, mutex: &Mutex, timeout: Option<Duration>) -> bool {
149+
// Examine the notification counter _before_ we unlock the mutex.
150+
let futex_value = self.futex.load(Relaxed);
151+
152+
// Unlock the mutex before going to sleep.
153+
mutex.unlock();
154+
155+
// Wait, but only if there hasn't been any
156+
// notification since we unlocked the mutex.
157+
let r = futex_wait(&self.futex, futex_value, timeout);
158+
159+
// Lock the mutex again.
160+
mutex.lock();
161+
162+
r
163+
}
164+
}
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
1-
mod pthread_condvar;
2-
mod pthread_mutex;
3-
mod pthread_remutex;
4-
mod pthread_rwlock;
5-
pub use pthread_condvar::{Condvar, MovableCondvar};
6-
pub use pthread_mutex::{MovableMutex, Mutex};
7-
pub use pthread_remutex::ReentrantMutex;
8-
pub use pthread_rwlock::{MovableRWLock, RWLock};
1+
cfg_if::cfg_if! {
2+
if #[cfg(any(
3+
target_os = "linux",
4+
target_os = "android",
5+
))] {
6+
mod futex;
7+
#[allow(dead_code)]
8+
mod pthread_mutex; // Only used for PthreadMutexAttr, needed by pthread_remutex.
9+
mod pthread_remutex; // FIXME: Implement this using a futex
10+
mod pthread_rwlock; // FIXME: Implement this using a futex
11+
pub use futex::{Mutex, MovableMutex, Condvar, MovableCondvar};
12+
pub use pthread_remutex::ReentrantMutex;
13+
pub use pthread_rwlock::{RWLock, MovableRWLock};
14+
} else {
15+
mod pthread_mutex;
16+
mod pthread_remutex;
17+
mod pthread_rwlock;
18+
mod pthread_condvar;
19+
pub use pthread_mutex::{Mutex, MovableMutex};
20+
pub use pthread_remutex::ReentrantMutex;
21+
pub use pthread_rwlock::{RWLock, MovableRWLock};
22+
pub use pthread_condvar::{Condvar, MovableCondvar};
23+
}
24+
}

‎library/std/src/sys/unix/time.rs‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ use crate::convert::TryInto;
99
const NSEC_PER_SEC: u64 = 1_000_000_000;
1010

1111
#[derive(Copy, Clone)]
12-
struct Timespec {
13-
t: libc::timespec,
12+
pub(incrate::sys::unix)struct Timespec {
13+
pubt: libc::timespec,
1414
}
1515

1616
impl Timespec {
1717
const fn zero() -> Timespec {
1818
Timespec { t: libc::timespec { tv_sec: 0, tv_nsec: 0 } }
1919
}
2020

21-
fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
21+
pubfn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
2222
if self >= other {
2323
// NOTE(eddyb) two aspects of this `if`-`else` are required for LLVM
2424
// to optimize it into a branchless form (see also #75545):
@@ -51,7 +51,7 @@ impl Timespec {
5151
}
5252
}
5353

54-
fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
54+
pubfn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
5555
let mut secs = other
5656
.as_secs()
5757
.try_into() // <- target type would be `libc::time_t`
@@ -68,7 +68,7 @@ impl Timespec {
6868
Some(Timespec { t: libc::timespec { tv_sec: secs, tv_nsec: nsec as _ } })
6969
}
7070

71-
fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
71+
pubfn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
7272
let mut secs = other
7373
.as_secs()
7474
.try_into() // <- target type would be `libc::time_t`
@@ -266,6 +266,7 @@ mod inner {
266266
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
267267
mod inner {
268268
use crate::fmt;
269+
use crate::mem::MaybeUninit;
269270
use crate::sys::cvt;
270271
use crate::time::Duration;
271272

@@ -285,7 +286,7 @@ mod inner {
285286

286287
impl Instant {
287288
pub fn now() -> Instant {
288-
Instant { t: now(libc::CLOCK_MONOTONIC) }
289+
Instant { t: Timespec::now(libc::CLOCK_MONOTONIC) }
289290
}
290291

291292
pub fn checked_sub_instant(&self, other: &Instant) -> Option<Duration> {
@@ -312,7 +313,7 @@ mod inner {
312313

313314
impl SystemTime {
314315
pub fn now() -> SystemTime {
315-
SystemTime { t: now(libc::CLOCK_REALTIME) }
316+
SystemTime { t: Timespec::now(libc::CLOCK_REALTIME) }
316317
}
317318

318319
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
@@ -348,9 +349,11 @@ mod inner {
348349
#[cfg(any(target_os = "dragonfly", target_os = "espidf"))]
349350
pub type clock_t = libc::c_ulong;
350351

351-
fn now(clock: clock_t) -> Timespec {
352-
let mut t = Timespec { t: libc::timespec { tv_sec: 0, tv_nsec: 0 } };
353-
cvt(unsafe { libc::clock_gettime(clock, &mut t.t) }).unwrap();
354-
t
352+
impl Timespec {
353+
pub fn now(clock: clock_t) -> Timespec {
354+
let mut t = MaybeUninit::uninit();
355+
cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
356+
Timespec { t: unsafe { t.assume_init() } }
357+
}
355358
}
356359
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /