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 332b9be

Browse files
authoredFeb 28, 2024
Rollup merge of #110543 - joboet:reentrant_lock, r=m-ou-se
Make `ReentrantLock` public Implements the ACP rust-lang/libs-team#193. ``@rustbot`` label +T-libs-api +S-waiting-on-ACP
2 parents c475e23 + 2aa8a1d commit 332b9be

File tree

5 files changed

+375
-207
lines changed

5 files changed

+375
-207
lines changed
 

‎library/std/src/io/stdio.rs

+35-10
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use crate::fs::File;
1111
use crate::io::{
1212
self, BorrowedCursor, BufReader, IoSlice, IoSliceMut, LineWriter, Lines, SpecReadByte,
1313
};
14+
use crate::panic::{RefUnwindSafe, UnwindSafe};
1415
use crate::sync::atomic::{AtomicBool, Ordering};
15-
use crate::sync::{Arc, Mutex, MutexGuard, OnceLock, ReentrantMutex, ReentrantMutexGuard};
16+
use crate::sync::{Arc, Mutex, MutexGuard, OnceLock, ReentrantLock, ReentrantLockGuard};
1617
use crate::sys::stdio;
1718

1819
type LocalStream = Arc<Mutex<Vec<u8>>>;
@@ -545,7 +546,7 @@ pub struct Stdout {
545546
// FIXME: this should be LineWriter or BufWriter depending on the state of
546547
// stdout (tty or not). Note that if this is not line buffered it
547548
// should also flush-on-panic or some form of flush-on-abort.
548-
inner: &'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>,
549+
inner: &'static ReentrantLock<RefCell<LineWriter<StdoutRaw>>>,
549550
}
550551

551552
/// A locked reference to the [`Stdout`] handle.
@@ -567,10 +568,10 @@ pub struct Stdout {
567568
#[must_use = "if unused stdout will immediately unlock"]
568569
#[stable(feature = "rust1", since = "1.0.0")]
569570
pub struct StdoutLock<'a> {
570-
inner: ReentrantMutexGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
571+
inner: ReentrantLockGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
571572
}
572573

573-
static STDOUT: OnceLock<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = OnceLock::new();
574+
static STDOUT: OnceLock<ReentrantLock<RefCell<LineWriter<StdoutRaw>>>> = OnceLock::new();
574575

575576
/// Constructs a new handle to the standard output of the current process.
576577
///
@@ -624,7 +625,7 @@ static STDOUT: OnceLock<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = OnceLo
624625
pub fn stdout() -> Stdout {
625626
Stdout {
626627
inner: STDOUT
627-
.get_or_init(|| ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())))),
628+
.get_or_init(|| ReentrantLock::new(RefCell::new(LineWriter::new(stdout_raw())))),
628629
}
629630
}
630631

