Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1cf7f38

Browse files
committedJul 30, 2024··
std: refactor pthread-based synchronization
The non-trivial code for `pthread_condvar` is duplicated across the thread parking and the `Mutex`/`Condvar` implementations. This PR moves that code into `sys::pal`, which now exposes an `unsafe` wrapper type for `pthread_mutex_t` and `pthread_condvar_t`. Additionally, this PR replaces `LazyBox` with `OnceBox`, thus simplifying the allocation logic.
1 parent 1ddedba commit 1cf7f38

File tree

13 files changed

+536
-542
lines changed

13 files changed

+536
-542
lines changed
 

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod process;
3131
pub mod rand;
3232
pub mod stack_overflow;
3333
pub mod stdio;
34+
pub mod sync;
3435
pub mod thread;
3536
pub mod thread_parking;
3637
pub mod time;
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use super::Mutex;
2+
use crate::cell::UnsafeCell;
3+
use crate::pin::Pin;
4+
use crate::sys::pal::time::Timespec;
5+
#[cfg(not(target_os = "nto"))]
6+
use crate::sys::pal::time::TIMESPEC_MAX;
7+
#[cfg(target_os = "nto")]
8+
use crate::sys::pal::time::TIMESPEC_MAX_CAPPED;
9+
use crate::time::Duration;
10+
11+
pub struct Condvar {
12+
inner: UnsafeCell<libc::pthread_cond_t>,
13+
}
14+
15+
impl Condvar {
16+
pub fn new() -> Condvar {
17+
Condvar { inner: UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER) }
18+
}
19+
20+
#[inline]
21+
fn raw(&self) -> *mut libc::pthread_cond_t {
22+
self.inner.get()
23+
}
24+
25+
/// # Safety
26+
/// `init` must have been called.
27+
#[inline]
28+
pub unsafe fn notify_one(self: Pin<&Self>) {
29+
let r = unsafe { libc::pthread_cond_signal(self.raw()) };
30+
debug_assert_eq!(r, 0);
31+
}
32+
33+
/// # Safety
34+
/// `init` must have been called.
35+
#[inline]
36+
pub unsafe fn notify_all(self: Pin<&Self>) {
37+
let r = unsafe { libc::pthread_cond_broadcast(self.raw()) };
38+
debug_assert_eq!(r, 0);
39+
}
40+
41+
/// # Safety
42+
/// * `init` must have been called.
43+
/// * `mutex` must be locked by the current thread.
44+
/// * This condition variable may only be used with the same mutex.
45+
#[inline]
46+
pub unsafe fn wait(self: Pin<&Self>, mutex: Pin<&Mutex>) {
47+
let r = unsafe { libc::pthread_cond_wait(self.raw(), mutex.raw()) };
48+
debug_assert_eq!(r, 0);
49+
}
50+
51+
/// # Safety
52+
/// * `init` must have been called.
53+
/// * `mutex` must be locked by the current thread.
54+
/// * This condition variable may only be used with the same mutex.
55+
pub unsafe fn wait_timeout(&self, mutex: Pin<&Mutex>, dur: Duration) -> bool {
56+
let mutex = mutex.raw();
57+
58+
// OSX implementation of `pthread_cond_timedwait` is buggy
59+
// with super long durations. When duration is greater than
60+
// 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
61+
// in macOS Sierra returns error 316.
62+
//
63+
// This program demonstrates the issue:
64+
// https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
65+
//
66+
// To work around this issue, the timeout is clamped to 1000 years.
67+
#[cfg(target_vendor = "apple")]
68+
let dur = Duration::min(dur, Duration::from_secs(1000 * 365 * 86400));
69+
70+
let timeout = Timespec::now(Self::CLOCK).checked_add_duration(&dur);
71+
72+
#[cfg(not(target_os = "nto"))]
73+
let timeout = timeout.and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
74+
75+
#[cfg(target_os = "nto")]
76+
let timeout = timeout.and_then(|t| t.to_timespec_capped()).unwrap_or(TIMESPEC_MAX_CAPPED);
77+
78+
let r = unsafe { libc::pthread_cond_timedwait(self.raw(), mutex, &timeout) };
79+
assert!(r == libc::ETIMEDOUT || r == 0);
80+
r == 0
81+
}
82+
}
83+
84+
#[cfg(not(any(
85+
target_os = "android",
86+
target_vendor = "apple",
87+
target_os = "espidf",
88+
target_os = "horizon",
89+
target_os = "l4re",
90+
target_os = "redox",
91+
)))]
92+
impl Condvar {
93+
pub const PRECISE_TIMEOUT: bool = true;
94+
const CLOCK: libc::clockid_t = libc::CLOCK_MONOTONIC;
95+
96+
/// # Safety
97+
/// May only be called once.
98+
pub unsafe fn init(self: Pin<&mut Self>) {
99+
use crate::mem::MaybeUninit;
100+
101+
struct AttrGuard<'a>(pub &'a mut MaybeUninit<libc::pthread_condattr_t>);
102+
impl Drop for AttrGuard<'_> {
103+
fn drop(&mut self) {
104+
unsafe {
105+
let result = libc::pthread_condattr_destroy(self.0.as_mut_ptr());
106+
assert_eq!(result, 0);
107+
}
108+
}
109+
}
110+
111+
unsafe {
112+
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
113+
let r = libc::pthread_condattr_init(attr.as_mut_ptr());
114+
assert_eq!(r, 0);
115+
let attr = AttrGuard(&mut attr);
116+
let r = libc::pthread_condattr_setclock(attr.0.as_mut_ptr(), Self::CLOCK);
117+
assert_eq!(r, 0);
118+
let r = libc::pthread_cond_init(self.raw(), attr.0.as_ptr());
119+
assert_eq!(r, 0);
120+
}
121+
}
122+
}
123+
124+
// `pthread_condattr_setclock` is unfortunately not supported on these platforms.
125+
#[cfg(any(
126+
target_os = "android",
127+
target_vendor = "apple",
128+
target_os = "espidf",
129+
target_os = "horizon",
130+
target_os = "l4re",
131+
target_os = "redox",
132+
))]
133+
impl Condvar {
134+
pub const PRECISE_TIMEOUT: bool = false;
135+
const CLOCK: libc::clockid_t = libc::CLOCK_REALTIME;
136+
137+
/// # Safety
138+
/// May only be called once.
139+
pub unsafe fn init(self: Pin<&mut Self>) {
140+
if cfg!(any(target_os = "espidf", target_os = "horizon")) {
141+
// NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
142+
// So on that platform, init() should always be called.
143+
//
144+
// Similar story for the 3DS (horizon).
145+
let r = unsafe { libc::pthread_cond_init(self.raw(), crate::ptr::null()) };
146+
assert_eq!(r, 0);
147+
}
148+
}
149+
}
150+
151+
impl !Unpin for Condvar {}
152+
153+
unsafe impl Sync for Condvar {}
154+
unsafe impl Send for Condvar {}
155+
156+
impl Drop for Condvar {
157+
#[inline]
158+
fn drop(&mut self) {
159+
let r = unsafe { libc::pthread_cond_destroy(self.raw()) };
160+
if cfg!(target_os = "dragonfly") {
161+
// On DragonFly pthread_cond_destroy() returns EINVAL if called on
162+
// a condvar that was just initialized with
163+
// libc::PTHREAD_COND_INITIALIZER. Once it is used or
164+
// pthread_cond_init() is called, this behaviour no longer occurs.
165+
debug_assert!(r == 0 || r == libc::EINVAL);
166+
} else {
167+
debug_assert_eq!(r, 0);
168+
}
169+
}
170+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![cfg(not(any(
2+
target_os = "linux",
3+
target_os = "android",
4+
all(target_os = "emscripten", target_feature = "atomics"),
5+
target_os = "freebsd",
6+
target_os = "openbsd",
7+
target_os = "dragonfly",
8+
target_os = "fuchsia",
9+
)))]
10+
#![forbid(unsafe_op_in_unsafe_fn)]
11+
12+
mod condvar;
13+
mod mutex;
14+
15+
pub use condvar::Condvar;
16+
pub use mutex::Mutex;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use super::super::cvt_nz;
2+
use crate::cell::UnsafeCell;
3+
use crate::io::Error;
4+
use crate::mem::MaybeUninit;
5+
use crate::pin::Pin;
6+
7+
pub struct Mutex {
8+
inner: UnsafeCell<libc::pthread_mutex_t>,
9+
}
10+
11+
impl Mutex {
12+
pub fn new() -> Mutex {
13+
Mutex { inner: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER) }
14+
}
15+
16+
pub(super) fn raw(&self) -> *mut libc::pthread_mutex_t {
17+
self.inner.get()
18+
}
19+
20+
/// # Safety
21+
/// Must only be called once.
22+
pub unsafe fn init(self: Pin<&mut Self>) {
23+
// Issue #33770
24+
//
25+
// A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have
26+
// a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you
27+
// try to re-lock it from the same thread when you already hold a lock
28+
// (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_init.html).
29+
// This is the case even if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
30+
// (https://github.com/rust-lang/rust/issues/33770#issuecomment-220847521) -- in that
31+
// case, `pthread_mutexattr_settype(PTHREAD_MUTEX_DEFAULT)` will of course be the same
32+
// as setting it to `PTHREAD_MUTEX_NORMAL`, but not setting any mode will result in
33+
// a Mutex where re-locking is UB.
34+
//
35+
// In practice, glibc takes advantage of this undefined behavior to
36+
// implement hardware lock elision, which uses hardware transactional
37+
// memory to avoid acquiring the lock. While a transaction is in
38+
// progress, the lock appears to be unlocked. This isn't a problem for
39+
// other threads since the transactional memory will abort if a conflict
40+
// is detected, however no abort is generated when re-locking from the
41+
// same thread.
42+
//
43+
// Since locking the same mutex twice will result in two aliasing &mut
44+
// references, we instead create the mutex with type
45+
// PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to
46+
// re-lock it from the same thread, thus avoiding undefined behavior.
47+
unsafe {
48+
let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
49+
cvt_nz(libc::pthread_mutexattr_init(attr.as_mut_ptr())).unwrap();
50+
let attr = AttrGuard(&mut attr);
51+
cvt_nz(libc::pthread_mutexattr_settype(
52+
attr.0.as_mut_ptr(),
53+
libc::PTHREAD_MUTEX_NORMAL,
54+
))
55+
.unwrap();
56+
cvt_nz(libc::pthread_mutex_init(self.raw(), attr.0.as_ptr())).unwrap();
57+
}
58+
}
59+
60+
/// # Safety
61+
/// * If `init` was not called, reentrant locking causes undefined behaviour.
62+
/// * Destroying a locked mutex causes undefined behaviour.
63+
pub unsafe fn lock(self: Pin<&Self>) {
64+
#[cold]
65+
#[inline(never)]
66+
fn fail(r: i32) -> ! {
67+
let error = Error::from_raw_os_error(r);
68+
panic!("failed to lock mutex: {error}");
69+
}
70+
71+
let r = unsafe { libc::pthread_mutex_lock(self.raw()) };
72+
// As we set the mutex type to `PTHREAD_MUTEX_NORMAL` above, we expect
73+
// the lock call to never fail. Unfortunately however, some platforms
74+
// (Solaris) do not conform to the standard, and instead always provide
75+
// deadlock detection. How kind of them! Unfortunately that means that
76+
// we need to check the error code here. To save us from UB on other
77+
// less well-behaved platforms in the future, we do it even on "good"
78+
// platforms like macOS. See #120147 for more context.
79+
if r != 0 {
80+
fail(r)
81+
}
82+
}
83+
84+
/// # Safety
85+
/// * If `init` was not called, reentrant locking causes undefined behaviour.
86+
/// * Destroying a locked mutex causes undefined behaviour.
87+
pub unsafe fn try_lock(self: Pin<&Self>) -> bool {
88+
unsafe { libc::pthread_mutex_trylock(self.raw()) == 0 }
89+
}
90+
91+
/// # Safety
92+
/// The mutex must be locked by the current thread.
93+
pub unsafe fn unlock(self: Pin<&Self>) {
94+
let r = unsafe { libc::pthread_mutex_unlock(self.raw()) };
95+
debug_assert_eq!(r, 0);
96+
}
97+
}
98+
99+
impl !Unpin for Mutex {}
100+
101+
unsafe impl Send for Mutex {}
102+
unsafe impl Sync for Mutex {}
103+
104+
impl Drop for Mutex {
105+
fn drop(&mut self) {
106+
// SAFETY:
107+
// If `lock` or `init` was called, the mutex must have been pinned, so
108+
// it is still at the same location. Otherwise, `inner` must contain
109+
// `PTHREAD_MUTEX_INITIALIZER`, which is valid at all locations. Thus,
110+
// this call always destroys a valid mutex.
111+
let r = unsafe { libc::pthread_mutex_destroy(self.raw()) };
112+
if cfg!(target_os = "dragonfly") {
113+
// On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
114+
// mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
115+
// Once it is used (locked/unlocked) or pthread_mutex_init() is called,
116+
// this behaviour no longer occurs.
117+
debug_assert!(r == 0 || r == libc::EINVAL);
118+
} else {
119+
debug_assert_eq!(r, 0);
120+
}
121+
}
122+
}
123+
124+
struct AttrGuard<'a>(pub &'a mut MaybeUninit<libc::pthread_mutexattr_t>);
125+
126+
impl Drop for AttrGuard<'_> {
127+
fn drop(&mut self) {
128+
unsafe {
129+
let result = libc::pthread_mutexattr_destroy(self.0.as_mut_ptr());
130+
assert_eq!(result, 0);
131+
}
132+
}
133+
}
Lines changed: 54 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,198 +1,89 @@
1-
use crate::cell::UnsafeCell;
1+
#![forbid(unsafe_op_in_unsafe_fn)]
2+
3+
use crate::pin::Pin;
24
use crate::ptr;
3-
use crate::sync::atomic::AtomicPtr;
5+
use crate::sync::atomic::AtomicUsize;
46
use crate::sync::atomic::Ordering::Relaxed;
5-
use crate::sys::sync::{mutex, Mutex};
6-
#[cfg(not(target_os = "nto"))]
7-
use crate::sys::time::TIMESPEC_MAX;
8-
#[cfg(target_os = "nto")]
9-
use crate::sys::time::TIMESPEC_MAX_CAPPED;
10-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
11-
use crate::time::Duration;
12-
13-
struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>);
7+
use crate::sys::pal::sync as pal;
8+
use crate::sys::sync::Mutex;
9+
use crate::sys_common::once_box::OnceBox;
10+
use crate::time::{Duration, Instant};
1411

