diff options
| author | Connor Tsui <connor.tsui20@gmail.com> | 2025-08-22 14:58:42 -0400 |
|---|---|---|
| committer | Connor Tsui <connor.tsui20@gmail.com> | 2025-08-22 14:59:34 -0400 |
| commit | 06eb782c4e727c33d8548a791998a0a2cbedceae (patch) | |
| tree | 8cb98f0614e2e77e6156a20edead39f5d86975e9 | |
| parent | 6ba0ce40941eee1ca02e9ba49c791ada5158747a (diff) | |
| download | rust-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.rs | 9 | ||||
| -rw-r--r-- | library/std/tests/sync/lazy_lock.rs | 84 |
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; +} |
