Skip to content

Commit 81b757c

Browse files
committed
Auto merge of #100603 - tmandry:zst-guards, r=dtolnay
Optimize away poison guards when std is built with panic=abort > **Note**: To take advantage of this PR, you will have to use `-Zbuild-std` or build your own toolchain. rustup toolchains always link to a libstd that was compiled with `panic=unwind`, since it's compatible with `panic=abort` code. When std is compiled with `panic=abort` we can remove a lot of the poison machinery from the locks. This changes the `Flag` and `Guard` types to be ZSTs. It also adds an uninhabited member to `PoisonError` so the compiler knows it can optimize away the `Result::Err` paths, and make `LockResult<T>` layout-equivalent to `T`. ### Is this a breaking change? `PoisonError::new` now panics if invoked from a libstd built with `panic="abort"` (or any non-`unwind` strategy). It is unclear to me whether to consider this a breaking change. In order to encounter this behavior, **both of the following must be true**: #### Using a libstd with `panic="abort"` This is pretty uncommon. We don't build libstd with that in rustup, except in (Tier 2-3) platforms that do not support unwinding, **most notably wasm**. Most people who do this are using cargo's `-Z build-std` feature, which is unstable. `panic="abort"` is not a supported option in Rust's build system. It is possible to configure it using `CARGO_TARGET_xxx_RUSTFLAGS`, but I believe this only works on **non-host** platforms. #### Creating `PoisonError` manually This is also unlikely. The only common use case I can think of is in tests, and you can't run tests with `panic="abort"` without the unstable `-Z panic_abort_tests` flag. It's possible that someone is implementing their own locks using std's `PoisonError` **and** defining "thread failure" to mean something other than "panic". If this is the case then we would break their code if it was used with a `panic="abort"` libstd. The locking crates I know of don't replicate std's poison API, but I haven't done much research into this yet. I've touched on a fair number of considerations here. Which ones do people consider relevant?
2 parents bb89df6 + 6b9289c commit 81b757c

File tree

1 file changed

+49
-2
lines changed

1 file changed

+49
-2
lines changed

library/std/src/sync/poison.rs

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use crate::error::Error;
22
use crate::fmt;
3+
4+
#[cfg(panic = "unwind")]
35
use crate::sync::atomic::{AtomicBool, Ordering};
6+
#[cfg(panic = "unwind")]
47
use crate::thread;
58

69
pub struct Flag {
10+
#[cfg(panic = "unwind")]
711
failed: AtomicBool,
812
}
913