1512
pub struct Condvar {
16-
inner: LazyBox<AllocatedCondvar>,
17-
mutex: AtomicPtr<libc::pthread_mutex_t>,
13+
cvar: OnceBox<pal::Condvar>,
14+
mutex: AtomicUsize,
1815
}
1916

20-
#[inline]
21-
fn raw(c: &Condvar) -> *mut libc::pthread_cond_t {
22-
c.inner.0.get()
23-
}
24-
25-
unsafe impl Send for AllocatedCondvar {}
26-
unsafe impl Sync for AllocatedCondvar {}
27-
28-
impl LazyInit for AllocatedCondvar {
29-
fn init() -> Box<Self> {
30-
let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)));
31-
32-
cfg_if::cfg_if! {
33-
if #[cfg(any(
34-
target_os = "l4re",
35-
target_os = "android",
36-
target_os = "redox",
37-
target_vendor = "apple",
38-
))] {
39-
// `pthread_condattr_setclock` is unfortunately not supported on these platforms.
40-
} else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
41-
// NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
42-
// So on that platform, init() should always be called
43-
// Moreover, that platform does not have pthread_condattr_setclock support,
44-
// hence that initialization should be skipped as well
45-
//
46-
// Similar story for the 3DS (horizon).
47-
let r = unsafe { libc::pthread_cond_init(condvar.0.get(), crate::ptr::null()) };
48-
assert_eq!(r, 0);
49-
} else {
50-
use crate::mem::MaybeUninit;
51-
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
52-
let r = unsafe { libc::pthread_condattr_init(attr.as_mut_ptr()) };
53-
assert_eq!(r, 0);
54-
let r = unsafe { libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC) };
55-
assert_eq!(r, 0);
56-
let r = unsafe { libc::pthread_cond_init(condvar.0.get(), attr.as_ptr()) };
57-
assert_eq!(r, 0);
58-
let r = unsafe { libc::pthread_condattr_destroy(attr.as_mut_ptr()) };
59-
assert_eq!(r, 0);
60-
}
61-
}
62-
63-
condvar
17+
impl Condvar {
18+
pub const fn new() -> Condvar {
19+
Condvar { cvar: OnceBox::new(), mutex: AtomicUsize::new(0) }
6420
}
65-
}
6621

