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 4553772

Browse files
committedOct 1, 2024··
std: replace LazyBox with OnceBox
This PR replaces the `LazyBox` wrapper used to allocate the pthread primitives with `OnceBox`, which has a more familiar API mirroring that of `OnceLock`. This cleans up the code in preparation for larger changes like #128184 (from which this PR was split) and allows some neat optimizations, like avoid an acquire-load of the allocation pointer in `Mutex::unlock`, where the initialization of the allocation must have already been observed. Additionally, I've gotten rid of the TEEOS `Condvar` code, it's just a duplicate of the pthread one anyway and I didn't want to repeat myself.
1 parent 21aa500 commit 4553772

File tree

12 files changed

+185
-284
lines changed

12 files changed

+185
-284
lines changed
 

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ cfg_if::cfg_if! {
1212
))] {
1313
mod futex;
1414
pub use futex::Condvar;
15-
} else if #[cfg(target_family = "unix")] {
15+
} else if #[cfg(any(
16+
target_family = "unix",
17+
target_os = "teeos",
18+
))] {
1619
mod pthread;
1720
pub use pthread::Condvar;
1821
} else if #[cfg(all(target_os = "windows", target_vendor = "win7"))] {
@@ -24,9 +27,6 @@ cfg_if::cfg_if! {
2427
} else if #[cfg(target_os = "solid_asp3")] {
2528
mod itron;
2629
pub use itron::Condvar;
27-
} else if #[cfg(target_os = "teeos")] {
28-
mod teeos;
29-
pub use teeos::Condvar;
3030
} else if #[cfg(target_os = "xous")] {
3131
mod xous;
3232
pub use xous::Condvar;

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

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,25 @@ use crate::cell::UnsafeCell;
22
use crate::ptr;
33
use crate::sync::atomic::AtomicPtr;
44
use crate::sync::atomic::Ordering::Relaxed;
5-
use crate::sys::sync::{Mutex, mutex};
5+
use crate::sys::sync::{Mutex, OnceBox};
66
#[cfg(not(target_os = "nto"))]
77
use crate::sys::time::TIMESPEC_MAX;
88
#[cfg(target_os = "nto")]
99
use crate::sys::time::TIMESPEC_MAX_CAPPED;
10-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
1110
use crate::time::Duration;
1211

1312
struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>);
1413

1514
pub struct Condvar {
16-
inner: LazyBox<AllocatedCondvar>,
15+
inner: OnceBox<AllocatedCondvar>,
1716
mutex: AtomicPtr<libc::pthread_mutex_t>,
1817
}
1918

20-
#[inline]
21-
fn raw(c: &Condvar) -> *mut libc::pthread_cond_t {
22-
c.inner.0.get()
23-
}
24-
2519
unsafe impl Send for AllocatedCondvar {}
2620
unsafe impl Sync for AllocatedCondvar {}
2721

