Beispiel
once_cell 1.21.4: Ensure OnceCell::wait under parking_lot feature does not prematurely return uninitialized memory when an initializer panics
Verifiziertes Beispiel für cargo once_cell 1.21.4: Ensure OnceCell::wait under parking_lot feature does not prematurely return uninitialized memory when an…
sha256:aa670fbfe7d7615096a4cc1bf4da208ff59177e8e9774a7ce78b2ca5082aa8e9
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 3
- Signaturschlüssel, die es gebaut haben
- 3
Deklarierte Umgebung
rust linux x64 rust rust cargo
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| rust 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-16 |
| rust 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1 |
2026-08-18 |
| rust 1 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · cargo@1rust:1-alpine@sha256:a10e64dd139b… |
2026-09-08 |
Fall
HOW- Ziel
- Ensure OnceCell::wait under parking_lot feature does not prematurely return uninitialized memory when an initializer panics
- Pakete
- Symbole
-
- once_cell::sync::OnceCell::wait
- once_cell::sync::OnceCell::get_or_try_init
- once_cell::sync::OnceCell::get_or_init
- Umgebung
- rust
- Erstellt
- 2026-08-16T08:11:50Z
Contract
- assert OnceCell::wait under parking_lot feature stays parked across a panicking get_or_try_init until a retry succeeds
- assert multiple concurrent OnceCell::wait threads re-park when an initializer unwinds and reliably observe the value once completed
- assert OnceCell::get_or_try_init leaves the cell uninitialized after a panic, allowing subsequent initializers to run
Dateien
- Cargo.lock
- Cargo.toml
- NOTES.md
- csx.json
- src/lib.rs
Quelltext
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "csx-sample-once-cell-wait-panic"
version = "1.0.0"
dependencies = [
"once_cell",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
dependencies = [
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[package]
name = "csx-sample-once-cell-wait-panic"
version = "1.0.0"
edition = "2021"
license = "MIT-0"
[dependencies]
once_cell = { version = "1.21.4", features = ["parking_lot"] }
# Implementation Notes
## search_known_solution Findings
`search_known_solution` returned known sample `sha256:36b55cf55782c3258dcb2509e721eb8edf4affdd15e52754fe01d12bb5fa27c9`, which explores migration to standard library types (`std::sync::OnceLock`, `LazyLock`, `std::cell::OnceCell`) and measuring compiler feature gates. We targeted a completely distinct bugfix from `once_cell 1.21.4` (PR #295): fixing unsound premature wakeups in `OnceCell::wait` under the `parking_lot` feature when an initializer panics.
## Naive Model Expectation
A model would assume `OnceCell::wait()` unconditionally blocks until initialization successfully completes regardless of whether concurrent initializer closures panic during execution or whether the `parking_lot` backend is activated.
## Failure Mode on Previous Version
On `once_cell <= 1.21.3` with `--features=parking_lot`, when an initializer closure panics, the unwind cleanup unparks all waiting threads and `wait()` exits without re-checking the state in a loop, silently returning uninitialized memory via `get_unchecked()` before any value is written.
{"case":{"caseId":"case:sha256:66e2b032d56331ef25db06c7e91b5eac4f951ad7319fa8a9bc9bf7bade6e65a1","contract":["assert OnceCell::wait under parking_lot feature stays parked across a panicking get_or_try_init until a retry succeeds","assert multiple concurrent OnceCell::wait threads re-park when an initializer unwinds and reliably observe the value once completed","assert OnceCell::get_or_try_init leaves the cell uninitialized after a panic, allowing subsequent initializers to run"],"goal":"Ensure OnceCell::wait under parking_lot feature does not prematurely return uninitialized memory when an initializer panics","kind":"HOW","packages":["pkg:cargo/once_cell@1.21.4"],"schemaVersion":1,"symbols":["once_cell::sync::OnceCell::wait","once_cell::sync::OnceCell::get_or_try_init","once_cell::sync::OnceCell::get_or_init"]},"contractCommand":["cargo","test","--offline"],"environment":{"arch":"x64","ecosystem":"cargo","executionContext":"rust","language":"rust","os":"linux","packageManager":"cargo","runtime":"rust","schemaVersion":1},"license":"MIT-0","packages":["pkg:cargo/once_cell@1.21.4"],"schemaVersion":1,"symbols":["once_cell::sync::OnceCell::wait","once_cell::sync::OnceCell::get_or_try_init","once_cell::sync::OnceCell::get_or_init"],"verifierAdapter":"cargo@1"}
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Barrier;
use std::thread;
use std::time::Duration;
use once_cell::sync::OnceCell;
/// Asserts that `OnceCell::wait` (under the `parking_lot` feature) does not
/// return prematurely when a concurrent initializer panics, and instead keeps
/// waiting until a subsequent initializer completes successfully.
///
/// In `once_cell <= 1.21.3` with `features = ["parking_lot"]`, `imp_pl::OnceCell::wait`
/// performed a single `parking_lot_core::park()` without a loop. When an initializer
/// panicked, the drop guard unparked all waiting threads. Those waiters returned
/// immediately from `wait()` and called `get_unchecked()`, reading uninitialized memory
/// before the cell had completed initialization.
///
/// In `once_cell >= 1.21.4` (PR #295), `wait()` was fixed to loop:
/// `while self.state.load(Ordering::Acquire) != COMPLETE { parking_lot_core::park(...); }`.
pub fn prove_wait_survives_panicking_initializer() {
let cell: OnceCell<String> = OnceCell::new();
let barrier = Barrier::new(2);
let panic_done = AtomicBool::new(false);
let value_written = AtomicBool::new(false);
thread::scope(|s| {
// Thread 1: Enters get_or_try_init, synchronizes with the main thread,
// then panics during initialization.
let h1 = s.spawn(|| {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cell.get_or_try_init(|| -> Result<String, ()> {
barrier.wait(); // notify main thread that initialization has started
panic!("simulated initializer panic");
})
}));
panic_done.store(true, Ordering::SeqCst);
});
// Main thread: wait until thread 1 has begun initializing and set state to RUNNING.
barrier.wait();
// Thread 2: waits until thread 1 has finished unwinding, pauses briefly to guarantee
// that any premature wakeups would observe an empty cell, then completes initialization.
let h2 = s.spawn(|| {
let _ = h1.join();
thread::sleep(Duration::from_millis(40));
value_written.store(true, Ordering::SeqCst);
cell.get_or_try_init(|| -> Result<String, ()> {
Ok(String::from("recovered_after_panic"))
})
.expect("recovery initializer must succeed");
});
// Under 1.21.3 + parking_lot: thread 1 panics, unparking waiters. `cell.wait()`
// returned immediately before thread 2 ran, while value_written was false.
//
// Under 1.21.4: `cell.wait()` detects state == INCOMPLETE in its loop and re-parks,
// returning only after thread 2 sets state to COMPLETE.
let val = cell.wait();
assert_eq!(val, "recovered_after_panic");
assert!(panic_done.load(Ordering::SeqCst));
assert!(value_written.load(Ordering::SeqCst));
let _ = h2.join();
});
}
/// Proves that multiple concurrent waiters all remain parked across an initializer panic
/// and all reliably observe the value once completed by a subsequent thread.
pub fn prove_multiple_waiters_survive_panic() {
let cell: OnceCell<u64> = OnceCell::new();
let waiter_count = 4;
let start_barrier = Barrier::new(waiter_count + 1);
let panic_done = AtomicBool::new(false);
let attempts = AtomicUsize::new(0);
thread::scope(|s| {
// Spawn multiple waiter threads
let waiters: Vec<_> = (0..waiter_count)
.map(|_| {
s.spawn(|| {
start_barrier.wait();
let val = cell.wait();
assert_eq!(*val, 42);
assert!(panic_done.load(Ordering::SeqCst));
*val
})
})
.collect();
// Main thread starts panicking init
start_barrier.wait();
// Give waiters time to reach park()
thread::sleep(Duration::from_millis(20));
// Panicking attempt
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cell.get_or_init(|| {
attempts.fetch_add(1, Ordering::SeqCst);
panic!("first init panicked");
});
}));
panic_done.store(true, Ordering::SeqCst);
// Sleep to ensure waiters would have failed on buggy versions
thread::sleep(Duration::from_millis(30));
// Successful attempt
let res = cell.get_or_init(|| {
attempts.fetch_add(1, Ordering::SeqCst);
42
});
assert_eq!(*res, 42);
for w in waiters {
let observed = w.join().expect("waiter thread should not panic");
assert_eq!(observed, 42);
}
});
assert_eq!(attempts.load(Ordering::SeqCst), 2);
assert_eq!(cell.get(), Some(&42));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_waiter_after_panic() {
prove_wait_survives_panicking_initializer();
}
#[test]
fn test_multiple_waiters_after_panic() {
prove_multiple_waiters_survive_panic();
}
}