about summary refs log tree commit diff
diff options
context:
space:
mode:
authorConnor Tsui <connor.tsui20@gmail.com>2025-08-22 14:58:42 -0400
committerConnor Tsui <connor.tsui20@gmail.com>2025-08-22 14:59:34 -0400
commit06eb782c4e727c33d8548a791998a0a2cbedceae (patch)
tree8cb98f0614e2e77e6156a20edead39f5d86975e9
parent6ba0ce40941eee1ca02e9ba49c791ada5158747a (diff)
downloadrust-06eb782c4e727c33d8548a791998a0a2cbedceae.tar.gz
rust-06eb782c4e727c33d8548a791998a0a2cbedceae.zip
modify `LazyLock` poison panic message
Fixes an issue where if the underlying `Once` panics because it is
poisoned, the panic displays the wrong message.

Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
-rw-r--r--library/std/src/sync/lazy_lock.rs9
-rw-r--r--library/std/tests/sync/lazy_lock.rs84
2 files changed, 59 insertions, 34 deletions
diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs
index a40e29a772a..3231125f7a1 100644
--- a/library/std/src/sync/lazy_lock.rs
+++ b/library/std/src/sync/lazy_lock.rs
@@ -244,7 +244,11 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
     #[inline]
     #[stable(feature = "lazy_cell", since = "1.80.0")]
     pub fn force(this: &LazyLock<T, F>) -> &T {
-        this.once.call_once(|| {
+        this.once.call_once_force(|state| {
+            if state.is_poisoned() {
+                panic_poisoned();
+            }
+
             // SAFETY: `call_once` only runs this closure once, ever.
             let data = unsafe { &mut *this.data.get() };
             let f = unsafe { ManuallyDrop::take(&mut data.f) };
@@ -257,8 +261,7 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
         // * the closure was called and initialized `value`.
         // * the closure was called and panicked, so this point is never reached.
         // * the closure was not called, but a previous call initialized `value`.
-        // * the closure was not called because the Once is poisoned, so this point
-        //   is never reached.
+        // * the closure was not called because the Once is poisoned, which we handled above.
         // So `value` has definitely been initialized and will not be modified again.
         unsafe { &*(*this.data.get()).value }
     }
diff --git a/library/std/tests/sync/lazy_lock.rs b/library/std/tests/sync/lazy_lock.rs
index 6c14b79f2ce..68aeea834b4 100644
--- a/library/std/tests/sync/lazy_lock.rs
+++ b/library/std/tests/sync/lazy_lock.rs
@@ -34,16 +34,6 @@ fn lazy_default() {
 }
 
 #[test]
-#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
-fn lazy_poisoning() {
-    let x: LazyCell<String> = LazyCell::new(|| panic!("kaboom"));
-    for _ in 0..2 {
-        let res = panic::catch_unwind(panic::AssertUnwindSafe(|| x.len()));
-        assert!(res.is_err());
-    }
-}
-
-#[test]
 #[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
 fn sync_lazy_new() {
     static CALLED: AtomicUsize = AtomicUsize::new(0);
@@ -123,16 +113,6 @@ fn static_sync_lazy_via_fn() {
     assert_eq!(xs(), &vec![1, 2, 3]);
 }
 
-#[test]
-#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
-fn sync_lazy_poisoning() {
-    let x: LazyLock<String> = LazyLock::new(|| panic!("kaboom"));
-    for _ in 0..2 {
-        let res = panic::catch_unwind(|| x.len());
-        assert!(res.is_err());
-    }
-}
-
 // Check that we can infer `T` from closure's type.
 #[test]
 fn lazy_type_inference() {
@@ -146,17 +126,6 @@ fn is_sync_send() {
 }
 
 #[test]
-#[should_panic = "has previously been poisoned"]
-fn lazy_force_mut_panic() {
-    let mut lazy = LazyLock::<String>::new(|| panic!());
-    panic::catch_unwind(panic::AssertUnwindSafe(|| {
-        let _ = LazyLock::force_mut(&mut lazy);
-    }))
-    .unwrap_err();
-    let _ = &*lazy;
-}
-
-#[test]
 fn lazy_force_mut() {
     let s = "abc".to_owned();
     let mut lazy = LazyLock::new(move || s);
@@ -165,3 +134,56 @@ fn lazy_force_mut() {
     p.clear();
     LazyLock::force_mut(&mut lazy);
 }
+
+#[test]
+#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
+fn lazy_poisoning() {
+    let x: LazyCell<String> = LazyCell::new(|| panic!("kaboom"));
+    for _ in 0..2 {
+        let res = panic::catch_unwind(panic::AssertUnwindSafe(|| x.len()));
+        assert!(res.is_err());
+    }
+}
+
+/// Verifies that when a `LazyLock` is poisoned, it panics with the correct error message ("LazyLock
+/// instance has previously been poisoned") instead of the underlying `Once` error message.
+#[test]
+#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
+#[should_panic(expected = "LazyLock instance has previously been poisoned")]
+fn lazy_lock_deref_panic() {
+    let lazy: LazyLock<String> = LazyLock::new(|| panic!("initialization failed"));
+
+    // First access will panic during initialization.
+    let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
+        let _ = &*lazy;
+    }));
+
+    // Second access should panic with the poisoned message.
+    let _ = &*lazy;
+}
+
+#[test]
+#[should_panic(expected = "LazyLock instance has previously been poisoned")]
+fn lazy_lock_deref_mut_panic() {
+    let mut lazy: LazyLock<String> = LazyLock::new(|| panic!("initialization failed"));
+
+    // First access will panic during initialization.
+    let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| {
+        let _ = LazyLock::force_mut(&mut lazy);
+    }));
+
+    // Second access should panic with the poisoned message.
+    let _ = &*lazy;
+}
+
+/// Verifies that when the initialization closure panics with a custom message, that message is
+/// preserved and not overridden by `LazyLock`.
+#[test]
+#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
+#[should_panic(expected = "custom panic message from closure")]
+fn lazy_lock_preserves_closure_panic_message() {
+    let lazy: LazyLock<String> = LazyLock::new(|| panic!("custom panic message from closure"));
+
+    // This should panic with the original message from the closure.
+    let _ = &*lazy;
+}