67-
impl Drop for AllocatedCondvar {
6822
#[inline]
69-
fn drop(&mut self) {
70-
let r = unsafe { libc::pthread_cond_destroy(self.0.get()) };
71-
if cfg!(target_os = "dragonfly") {
72-
// On DragonFly pthread_cond_destroy() returns EINVAL if called on
73-
// a condvar that was just initialized with
74-
// libc::PTHREAD_COND_INITIALIZER. Once it is used or
75-
// pthread_cond_init() is called, this behaviour no longer occurs.
76-
debug_assert!(r == 0 || r == libc::EINVAL);
77-
} else {
78-
debug_assert_eq!(r, 0);
79-
}
80-
}
81-
}
82-
83-
impl Condvar {
84-
pub const fn new() -> Condvar {
85-
Condvar { inner: LazyBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
23+
fn get(&self) -> Pin<&pal::Condvar> {
24+
self.cvar.get_or_init(|| {
25+
let mut cvar = Box::pin(pal::Condvar::new());
26+
// SAFETY: we only call `init` once, namely here.
27+
unsafe { cvar.as_mut().init() };
28+
cvar
29+
})
8630
}
8731

8832
#[inline]
89-
fn verify(&self, mutex: *mut libc::pthread_mutex_t) {
90-
// Relaxed is okay here because we never read through `self.addr`, and only use it to
33+
fn verify(&self, mutex: Pin<&pal::Mutex>) {
34+
let addr = ptr::from_ref::<pal::Mutex>(&mutex).addr();
35+
// Relaxed is okay here because we never read through `self.mutex`, and only use it to
9136
// compare addresses.
92-
match self.mutex.compare_exchange(ptr::null_mut(), mutex, Relaxed, Relaxed) {
93-
Ok(_) => {} // Stored the address
94-
Err(n) if n == mutex => {} // Lost a race to store the same address
37+
match self.mutex.compare_exchange(0, addr, Relaxed, Relaxed) {
38+
Ok(_) => {} // Stored the address
39+
Err(n) if n == addr => {} // Lost a race to store the same address
9540
_ => panic!("attempted to use a condition variable with two mutexes"),
9641
}
9742
}
9843

9944
#[inline]
10045
pub fn notify_one(&self) {
101-
let r = unsafe { libc::pthread_cond_signal(raw(self)) };
102-
debug_assert_eq!(r, 0);
46+
// SAFETY: we called `init` above.
47+
unsafe { self.get().notify_one() }
10348
}
10449

10550
#[inline]
10651
pub fn notify_all(&self) {
107-
let r = unsafe { libc::pthread_cond_broadcast(raw(self)) };
108-
debug_assert_eq!(r, 0);
52+
// SAFETY: we called `init` above.
53+
unsafe { self.get().notify_all() }
10954
}
11055

11156
#[inline]
11257
pub unsafe fn wait(&self, mutex: &Mutex) {
113-
let mutex = mutex::raw(mutex);
114-
self.verify(mutex);
115-
let r = libc::pthread_cond_wait(raw(self), mutex);
116-
debug_assert_eq!(r, 0);
117-
}
118-
119-
// This implementation is used on systems that support pthread_condattr_setclock
120-
// where we configure condition variable to use monotonic clock (instead of
121-
// default system clock). This approach avoids all problems that result
122-
// from changes made to the system time.
123-
#[cfg(not(any(
124-
target_os = "android",
125-
target_os = "espidf",
126-
target_os = "horizon",
127-
target_vendor = "apple",
128-
)))]
129-
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
130-
use crate::sys::time::Timespec;
131-
132-
let mutex = mutex::raw(mutex);
58+
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
59+
// must have been initialized already.
60+
let mutex = unsafe { mutex.pal.get().unwrap_unchecked() };
13361
self.verify(mutex);
134-
135-
#[cfg(not(target_os = "nto"))]
136-
let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
137-
.checked_add_duration(&dur)
138-
.and_then(|t| t.to_timespec())
139-
.unwrap_or(TIMESPEC_MAX);
140-
141-
#[cfg(target_os = "nto")]
142-
let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
143-
.checked_add_duration(&dur)
144-
.and_then(|t| t.to_timespec_capped())
145-
.unwrap_or(TIMESPEC_MAX_CAPPED);
146-
147-
let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
148-
assert!(r == libc::ETIMEDOUT || r == 0);
149-
r == 0
62+
// SAFETY: we called `init` above, we verified that this condition
63+
// variable is only used with `mutex` and the caller guarantees that
64+
// `mutex` is locked by the current thread.
65+
unsafe { self.get().wait(mutex) }
15066
}
15167

