#![cfg(not(loom))]
use core::ptr::NonNull;
use core::sync::atomic::{AtomicU32, Ordering};
use once_ptr_cell::{OncePtrCell, RollbackProbe};
#[repr(align(4))]
struct Payload {
marker: u32,
}
fn leak(marker: u32) -> NonNull<Payload> {
NonNull::from(Box::leak(Box::new(Payload { marker })))
}
#[test]
fn get_is_none_until_initialised() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
assert!(cell.get().is_none());
assert!(!cell.dbg_is_ready());
}
#[test]
fn init_runs_once_then_fast_path() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
let calls = AtomicU32::new(0);
let p1 = cell
.get_or_try_init(|| {
calls.fetch_add(1, Ordering::Relaxed);
Some(leak(0x1111))
})
.unwrap();
let p2 = cell
.get_or_try_init(|| {
calls.fetch_add(1, Ordering::Relaxed);
Some(leak(0x2222))
})
.unwrap();
assert_eq!(p1, p2, "same published pointer");
assert_eq!(calls.load(Ordering::Relaxed), 1, "init ran exactly once");
assert!(cell.dbg_is_ready());
assert_eq!(unsafe { p1.as_ref().marker }, 0x1111);
assert_eq!(cell.get(), Some(p1));
unsafe { drop(Box::from_raw(p1.as_ptr())) };
}
#[test]
fn panicking_init_rolls_back_and_subsequent_call_succeeds() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cell.get_or_try_init(|| -> Option<NonNull<Payload>> {
panic!("simulated init panic");
})
}));
assert!(
result.is_err(),
"the panic must propagate out of get_or_try_init"
);
let cell = std::sync::Arc::new(cell);
let cell2 = std::sync::Arc::clone(&cell);
let (tx, rx) = std::sync::mpsc::channel();
let handle = std::thread::spawn(move || {
let ok = cell2.get_or_try_init(|| Some(leak(0xABCD))).is_some();
let _ = tx.send(ok);
});
let init_succeeded = rx.recv_timeout(std::time::Duration::from_secs(5)).expect(
"get_or_try_init after a panicking init did not return within 5s \
-- the INITIALIZING sentinel is stuck forever (the rollback \
guard's livelock is back)",
);
assert!(init_succeeded, "init must succeed");
handle
.join()
.expect("the worker thread must not panic on the green path");
let p = cell.get().expect("cell is ready after the signal above");
assert!(cell.dbg_is_ready());
assert_eq!(cell.get(), Some(p));
assert_eq!(unsafe { p.as_ref().marker }, 0xABCD);
unsafe { drop(Box::from_raw(p.as_ptr())) };
}
#[test]
fn concurrent_get_or_try_init_started_before_unwind_completes_still_succeeds() {
let cell = std::sync::Arc::new(OncePtrCell::<Payload>::new());
let in_init = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let loser_about_to_call = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let (cw, iw, lw) = (
std::sync::Arc::clone(&cell),
std::sync::Arc::clone(&in_init),
std::sync::Arc::clone(&loser_about_to_call),
);
let winner = std::thread::spawn(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cw.get_or_try_init(|| -> Option<NonNull<Payload>> {
iw.store(true, Ordering::Release);
while !lw.load(Ordering::Acquire) {
std::thread::yield_now();
}
panic!("simulated init panic with a concurrent caller in flight");
})
}));
assert!(
result.is_err(),
"the panic must propagate out of get_or_try_init"
);
});
let (tx, rx) = std::sync::mpsc::channel();
let (cl, il, ll) = (
std::sync::Arc::clone(&cell),
std::sync::Arc::clone(&in_init),
std::sync::Arc::clone(&loser_about_to_call),
);
let loser = std::thread::spawn(move || {
while !il.load(Ordering::Acquire) {
std::thread::yield_now();
}
ll.store(true, Ordering::Release);
let ok = cl.get_or_try_init(|| Some(leak(0xF00D))).is_some();
let _ = tx.send(ok);
});
let loser_succeeded = rx.recv_timeout(std::time::Duration::from_secs(5)).expect(
"the loser did not return within 5s -- a concurrent caller in flight \
when the winner's init unwound was not woken (the INITIALIZING \
sentinel is stuck)",
);
assert!(
loser_succeeded,
"the concurrent caller must succeed after the winner's rollback -- either by \
re-racing and winning the CAS itself, or by observing the live sentinel and \
spinning until the winner's rollback wakes it"
);
winner
.join()
.expect("winner thread must not panic outside its own catch_unwind");
loser.join().expect("loser thread must not panic");
let p = cell
.get()
.expect("cell is ready after the loser's successful init");
assert!(cell.dbg_is_ready());
assert_eq!(unsafe { p.as_ref().marker }, 0xF00D);
unsafe { drop(Box::from_raw(p.as_ptr())) };
}
#[test]
#[should_panic(expected = "init returned the null/sentinel address")]
fn init_returning_the_sentinel_address_panics() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
let sentinel = NonNull::new(core::ptr::without_provenance_mut::<Payload>(1)).unwrap();
let _ = cell.get_or_try_init(|| Some(sentinel));
}
#[test]
fn oom_rolls_back_and_retry_succeeds() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
let first = cell.get_or_try_init(|| None);
assert!(first.is_none(), "OOM attempt returns None");
assert!(!cell.dbg_is_ready(), "sentinel rolled back to UNINIT");
assert!(cell.get().is_none());
let p = cell.get_or_try_init(|| Some(leak(0x9999))).unwrap();
assert!(cell.dbg_is_ready());
assert_eq!(cell.get(), Some(p));
assert_eq!(unsafe { p.as_ref().marker }, 0x9999);
unsafe { drop(Box::from_raw(p.as_ptr())) };
}
#[test]
#[should_panic(expected = "OncePtrCell<T> requires align_of::<T>() >= 2")]
fn align_of_one_payload_panics_at_construction() {
let _ = OncePtrCell::<u8>::default();
}
#[test]
fn dbg_rollback_reenterable_happy_path_and_not_applicable_arm() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
assert_eq!(
cell.dbg_rollback_reenterable(),
RollbackProbe::Proven,
"on a fresh UNINIT cell the probe must observe and restore UNINIT"
);
assert!(!cell.dbg_is_ready());
assert!(cell.get().is_none());
let p = cell.get_or_try_init(|| Some(leak(0x7777))).unwrap();
assert!(cell.dbg_is_ready());
assert_eq!(cell.get(), Some(p));
assert_eq!(unsafe { p.as_ref().marker }, 0x7777);
assert_eq!(
cell.dbg_rollback_reenterable(),
RollbackProbe::NotApplicable,
"on an already-READY cell the probe is not applicable"
);
assert!(cell.dbg_is_ready());
assert_eq!(cell.get(), Some(p));
unsafe { drop(Box::from_raw(p.as_ptr())) };
}
#[test]
fn debug_reports_the_three_states_without_a_t_debug_bound() {
let cell: OncePtrCell<Payload> = OncePtrCell::new();
assert_eq!(format!("{cell:?}"), "OncePtrCell(Uninit)");
let p = cell
.get_or_try_init(|| {
assert_eq!(format!("{cell:?}"), "OncePtrCell(Initializing)");
Some(leak(0xBEEF))
})
.unwrap();
assert_eq!(format!("{cell:?}"), format!("OncePtrCell(Ready({p:p}))"));
unsafe { drop(Box::from_raw(p.as_ptr())) };
}
#[test]
fn layout_matches_a_single_atomic_ptr() {
assert_eq!(
core::mem::size_of::<OncePtrCell<Payload>>(),
core::mem::size_of::<core::sync::atomic::AtomicPtr<Payload>>(),
"OncePtrCell<T> must be exactly one word, matching its documented \
layout guarantee"
);
assert_eq!(
core::mem::align_of::<OncePtrCell<Payload>>(),
core::mem::align_of::<core::sync::atomic::AtomicPtr<Payload>>(),
"OncePtrCell<T> must have the same alignment as the AtomicPtr<T> it wraps"
);
}