@@ -635,7 +636,7 @@ pub fn cleanup() {
635636
let mut initialized = false;
636637
let stdout = STDOUT.get_or_init(|| {
637638
initialized = true;
638-
ReentrantMutex::new(RefCell::new(LineWriter::with_capacity(0, stdout_raw())))
639+
ReentrantLock::new(RefCell::new(LineWriter::with_capacity(0, stdout_raw())))
639640
});
640641

641642
if !initialized {
@@ -678,6 +679,12 @@ impl Stdout {
678679
}
679680
}
680681

682+
#[stable(feature = "catch_unwind", since = "1.9.0")]
683+
impl UnwindSafe for Stdout {}
684+
685+
#[stable(feature = "catch_unwind", since = "1.9.0")]
686+
impl RefUnwindSafe for Stdout {}
687+
681688
#[stable(feature = "std_debug", since = "1.16.0")]
682689
impl fmt::Debug for Stdout {
683690
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -737,6 +744,12 @@ impl Write for &Stdout {
737744
}
738745
}
739746

747+
#[stable(feature = "catch_unwind", since = "1.9.0")]
748+
impl UnwindSafe for StdoutLock<'_> {}
749+
750+
#[stable(feature = "catch_unwind", since = "1.9.0")]
751+
impl RefUnwindSafe for StdoutLock<'_> {}
752+
740753
#[stable(feature = "rust1", since = "1.0.0")]
741754
impl Write for StdoutLock<'_> {
742755
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@@ -786,7 +799,7 @@ impl fmt::Debug for StdoutLock<'_> {
786799
/// standard library or via raw Windows API calls, will fail.
787800
#[stable(feature = "rust1", since = "1.0.0")]
788801
pub struct Stderr {
789-
inner: &'static ReentrantMutex<RefCell<StderrRaw>>,
802+
inner: &'static ReentrantLock<RefCell<StderrRaw>>,
790803
}
791804

792805
/// A locked reference to the [`Stderr`] handle.
@@ -808,7 +821,7 @@ pub struct Stderr {
808821
#[must_use = "if unused stderr will immediately unlock"]
809822
#[stable(feature = "rust1", since = "1.0.0")]
810823
pub struct StderrLock<'a> {
811-
inner: ReentrantMutexGuard<'a, RefCell<StderrRaw>>,
824+
inner: ReentrantLockGuard<'a, RefCell<StderrRaw>>,
812825
}
813826

814827
/// Constructs a new handle to the standard error of the current process.
@@ -862,8 +875,8 @@ pub fn stderr() -> Stderr {
862875
// Note that unlike `stdout()` we don't use `at_exit` here to register a
863876
// destructor. Stderr is not buffered, so there's no need to run a
864877
// destructor for flushing the buffer
865-
static INSTANCE: ReentrantMutex<RefCell<StderrRaw>> =
866-
ReentrantMutex::new(RefCell::new(stderr_raw()));
878+
static INSTANCE: ReentrantLock<RefCell<StderrRaw>> =
879+
ReentrantLock::new(RefCell::new(stderr_raw()));
867880

868881
Stderr { inner: &INSTANCE }
869882
}
@@ -898,6 +911,12 @@ impl Stderr {
898911
}
899912
}
900913

914+
#[stable(feature = "catch_unwind", since = "1.9.0")]
915+
impl UnwindSafe for Stderr {}
916+
917+
#[stable(feature = "catch_unwind", since = "1.9.0")]
918+
impl RefUnwindSafe for Stderr {}
919+
901920
#[stable(feature = "std_debug", since = "1.16.0")]
902921
impl fmt::Debug for Stderr {
903922
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -957,6 +976,12 @@ impl Write for &Stderr {
957976
}
958977
}
959978

979+
#[stable(feature = "catch_unwind", since = "1.9.0")]
980+
impl UnwindSafe for StderrLock<'_> {}
981+
982+
#[stable(feature = "catch_unwind", since = "1.9.0")]
983+
impl RefUnwindSafe for StderrLock<'_> {}
984+
960985
#[stable(feature = "rust1", since = "1.0.0")]
961986
impl Write for StderrLock<'_> {
962987
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ pub use self::lazy_lock::LazyLock;
184184
#[stable(feature = "once_cell", since = "1.70.0")]
185185
pub use self::once_lock::OnceLock;
186186

187-
pub(crate) use self::remutex::{ReentrantMutex, ReentrantMutexGuard};
187+
#[unstable(feature = "reentrant_lock", issue = "121440")]
188+
pub use self::reentrant_lock::{ReentrantLock, ReentrantLockGuard};
188189

189190
pub mod mpsc;
190191