152-
// This implementation is modeled after libcxx's condition_variable
153-
// https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
154-
// https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
155-
#[cfg(any(
156-
target_os = "android",
157-
target_os = "espidf",
158-
target_os = "horizon",
159-
target_vendor = "apple",
160-
))]
16168
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
162-
use crate::sys::time::SystemTime;
163-
use crate::time::Instant;
164-
165-
let mutex = mutex::raw(mutex);
69+
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
70+
// must have been initialized already.
71+
let mutex = unsafe { mutex.pal.get().unwrap_unchecked() };
16672
self.verify(mutex);
16773

168-
// OSX implementation of `pthread_cond_timedwait` is buggy
169-
// with super long durations. When duration is greater than
170-
// 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
171-
// in macOS Sierra returns error 316.
172-
//
173-
// This program demonstrates the issue:
174-
// https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
175-
//
176-
// To work around this issue, and possible bugs of other OSes, timeout
177-
// is clamped to 1000 years, which is allowable per the API of `wait_timeout`
178-
// because of spurious wakeups.
179-
let dur = Duration::min(dur, Duration::from_secs(1000 * 365 * 86400));
180-
181-
// pthread_cond_timedwait uses system time, but we want to report timeout
182-
// based on stable time.
183-
let now = Instant::now();
184-
185-
let timeout = SystemTime::now()
186-
.t
187-
.checked_add_duration(&dur)
188-
.and_then(|t| t.to_timespec())
189-
.unwrap_or(TIMESPEC_MAX);
190-
191-
let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
192-
debug_assert!(r == libc::ETIMEDOUT || r == 0);
193-
194-
// ETIMEDOUT is not a totally reliable method of determining timeout due
195-
// to clock shifts, so do the check ourselves
196-
now.elapsed() < dur
74+
if pal::Condvar::PRECISE_TIMEOUT {
75+
// SAFETY: we called `init` above, we verified that this condition
76+
// variable is only used with `mutex` and the caller guarantees that
77+
// `mutex` is locked by the current thread.
78+
unsafe { self.get().wait_timeout(mutex, dur) }
79+
} else {
80+
// Timeout reports are not reliable, so do the check ourselves.
81+
let now = Instant::now();
82+
// SAFETY: we called `init` above, we verified that this condition
83+
// variable is only used with `mutex` and the caller guarantees that
84+
// `mutex` is locked by the current thread.
85+
let woken = unsafe { self.get().wait_timeout(mutex, dur) };
86+
woken || now.elapsed() < dur
87+
}
19788
}
19889
}

‎library/std/src/sys/sync/condvar/sgx.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,43 @@
11
use crate::sys::pal::waitqueue::{SpinMutex, WaitQueue, WaitVariable};
22
use crate::sys::sync::Mutex;
3-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
3+
use crate::sys_common::once_box::OnceBox;
44
use crate::time::Duration;
55

6-
/// FIXME: `UnsafeList` is not movable.
7-
struct AllocatedCondvar(SpinMutex<WaitVariable<()>>);
8-
6+
// FIXME: `UnsafeList` is not movable.
97
pub struct Condvar {
10-
inner: LazyBox<AllocatedCondvar>,
11-
}
12-
13-
impl LazyInit for AllocatedCondvar {
14-
fn init() -> Box<Self> {
15-
Box::new(AllocatedCondvar(SpinMutex::new(WaitVariable::new(()))))
16-
}
8+
inner: OnceBox<SpinMutex<WaitVariable<()>>>,
179
}
1810

