Skip to content

Commit f2071c0

Browse files
committed
std: allow Condvar to be used with multiple Mutexes
1 parent c90bcb9 commit f2071c0

File tree

4 files changed

+130
-61
lines changed

4 files changed

+130
-61
lines changed

library/std/src/sync/nonpoison/condvar.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ impl Condvar {
8282
/// the predicate must always be checked each time this function returns to
8383
/// protect against spurious wakeups.
8484
///
85-
/// # Panics
86-
///
87-
/// This function may [`panic!`] if it is used with more than one mutex
88-
/// over time.
89-
///
9085
/// [`notify_one`]: Self::notify_one
9186
/// [`notify_all`]: Self::notify_all
9287
///

library/std/src/sync/poison/condvar.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ use crate::time::{Duration, Instant};
1313
/// determining that a thread must block.
1414
///
1515
/// Functions in this module will block the current **thread** of execution.
16-
/// Note that any attempt to use multiple mutexes on the same condition
17-
/// variable may result in a runtime panic.
1816
///
1917
/// # Examples
2018
///
@@ -85,11 +83,6 @@ impl Condvar {
8583
/// poisoned when this thread re-acquires the lock. For more information,
8684
/// see information about [poisoning] on the [`Mutex`] type.
8785
///
88-
/// # Panics
89-
///
90-
/// This function may [`panic!`] if it is used with more than one mutex
91-
/// over time.
92-
///
9386
/// [`notify_one`]: Self::notify_one
9487
/// [`notify_all`]: Self::notify_all
9588
/// [poisoning]: super::Mutex#poisoning

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

Lines changed: 129 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,168 @@
11
#![forbid(unsafe_op_in_unsafe_fn)]
22

3+
use crate::mem::DropGuard;
34
use crate::pin::Pin;
4-
use crate::ptr;
5-
use crate::sync::atomic::Ordering::Relaxed;
6-
use crate::sync::atomic::{Atomic, AtomicUsize};
75
use crate::sys::pal::sync as pal;
86
use crate::sys::sync::{Mutex, OnceBox};
97
use crate::time::{Duration, Instant};
108

9+
struct StateGuard<'a> {
10+
mutex: Pin<&'a pal::Mutex>,
11+
}
12+
13+
impl<'a> Drop for StateGuard<'a> {
14+
fn drop(&mut self) {
15+
unsafe { self.mutex.unlock() };
16+
}
17+
}
18+
19+
struct State {
20+
mutex: pal::Mutex,
21+
condvar: pal::Condvar,
22+
}
23+
24+
impl State {
25+
fn condvar(self: Pin<&Self>) -> Pin<&pal::Condvar> {
26+
unsafe { self.map_unchecked(|this| &this.condvar) }
27+
}
28+
29+
fn condvar_mut(self: Pin<&mut Self>) -> Pin<&mut pal::Condvar> {
30+
unsafe { self.map_unchecked_mut(|this| &mut this.condvar) }
31+
}
32+
33+
/// Locks the `mutex` field and returns a [`StateGuard`] that unlocks the
34+
/// mutex when it is dropped.
35+
///
36+
/// # Safety
37+
///
38+
/// * The `mutex` field must not be locked by this thread.
39+
/// * Dismissing the guard leads to undefined behaviour when this `State`
40+
/// is dropped, as it is undefined behaviour to destroy a locked mutex.
41+
unsafe fn lock(self: Pin<&Self>) -> StateGuard<'_> {
42+
let mutex = unsafe { self.map_unchecked(|this| &this.mutex) };
43+
unsafe { mutex.lock() };
44+
StateGuard { mutex }
45+
}
46+
}
47+
1148
pub struct Condvar {
12-
cvar: OnceBox<pal::Condvar>,
13-
mutex: Atomic<usize>,
49+
state: OnceBox<State>,
1450
}
1551

