Skip to content

Commit c5df2f0

Browse files
authored
Rollup merge of #98707 - joboet:fuchsia_locks, r=m-ou-se
std: use futex-based locks on Fuchsia This switches `Condvar` and `RwLock` to the futex-based implementation currently used on Linux and some BSDs. Additionally, `Mutex` now has its own, priority-inheriting implementation based on the mutex in Fuchsia's `libsync`. It differs from the original in that it panics instead of aborting when reentrant locking is detected. ````@rustbot```` ping fuchsia r? ````@m-ou-se````
2 parents 1673f14 + 8ba02f1 commit c5df2f0

File tree

5 files changed

+256
-64
lines changed

5 files changed

+256
-64
lines changed

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

+21-7
Original file line numberDiff line numberDiff line change
@@ -240,25 +240,34 @@ pub fn futex_wake_all(futex: &AtomicU32) {
240240
}
241241

242242
#[cfg(target_os = "fuchsia")]
243-
mod zircon {
244-
type zx_time_t = i64;
245-
type zx_futex_t = crate::sync::atomic::AtomicU32;
246-
type zx_handle_t = u32;
247-
type zx_status_t = i32;
243+
pub mod zircon {
244+
pub type zx_futex_t = crate::sync::atomic::AtomicU32;
245+
pub type zx_handle_t = u32;
246+
pub type zx_status_t = i32;
247+
pub type zx_time_t = i64;
248248

249249
pub const ZX_HANDLE_INVALID: zx_handle_t = 0;
250-
pub const ZX_ERR_TIMED_OUT: zx_status_t = -21;
250+
251251
pub const ZX_TIME_INFINITE: zx_time_t = zx_time_t::MAX;
252252

253+
pub const ZX_OK: zx_status_t = 0;
254+
pub const ZX_ERR_INVALID_ARGS: zx_status_t = -10;
255+
pub const ZX_ERR_BAD_HANDLE: zx_status_t = -11;
256+
pub const ZX_ERR_WRONG_TYPE: zx_status_t = -12;
257+
pub const ZX_ERR_BAD_STATE: zx_status_t = -20;
258+
pub const ZX_ERR_TIMED_OUT: zx_status_t = -21;
259+
253260
extern "C" {
261+
pub fn zx_clock_get_monotonic() -> zx_time_t;
254262
pub fn zx_futex_wait(
255263
value_ptr: *const zx_futex_t,
256264
current_value: zx_futex_t,
257265
new_futex_owner: zx_handle_t,
258266
deadline: zx_time_t,
259267
) -> zx_status_t;
260268
pub fn zx_futex_wake(value_ptr: *const zx_futex_t, wake_count: u32) -> zx_status_t;
261-
pub fn zx_clock_get_monotonic() -> zx_time_t;
269+
pub fn zx_futex_wake_single_owner(value_ptr: *const zx_futex_t) -> zx_status_t;
270+
pub fn zx_thread_self() -> zx_handle_t;
262271
}
263272
}
264273

@@ -287,3 +296,8 @@ pub fn futex_wake(futex: &AtomicU32) -> bool {
287296
unsafe { zircon::zx_futex_wake(futex, 1) };
288297
false
289298
}
299+
300+
#[cfg(target_os = "fuchsia")]
301+
pub fn futex_wake_all(futex: &AtomicU32) {
302+
unsafe { zircon::zx_futex_wake(futex, u32::MAX) };
303+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//! A priority inheriting mutex for Fuchsia.
2+
//!
3+
//! This is a port of the [mutex in Fuchsia's libsync]. Contrary to the original,
4+
//! it does not abort the process when reentrant locking is detected, but deadlocks.
5+
//!
6+
//! Priority inheritance is achieved by storing the owning thread's handle in an
7+
//! atomic variable. Fuchsia's futex operations support setting an owner thread
8+
//! for a futex, which can boost that thread's priority while the futex is waited
9+
//! upon.
10+
//!
11+
//! libsync is licenced under the following BSD-style licence:
12+
//!
13+
//! Copyright 2016 The Fuchsia Authors.
14+
//!
15+
//! Redistribution and use in source and binary forms, with or without
16+
//! modification, are permitted provided that the following conditions are
17+
//! met:
18+
//!
19+
//! * Redistributions of source code must retain the above copyright
20+
//! notice, this list of conditions and the following disclaimer.
21+
//! * Redistributions in binary form must reproduce the above
22+
//! copyright notice, this list of conditions and the following
23+
//! disclaimer in the documentation and/or other materials provided
24+
//! with the distribution.
25+
//!
26+
//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27+
//! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28+
//! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29+
//! A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30+
//! OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31+
//! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32+
//! LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33+
//! DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34+
//! THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35+
//! (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36+
//! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37+
//!
38+
//! [mutex in Fuchsia's libsync]: https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/sync/mutex.c
39+
40+
use crate::sync::atomic::{
41+
AtomicU32,
42+
Ordering::{Acquire, Relaxed, Release},
43+
};
44+
use crate::sys::futex::zircon::{
45+
zx_futex_wait, zx_futex_wake_single_owner, zx_handle_t, zx_thread_self, ZX_ERR_BAD_HANDLE,
46+
ZX_ERR_BAD_STATE, ZX_ERR_INVALID_ARGS, ZX_ERR_TIMED_OUT, ZX_ERR_WRONG_TYPE, ZX_OK,
47+
ZX_TIME_INFINITE,
48+
};
49+
50+
// The lowest two bits of a `zx_handle_t` are always set, so the lowest bit is used to mark the
51+
// mutex as contested by clearing it.
52+
const CONTESTED_BIT: u32 = 1;
53+
// This can never be a valid `zx_handle_t`.
54+
const UNLOCKED: u32 = 0;
55+
56+
pub type MovableMutex = Mutex;
57+
58+
pub struct Mutex {
59+
futex: AtomicU32,
60+
}
61+
62+
#[inline]
63+
fn to_state(owner: zx_handle_t) -> u32 {
64+
owner
65+
}
66+
67+
#[inline]
68+
fn to_owner(state: u32) -> zx_handle_t {
69+
state | CONTESTED_BIT
70+
}
71+
72+
#[inline]
73+
fn is_contested(state: u32) -> bool {
74+
state & CONTESTED_BIT == 0
75+
}
76+
77+
#[inline]
78+
fn mark_contested(state: u32) -> u32 {
79+
state & !CONTESTED_BIT
80+
}
81+
82+
impl Mutex {
83+
#[inline]
84+
pub const fn new() -> Mutex {
85+
Mutex { futex: AtomicU32::new(UNLOCKED) }
86+
}
87+
88+
#[inline]
89+
pub unsafe fn init(&mut self) {}
90+
91+
#[inline]
92+
pub unsafe fn try_lock(&self) -> bool {
93+
let thread_self = zx_thread_self();
94+
self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed).is_ok()
95+
}
96+
97+
#[inline]
98+
pub unsafe fn lock(&self) {
99+
let thread_self = zx_thread_self();
100+
if let Err(state) =
101+
self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed)
102+
{
103+
self.lock_contested(state, thread_self);
104+
}
105+
}
106+
107+
#[cold]
108+
fn lock_contested(&self, mut state: u32, thread_self: zx_handle_t) {
109+
let owned_state = mark_contested(to_state(thread_self));
110+
loop {
111+
// Mark the mutex as contested if it is not already.
112+
let contested = mark_contested(state);
113+
if is_contested(state)
114+
|| self.futex.compare_exchange(state, contested, Relaxed, Relaxed).is_ok()
115+
{
116+
// The mutex has been marked as contested, wait for the state to change.
117+
unsafe {
118+
match zx_futex_wait(
119+
&self.futex,
120+
AtomicU32::new(contested),
121+
to_owner(state),
122+
ZX_TIME_INFINITE,
123+
) {
124+
ZX_OK | ZX_ERR_BAD_STATE | ZX_ERR_TIMED_OUT => (),
125+
// Note that if a thread handle is reused after its associated thread
126+
// exits without unlocking the mutex, an arbitrary thread's priority
127+
// could be boosted by the wait, but there is currently no way to
128+
// prevent that.
129+
ZX_ERR_INVALID_ARGS | ZX_ERR_BAD_HANDLE | ZX_ERR_WRONG_TYPE => {
130+
panic!(
131+
"either the current thread is trying to lock a mutex it has
132+
already locked, or the previous owner did not unlock the mutex
133+
before exiting"
134+
)
135+
}
136+
error => panic!("unexpected error in zx_futex_wait: {error}"),
137+
}
138+
}
139+
}
140+
141+
// The state has changed or a wakeup occured, try to lock the mutex.
142+
match self.futex.compare_exchange(UNLOCKED, owned_state, Acquire, Relaxed) {
143+
Ok(_) => return,
144+
Err(updated) => state = updated,
145+
}
146+
}
147+
}
148+
149+
#[inline]
150+
pub unsafe fn unlock(&self) {
151+
if is_contested(self.futex.swap(UNLOCKED, Release)) {
152+
// The woken thread will mark the mutex as contested again,
153+
// and return here, waking until there are no waiters left,
154+
// in which case this is a noop.
155+
self.wake();
156+
}
157+
}
158+
159+
#[cold]
160+
fn wake(&self) {
161+
unsafe {
162+
zx_futex_wake_single_owner(&self.futex);
163+
}
164+
}
165+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use super::Mutex;
2+
use crate::sync::atomic::{AtomicU32, Ordering::Relaxed};
3+
use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
4+
use crate::time::Duration;
5+
6+
pub type MovableCondvar = Condvar;
7+
8+
pub struct Condvar {
9+
// The value of this atomic is simply incremented on every notification.
10+
// This is used by `.wait()` to not miss any notifications after
11+
// unlocking the mutex and before waiting for notifications.
12+
futex: AtomicU32,
13+
}
14+
15+
impl Condvar {
16+
#[inline]
17+
pub const fn new() -> Self {
18+
Self { futex: AtomicU32::new(0) }
19+
}
20+
21+
// All the memory orderings here are `Relaxed`,
22+
// because synchronization is done by unlocking and locking the mutex.
23+
24+
pub unsafe fn notify_one(&self) {
25+
self.futex.fetch_add(1, Relaxed);
26+
futex_wake(&self.futex);
27+
}
28+
29+
pub unsafe fn notify_all(&self) {
30+
self.futex.fetch_add(1, Relaxed);
31+
futex_wake_all(&self.futex);
32+
}
33+
34+
pub unsafe fn wait(&self, mutex: &Mutex) {
35+
self.wait_optional_timeout(mutex, None);
36+
}
37+
38+
pub unsafe fn wait_timeout(&self, mutex: &Mutex, timeout: Duration) -> bool {
39+
self.wait_optional_timeout(mutex, Some(timeout))
40+
}
41+
42+
unsafe fn wait_optional_timeout(&self, mutex: &Mutex, timeout: Option<Duration>) -> bool {
43+
// Examine the notification counter _before_ we unlock the mutex.
44+
let futex_value = self.futex.load(Relaxed);
45+
46+
// Unlock the mutex before going to sleep.
47+
mutex.unlock();
48+
49+
// Wait, but only if there hasn't been any
50+
// notification since we unlocked the mutex.
51+
let r = futex_wait(&self.futex, futex_value, timeout);
52+
53+
// Lock the mutex again.
54+
mutex.lock();
55+
56+
r
57+
}
58+
}

library/std/src/sys/unix/locks/futex.rs library/std/src/sys/unix/locks/futex_mutex.rs

+1-55
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ use crate::sync::atomic::{
22
AtomicU32,
33
Ordering::{Acquire, Relaxed, Release},
44
};
5-
use crate::sys::futex::{futex_wait, futex_wake, futex_wake_all};
6-
use crate::time::Duration;
5+
use crate::sys::futex::{futex_wait, futex_wake};
76

87
pub type MovableMutex = Mutex;
9-
pub type MovableCondvar = Condvar;
108

119
pub struct Mutex {
1210
/// 0: unlocked
@@ -101,55 +99,3 @@ impl Mutex {
10199
futex_wake(&self.futex);
102100
}
103101
}
104-
105-
pub struct Condvar {
106-
// The value of this atomic is simply incremented on every notification.
107-
// This is used by `.wait()` to not miss any notifications after
108-
// unlocking the mutex and before waiting for notifications.
109-
futex: AtomicU32,
110-
}
111-
112-
impl Condvar {
113-
#[inline]
114-
pub const fn new() -> Self {
115-
Self { futex: AtomicU32::new(0) }
116-
}
117-
118-
// All the memory orderings here are `Relaxed`,
119-
// because synchronization is done by unlocking and locking the mutex.
120-
121-
pub unsafe fn notify_one(&self) {
122-
self.futex.fetch_add(1, Relaxed);
123-
futex_wake(&self.futex);
124-
}
125-
126-
pub unsafe fn notify_all(&self) {
127-
self.futex.fetch_add(1, Relaxed);
128-
futex_wake_all(&self.futex);
129-
}
130-
131-
pub unsafe fn wait(&self, mutex: &Mutex) {
132-
self.wait_optional_timeout(mutex, None);
133-
}
134-
135-
pub unsafe fn wait_timeout(&self, mutex: &Mutex, timeout: Duration) -> bool {
136-
self.wait_optional_timeout(mutex, Some(timeout))
137-
}
138-
139-
unsafe fn wait_optional_timeout(&self, mutex: &Mutex, timeout: Option<Duration>) -> bool {
140-
// Examine the notification counter _before_ we unlock the mutex.
141-
let futex_value = self.futex.load(Relaxed);
142-
143-
// Unlock the mutex before going to sleep.
144-
mutex.unlock();
145-
146-
// Wait, but only if there hasn't been any
147-
// notification since we unlocked the mutex.
148-
let r = futex_wait(&self.futex, futex_value, timeout);
149-
150-
// Lock the mutex again.
151-
mutex.lock();
152-
153-
r
154-
}
155-
}

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@ cfg_if::cfg_if! {
77
target_os = "openbsd",
88
target_os = "dragonfly",
99
))] {
10-
mod futex;
10+
mod futex_mutex;
1111
mod futex_rwlock;
12-
pub(crate) use futex::{Mutex, MovableMutex, MovableCondvar};
12+
mod futex_condvar;
13+
pub(crate) use futex_mutex::{Mutex, MovableMutex};
1314
pub(crate) use futex_rwlock::{RwLock, MovableRwLock};
15+
pub(crate) use futex_condvar::MovableCondvar;
16+
} else if #[cfg(target_os = "fuchsia")] {
17+
mod fuchsia_mutex;
18+
mod futex_rwlock;
19+
mod futex_condvar;
20+
pub(crate) use fuchsia_mutex::{Mutex, MovableMutex};
21+
pub(crate) use futex_rwlock::{RwLock, MovableRwLock};
22+
pub(crate) use futex_condvar::MovableCondvar;
1423
} else {
1524
mod pthread_mutex;
1625
mod pthread_rwlock;

0 commit comments

Comments
 (0)