@@ -196,5 +197,5 @@ mod mutex;
196197
pub(crate) mod once;
197198
mod once_lock;
198199
mod poison;
199-
mod remutex;
200+
mod reentrant_lock;
200201
mod rwlock;
+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
#[cfg(all(test, not(target_os = "emscripten")))]
2+
mod tests;
3+
4+
use crate::cell::UnsafeCell;
5+
use crate::fmt;
6+
use crate::ops::Deref;
7+
use crate::panic::{RefUnwindSafe, UnwindSafe};
8+
use crate::sync::atomic::{AtomicUsize, Ordering::Relaxed};
9+
use crate::sys::locks as sys;
10+
11+
/// A re-entrant mutual exclusion lock
12+
///
13+
/// This lock will block *other* threads waiting for the lock to become
14+
/// available. The thread which has already locked the mutex can lock it
15+
/// multiple times without blocking, preventing a common source of deadlocks.
16+
///
17+
/// # Examples
18+
///
19+
/// Allow recursively calling a function needing synchronization from within
20+
/// a callback (this is how [`StdoutLock`](crate::io::StdoutLock) is currently
21+
/// implemented):
22+
///
23+
/// ```
24+
/// #![feature(reentrant_lock)]
25+
///
26+
/// use std::cell::RefCell;
27+
/// use std::sync::ReentrantLock;
28+
///
29+
/// pub struct Log {
30+
/// data: RefCell<String>,
31+
/// }
32+
///
33+
/// impl Log {
34+
/// pub fn append(&self, msg: &str) {
35+
/// self.data.borrow_mut().push_str(msg);
36+
/// }
37+
/// }
38+
///
39+
/// static LOG: ReentrantLock<Log> = ReentrantLock::new(Log { data: RefCell::new(String::new()) });
40+
///
41+
/// pub fn with_log<R>(f: impl FnOnce(&Log) -> R) -> R {
42+
/// let log = LOG.lock();
43+
/// f(&*log)
44+
/// }
45+
///
46+
/// with_log(|log| {
47+
/// log.append("Hello");
48+
/// with_log(|log| log.append(" there!"));
49+
/// });
50+
/// ```
51+
///
52+
// # Implementation details
53+
//
54+
// The 'owner' field tracks which thread has locked the mutex.
55+
//
56+
// We use current_thread_unique_ptr() as the thread identifier,
57+
// which is just the address of a thread local variable.
58+
//
59+
// If `owner` is set to the identifier of the current thread,
60+
// we assume the mutex is already locked and instead of locking it again,
61+
// we increment `lock_count`.
62+
//
63+
// When unlocking, we decrement `lock_count`, and only unlock the mutex when
64+
// it reaches zero.
65+
//
66+
// `lock_count` is protected by the mutex and only accessed by the thread that has
67+
// locked the mutex, so needs no synchronization.
68+
//
69+
// `owner` can be checked by other threads that want to see if they already
70+
// hold the lock, so needs to be atomic. If it compares equal, we're on the
71+
// same thread that holds the mutex and memory access can use relaxed ordering
72+
// since we're not dealing with multiple threads. If it's not equal,
73+
// synchronization is left to the mutex, making relaxed memory ordering for
74+
// the `owner` field fine in all cases.
75+
#[unstable(feature = "reentrant_lock", issue = "121440")]
76+
pub struct ReentrantLock<T: ?Sized> {
77+
mutex: sys::Mutex,
78+
owner: AtomicUsize,
79+
lock_count: UnsafeCell<u32>,
80+
data: T,
81+
}
82+
83+
#[unstable(feature = "reentrant_lock", issue = "121440")]
84+
unsafe impl<T: Send + ?Sized> Send for ReentrantLock<T> {}
85+
#[unstable(feature = "reentrant_lock", issue = "121440")]
86+
unsafe impl<T: Send + ?Sized> Sync for ReentrantLock<T> {}
87+
88+
// Because of the `UnsafeCell`, these traits are not implemented automatically
89+
#[unstable(feature = "reentrant_lock", issue = "121440")]
90+
impl<T: UnwindSafe + ?Sized> UnwindSafe for ReentrantLock<T> {}
91+
#[unstable(feature = "reentrant_lock", issue = "121440")]
92+
impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for ReentrantLock<T> {}
93+
94+
/// An RAII implementation of a "scoped lock" of a re-entrant lock. When this
95+
/// structure is dropped (falls out of scope), the lock will be unlocked.
96+
///
97+
/// The data protected by the mutex can be accessed through this guard via its
98+
/// [`Deref`] implementation.
99+
///
100+
/// This structure is created by the [`lock`](ReentrantLock::lock) method on
101+
/// [`ReentrantLock`].
102+
///
103+
/// # Mutability
104+
///
105+
/// Unlike [`MutexGuard`](super::MutexGuard), `ReentrantLockGuard` does not
106+
/// implement [`DerefMut`](crate::ops::DerefMut), because implementation of
107+
/// the trait would violate Rust’s reference aliasing rules. Use interior
108+
/// mutability (usually [`RefCell`](crate::cell::RefCell)) in order to mutate
109+
/// the guarded data.
110+
#[must_use = "if unused the ReentrantLock will immediately unlock"]
111+
#[unstable(feature = "reentrant_lock", issue = "121440")]
112+
pub struct ReentrantLockGuard<'a, T: ?Sized + 'a> {
113+
lock: &'a ReentrantLock<T>,
114+
}
115+
116+
#[unstable(feature = "reentrant_lock", issue = "121440")]
117+
impl<T: ?Sized> !Send for ReentrantLockGuard<'_, T> {}
118+
119+
#[unstable(feature = "reentrant_lock", issue = "121440")]
120+
impl<T> ReentrantLock<T> {
121+
/// Creates a new re-entrant lock in an unlocked state ready for use.
122+
///
123+
/// # Examples
124+
///
125+
/// ```
126+
/// #![feature(reentrant_lock)]
127+
/// use std::sync::ReentrantLock;
128+
///
129+
/// let lock = ReentrantLock::new(0);
130+
/// ```
131+
pub const fn new(t: T) -> ReentrantLock<T> {
132+
ReentrantLock {
133+
mutex: sys::Mutex::new(),
134+
owner: AtomicUsize::new(0),
135+
lock_count: UnsafeCell::new(0),
136+
data: t,
137+
}
138+
}
139+
140+
/// Consumes this lock, returning the underlying data.
141+
///
142+
/// # Examples
143+
///
144+
/// ```
145+
/// #![feature(reentrant_lock)]
146+
///
147+
/// use std::sync::ReentrantLock;
148+
///
149+
/// let lock = ReentrantLock::new(0);
150+
/// assert_eq!(lock.into_inner(), 0);
151+
/// ```
152+
pub fn into_inner(self) -> T {
153+
self.data
154+
}
155+
}
156+
157+
#[unstable(feature = "reentrant_lock", issue = "121440")]
158+
impl<T: ?Sized> ReentrantLock<T> {
159+
/// Acquires the lock, blocking the current thread until it is able to do
160+
/// so.
161+
///
162+
/// This function will block the caller until it is available to acquire
163+
/// the lock. Upon returning, the thread is the only thread with the lock
164+
/// held. When the thread calling this method already holds the lock, the
165+
/// call succeeds without blocking.
166+
///
167+
/// # Examples
168+
///
169+
/// ```
170+
/// #![feature(reentrant_lock)]
171+
/// use std::cell::Cell;
172+
/// use std::sync::{Arc, ReentrantLock};
173+
/// use std::thread;
174+
///
175+
/// let lock = Arc::new(ReentrantLock::new(Cell::new(0)));
176+
/// let c_lock = Arc::clone(&lock);
177+
///
178+
/// thread::spawn(move || {
179+
/// c_lock.lock().set(10);
180+
/// }).join().expect("thread::spawn failed");
181+
/// assert_eq!(lock.lock().get(), 10);
182+
/// ```
183+
pub fn lock(&self) -> ReentrantLockGuard<'_, T> {
184+
let this_thread = current_thread_unique_ptr();
185+
// Safety: We only touch lock_count when we own the lock.
186+
unsafe {
187+
if self.owner.load(Relaxed) == this_thread {
188+
self.increment_lock_count().expect("lock count overflow in reentrant mutex");
189+
} else {
190+
self.mutex.lock();
191+
self.owner.store(this_thread, Relaxed);
192+
debug_assert_eq!(*self.lock_count.get(), 0);
193+
*self.lock_count.get() = 1;
194+
}
195+
}
196+
ReentrantLockGuard { lock: self }
197+
}
198+
199+
/// Returns a mutable reference to the underlying data.
200+
///
201+
/// Since this call borrows the `ReentrantLock` mutably, no actual locking
202+
/// needs to take place -- the mutable borrow statically guarantees no locks
203+
/// exist.
204+
///
205+
/// # Examples
206+
///
207+
/// ```
208+
/// #![feature(reentrant_lock)]
209+
/// use std::sync::ReentrantLock;
210+
///
211+
/// let mut lock = ReentrantLock::new(0);
212+
/// *lock.get_mut() = 10;
213+
/// assert_eq!(*lock.lock(), 10);
214+
/// ```
215+
pub fn get_mut(&mut self) -> &mut T {
216+
&mut self.data
217+
}
218+
219+
/// Attempts to acquire this lock.
220+
///
221+
/// If the lock could not be acquired at this time, then `None` is returned.
222+
/// Otherwise, an RAII guard is returned.
223+
///
224+
/// This function does not block.
225+
pub(crate) fn try_lock(&self) -> Option<ReentrantLockGuard<'_, T>> {
226+
let this_thread = current_thread_unique_ptr();
227+
// Safety: We only touch lock_count when we own the lock.
228+
unsafe {
229+
if self.owner.load(Relaxed) == this_thread {
230+
self.increment_lock_count()?;
231+
Some(ReentrantLockGuard { lock: self })
232+
} else if self.mutex.try_lock() {
233+
self.owner.store(this_thread, Relaxed);
234+
debug_assert_eq!(*self.lock_count.get(), 0);
235+
*self.lock_count.get() = 1;
236+
Some(ReentrantLockGuard { lock: self })
237+
} else {
238+
None
239+
}
240+
}
241+
}
242+
243+
unsafe fn increment_lock_count(&self) -> Option<()> {
244+
*self.lock_count.get() = (*self.lock_count.get()).checked_add(1)?;
245+
Some(())
246+
}
247+
}
248+
249+
#[unstable(feature = "reentrant_lock", issue = "121440")]
250+
impl<T: fmt::Debug + ?Sized> fmt::Debug for ReentrantLock<T> {
251+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252+
let mut d = f.debug_struct("ReentrantLock");
253+
match self.try_lock() {
254+
Some(v) => d.field("data", &&*v),
255+
None => d.field("data", &format_args!("<locked>")),
256+
};
257+
d.finish_non_exhaustive()
258+
}
259+
}
260+
261+
#[unstable(feature = "reentrant_lock", issue = "121440")]
262+
impl<T: Default> Default for ReentrantLock<T> {
263+
fn default() -> Self {
264+
Self::new(T::default())
265+
}
266+
}
267+
268+
#[unstable(feature = "reentrant_lock", issue = "121440")]
269+
impl<T> From<T> for ReentrantLock<T> {
270+
fn from(t: T) -> Self {
271+
Self::new(t)
272+
}
273+
}
274+
275+
#[unstable(feature = "reentrant_lock", issue = "121440")]
276+
impl<T: ?Sized> Deref for ReentrantLockGuard<'_, T> {
277+
type Target = T;
278+
279+
fn deref(&self) -> &T {
280+
&self.lock.data
281+
}
282+
}
283+
284+
#[unstable(feature = "reentrant_lock", issue = "121440")]
285+
impl<T: fmt::Debug + ?Sized> fmt::Debug for ReentrantLockGuard<'_, T> {
286+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287+
(**self).fmt(f)
288+
}
289+
}
290+
291+
#[unstable(feature = "reentrant_lock", issue = "121440")]
292+
impl<T: fmt::Display + ?Sized> fmt::Display for ReentrantLockGuard<'_, T> {
293+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294+
(**self).fmt(f)
295+
}
296+
}
297+
298+
#[unstable(feature = "reentrant_lock", issue = "121440")]
299+
impl<T: ?Sized> Drop for ReentrantLockGuard<'_, T> {
300+
#[inline]
301+
fn drop(&mut self) {
302+
// Safety: We own the lock.
303+
unsafe {
304+
*self.lock.lock_count.get() -= 1;
305+
if *self.lock.lock_count.get() == 0 {
306+
self.lock.owner.store(0, Relaxed);
307+
self.lock.mutex.unlock();
308+
}
309+
}
310+
}
311+
}
312+
313+
/// Get an address that is unique per running thread.
314+
///
315+
/// This can be used as a non-null usize-sized ID.
316+
pub(crate) fn current_thread_unique_ptr() -> usize {
317+
// Use a non-drop type to make sure it's still available during thread destruction.
318+
thread_local! { static X: u8 = const { 0 } }
319+
X.with(|x| <*const _>::addr(x))
320+
}