1652
impl Condvar {
1753
pub const fn new() -> Condvar {
18-
Condvar { cvar: OnceBox::new(), mutex: AtomicUsize::new(0) }
54+
Condvar { state: OnceBox::new() }
1955
}
2056

2157
#[inline]
22-
fn get(&self) -> Pin<&pal::Condvar> {
23-
self.cvar.get_or_init(|| {
24-
let mut cvar = Box::pin(pal::Condvar::new());
58+
fn state(&self) -> Pin<&State> {
59+
self.state.get_or_init(|| {
60+
let mut state =
61+
Box::pin(State { mutex: pal::Mutex::new(), condvar: pal::Condvar::new() });
62+
2563
// SAFETY: we only call `init` once per `pal::Condvar`, namely here.
26-
unsafe { cvar.as_mut().init() };
27-
cvar
64+
unsafe { state.as_mut().condvar_mut().init() };
65+
state
2866
})
2967
}
3068

31-
#[inline]
32-
fn verify(&self, mutex: Pin<&pal::Mutex>) {
33-
let addr = ptr::from_ref::<pal::Mutex>(&mutex).addr();
34-
// Relaxed is okay here because we never read through `self.mutex`, and only use it to
35-
// compare addresses.
36-
match self.mutex.compare_exchange(0, addr, Relaxed, Relaxed) {
37-
Ok(_) => {} // Stored the address
38-
Err(n) if n == addr => {} // Lost a race to store the same address
39-
_ => panic!("attempted to use a condition variable with two mutexes"),
40-
}
41-
}
42-
43-
#[inline]
4469
pub fn notify_one(&self) {
70+
let state = self.state();
71+
// Notifications might be sent right after a mutex used with `wait` or
72+
// `wait_timeout` is unlocked. Waiting until the state mutex is
73+
// available ensures that the thread unlocking the mutex is enqueued
74+
// on the inner condition variable, as the mutex is only unlocked
75+
// with the state mutex held.
76+
//
77+
// Releasing the state mutex before issuing the notification stops
78+
// the awakened threads from having to wait on this thread unlocking
79+
// the mutex.
80+
//
81+
// SAFETY:
82+
// The functions in this module are never called recursively, so the
83+
// state mutex cannot be currently locked by this thread.
84+
drop(unsafe { state.lock() });
4585
// SAFETY: we called `init` above.
46-
unsafe { self.get().notify_one() }
86+
unsafe { state.condvar().notify_one() }
4787
}
4888

49-
#[inline]
5089
pub fn notify_all(&self) {
90+
let state = self.state();
91+
// Notifications might be sent right after a mutex used with `wait` or
92+
// `wait_timeout` is unlocked. Waiting until the state mutex is
93+
// available ensures that the thread unlocking the mutex is enqueued
94+
// on the inner condition variable, as the mutex is only unlocked
95+
// with the state mutex held.
96+
//
97+
// Releasing the state mutex before issuing the notification stops
98+
// the awakened threads from having to wait on this thread unlocking
99+
// the mutex.
100+
//
101+
// SAFETY:
102+
// The functions in this module are never called recursively, so the
103+
// state mutex cannot be currently locked by this thread.
104+
drop(unsafe { state.lock() });
51105
// SAFETY: we called `init` above.
52-
unsafe { self.get().notify_all() }
106+
unsafe { state.condvar().notify_all() }
53107
}
54108

55-
#[inline]
56109
pub unsafe fn wait(&self, mutex: &Mutex) {
57-
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
58-
// must have been initialized already.
59-
let mutex = unsafe { mutex.pal.get_unchecked() };
60-
self.verify(mutex);
61-
// SAFETY: we called `init` above, we verified that this condition
62-
// variable is only used with `mutex` and the caller guarantees that
63-
// `mutex` is locked by the current thread.
64-
unsafe { self.get().wait(mutex) }
110+
let state = self.state();
111+
// Lock the state mutex before unlocking `mutex` to ensure that
112+
// notifications occurring before this thread is enqueued on the
113+
// condvar are not missed.
114+
//
115+
// SAFETY:
116+
// The functions in this module are never called recursively, so the
117+
// state mutex cannot be currently locked by this thread.
118+
let guard = unsafe { state.lock() };
119+
120+
// SAFETY:
121+
// The caller must guarantee that `mutex` is currently locked by this
122+
// thread.
123+
unsafe { mutex.unlock() };
124+
// Ensure that the mutex is locked when this function returns or panics.
125+
let _relock = DropGuard::new(mutex, |mutex| mutex.lock());
126+
127+
// SAFETY:
128+
// * `init` was called above
129+
// * the condition variable is only ever used with the state mutex
130+
// * the state mutex was locked above
131+
unsafe { state.condvar().wait(guard.mutex) };
65132
}
66133

67134
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
68-
// SAFETY: the caller guarantees that the lock is owned, thus the mutex
69-
// must have been initialized already.
70-
let mutex = unsafe { mutex.pal.get_unchecked() };
71-
self.verify(mutex);
135+
let state = self.state();
136+
// Lock the state mutex before unlocking `mutex` to ensure that
137+
// notifications occurring before this thread is enqueued on the
138+
// condvar are not missed.
139+
//
140+
// SAFETY:
141+
// The functions in this module are never called recursively, so the
142+
// state mutex cannot be currently locked by this thread.
143+
let guard = unsafe { state.lock() };
144+
145+
// SAFETY:
146+
// The caller must guarantee that `mutex` is currently locked by this
147+
// thread.
148+
unsafe { mutex.unlock() };
149+
// Ensure that the mutex is locked when this function returns or panics.
150+
let _relock = DropGuard::new(mutex, |mutex| mutex.lock());
72151

73152
if pal::Condvar::PRECISE_TIMEOUT {
74-
// SAFETY: we called `init` above, we verified that this condition
75-
// variable is only used with `mutex` and the caller guarantees that
76-
// `mutex` is locked by the current thread.
77-
unsafe { self.get().wait_timeout(mutex, dur) }
153+
// SAFETY:
154+
// * `init` was called above
155+
// * the condition variable is only ever used with the state mutex
156+
// * the state mutex was locked above
157+
unsafe { state.condvar().wait_timeout(guard.mutex, dur) }
78158
} else {
79159
// Timeout reports are not reliable, so do the check ourselves.
80160
let now = Instant::now();
81-
// SAFETY: we called `init` above, we verified that this condition
82-
// variable is only used with `mutex` and the caller guarantees that
83-
// `mutex` is locked by the current thread.
84-
let woken = unsafe { self.get().wait_timeout(mutex, dur) };
161+
// SAFETY:
162+
// * `init` was called above
163+
// * the condition variable is only ever used with the state mutex
164+
// * the state mutex was locked above
165+
let woken = unsafe { state.condvar().wait_timeout(guard.mutex, dur) };
85166
woken || now.elapsed() < dur
86167
}
87168
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::sys::pal::sync as pal;
66
use crate::sys::sync::OnceBox;
77

88
pub struct Mutex {
9-
pub(in crate::sys::sync) pal: OnceBox<pal::Mutex>,
9+
pal: OnceBox<pal::Mutex>,
1010
}
1111

1212
impl Mutex {

0 commit comments

Comments
 (0)