28-
impl LazyInit for AllocatedCondvar {
29-
fn init() -> Box<Self> {
22+
impl AllocatedCondvar {
23+
fn new() -> Box<Self> {
3024
let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)));
3125

3226
cfg_if::cfg_if! {
@@ -37,7 +31,7 @@ impl LazyInit for AllocatedCondvar {
3731
target_vendor = "apple",
3832
))] {
3933
// `pthread_condattr_setclock` is unfortunately not supported on these platforms.
40-
} else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] {
34+
} else if #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "teeos"))] {
4135
// NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
4236
// So on that platform, init() should always be called
4337
// Moreover, that platform does not have pthread_condattr_setclock support,
@@ -82,7 +76,11 @@ impl Drop for AllocatedCondvar {
8276

8377
impl Condvar {
8478
pub const fn new() -> Condvar {
85-
Condvar { inner: LazyBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
79+
Condvar { inner: OnceBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
80+
}
81+
82+
fn get(&self) -> *mut libc::pthread_cond_t {
83+
self.inner.get_or_init(AllocatedCondvar::new).0.get()
8684
}
8785

8886
#[inline]
@@ -98,21 +96,21 @@ impl Condvar {
9896

9997
#[inline]
10098
pub fn notify_one(&self) {
101-
let r = unsafe { libc::pthread_cond_signal(raw(self)) };
99+
let r = unsafe { libc::pthread_cond_signal(self.get()) };
102100
debug_assert_eq!(r, 0);
103101
}
104102

105103
#[inline]
106104
pub fn notify_all(&self) {
107-
let r = unsafe { libc::pthread_cond_broadcast(raw(self)) };
105+
let r = unsafe { libc::pthread_cond_broadcast(self.get()) };
108106
debug_assert_eq!(r, 0);
109107
}
110108

111109
#[inline]
112110
pub unsafe fn wait(&self, mutex: &Mutex) {
113-
let mutex = mutex::raw(mutex);
111+
let mutex = mutex.get_assert_locked();
114112
self.verify(mutex);
115-
let r = libc::pthread_cond_wait(raw(self), mutex);
113+
let r = libc::pthread_cond_wait(self.get(), mutex);
116114
debug_assert_eq!(r, 0);
117115
}
118116

@@ -129,7 +127,7 @@ impl Condvar {
129127
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
130128
use crate::sys::time::Timespec;
131129

132-
let mutex = mutex::raw(mutex);
130+
let mutex = mutex.get_assert_locked();
133131
self.verify(mutex);
134132

135133
#[cfg(not(target_os = "nto"))]
@@ -144,7 +142,7 @@ impl Condvar {
144142
.and_then(|t| t.to_timespec_capped())
145143
.unwrap_or(TIMESPEC_MAX_CAPPED);
146144

147-
let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
145+
let r = libc::pthread_cond_timedwait(self.get(), mutex, &timeout);
148146
assert!(r == libc::ETIMEDOUT || r == 0);
149147
r == 0
150148
}
@@ -162,7 +160,7 @@ impl Condvar {
162160
use crate::sys::time::SystemTime;
163161
use crate::time::Instant;
164162

165-
let mutex = mutex::raw(mutex);
163+
let mutex = mutex.get_assert_locked();
166164
self.verify(mutex);
167165

168166
// OSX implementation of `pthread_cond_timedwait` is buggy
@@ -188,7 +186,7 @@ impl Condvar {
188186
.and_then(|t| t.to_timespec())
189187
.unwrap_or(TIMESPEC_MAX);
190188

191-
let r = libc::pthread_cond_timedwait(raw(self), mutex, &timeout);
189+
let r = libc::pthread_cond_timedwait(self.get(), mutex, &timeout);
192190
debug_assert!(r == libc::ETIMEDOUT || r == 0);
193191

194192
// ETIMEDOUT is not a totally reliable method of determining timeout due

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

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

6-
/// FIXME: `UnsafeList` is not movable.
7-
struct AllocatedCondvar(SpinMutex<WaitVariable<()>>);
8-
95
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-
}
6+
// FIXME: `UnsafeList` is not movable.
7+
inner: OnceBox<SpinMutex<WaitVariable<()>>>,
178
}
189

1910
impl Condvar {
2011
pub const fn new() -> Condvar {
21-
Condvar { inner: LazyBox::new() }
12+
Condvar { inner: OnceBox::new() }
13+
}
14+
15+
fn get(&self) -> &SpinMutex<WaitVariable<()>> {
16+
self.inner.get_or_init(|| Box::new(SpinMutex::new(WaitVariable::new(()))))
2217
}
2318

2419
#[inline]
2520
pub fn notify_one(&self) {
26-
let _ = WaitQueue::notify_one(self.inner.0.lock());
21+
let _ = WaitQueue::notify_one(self.get().lock());
2722
}
2823

2924
#[inline]
3025
pub fn notify_all(&self) {
31-
let _ = WaitQueue::notify_all(self.inner.0.lock());
26+
let _ = WaitQueue::notify_all(self.get().lock());
3227
}
3328

3429
pub unsafe fn wait(&self, mutex: &Mutex) {
35-
let guard = self.inner.0.lock();
30+
let guard = self.get().lock();
3631
WaitQueue::wait(guard, || unsafe { mutex.unlock() });
3732
mutex.lock()
3833
}
3934

4035
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
41-
let success = WaitQueue::wait_timeout(&self.inner.0, dur, || unsafe { mutex.unlock() });
36+
let success = WaitQueue::wait_timeout(self.get(), dur, || unsafe { mutex.unlock() });
4237
mutex.lock();
4338
success
4439
}

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

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
mod condvar;
22
mod mutex;
33
mod once;
4+
mod once_box;
45
mod rwlock;
56
mod thread_parking;
67

78
pub use condvar::Condvar;
89
pub use mutex::Mutex;
910
pub use once::{Once, OnceState};
11+
#[allow(unused)] // Only used on some platforms.
12+
use once_box::OnceBox;
1013
pub use rwlock::RwLock;
1114
pub use thread_parking::Parker;

‎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};

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

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,19 @@ use crate::cell::UnsafeCell;
22
use crate::io::Error;
33
use crate::mem::{MaybeUninit, forget};
44
use crate::sys::cvt_nz;
5-
use crate::sys_common::lazy_box::{LazyBox, LazyInit};
5+
use crate::sys::sync::OnceBox;
66

77
struct AllocatedMutex(UnsafeCell<libc::pthread_mutex_t>);
88

99
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()
10+
inner: OnceBox<AllocatedMutex>,
1611
}
1712

1813
unsafe impl Send for AllocatedMutex {}
1914
unsafe impl Sync for AllocatedMutex {}
2015

21-
impl LazyInit for AllocatedMutex {
22-
fn init() -> Box<Self> {
16+
impl AllocatedMutex {
17+
fn new() -> Box<Self> {
2318
let mutex = Box::new(AllocatedMutex(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)));
2419

2520
// Issue #33770
@@ -60,24 +55,6 @@ impl LazyInit for AllocatedMutex {
6055

6156
mutex
6257
}
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-
}
8158
}
8259

8360
impl Drop for AllocatedMutex {
@@ -99,19 +76,41 @@ impl Drop for AllocatedMutex {
9976
impl Mutex {
10077
#[inline]
10178
pub const fn new() -> Mutex {
102-
Mutex { inner: LazyBox::new() }
79+
Mutex { inner: OnceBox::new() }
80+
}
81+
82+
/// Gets access to the pthread mutex under the assumption that the mutex is
83+
/// locked.
84+
///
85+
/// This allows skipping the initialization check, as the mutex can only be
86+
/// locked if it is already initialized, and allows relaxing the ordering
87+
/// on the pointer load, since the allocation cannot have been modified
88+
/// since the `lock` and the lock must have occurred on the current thread.
89+
///
90+
/// # Safety
91+
/// Causes undefined behaviour if the mutex is not locked.
92+
#[inline]
93+
pub(crate) unsafe fn get_assert_locked(&self) -> *mut libc::pthread_mutex_t {
94+
unsafe { self.inner.get_unchecked().0.get() }
10395
}
10496

10597
#[inline]
106-
pub unsafe fn lock(&self) {
98+
fn get(&self) -> *mut libc::pthread_mutex_t {
99+
// If initialization fails, the mutex is destroyed. This is always sound,
100+
// however, as the mutex cannot have been locked yet.
101+
self.inner.get_or_init(AllocatedMutex::new).0.get()
102+
}
103+
104+
#[inline]
105+
pub fn lock(&self) {
107106
#[cold]
108107
#[inline(never)]
109108
fn fail(r: i32) -> ! {
110109
let error = Error::from_raw_os_error(r);
111110
panic!("failed to lock mutex: {error}");
112111
}
113112

114-
let r = libc::pthread_mutex_lock(raw(self));
113+
let r = unsafe { libc::pthread_mutex_lock(self.get()) };
115114
// As we set the mutex type to `PTHREAD_MUTEX_NORMAL` above, we expect
116115
// the lock call to never fail. Unfortunately however, some platforms
117116
// (Solaris) do not conform to the standard, and instead always provide
@@ -126,13 +125,29 @@ impl Mutex {
126125

127126
#[inline]
128127
pub unsafe fn unlock(&self) {
129-
let r = libc::pthread_mutex_unlock(raw(self));
128+
let r = libc::pthread_mutex_unlock(self.get_assert_locked());
130129
debug_assert_eq!(r, 0);
131130
}
132131

133132
#[inline]
134-
pub unsafe fn try_lock(&self) -> bool {
135-
libc::pthread_mutex_trylock(raw(self)) == 0
133+
pub fn try_lock(&self) -> bool {
134+
unsafe { libc::pthread_mutex_trylock(self.get()) == 0 }
135+
}
136+
}
137+
138+
impl Drop for Mutex {
139+
fn drop(&mut self) {
140+
let Some(mutex) = self.inner.take() else { return };
141+
// We're not allowed to pthread_mutex_destroy a locked mutex,
142+
// so check first if it's unlocked.
143+
if unsafe { libc::pthread_mutex_trylock(mutex.0.get()) == 0 } {
144+
unsafe { libc::pthread_mutex_unlock(mutex.0.get()) };
145+
drop(mutex);
146+
} else {
147+
// The mutex is locked. This happens if a MutexGuard is leaked.
148+
// In this case, we just leak the Mutex too.
149+
forget(mutex);
150+
}
136151
}
137152
}
138153

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
use crate::sys::pal::waitqueue::{SpinMutex, WaitQueue, WaitVariable, try_lock_or_false};
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::sync::OnceBox;
63

74
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-
}
5+
// FIXME: `UnsafeList` is not movable.
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+
fn get(&self) -> &SpinMutex<WaitVariable<bool>> {
16+
self.inner.get_or_init(|| Box::new(SpinMutex::new(WaitVariable::new(false))))
2117
}
2218

2319
#[inline]
2420
pub fn lock(&self) {
25-
let mut guard = self.inner.0.lock();
21+
let mut guard = self.get().lock();
2622
if *guard.lock_var() {
2723
// Another thread has the lock, wait
2824
WaitQueue::wait(guard, || {})
@@ -35,7 +31,9 @@ impl Mutex {
3531

3632
#[inline]
3733
pub unsafe fn unlock(&self) {
38-
let guard = self.inner.0.lock();
34+
// SAFETY: the mutex was locked by the current thread, so it has been
35+
// initialized already.
36+
let guard = unsafe { self.inner.get_unchecked().lock() };
3937
if let Err(mut guard) = WaitQueue::notify_one(guard) {
4038
// No other waiters, unlock
4139
*guard.lock_var_mut() = false;
@@ -46,7 +44,7 @@ impl Mutex {
4644

4745
#[inline]
4846
pub fn try_lock(&self) -> bool {
49-
let mut guard = try_lock_or_false!(self.inner.0);
47+
let mut guard = try_lock_or_false!(self.get());
5048
if *guard.lock_var() {
5149
// Another thread has the lock
5250
false

‎library/std/src/sys/sync/once_box.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! A racily-initialized alternative to `OnceLock<Box<T>>`.
2+
//!
3+
//! This is used to implement synchronization primitives that need allocation,
4+
//! like the pthread versions.
5+
6+
#![allow(dead_code)] // Only used on some platforms.
7+
8+
use crate::mem::replace;
9+
use crate::ptr::null_mut;
10+
use crate::sync::atomic::AtomicPtr;
11+
use crate::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed};
12+
13+
pub(crate) struct OnceBox<T> {
14+
ptr: AtomicPtr<T>,
15+
}
16+
17+
impl<T> OnceBox<T> {
18+
#[inline]
19+
pub const fn new() -> Self {
20+
Self { ptr: AtomicPtr::new(null_mut()) }
21+
}
22+
23+
/// Gets access to the value, assuming it is already initialized and this
24+
/// initialization has been observed by the current thread.
25+
///
26+
/// Since all modifications to the pointer have already been observed, the
27+
/// pointer load in this function can be performed with relaxed ordering,
28+
/// potentially allowing the optimizer to turn code like this:
29+
/// ```rust
30+
/// once_box.get_or_init(|| Box::new(42));
31+
/// unsafe { once_box.get_unchecked() }
32+
/// ```
33+
/// into
34+
/// ```rust
35+
/// once_box.get_or_init(|| Box::new(42))
36+
/// ```
37+
///
38+
/// # Safety
39+
/// This causes undefined behaviour if the assumption above is violated.
40+
#[inline]
41+
pub unsafe fn get_unchecked(&self) -> &T {
42+
unsafe { &*self.ptr.load(Relaxed) }
43+
}
44+
45+
#[inline]
46+
pub fn get_or_init(&self, f: impl FnOnce() -> Box<T>) -> &T {
47+
let ptr = self.ptr.load(Acquire);
48+
match unsafe { ptr.as_ref() } {
49+
Some(val) => val,
50+
None => self.initialize(f),
51+
}
52+
}
53+
54+
#[inline]
55+
pub fn take(&mut self) -> Option<Box<T>> {
56+
let ptr = replace(self.ptr.get_mut(), null_mut());
57+
if !ptr.is_null() { Some(unsafe { Box::from_raw(ptr) }) } else { None }
58+
}
59+
60+
#[cold]
61+
fn initialize(&self, f: impl FnOnce() -> Box<T>) -> &T {
62+
let new_ptr = Box::into_raw(f());
63+
match self.ptr.compare_exchange(null_mut(), new_ptr, AcqRel, Acquire) {
64+
Ok(_) => unsafe { &*new_ptr },
65+
Err(ptr) => {
66+
// Lost the race to another thread.
67+
// Drop the value we created, and use the one from the other thread instead.
68+
drop(unsafe { Box::from_raw(new_ptr) });
69+
unsafe { &*ptr }
70+
}
71+
}
72+
}
73+
}
74+
75+
unsafe impl<T: Send> Send for OnceBox<T> {}
76+
unsafe impl<T: Send + Sync> Sync for OnceBox<T> {}
77+
78+
impl<T> Drop for OnceBox<T> {
79+
fn drop(&mut self) {
80+
self.take();
81+
}
82+
}

‎library/std/src/sys/sync/rwlock/teeos.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ impl RwLock {
1414

1515
#[inline]
1616
pub fn read(&self) {
17-
unsafe { self.inner.lock() };
17+
self.inner.lock()
1818
}
1919

2020
#[inline]
2121
pub fn try_read(&self) -> bool {
22-
unsafe { self.inner.try_lock() }
22+
self.inner.try_lock()
2323
}
2424

2525
#[inline]
2626
pub fn write(&self) {
27-
unsafe { self.inner.lock() };
27+
self.inner.lock()
2828
}
2929

3030
#[inline]
3131
pub unsafe fn try_write(&self) -> bool {
32-
unsafe { self.inner.try_lock() }
32+
self.inner.try_lock()
3333
}
3434

3535
#[inline]

‎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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ mod tests;
2222

2323
pub mod fs;
2424
pub mod io;
25-
pub mod lazy_box;
2625
pub mod process;
2726
pub mod wstr;
2827
pub mod wtf8;

0 commit comments

Comments
 (0)
Please sign in to comment.