1911
impl Condvar {
2012
pub const fn new() -> Condvar {
21-
Condvar { inner: LazyBox::new() }
13+
Condvar { inner: OnceBox::new() }
14+
}
15+
16+
#[inline]
17+
fn get(&self) -> &SpinMutex<WaitVariable<()>> {
18+
self.inner.get_or_init(|| Box::pin(SpinMutex::new(WaitVariable::new(())))).get_ref()
2219
}
2320

2421
#[inline]
2522
pub fn notify_one(&self) {
26-
let _ = WaitQueue::notify_one(self.inner.0.lock());
23+
let guard = self.get().lock();
24+
let _ = WaitQueue::notify_one(guard);
2725
}
2826

2927
#[inline]
3028
pub fn notify_all(&self) {
31-
let _ = WaitQueue::notify_all(self.inner.0.lock());
29+
let guard = self.get().lock();
30+
let _ = WaitQueue::notify_all(guard);
3231
}
3332

3433
pub unsafe fn wait(&self, mutex: &Mutex) {
35-
let guard = self.inner.0.lock();
34+
let guard = self.get().lock();
3635
WaitQueue::wait(guard, || unsafe { mutex.unlock() });
3736
mutex.lock()
3837
}
3938

4039
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
41-
let success = WaitQueue::wait_timeout(&self.inner.0, dur, || unsafe { mutex.unlock() });
40+
let success = WaitQueue::wait_timeout(self.get(), dur, || unsafe { mutex.unlock() });
4241
mutex.lock();
4342
success
4443
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ cfg_if::cfg_if! {
1919
target_os = "teeos",
2020
))] {
2121
mod pthread;
22-
pub use pthread::{Mutex, raw};
22+
pub use pthread::Mutex;
2323
} else if #[cfg(all(target_os = "windows", target_vendor = "win7"))] {
2424
mod windows7;
2525
pub use windows7::{Mutex, raw};
Lines changed: 40 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,66 @@
1-
use crate::cell::UnsafeCell;
2-
use crate::io::Error;
3-
use crate::mem::{forget, MaybeUninit};
4-
use crate::sys::cvt_nz;
5-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
1+
#![forbid(unsafe_op_in_unsafe_fn)]
62

7-
struct AllocatedMutex(UnsafeCell<libc::pthread_mutex_t>);
3+
use crate::mem::forget;
4+
use crate::pin::Pin;
5+
use crate::sys::pal::sync as pal;
6+
use crate::sys_common::once_box::OnceBox;
87

98
pub struct Mutex {
10-
inner: LazyBox<AllocatedMutex>,
11-
}
12-
13-
#[inline]
14-
pub unsafe fn raw(m: &Mutex) -> *mut libc::pthread_mutex_t {
15-
m.inner.0.get()
16-
}
17-
18-
unsafe impl Send for AllocatedMutex {}
19-
unsafe impl Sync for AllocatedMutex {}
20-
21-
impl LazyInit for AllocatedMutex {
22-
fn init() -> Box<Self> {
23-
let mutex = Box::new(AllocatedMutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)));
24-
25-
// Issue #33770
26-
//
27-
// A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have
28-
// a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you
29-
// try to re-lock it from the same thread when you already hold a lock
30-
// (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_init.html).
31-
// This is the case even if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
32-
// (https://github.com/rust-lang/rust/issues/33770#issuecomment-220847521) -- in that
33-
// case, `pthread_mutexattr_settype(PTHREAD_MUTEX_DEFAULT)` will of course be the same
34-
// as setting it to `PTHREAD_MUTEX_NORMAL`, but not setting any mode will result in
35-
// a Mutex where re-locking is UB.
36-
//
37-
// In practice, glibc takes advantage of this undefined behavior to
38-
// implement hardware lock elision, which uses hardware transactional
39-
// memory to avoid acquiring the lock. While a transaction is in
40-
// progress, the lock appears to be unlocked. This isn't a problem for
41-
// other threads since the transactional memory will abort if a conflict
42-
// is detected, however no abort is generated when re-locking from the
43-
// same thread.
44-
//
45-
// Since locking the same mutex twice will result in two aliasing &mut
46-
// references, we instead create the mutex with type
47-
// PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to
48-
// re-lock it from the same thread, thus avoiding undefined behavior.
49-
unsafe {
50-
let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
51-
cvt_nz(libc::pthread_mutexattr_init(attr.as_mut_ptr())).unwrap();
52-
let attr = PthreadMutexAttr(&mut attr);
53-
cvt_nz(libc::pthread_mutexattr_settype(
54-
attr.0.as_mut_ptr(),
55-
libc::PTHREAD_MUTEX_NORMAL,
56-
))
57-
.unwrap();
58-
cvt_nz(libc::pthread_mutex_init(mutex.0.get(), attr.0.as_ptr())).unwrap();
59-
}
60-
61-
mutex
62-
}
63-
64-
fn destroy(mutex: Box<Self>) {
65-
// We're not allowed to pthread_mutex_destroy a locked mutex,
66-
// so check first if it's unlocked.
67-
if unsafe { libc::pthread_mutex_trylock(mutex.0.get()) == 0 } {
68-
unsafe { libc::pthread_mutex_unlock(mutex.0.get()) };
69-
drop(mutex);
70-
} else {
71-
// The mutex is locked. This happens if a MutexGuard is leaked.
72-
// In this case, we just leak the Mutex too.
73-
forget(mutex);
74-
}
75-
}
76-
77-
fn cancel_init(_: Box<Self>) {
78-
// In this case, we can just drop it without any checks,
79-
// since it cannot have been locked yet.
80-
}
81-
}
82-
83-
impl Drop for AllocatedMutex {
84-
#[inline]
85-
fn drop(&mut self) {
86-
let r = unsafe { libc::pthread_mutex_destroy(self.0.get()) };
87-
if cfg!(target_os = "dragonfly") {
88-
// On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
89-
// mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
90-
// Once it is used (locked/unlocked) or pthread_mutex_init() is called,
91-
// this behaviour no longer occurs.
92-
debug_assert!(r == 0 || r == libc::EINVAL);
93-
} else {
94-
debug_assert_eq!(r, 0);
95-
}
96-
}
9+
pub pal: OnceBox<pal::Mutex>,
9710
}
9811

