about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-02-14 10:07:01 +0000
committerbors <bors@rust-lang.org>2024-02-14 10:07:01 +0000
commit81b757c670483604c5ad04370bc505ba3d21356a (patch)
tree85b37aa43f3b13816e1ae13e634775d0a1c66acd /library/std/src
parentbb89df6903539e7014b8db29bccd6a9ee9553122 (diff)
parent6b9289c0862fa86ff3fcafe05011abb42c677e6f (diff)
downloadrust-81b757c670483604c5ad04370bc505ba3d21356a.tar.gz
rust-81b757c670483604c5ad04370bc505ba3d21356a.zip
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?
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/sync/poison.rs51
1 files changed, 49 insertions, 2 deletions
diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs
index 741312d5537..1e978bec4b4 100644
--- a/library/std/src/sync/poison.rs
+++ b/library/std/src/sync/poison.rs
@@ -1,9 +1,13 @@
 use crate::error::Error;
 use crate::fmt;
+
+#[cfg(panic = "unwind")]
 use crate::sync::atomic::{AtomicBool, Ordering};
+#[cfg(panic = "unwind")]
 use crate::thread;
 
 pub struct Flag {
+    #[cfg(panic = "unwind")]
     failed: AtomicBool,
 }
 
@@ -21,7 +25,10 @@ pub struct Flag {
 impl Flag {
     #[inline]
     pub const fn new() -> Flag {
-        Flag { failed: AtomicBool::new(false) }
+        Flag {
+            #[cfg(panic = "unwind")]
+            failed: AtomicBool::new(false),
+        }
     }
 
     /// Check the flag for an unguarded borrow, where we only care about existing poison.
@@ -33,11 +40,15 @@ impl Flag {
     /// Check the flag for a guarded borrow, where we may also set poison when `done`.
     #[inline]
     pub fn guard(&self) -> LockResult<Guard> {
-        let ret = Guard { panicking: thread::panicking() };
+        let ret = Guard {
+            #[cfg(panic = "unwind")]
+            panicking: thread::panicking(),
+        };
         if self.get() { Err(PoisonError::new(ret)) } else { Ok(ret) }
     }
 
     #[inline]
+    #[cfg(panic = "unwind")]
     pub fn done(&self, guard: &Guard) {
         if !guard.panicking && thread::panicking() {
             self.failed.store(true, Ordering::Relaxed);
@@ -45,17 +56,30 @@ impl Flag {
     }
 
     #[inline]
+    #[cfg(not(panic = "unwind"))]
+    pub fn done(&self, _guard: &Guard) {}
+
+    #[inline]
+    #[cfg(panic = "unwind")]
     pub fn get(&self) -> bool {
         self.failed.load(Ordering::Relaxed)
     }
 
+    #[inline(always)]
+    #[cfg(not(panic = "unwind"))]
+    pub fn get(&self) -> bool {
+        false
+    }
+
     #[inline]
     pub fn clear(&self) {
+        #[cfg(panic = "unwind")]
         self.failed.store(false, Ordering::Relaxed)
     }
 }
 
 pub struct Guard {
+    #[cfg(panic = "unwind")]
     panicking: bool,
 }
 
@@ -95,6 +119,8 @@ pub struct Guard {
 #[stable(feature = "rust1", since = "1.0.0")]
 pub struct PoisonError<T> {
     guard: T,
+    #[cfg(not(panic = "unwind"))]
+    _never: !,
 }
 
 /// An enumeration of possible errors associated with a [`TryLockResult`] which
@@ -165,11 +191,27 @@ impl<T> PoisonError<T> {
     ///
     /// This is generally created by methods like [`Mutex::lock`](crate::sync::Mutex::lock)
     /// or [`RwLock::read`](crate::sync::RwLock::read).
+    ///
+    /// This method may panic if std was built with `panic="abort"`.
+    #[cfg(panic = "unwind")]
     #[stable(feature = "sync_poison", since = "1.2.0")]
     pub fn new(guard: T) -> PoisonError<T> {
         PoisonError { guard }
     }
 
+    /// Creates a `PoisonError`.
+    ///
+    /// This is generally created by methods like [`Mutex::lock`](crate::sync::Mutex::lock)
+    /// or [`RwLock::read`](crate::sync::RwLock::read).
+    ///
+    /// This method may panic if std was built with `panic="abort"`.
+    #[cfg(not(panic = "unwind"))]
+    #[stable(feature = "sync_poison", since = "1.2.0")]
+    #[track_caller]
+    pub fn new(_guard: T) -> PoisonError<T> {
+        panic!("PoisonError created in a libstd built with panic=\"abort\"")
+    }
+
     /// Consumes this error indicating that a lock is poisoned, returning the
     /// underlying guard to allow access regardless.
     ///
@@ -225,6 +267,7 @@ impl<T> From<PoisonError<T>> for TryLockError<T> {
 impl<T> fmt::Debug for TryLockError<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
+            #[cfg(panic = "unwind")]
             TryLockError::Poisoned(..) => "Poisoned(..)".fmt(f),
             TryLockError::WouldBlock => "WouldBlock".fmt(f),
         }
@@ -235,6 +278,7 @@ impl<T> fmt::Debug for TryLockError<T> {
 impl<T> fmt::Display for TryLockError<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
+            #[cfg(panic = "unwind")]
             TryLockError::Poisoned(..) => "poisoned lock: another task failed inside",
             TryLockError::WouldBlock => "try_lock failed because the operation would block",
         }
@@ -247,6 +291,7 @@ impl<T> Error for TryLockError<T> {
     #[allow(deprecated, deprecated_in_future)]
     fn description(&self) -> &str {
         match *self {
+            #[cfg(panic = "unwind")]
             TryLockError::Poisoned(ref p) => p.description(),
             TryLockError::WouldBlock => "try_lock failed because the operation would block",
         }
@@ -255,6 +300,7 @@ impl<T> Error for TryLockError<T> {
     #[allow(deprecated)]
     fn cause(&self) -> Option<&dyn Error> {
         match *self {
+            #[cfg(panic = "unwind")]
             TryLockError::Poisoned(ref p) => Some(p),
             _ => None,
         }
@@ -267,6 +313,7 @@ where
 {
     match result {
         Ok(t) => Ok(f(t)),
+        #[cfg(panic = "unwind")]
         Err(PoisonError { guard }) => Err(PoisonError::new(f(guard))),
     }
 }