‎library/std/src/sync/remutex/tests.rs renamed to ‎library/std/src/sync/reentrant_lock/tests.rs

+17-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
use super::{ReentrantMutex, ReentrantMutexGuard};
1+
use super::{ReentrantLock, ReentrantLockGuard};
22
use crate::cell::RefCell;
33
use crate::sync::Arc;
44
use crate::thread;
55

66
#[test]
77
fn smoke() {
8-
let m = ReentrantMutex::new(());
8+
let l = ReentrantLock::new(());
99
{
10-
let a = m.lock();
10+
let a = l.lock();
1111
{
12-
let b = m.lock();
12+
let b = l.lock();
1313
{
14-
let c = m.lock();
14+
let c = l.lock();
1515
assert_eq!(*c, ());
1616
}
1717
assert_eq!(*b, ());
@@ -22,15 +22,15 @@ fn smoke() {
2222

2323
#[test]
2424
fn is_mutex() {
25-
let m = Arc::new(ReentrantMutex::new(RefCell::new(0)));
26-
let m2 = m.clone();
27-
let lock = m.lock();
25+
let l = Arc::new(ReentrantLock::new(RefCell::new(0)));
26+
let l2 = l.clone();
27+
let lock = l.lock();
2828
let child = thread::spawn(move || {
29-
let lock = m2.lock();
29+
let lock = l2.lock();
3030
assert_eq!(*lock.borrow(), 4950);
3131
});
3232
for i in 0..100 {
33-
let lock = m.lock();
33+
let lock = l.lock();
3434
*lock.borrow_mut() += i;
3535
}
3636
drop(lock);
@@ -39,20 +39,20 @@ fn is_mutex() {
3939

4040
#[test]
4141
fn trylock_works() {
42-
let m = Arc::new(ReentrantMutex::new(()));
43-
let m2 = m.clone();
44-
let _lock = m.try_lock();
45-
let _lock2 = m.try_lock();
42+
let l = Arc::new(ReentrantLock::new(()));
43+
let l2 = l.clone();
44+
let _lock = l.try_lock();
45+
let _lock2 = l.try_lock();
4646
thread::spawn(move || {
47-
let lock = m2.try_lock();
47+
let lock = l2.try_lock();
4848
assert!(lock.is_none());
4949
})
5050
.join()
5151
.unwrap();
52-
let _lock3 = m.try_lock();
52+
let _lock3 = l.try_lock();
5353
}
5454

55-
pub struct Answer<'a>(pub ReentrantMutexGuard<'a, RefCell<u32>>);
55+
pub struct Answer<'a>(pub ReentrantLockGuard<'a, RefCell<u32>>);
5656
impl Drop for Answer<'_> {
5757
fn drop(&mut self) {
5858
*self.0.borrow_mut() = 42;

‎library/std/src/sync/remutex.rs

-178
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.