9912
impl Mutex {
10013
#[inline]
10114
pub const fn new() -> Mutex {
102-
Mutex { inner: LazyBox::new() }
15+
Mutex { pal: OnceBox::new() }
10316
}
10417

10518
#[inline]
106-
pub unsafe fn lock(&self) {
107-
#[cold]
108-
#[inline(never)]
109-
fn fail(r: i32) -> ! {
110-
let error = Error::from_raw_os_error(r);
111-
panic!("failed to lock mutex: {error}");
112-
}
19+
fn get(&self) -> Pin<&pal::Mutex> {
20+
// If the initialization race is lost, the new mutex is destroyed.
21+
// This is sound however, as it cannot have been locked.
22+
self.pal.get_or_init(|| {
23+
let mut pal = Box::pin(pal::Mutex::new());
24+
// SAFETY: we only call `init` once, namely here.
25+
unsafe { pal.as_mut().init() };
26+
pal
27+
})
28+
}
11329

114-
let r = libc::pthread_mutex_lock(raw(self));
115-
// As we set the mutex type to `PTHREAD_MUTEX_NORMAL` above, we expect
116-
// the lock call to never fail. Unfortunately however, some platforms
117-
// (Solaris) do not conform to the standard, and instead always provide
118-
// deadlock detection. How kind of them! Unfortunately that means that
119-
// we need to check the error code here. To save us from UB on other
120-
// less well-behaved platforms in the future, we do it even on "good"
121-
// platforms like macOS. See #120147 for more context.
122-
if r != 0 {
123-
fail(r)
124-
}
30+
#[inline]
31+
pub fn lock(&self) {
32+
// SAFETY: we call `init` above, therefore reentrant locking is safe.
33+
// In `drop` we ensure that the mutex is not destroyed while locked.
34+
unsafe { self.get().lock() }
12535
}
12636

12737
#[inline]
12838
pub unsafe fn unlock(&self) {
129-
let r = libc::pthread_mutex_unlock(raw(self));
130-
debug_assert_eq!(r, 0);
39+
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
40+
// must have been initialized already.
41+
unsafe { self.pal.get().unwrap_unchecked().unlock() }
13142
}
13243

13344
#[inline]
134-
pub unsafe fn try_lock(&self) -> bool {
135-
libc::pthread_mutex_trylock(raw(self)) == 0
45+
pub fn try_lock(&self) -> bool {
46+
// SAFETY: we call `init` above, therefore reentrant locking is safe.
47+
// In `drop` we ensure that the mutex is not destroyed while locked.
48+
unsafe { self.get().try_lock() }
13649
}
13750
}
13851

139-
pub(super) struct PthreadMutexAttr<'a>(pub &'a mut MaybeUninit<libc::pthread_mutexattr_t>);
140-
141-
impl Drop for PthreadMutexAttr<'_> {
52+
impl Drop for Mutex {
14253
fn drop(&mut self) {
143-
unsafe {
144-
let result = libc::pthread_mutexattr_destroy(self.0.as_mut_ptr());
145-
debug_assert_eq!(result, 0);
54+
let Some(pal) = self.pal.take() else { return };
55+
// We're not allowed to pthread_mutex_destroy a locked mutex,
56+
// so check first if it's unlocked.
57+
if unsafe { pal.as_ref().try_lock() } {
58+
unsafe { pal.as_ref().unlock() };
59+
drop(pal)
60+
} else {
61+
// The mutex is locked. This happens if a MutexGuard is leaked.
62+
// In this case, we just leak the Mutex too.
63+
forget(pal)
14664
}
14765
}
14866
}

‎library/std/src/sys/sync/mutex/sgx.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
use crate::sys::pal::waitqueue::{try_lock_or_false, SpinMutex, WaitQueue, WaitVariable};
2-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
3-
4-
/// FIXME: `UnsafeList` is not movable.
5-
struct AllocatedMutex(SpinMutex<WaitVariable<bool>>);
2+
use crate::sys_common::once_box::OnceBox;
63

4+
// FIXME: `UnsafeList` is not movable.
75
pub struct Mutex {
8-
inner: LazyBox<AllocatedMutex>,
9-
}
10-
11-
impl LazyInit for AllocatedMutex {
12-
fn init() -> Box<Self> {
13-
Box::new(AllocatedMutex(SpinMutex::new(WaitVariable::new(false))))
14-
}
6+
inner: OnceBox<SpinMutex<WaitVariable<bool>>>,
157
}
168

179
// Implementation according to “Operating Systems: Three Easy Pieces”, chapter 28
1810
impl Mutex {
1911
pub const fn new() -> Mutex {
20-
Mutex { inner: LazyBox::new() }
12+
Mutex { inner: OnceBox::new() }
13+
}
14+
15+
#[inline]
16+
fn get(&self) -> &SpinMutex<WaitVariable<bool>> {
17+
self.inner.get_or_init(|| Box::pin(SpinMutex::new(WaitVariable::new(false)))).get_ref()
2118
}
2219

2320
#[inline]
2421
pub fn lock(&self) {
25-
let mut guard = self.inner.0.lock();
22+
let mut guard = self.get().lock();
2623
if *guard.lock_var() {
2724
// Another thread has the lock, wait
2825
WaitQueue::wait(guard, || {})
@@ -35,7 +32,7 @@ impl Mutex {
3532

3633
#[inline]
3734
pub unsafe fn unlock(&self) {
38-
let guard = self.inner.0.lock();
35+
let guard = self.get().lock();
3936
if let Err(mut guard) = WaitQueue::notify_one(guard) {
4037
// No other waiters, unlock
4138
*guard.lock_var_mut() = false;
@@ -46,7 +43,7 @@ impl Mutex {
4643

4744
#[inline]
4845
pub fn try_lock(&self) -> bool {
49-
let mut guard = try_lock_or_false!(self.inner.0);
46+
let mut guard = try_lock_or_false!(self.get());
5047
if *guard.lock_var() {
5148
// Another thread has the lock
5249
false
Lines changed: 32 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,19 @@
11
//! Thread parking without `futex` using the `pthread` synchronization primitives.
22
3-
use crate::cell::UnsafeCell;
4-
use crate::marker::PhantomPinned;
53
use crate::pin::Pin;
6-
use crate::ptr::addr_of_mut;
74
use crate::sync::atomic::AtomicUsize;
85
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
9-
#[cfg(not(target_os = "nto"))]
10-
use crate::sys::time::TIMESPEC_MAX;
11-
#[cfg(target_os = "nto")]
12-
use crate::sys::time::TIMESPEC_MAX_CAPPED;
6+
use crate::sys::pal::sync::{Condvar, Mutex};
137
use crate::time::Duration;
148

159
const EMPTY: usize = 0;
1610
const PARKED: usize = 1;
1711
const NOTIFIED: usize = 2;
1812

19-
unsafe fn lock(lock: *mut libc::pthread_mutex_t) {
20-
let r = libc::pthread_mutex_lock(lock);
21-
debug_assert_eq!(r, 0);
22-
}
23-
24-
unsafe fn unlock(lock: *mut libc::pthread_mutex_t) {
25-
let r = libc::pthread_mutex_unlock(lock);
26-
debug_assert_eq!(r, 0);
27-
}
28-
29-
unsafe fn notify_one(cond: *mut libc::pthread_cond_t) {
30-
let r = libc::pthread_cond_signal(cond);
31-
debug_assert_eq!(r, 0);
32-
}
33-
34-
unsafe fn wait(cond: *mut libc::pthread_cond_t, lock: *mut libc::pthread_mutex_t) {
35-
let r = libc::pthread_cond_wait(cond, lock);
36-
debug_assert_eq!(r, 0);
37-
}
38-
39-
unsafe fn wait_timeout(
40-
cond: *mut libc::pthread_cond_t,
41-
lock: *mut libc::pthread_mutex_t,
42-
dur: Duration,
43-
) {
44-
// Use the system clock on systems that do not support pthread_condattr_setclock.
45-
// This unfortunately results in problems when the system time changes.
46-
#[cfg(any(target_os = "espidf", target_os = "horizon", target_vendor = "apple"))]
47-
let (now, dur) = {
48-
use crate::cmp::min;
49-
use crate::sys::time::SystemTime;
50-
51-
// OSX implementation of `pthread_cond_timedwait` is buggy
52-
// with super long durations. When duration is greater than
53-
// 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
54-
// in macOS Sierra return error 316.
55-
//
56-
// This program demonstrates the issue:
57-
// https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
58-
//
59-
// To work around this issue, and possible bugs of other OSes, timeout
60-
// is clamped to 1000 years, which is allowable per the API of `park_timeout`
61-
// because of spurious wakeups.
62-
let dur = min(dur, Duration::from_secs(1000 * 365 * 86400));
63-
let now = SystemTime::now().t;
64-
(now, dur)
65-
};
66-
// Use the monotonic clock on other systems.
67-
#[cfg(not(any(target_os = "espidf", target_os = "horizon", target_vendor = "apple")))]
68-
let (now, dur) = {
69-
use crate::sys::time::Timespec;
70-
71-
(Timespec::now(libc::CLOCK_MONOTONIC), dur)
72-
};
73-
74-
#[cfg(not(target_os = "nto"))]
75-
let timeout =
76-
now.checked_add_duration(&dur).and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
77-
#[cfg(target_os = "nto")]
78-
let timeout = now
79-
.checked_add_duration(&dur)
80-
.and_then(|t| t.to_timespec_capped())
81-
.unwrap_or(TIMESPEC_MAX_CAPPED);
82-
let r = libc::pthread_cond_timedwait(cond, lock, &timeout);
83-
debug_assert!(r == libc::ETIMEDOUT || r == 0);
84-
}
85-
8613
pub struct Parker {
8714
state: AtomicUsize,
88-
lock: UnsafeCell<libc::pthread_mutex_t>,
89-
cvar: UnsafeCell<libc::pthread_cond_t>,
90-
// The `pthread` primitives require a stable address, so make this struct `!Unpin`.
91-
_pinned: PhantomPinned,
15+
lock: Mutex,
16+
cvar: Condvar,
9217
}
9318

9419
impl Parker {
@@ -97,38 +22,21 @@ impl Parker {
9722
/// # Safety
9823
/// The constructed parker must never be moved.
9924
pub unsafe fn new_in_place(parker: *mut Parker) {
100-
// Use the default mutex implementation to allow for simpler initialization.
101-
// This could lead to undefined behaviour when deadlocking. This is avoided
102-
// by not deadlocking. Note in particular the unlocking operation before any
103-
// panic, as code after the panic could try to park again.
104-
addr_of_mut!((*parker).state).write(AtomicUsize::new(EMPTY));
105-
addr_of_mut!((*parker).lock).write(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER));
25+
parker.write(Parker {
26+
state: AtomicUsize::new(EMPTY),
27+
lock: Mutex::new(),
28+
cvar: Condvar::new(),
29+
});
10630

107-
cfg_if::cfg_if! {
108-
if #[cfg(any(
109-
target_os = "l4re",
110-
target_os = "android",
111-
target_os = "redox",
112-
target_os = "vita",
113-
target_vendor = "apple",
114-
))] {
115-
addr_of_mut!((*parker).cvar).write(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER));
116-
} else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
117-
let r = libc::pthread_cond_init(addr_of_mut!((*parker).cvar).cast(), crate::ptr::null());
118-
assert_eq!(r, 0);
119-
} else {
120-
use crate::mem::MaybeUninit;
121-
let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
122-
let r = libc::pthread_condattr_init(attr.as_mut_ptr());
123-
assert_eq!(r, 0);
124-
let r = libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC);
125-
assert_eq!(r, 0);
126-
let r = libc::pthread_cond_init(addr_of_mut!((*parker).cvar).cast(), attr.as_ptr());
127-
assert_eq!(r, 0);
128-
let r = libc::pthread_condattr_destroy(attr.as_mut_ptr());
129-
assert_eq!(r, 0);
130-
}
131-
}
31+
Pin::new_unchecked(&mut (*parker).cvar).init();
32+
}
33+
34+
fn lock(self: Pin<&Self>) -> Pin<&Mutex> {
35+
unsafe { self.map_unchecked(|p| &p.lock) }
36+
}
37+
38+
fn cvar(self: Pin<&Self>) -> Pin<&Condvar> {
39+
unsafe { self.map_unchecked(|p| &p.cvar) }
13240
}
13341

13442
// This implementation doesn't require `unsafe`, but other implementations
@@ -143,7 +51,7 @@ impl Parker {
14351
}
14452

14553
// Otherwise we need to coordinate going to sleep
146-
lock(self.lock.get());
54+
self.lock().lock();
14755
match self.state.compare_exchange(EMPTY, PARKED, Relaxed, Relaxed) {
14856
Ok(_) => {}
14957
Err(NOTIFIED) => {
@@ -155,28 +63,28 @@ impl Parker {
15563
// read from the write it made to `state`.
15664
let old = self.state.swap(EMPTY, Acquire);
15765

158-
unlock(self.lock.get());
66+
self.lock().unlock();
15967

16068
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
16169
return;
16270
} // should consume this notification, so prohibit spurious wakeups in next park.
16371
Err(_) => {
164-
unlock(self.lock.get());
72+
self.lock().unlock();
16573

16674
panic!("inconsistent park state")
16775
}
16876
}
16977

17078
loop {
171-
wait(self.cvar.get(), self.lock.get());
79+
self.cvar().wait(self.lock());
17280

17381
match self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed) {
17482
Ok(_) => break, // got a notification
17583
Err(_) => {} // spurious wakeup, go back to sleep
17684
}
17785
}
17886

179-
unlock(self.lock.get());
87+
self.lock().unlock();
18088
}
18189

18290
// This implementation doesn't require `unsafe`, but other implementations
@@ -190,19 +98,19 @@ impl Parker {
19098
return;
19199
}
192100

193-
lock(self.lock.get());
101+
self.lock().lock();
194102
match self.state.compare_exchange(EMPTY, PARKED, Relaxed, Relaxed) {
195103
Ok(_) => {}
196104
Err(NOTIFIED) => {
197105
// We must read again here, see `park`.
198106
let old = self.state.swap(EMPTY, Acquire);
199-
unlock(self.lock.get());
107+
self.lock().unlock();
200108

201109
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
202110
return;
203111
} // should consume this notification, so prohibit spurious wakeups in next park.
204112
Err(_) => {
205-
unlock(self.lock.get());
113+
self.lock().unlock();
206114
panic!("inconsistent park_timeout state")
207115
}
208116
}
@@ -211,13 +119,13 @@ impl Parker {
211119
// from a notification we just want to unconditionally set the state back to
212120
// empty, either consuming a notification or un-flagging ourselves as
213121
// parked.
214-
wait_timeout(self.cvar.get(), self.lock.get(), dur);
122+
self.cvar().wait_timeout(self.lock(), dur);
215123

216124
match self.state.swap(EMPTY, Acquire) {
217-
NOTIFIED => unlock(self.lock.get()), // got a notification, hurray!
218-
PARKED => unlock(self.lock.get()), // no notification, alas
125+
NOTIFIED => self.lock().unlock(), // got a notification, hurray!
126+
PARKED => self.lock().unlock(), // no notification, alas
219127
n => {
220-
unlock(self.lock.get());
128+
self.lock().unlock();
221129
panic!("inconsistent park_timeout state: {n}")
222130
}
223131
}
@@ -249,21 +157,9 @@ impl Parker {
249157
// parked thread wakes it doesn't get woken only to have to wait for us
250158
// to release `lock`.
251159
unsafe {
252-
lock(self.lock.get());
253-
unlock(self.lock.get());
254-
notify_one(self.cvar.get());
160+
self.lock().lock();
161+
self.lock().unlock();
162+
self.cvar().notify_one();
255163
}
256164
}
257165
}
258-
259-
impl Drop for Parker {
260-
fn drop(&mut self) {
261-
unsafe {
262-
libc::pthread_cond_destroy(self.cvar.get_mut());
263-
libc::pthread_mutex_destroy(self.lock.get_mut());
264-
}
265-
}
266-
}
267-
268-
unsafe impl Sync for Parker {}
269-
unsafe impl Send for Parker {}

‎library/std/src/sys_common/lazy_box.rs

Lines changed: 0 additions & 88 deletions
This file was deleted.

‎library/std/src/sys_common/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod tests;
2222

2323
pub mod fs;
2424
pub mod io;
25-
pub mod lazy_box;
25+
pub mod once_box;
2626
pub mod process;
2727
pub mod wstr;
2828
pub mod wtf8;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#![allow(dead_code)] // Only used on some platforms.
2+
3+
// This is used to wrap pthread {Mutex, Condvar, RwLock} in.
4+
5+
use crate::mem::replace;
6+
use crate::pin::Pin;
7+
use crate::ptr::null_mut;
8+
use crate::sync::atomic::AtomicPtr;
9+
use crate::sync::atomic::Ordering::{AcqRel, Acquire};
10+
11+
pub(crate) struct OnceBox<T> {
12+
ptr: AtomicPtr<T>,
13+
}
14+
15+
impl<T> OnceBox<T> {
16+
#[inline]
17+
pub const fn new() -> Self {
18+
Self { ptr: AtomicPtr::new(null_mut()) }
19+
}
20+
21+
#[inline]
22+
pub fn get(&self) -> Option<Pin<&T>> {
23+
let val = unsafe { self.ptr.load(Acquire).as_ref()? };
24+
Some(unsafe { Pin::new_unchecked(val) })
25+
}
26+
27+
#[inline]
28+
pub fn get_or_init(&self, f: impl FnOnce() -> Pin<Box<T>>) -> Pin<&T> {
29+
match self.get() {
30+
Some(val) => val,
31+
None => self.init(f),
32+
}
33+
}
34+
35+
#[cold]
36+
fn init(&self, f: impl FnOnce() -> Pin<Box<T>>) -> Pin<&T> {
37+
let val = f();
38+
let ptr = Box::into_raw(unsafe { Pin::into_inner_unchecked(val) });
39+
let val = match self.ptr.compare_exchange(null_mut(), ptr, AcqRel, Acquire) {
40+
Ok(_) => ptr,
41+
Err(new) => {
42+
drop(unsafe { Box::from_raw(ptr) });
43+
new
44+
}
45+
};
46+
47+
unsafe { Pin::new_unchecked(&*val) }
48+
}
49+
50+
#[inline]
51+
pub fn take(&mut self) -> Option<Pin<Box<T>>> {
52+
let ptr = replace(self.ptr.get_mut(), null_mut());
53+
if !ptr.is_null() { Some(unsafe { Box::from_raw(ptr).into() }) } else { None }
54+
}
55+
}
56+
57+
impl<T> Drop for OnceBox<T> {
58+
fn drop(&mut self) {
59+
drop(self.take())
60+
}
61+
}

0 commit comments

Comments
 (0)
Please sign in to comment.