Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions library/std/src/sync/lazy_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) };
Expand All @@ -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 }
}
Expand Down
84 changes: 53 additions & 31 deletions library/std/tests/sync/lazy_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,6 @@ fn lazy_default() {
assert_eq!(CALLED.load(SeqCst), 1);
}

#[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() {
Expand Down Expand Up @@ -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() {
Expand All @@ -145,17 +125,6 @@ fn is_sync_send() {
assert_traits::<LazyLock<String>>();
}

#[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;
}
Comment on lines -148 to -157
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is moved down and renamed to lazy_deref_mut_panic


#[test]
fn lazy_force_mut() {
let s = "abc".to_owned();
Expand All @@ -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;
}
Loading