@@ -21,7 +25,10 @@ pub struct Flag {
2125
impl Flag {
2226
#[inline]
2327
pub const fn new() -> Flag {
24-
Flag { failed: AtomicBool::new(false) }
28+
Flag {
29+
#[cfg(panic = "unwind")]
30+
failed: AtomicBool::new(false),
31+
}
2532
}
2633

2734
/// Check the flag for an unguarded borrow, where we only care about existing poison.
@@ -33,29 +40,46 @@ impl Flag {
3340
/// Check the flag for a guarded borrow, where we may also set poison when `done`.
3441
#[inline]
3542
pub fn guard(&self) -> LockResult<Guard> {
36-
let ret = Guard { panicking: thread::panicking() };
43+
let ret = Guard {
44+
#[cfg(panic = "unwind")]
45+
panicking: thread::panicking(),
46+
};
3747
if self.get() { Err(PoisonError::new(ret)) } else { Ok(ret) }
3848
}
3949

4050
#[inline]
51+
#[cfg(panic = "unwind")]
4152
pub fn done(&self, guard: &Guard) {
4253
if !guard.panicking && thread::panicking() {
4354
self.failed.store(true, Ordering::Relaxed);
4455
}
4556
}
4657

4758
#[inline]
59+
#[cfg(not(panic = "unwind"))]
60+
pub fn done(&self, _guard: &Guard) {}
61+
62+
#[inline]
63+
#[cfg(panic = "unwind")]
4864
pub fn get(&self) -> bool {
4965
self.failed.load(Ordering::Relaxed)
5066
}
5167

68+
#[inline(always)]
69+
#[cfg(not(panic = "unwind"))]
70+
pub fn get(&self) -> bool {
71+
false
72+
}
73+
5274
#[inline]
5375
pub fn clear(&self) {
76+
#[cfg(panic = "unwind")]
5477
self.failed.store(false, Ordering::Relaxed)
5578
}
5679
}
5780

5881
pub struct Guard {
82+
#[cfg(panic = "unwind")]
5983
panicking: bool,
6084
}
6185

@@ -95,6 +119,8 @@ pub struct Guard {
95119
#[stable(feature = "rust1", since = "1.0.0")]
96120
pub struct PoisonError<T> {
97121
guard: T,
122+
#[cfg(not(panic = "unwind"))]
123+
_never: !,
98124
}
99125

100126
/// An enumeration of possible errors associated with a [`TryLockResult`] which
@@ -165,11 +191,27 @@ impl<T> PoisonError<T> {
165191
///
166192
/// This is generally created by methods like [`Mutex::lock`](crate::sync::Mutex::lock)
167193
/// or [`RwLock::read`](crate::sync::RwLock::read).
194+
///
195+
/// This method may panic if std was built with `panic="abort"`.
196+
#[cfg(panic = "unwind")]
168197
#[stable(feature = "sync_poison", since = "1.2.0")]
169198
pub fn new(guard: T) -> PoisonError<T> {
170199
PoisonError { guard }
171200
}
172201

202+
/// Creates a `PoisonError`.
203+
///
204+
/// This is generally created by methods like [`Mutex::lock`](crate::sync::Mutex::lock)
205+
/// or [`RwLock::read`](crate::sync::RwLock::read).
206+
///
207+
/// This method may panic if std was built with `panic="abort"`.
208+
#[cfg(not(panic = "unwind"))]
209+
#[stable(feature = "sync_poison", since = "1.2.0")]
210+
#[track_caller]
211+
pub fn new(_guard: T) -> PoisonError<T> {
212+
panic!("PoisonError created in a libstd built with panic=\"abort\"")
213+
}
214+
173215
/// Consumes this error indicating that a lock is poisoned, returning the
174216
/// underlying guard to allow access regardless.
175217
///
@@ -225,6 +267,7 @@ impl<T> From<PoisonError<T>> for TryLockError<T> {
225267
impl<T> fmt::Debug for TryLockError<T> {
226268
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227269
match *self {
270+
#[cfg(panic = "unwind")]
228271
TryLockError::Poisoned(..) => "Poisoned(..)".fmt(f),
229272
TryLockError::WouldBlock => "WouldBlock".fmt(f),
230273
}
@@ -235,6 +278,7 @@ impl<T> fmt::Debug for TryLockError<T> {
235278
impl<T> fmt::Display for TryLockError<T> {
236279
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237280
match *self {
281+
#[cfg(panic = "unwind")]
238282
TryLockError::Poisoned(..) => "poisoned lock: another task failed inside",
239283
TryLockError::WouldBlock => "try_lock failed because the operation would block",
240284
}
@@ -247,6 +291,7 @@ impl<T> Error for TryLockError<T> {
247291
#[allow(deprecated, deprecated_in_future)]
248292
fn description(&self) -> &str {
249293
match *self {
294+
#[cfg(panic = "unwind")]
250295
TryLockError::Poisoned(ref p) => p.description(),
251296
TryLockError::WouldBlock => "try_lock failed because the operation would block",
252297
}
@@ -255,6 +300,7 @@ impl<T> Error for TryLockError<T> {
255300
#[allow(deprecated)]
256301
fn cause(&self) -> Option<&dyn Error> {
257302
match *self {
303+
#[cfg(panic = "unwind")]
258304
TryLockError::Poisoned(ref p) => Some(p),
259305
_ => None,
260306
}
@@ -267,6 +313,7 @@ where
267313
{
268314
match result {
269315
Ok(t) => Ok(f(t)),
316+
#[cfg(panic = "unwind")]
270317
Err(PoisonError { guard }) => Err(PoisonError::new(f(guard))),
271318
}
272319
}

0 commit comments

Comments
 (0)