Skip to main content

once_ptr_cell/
imp.rs

1use core::marker::PhantomData;
2use core::ptr::NonNull;
3
4// The atomics are aliased so loom can shadow the REAL `OncePtrCell` type: under
5// `--cfg loom` the cell is built on `loom::sync::atomic`, so the shipped loom
6// tests (in `tests/`) model-check the actual implementation, not a hand-copied
7// transcription. Under normal builds it is `core::sync::atomic`, keeping the
8// crate `no_std` and allocation-free.
9//
10// CONSUMER HAZARD: `--cfg loom` is a global `RUSTFLAGS` cfg — it applies to
11// every crate in the build, not only the one whose loom suite you meant to
12// run. Under it `OncePtrCell::new` is NOT `const` (see its doc), so a
13// `static CELL: OncePtrCell<T> = OncePtrCell::new();` anywhere in the build
14// fails to compile. Scope the flag (`cargo test -p <crate> ...`), or supply a
15// `#[cfg(loom)]` const-capable stand-in in your own crate — see
16// `src/registry/bootstrap.rs`'s `loom_shim` in the `sefer-alloc` repository
17// this crate is extracted from, for a worked example.
18#[cfg(not(loom))]
19use core::sync::atomic::{AtomicPtr, Ordering};
20#[cfg(loom)]
21use loom::sync::atomic::{AtomicPtr, Ordering};
22
23/// The loser spin-wait hint. In a normal build this is [`core::hint::spin_loop`]
24/// (a PAUSE/YIELD CPU hint, no scheduler involvement). Under `--cfg loom` the
25/// real busy-spin is opaque to loom's model executor and would exhaust its
26/// branch budget ("processor must make progress"); there we yield to loom's
27/// fair scheduler instead, so it can advance the winner thread to its publish.
28/// Same happens-before semantics either way (a hint/yield synchronises nothing);
29/// only the scheduling nudge differs.
30#[cfg(loom)]
31#[inline]
32fn spin_hint() {
33    loom::thread::yield_now();
34}
35#[cfg(not(loom))]
36#[inline]
37fn spin_hint() {
38    core::hint::spin_loop();
39}
40
41/// The `INITIALIZING` sentinel address: a non-null, non-real marker meaning
42/// "one thread won the CAS and is currently running the init closure". Never
43/// dereferenced — only compared for pointer equality against the cell's stored
44/// value. An *aligned* pointer to `T` can never equal this address
45/// (`align_of::<T>() >= 2` is asserted at construction); a *misaligned or
46/// synthesised* pointer at this address is reachable from safe code and is
47/// rejected by a release-active `assert!` in
48/// [`OncePtrCell::get_or_try_init`], not by this constant alone.
49const SENTINEL_INITIALIZING: usize = 1;
50
51/// The outcome of [`OncePtrCell::dbg_rollback_reenterable`] — exactly the two
52/// answers that probe can give, and no third one it could never produce.
53///
54/// In particular there is no "rollback is broken" variant: the probe cannot
55/// distinguish that from "another thread legitimately owns the cell now",
56/// because both make its postcondition CAS fail identically. See the
57/// method's own docs for the full argument.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum RollbackProbe {
60    /// The rollback provably cleared the sentinel: the probe's postcondition
61    /// CAS re-won the cell afterwards, so no future winner or spinning loser
62    /// can be wedged by it. The cell is restored to `UNINIT` before
63    /// returning.
64    Proven,
65    /// The probe could not run its check, and this is NOT evidence that
66    /// rollback is broken. Either the cell was not `UNINIT` when the probe
67    /// entered (already `READY`, or owned by another thread at that
68    /// instant) — in which case the probe never touched it at all — or a
69    /// real `get_or_try_init` caller re-won the cell during the probe's own
70    /// rollback-then-reCAS window, in which case the probe still does not
71    /// touch it, but the cell is no longer necessarily `UNINIT`: the real
72    /// caller may already be running `init`, or may have published `READY`,
73    /// by the time this returns. Either way the probe never clobbers a
74    /// state it does not own.
75    NotApplicable,
76}
77
78/// A lazy, CAS-published pointer cell: `UNINIT -> INITIALIZING -> READY` over a
79/// single `AtomicPtr<T>`, with fallible init (OOM rolls back and losers
80/// re-race). See the [crate-level docs](crate) for the full state machine, the
81/// anti-livelock loser-spin rule, and the "usable inside a
82/// `#[global_allocator]`" niche.
83///
84/// The cell never drops, frees, or reads through the pointee — it only
85/// publishes and hands back the `*mut T` the init closure produced.
86///
87/// `#[repr(transparent)]`: the "one `AtomicPtr`"/"one word" claims made
88/// throughout this crate's docs are a LAYOUT GUARANTEE, not an
89/// implementation detail that happens to be true on the current compiler.
90/// `PhantomData<*mut T>` is the only other field; it is always zero-sized
91/// with alignment 1, which is exactly what `repr(transparent)` requires of
92/// every field beyond the one real one.
93#[repr(transparent)]
94pub struct OncePtrCell<T> {
95    /// The one word driving the state machine: `null` = `UNINIT`,
96    /// [`SENTINEL_INITIALIZING`] = `INITIALIZING`, any other value = `READY`
97    /// (a real published pointer).
98    ptr: AtomicPtr<T>,
99    /// `OncePtrCell<T>` behaves like it holds a `*mut T` it hands out; the
100    /// marker documents the relationship without owning a `T`.
101    _marker: PhantomData<*mut T>,
102}
103
104// The cell is `Send + Sync` UNCONDITIONALLY, exactly like the `AtomicPtr<T>` it
105// wraps — and for the same reason. The cell never dereferences `T` or hands out
106// a `&T`; it only stores and returns a RAW `*mut T` / `NonNull<T>`. Whether the
107// pointee is safe to *access* from another thread is the CALLER's contract (the
108// `get`/`get_or_try_init` accessors return raw pointers, and reading through
109// them is `unsafe`), not this type's — precisely the `AtomicPtr` model, which is
110// `Send + Sync` for every `T`. This is what lets the cell hold a pointer to a
111// `!Sync` payload (e.g. a per-thread heap) whose actual access the caller guards
112// by its own single-writer/`&mut` discipline. The `PhantomData<*mut T>` (present
113// only to document the "holds a `*mut T`" relationship and pin variance) is what
114// removes the auto-impls, so we restore them here.
115//
116// SAFETY: `ptr` is an `AtomicPtr`, so all concurrent access to the cell's own
117// state is race-free; the only value crossing a thread boundary through the cell
118// is a raw `*mut T`, which is `Send`/`Sync`-neutral (raw pointers carry no
119// sharing obligation — the obligation is on the caller's later deref). Identical
120// to `AtomicPtr<T>`'s own unconditional `Send + Sync`.
121unsafe impl<T> Send for OncePtrCell<T> {}
122// SAFETY: see the `Send` impl above.
123unsafe impl<T> Sync for OncePtrCell<T> {}
124
125/// RAII rollback guard held across the init closure: if `init`
126/// unwinds instead of returning, the winner thread's stack unwinds through
127/// this guard's `Drop`, which stores `null` with `Release` — exactly the
128/// same rollback the explicit OOM path performs. Without this, an unwinding
129/// `init` leaves the `INITIALIZING` sentinel stuck forever: every concurrent
130/// loser busy-spins at 100% CPU indefinitely (they spin on `==
131/// INITIALIZING`, which never changes), and every future
132/// `get_or_try_init`/`get` caller observes permanent `INITIALIZING` — a
133/// silent whole-process livelock, and a strictly worse outcome than the
134/// `OnceLock` equivalent, which leaves its cell uninitialised and lets the
135/// next caller retry.
136///
137/// Defused (via [`RollbackGuard::defuse`]) on both non-unwinding exits — the
138/// successful publish and the explicit `None`/OOM rollback — so the normal
139/// paths are unaffected; this guard only ever fires on the unwind path.
140///
141/// Test coverage note: `tests/cell_unit.rs`'s
142/// `panicking_init_rolls_back_and_subsequent_call_succeeds` proves a
143/// strictly weaker property than the one described above — that a
144/// SUBSEQUENT call on an already-quiescent cell succeeds after a panicking
145/// init unwound and rolled back. The same file's
146/// `concurrent_get_or_try_init_started_before_unwind_completes_still_succeeds`
147/// goes further: a real concurrent caller, whose own `get_or_try_init` call
148/// is issued no later than the point where it observes the winner already
149/// holds the sentinel, is never lost — either it observes the live sentinel
150/// and spins until the rollback wakes it, or it observes the already-rolled-
151/// back cell and wins the CAS itself directly. Both interleavings are
152/// possible depending on scheduling, and the test only guarantees success
153/// across whichever one actually happens; it does NOT deterministically
154/// force the spin-and-wake path specifically (that would need a hook inside
155/// the CAS/spin loop itself, which this crate does not have). A future
156/// change that made the rollback conditional (e.g. skipping it when no
157/// loser is observed waiting) would still very likely reintroduce a
158/// livelock this test would time out on, just not with airtight certainty
159/// that the spin branch itself was exercised on every run. Not closed by a
160/// loom test: loom's deterministic scheduling model and
161/// `std::panic::catch_unwind` do not compose cleanly (loom needs to replay
162/// every interleaving of an unwind path, which its own docs do not treat as
163/// a first-class supported pattern).
164struct RollbackGuard<'a, T> {
165    ptr: &'a AtomicPtr<T>,
166    defused: bool,
167}
168
169impl<'a, T> RollbackGuard<'a, T> {
170    #[inline]
171    fn new(ptr: &'a AtomicPtr<T>) -> Self {
172        Self {
173            ptr,
174            defused: false,
175        }
176    }
177
178    /// Disarm the guard: its `Drop` becomes a no-op. Call once the caller has
179    /// itself handled the `INITIALIZING` state (published `READY`, or
180    /// performed the explicit `None`/OOM rollback).
181    #[inline]
182    fn defuse(&mut self) {
183        self.defused = true;
184    }
185}
186
187impl<T> Drop for RollbackGuard<'_, T> {
188    #[inline]
189    fn drop(&mut self) {
190        if !self.defused {
191            // Same ordering rationale as the explicit OOM rollback in
192            // `get_or_try_init`: `Release` pairs with the retrying thread's
193            // later CAS `Acquire`; there is no partially-initialised state to
194            // synchronise (init never published), only the "cell is free
195            // again" fact.
196            self.ptr.store(core::ptr::null_mut(), Ordering::Release);
197        }
198    }
199}
200
201impl<T> OncePtrCell<T> {
202    /// Construct a fresh `UNINIT` cell (null pointer).
203    ///
204    /// **Not `const` under `--cfg loom`** (loom's atomics have no const
205    /// constructor); on normal builds it is `const` so the cell can live in a
206    /// `static`. Because `--cfg loom` is a global `RUSTFLAGS` cfg, this
207    /// applies to every crate in a build that sets it, not only crates that
208    /// mean to run loom against `OncePtrCell` itself — a
209    /// `static CELL: OncePtrCell<T> = OncePtrCell::new();` anywhere in such a
210    /// build fails to compile. Scope the flag to this crate
211    /// (`cargo test -p once-ptr-cell ...`), or supply your own
212    /// `#[cfg(loom)]` const-capable stand-in if you need the flag
213    /// workspace-wide.
214    ///
215    /// # Panics
216    ///
217    /// Panics if `align_of::<T>() == 1`. The `INITIALIZING` sentinel is encoded
218    /// as the address `1` (see the crate-level "Sentinel encoding" docs); that
219    /// encoding needs a spare low bit, which requires every valid aligned
220    /// address of `T` to be even — i.e. `align_of::<T>() >= 2`. In the
221    /// documented `static CELL: OncePtrCell<T> = OncePtrCell::new();` usage
222    /// this `assert!` is evaluated at compile time (a const-eval failure, not
223    /// a runtime panic); called from a non-const context (e.g. inside a
224    /// function, or via `OncePtrCell::<T>::default()`) with a `T` whose
225    /// alignment is 1, it panics at runtime instead.
226    #[cfg(not(loom))]
227    #[must_use]
228    pub const fn new() -> Self {
229        // Compile-time guard: the sentinel address (1) must not be a valid
230        // aligned address for `T`, or it could collide with a real pointer.
231        // Every `T` used behind this cell must have alignment >= 2.
232        assert!(
233            core::mem::align_of::<T>() >= 2,
234            "OncePtrCell<T> requires align_of::<T>() >= 2 so the INITIALIZING \
235             sentinel (address 1) can never collide with a real published pointer"
236        );
237        OncePtrCell {
238            ptr: AtomicPtr::new(core::ptr::null_mut()),
239            _marker: PhantomData,
240        }
241    }
242
243    /// Construct a fresh `UNINIT` cell (loom build — non-`const`).
244    ///
245    /// # Panics
246    ///
247    /// Panics if `align_of::<T>() == 1` — see the non-loom [`OncePtrCell::new`]
248    /// doc above for why (identical condition; this build cannot be `const` so
249    /// the check always runs at runtime here).
250    #[cfg(loom)]
251    #[must_use]
252    pub fn new() -> Self {
253        assert!(
254            core::mem::align_of::<T>() >= 2,
255            "OncePtrCell<T> requires align_of::<T>() >= 2"
256        );
257        OncePtrCell {
258            ptr: AtomicPtr::new(core::ptr::null_mut()),
259            _marker: PhantomData,
260        }
261    }
262
263    /// The `INITIALIZING` sentinel as a `*mut T` — a bare marker, never
264    /// dereferenced, constructed WITHOUT provenance (strict-provenance-clean).
265    #[inline]
266    fn sentinel() -> *mut T {
267        core::ptr::without_provenance_mut::<T>(SENTINEL_INITIALIZING)
268    }
269
270    /// `true` iff `p` is a real published pointer (non-null AND non-sentinel).
271    #[inline]
272    fn is_ready(p: *mut T) -> bool {
273        let a = p.addr();
274        a != 0 && a != SENTINEL_INITIALIZING
275    }
276
277    /// Return the published pointer if the cell is `READY`, else `None`.
278    ///
279    /// A pure `Acquire` load: no CAS, no init, no spin. `None` means the cell is
280    /// `UNINIT` or `INITIALIZING` right now (neither the sentinel nor null is
281    /// ever returned as `Some`).
282    ///
283    /// The returned pointer is the exact value the init closure produced; the
284    /// `Acquire` load pairs with the winner's `Release` publish, so any read the
285    /// caller performs through the pointer sees the fully initialised pointee.
286    #[inline]
287    #[must_use]
288    pub fn get(&self) -> Option<NonNull<T>> {
289        let p = self.ptr.load(Ordering::Acquire);
290        if Self::is_ready(p) {
291            // SAFETY: `is_ready(p)` just proved `p` is non-null (neither null
292            // nor the sentinel).
293            Some(unsafe { NonNull::new_unchecked(p) })
294        } else {
295            None
296        }
297    }
298
299    /// Get the published pointer, or run `init` to produce it — with the full
300    /// `UNINIT -> INITIALIZING -> READY` protocol, OOM rollback, and loser
301    /// re-race.
302    ///
303    /// Contract:
304    /// - **Fast path**: if the cell is already `READY`, returns the published
305    ///   pointer with one `Acquire` load; `init` is not called.
306    /// - **Winner**: the thread that CASes `null -> sentinel` calls `init`
307    ///   exactly once. `init` returns `Some(ptr)` on success (the cell
308    ///   publishes it with `Release` and returns it — `ptr` is leaked for the
309    ///   process lifetime, the cell never frees it), or `None` on OOM (the cell
310    ///   rolls the sentinel back to `null` and returns `None`; a later call may
311    ///   retry).
312    /// - **Loser**: a thread that loses the CAS spins with `Acquire` loads
313    ///   **only while the state is `INITIALIZING`**. When the winner publishes,
314    ///   the loser returns the same pointer. When the winner rolls back after
315    ///   OOM (state returns to `null`), the loser falls out of the spin and
316    ///   **re-races the CAS itself** — it does not wait for a `READY` that will
317    ///   never come.
318    ///
319    /// Returns `Some(published pointer)` (same value for all threads across a
320    /// successful lifetime) or `None` if `init` reported OOM on this thread's
321    /// winning attempt. The returned pointer is never null and never the
322    /// sentinel.
323    ///
324    /// `init` is [`FnOnce`], not `FnMut`, because ONE call to this method
325    /// invokes it **at most once**: whichever way the winner arm exits
326    /// (publish, OOM rollback, or unwind) it leaves the method, and the
327    /// loser arm never calls `init` at all — a loser that falls out of the
328    /// spin on a rollback re-races the CAS and, if it wins, is making its
329    /// own first and only call. `FnOnce` is therefore the accurate bound,
330    /// and it lets you pass a closure that consumes what it captures.
331    ///
332    /// `init` must be reentrancy-safe with respect to whatever the cell guards:
333    /// it runs while this thread holds the `INITIALIZING` sentinel, so it must
334    /// not itself call back into `get_or_try_init` on the SAME cell (that would
335    /// spin forever — the current thread is the only one able to publish).
336    ///
337    /// The restriction is **transitive, and multiple cells form a lock-order
338    /// graph**: `init` must not wait, through any chain of calls, on a cell
339    /// whose own initialiser can wait on this one. Two cells are enough for a
340    /// deadlock with no direct self-recursion anywhere — thread 1 wins `A` and
341    /// its `init` initialises `B`, while thread 2 wins `B` and its `init`
342    /// initialises `A`; both spin forever at 100% CPU. Acquire multiple cells
343    /// in a fixed global order, exactly as you would locks.
344    ///
345    /// `init` must also be fast and non-blocking: every loser thread spins for
346    /// exactly as long as the winner's `init` call takes (see the module docs'
347    /// "spin-wait" section) — there is no bounded-latency guarantee from the
348    /// cell itself, only from the caller keeping `init` short.
349    ///
350    /// Calling this from inside a `#[global_allocator]` adds further hard
351    /// obligations on `init` (no allocation, no unwind) — see the crate docs'
352    /// ["Using this inside a `#[global_allocator]`"](crate#using-this-inside-a-global_allocator)
353    /// section.
354    ///
355    /// # Panics
356    ///
357    /// Panics if the winning `init` call returns `Some(ptr)` where `ptr`'s
358    /// address is the reserved `INITIALIZING` sentinel (`1`) — a safe `init`
359    /// closure can construct and return this exact address, and publishing it
360    /// unguarded would make every reader (this thread's own fast path
361    /// included) misclassify the cell as still-initializing forever. This
362    /// check is release-active, not `debug_assert!`-gated.
363    ///
364    /// If `init` itself panics (unwinds) instead of returning, the panic
365    /// propagates out of `get_or_try_init` and the cell is left in `UNINIT`
366    /// (not wedged in `INITIALIZING`) — a later call, on any thread, may
367    /// retry `init`. This mirrors the OOM/`None` rollback above; the only
368    /// difference is how the winner exits. Note what this does and does not
369    /// buy: it keeps the CELL consistent, but it does not make the unwind
370    /// itself sound when the frame below is a `GlobalAlloc` method, where
371    /// unwinding is undefined behaviour regardless of this cell's state.
372    #[must_use = "`None` means `init` reported OOM and the cell was rolled \
373                  back to UNINIT — it is NOT initialised, and discarding \
374                  this hides the failure"]
375    #[inline]
376    pub fn get_or_try_init<F>(&self, init: F) -> Option<NonNull<T>>
377    where
378        F: FnOnce() -> Option<NonNull<T>>,
379    {
380        // Fast path, and nothing else: one `Acquire` load plus the readiness
381        // test. Everything the already-published case does NOT need — the
382        // claim CAS, the rollback guard, the release-active `assert!`, the
383        // loser spin, the re-race loop — lives in `init_slow`, which is
384        // `#[cold] #[inline(never)]` so none of it is inlined into a
385        // caller that only ever hits this branch.
386        let p = self.ptr.load(Ordering::Acquire);
387        if Self::is_ready(p) {
388            // SAFETY: `is_ready(p)` just proved `p` is non-null (neither the
389            // `null` UNINIT value nor the `SENTINEL_INITIALIZING` marker), so
390            // `p` is a real published pointer.
391            return Some(unsafe { NonNull::new_unchecked(p) });
392        }
393        self.init_slow(init)
394    }
395
396    /// The full `UNINIT -> INITIALIZING -> READY` protocol: claim CAS, init
397    /// closure, publish/rollback, loser spin, re-race. Split out of
398    /// [`OncePtrCell::get_or_try_init`] so the already-READY fast path stays
399    /// small enough to inline on its own; correctness is unchanged, and the
400    /// re-checked fast path at the top of the loop below is still needed
401    /// here (a re-racing loser re-enters it after a rollback).
402    ///
403    /// Note this does NOT deduplicate monomorphised code: the slow path is
404    /// still generic over `F`, so one copy exists per closure type. It only
405    /// keeps that copy out of the caller's hot path.
406    #[cold]
407    #[inline(never)]
408    fn init_slow<F>(&self, init: F) -> Option<NonNull<T>>
409    where
410        F: FnOnce() -> Option<NonNull<T>>,
411    {
412        loop {
413            // Re-checked fast path: a loser that fell out of the spin on a
414            // rollback, or lost the CAS to a winner that has since
415            // published, lands here.
416            let p = self.ptr.load(Ordering::Acquire);
417            if Self::is_ready(p) {
418                // SAFETY: `is_ready(p)` just proved `p` is non-null (neither
419                // the `null` UNINIT value nor the `SENTINEL_INITIALIZING`
420                // marker), so `p` is a real published pointer.
421                return Some(unsafe { NonNull::new_unchecked(p) });
422            }
423
424            // Slow path: race to become the initialising winner.
425            match self.ptr.compare_exchange(
426                core::ptr::null_mut(),
427                Self::sentinel(),
428                // Success `Acquire`: synchronises-with whichever `Release`
429                // store last returned the cell to `null` — the explicit OOM
430                // rollback in this function's own winner arm, the unwind
431                // guard's `Drop`, or either of `dbg_rollback_reenterable`'s
432                // two null-stores. It says nothing
433                // about the publish this thread is about to perform: an
434                // acquire cannot pair with a release that has not happened
435                // yet. The load-bearing pair for the pointee is this winner's
436                // own `Release` publish below against every reader's
437                // `Acquire` load.
438                //
439                // Whether `Relaxed` would suffice here — a rollback leaves no
440                // payload state for a new winner to acquire — remains an open
441                // question, and is DELIBERATELY not acted on. Weakening it
442                // needs BOTH a loom counterfactual proving the weaker form
443                // sound and a measurement showing it is worth anything, and
444                // the second half is unobtainable on the hardware this crate
445                // is developed on: `Acquire` on x86-64 is a plain load, so a
446                // local A/B can only ever report noise. The same applies to
447                // the loser spin's per-iteration `Acquire` below. Both stay
448                // as they are — over-strong, never under-strong — until
449                // someone can measure them on a weakly-ordered target
450                // (AArch64/ARM) with a model to back the change.
451                Ordering::Acquire,
452                // Failure `Relaxed`: we re-load in the spin loop below.
453                Ordering::Relaxed,
454            ) {
455                Ok(_) => {
456                    // ── Winner ──────────────────────────────────────────────
457                    // We hold the INITIALIZING sentinel; we are the sole
458                    // initialiser. Hold a rollback guard across `init()` so an
459                    // UNWINDING init (a panic in caller code, or the `assert!`
460                    // below firing) also rolls the sentinel back — see
461                    // `RollbackGuard`'s own doc for why this is load-bearing.
462                    let mut guard = RollbackGuard::new(&self.ptr);
463                    match init() {
464                        Some(ptr) => {
465                            let raw = ptr.as_ptr();
466                            // Release-active `assert!`, not `debug_assert!`
467                            // a SAFE init closure can construct
468                            // `NonNull::new(without_provenance_mut(1))` and
469                            // hand back the very SENTINEL address this cell
470                            // uses to mean "still initialising". In release,
471                            // a `debug_assert!` here compiles out, so the
472                            // sentinel would get published as if it were
473                            // READY — every current loser and every future
474                            // caller then spins forever, since the published
475                            // value reads back as `INITIALIZING`, not `READY`
476                            // (`is_ready`'s own definition), with no
477                            // diagnostic anywhere. Two integer compares on a
478                            // once-per-cell cold path is a negligible cost
479                            // for closing a violation of this method's own
480                            // documented "never null, never the sentinel"
481                            // guarantee that is reachable from 100% safe
482                            // code — exactly the class `debug_assert!` is
483                            // NOT meant for. If this fires, the rollback
484                            // guard above unwinds it cleanly.
485                            assert!(
486                                Self::is_ready(raw),
487                                "OncePtrCell: init returned the null/sentinel address"
488                            );
489                            // Publish with `Release` so every subsequent
490                            // `Acquire` load (fast path here, plus every loser's
491                            // spin-load) sees the fully constructed pointee.
492                            // This is THE ordering the Relaxed-publish
493                            // counterfactual breaks.
494                            self.ptr.store(raw, Ordering::Release);
495                            // Defuse: the guard's rollback must NOT fire now
496                            // that the real pointer is published.
497                            guard.defuse();
498                            return Some(ptr);
499                        }
500                        None => {
501                            // OOM: roll the sentinel back to null so losers
502                            // spinning on `== INITIALIZING` fall out and
503                            // re-race, and future callers can retry. `Release`
504                            // pairs with the retrying thread's later CAS
505                            // `Acquire`: there is no partially-initialised state
506                            // to synchronise (init never published), only the
507                            // "cell is free again" fact. Explicit here (rather
508                            // than relying on the guard's Drop) to keep this
509                            // path's ordering self-documenting; defuse first so
510                            // the guard does not redundantly store again.
511                            guard.defuse();
512                            self.ptr.store(core::ptr::null_mut(), Ordering::Release);
513                            return None;
514                        }
515                    }
516                }
517                Err(_) => {
518                    // ── Loser ───────────────────────────────────────────────
519                    // Spin ONLY while the state is INITIALIZING. This is the
520                    // anti-livelock rule: a `!= READY` spin would
521                    // deadlock if the winner rolled back to null after OOM
522                    // (READY never comes). Falling out on any non-INITIALIZING
523                    // observation lets us return READY (winner published) or
524                    // loop back to the top and re-race (winner rolled back).
525                    loop {
526                        let p = self.ptr.load(Ordering::Acquire);
527                        let a = p.addr();
528                        if a == SENTINEL_INITIALIZING {
529                            // Still initialising — keep spinning.
530                            spin_hint();
531                            continue;
532                        }
533                        if a != 0 {
534                            // READY: the winner published a real pointer.
535                            // SAFETY: `a != 0` (checked above) and `a !=
536                            // SENTINEL_INITIALIZING` (the `if` above this one
537                            // already returned/continued on that value), so
538                            // `p` is neither null nor the sentinel — a real
539                            // published pointer.
540                            return Some(unsafe { NonNull::new_unchecked(p) });
541                        }
542                        // null: the winner rolled back after OOM. Break out of
543                        // the spin and re-race the CAS from the top — do NOT
544                        // keep waiting for a READY that will never be published.
545                        break;
546                    }
547                    // Fall through to the outer loop: re-race.
548                }
549            }
550        }
551    }
552
553    /// Test-probe introspection: `true` iff the cell is currently `READY`
554    /// (holds a real, non-null, non-sentinel pointer). Says nothing about
555    /// the published *value* itself (that is [`OncePtrCell::get`]'s
556    /// contract).
557    ///
558    /// This is functionally identical to `get().is_some()` — same single
559    /// `Acquire` load, same predicate, no capability `get` lacks — it does
560    /// **not** avoid racing a concurrent init any differently than `get`
561    /// does (an earlier version of this doc claimed
562    /// otherwise). It exists as a named, self-documenting boolean
563    /// introspection primitive: a caller writing `assert!(cell.dbg_is_ready())`
564    /// reads as "assert the cell materialised" without an
565    /// `.is_some()`/`.is_none()` match at the call site. The `sefer-alloc`
566    /// allocator this crate was extracted from relies on exactly that: its
567    /// own `Registry::dbg_chunk_is_materialised` forwards here to assert
568    /// chunk-materialisation state in its regression tests.
569    ///
570    /// # Stability
571    ///
572    /// This is a deliberate, STABLE part of the public API — a
573    /// `dbg_`-prefixed test-probe surface, not a hidden implementation
574    /// detail. It carries the crate's normal semver guarantee like any
575    /// other public item; a `#[doc(hidden)]` posture was rejected precisely
576    /// because it would advertise this function to downstream consumers'
577    /// tests (see [`OncePtrCell::dbg_rollback_reenterable`]'s own doc) while
578    /// hiding it from the rustdoc those consumers would need to discover it
579    /// — see the crate README's "Test-probe API stability" section for the
580    /// full rationale.
581    #[inline]
582    #[must_use]
583    pub fn dbg_is_ready(&self) -> bool {
584        Self::is_ready(self.ptr.load(Ordering::Acquire))
585    }
586
587    /// Test-only anti-livelock rollback probe. Drives THIS cell through the
588    /// exact `null -> sentinel -> rollback -> re-CAS` sequence the internal
589    /// OOM-bailout runs, and proves the postcondition the whole design rests on:
590    /// after a rollback, a fresh `CAS(null -> sentinel)` MUST succeed (the
591    /// sentinel was genuinely cleared, so no future winner or spinning loser is
592    /// wedged).
593    ///
594    /// Returns [`RollbackProbe::Proven`] if the rollback provably cleared the
595    /// sentinel (the postcondition CAS re-won the cell; it is restored to
596    /// `UNINIT` before returning). [`RollbackProbe::NotApplicable`] covers
597    /// TWO distinct "could not test" cases, deliberately conflated because
598    /// neither is evidence rollback is broken: (a) the cell was not observed
599    /// `UNINIT` on the entry CAS (already `READY`, or another thread owned it
600    /// at that instant), or (b) the postcondition CAS in step 3 failed
601    /// because a real `get_or_try_init` caller raced in and re-won the cell
602    /// during the probe's own rollback-then-reCAS window — in that case the
603    /// probe leaves the cell alone (does NOT touch the new owner's state).
604    ///
605    /// **There is deliberately no "rollback is broken" variant.** This probe
606    /// cannot distinguish that from "someone else legitimately owns the cell
607    /// now" by construction — both look identical from here, the
608    /// postcondition CAS simply fails either way — so the return type
609    /// encodes exactly the two answers it can actually give, and no third
610    /// one it could never produce.
611    ///
612    /// Exists so a consumer's test can drive the rollback on a REAL, LIVE cell
613    /// (e.g. a process-global registry chunk) — proving the shipped code path,
614    /// not a copy — without a process-terminating OOM. The whole probe is a
615    /// bounded, single-threaded sequence of atomic ops; callers MUST pick a
616    /// cell no other thread is concurrently initialising. The entry CAS is
617    /// only a POINT-IN-TIME check, not mutual exclusion across the whole
618    /// probe: if the cell is not observed `UNINIT` at that instant, the probe
619    /// returns [`RollbackProbe::NotApplicable`] and touches nothing, but a
620    /// concurrent
621    /// [`OncePtrCell::get_or_try_init`] racing in AFTER the entry CAS (during
622    /// the probe's own rollback-then-reCAS window) is not excluded by it — the
623    /// probe's final restore step accounts for that by only touching the cell
624    /// when its own postcondition CAS actually re-won ownership (see the
625    /// step-by-step comments in the body).
626    ///
627    /// # Stability
628    ///
629    /// This is a deliberate, STABLE part of the public API, not
630    /// `#[doc(hidden)]`. This function is explicitly written to be called
631    /// FROM a downstream consumer's own test suite ("a consumer's test can
632    /// drive the rollback on a REAL, LIVE cell" above) — a `#[doc(hidden)]`
633    /// posture would have advertised it to those consumers while hiding it
634    /// from the rustdoc they would need to find it in the first place, an
635    /// unresolvable contradiction the crate's rust-intel audit caught. See
636    /// the crate README's "Test-probe API stability" section for the full
637    /// rationale and the rejected feature-flag alternative.
638    #[must_use]
639    pub fn dbg_rollback_reenterable(&self) -> RollbackProbe {
640        // Step 1: only proceed if the cell is UNINIT (null). If it is already
641        // READY or contended, do not touch it.
642        if self
643            .ptr
644            .compare_exchange(
645                core::ptr::null_mut(),
646                Self::sentinel(),
647                Ordering::Acquire,
648                Ordering::Relaxed,
649            )
650            .is_err()
651        {
652            return RollbackProbe::NotApplicable;
653        }
654
655        // Step 2: run the EXACT rollback the internal OOM-bailout runs (sentinel
656        // -> null, Release).
657        self.ptr.store(core::ptr::null_mut(), Ordering::Release);
658
659        // Step 3: prove the postcondition — a fresh CAS(null -> sentinel) must
660        // now succeed.
661        let postcondition_holds = self
662            .ptr
663            .compare_exchange(
664                core::ptr::null_mut(),
665                Self::sentinel(),
666                Ordering::Acquire,
667                Ordering::Relaxed,
668            )
669            .is_ok();
670
671        // Step 4: restore to null, exactly as observed on entry — but ONLY if
672        // step 3's CAS actually re-won ownership of the cell (postcondition
673        // held). If it failed, the cell is not ours any more: a real
674        // `get_or_try_init` caller raced in during the window between step 2's
675        // rollback and step 3's CAS, won the CAS itself, and may already be
676        // running (or have finished) the caller's init closure. Storing null
677        // unconditionally here would clobber that other owner's sentinel (or
678        // its published pointer) out from under it — the exact clobber this
679        // probe must not cause. When we did not re-win, leave the cell alone
680        // and report "not applicable" rather than a false rollback failure: a
681        // concurrent owner racing in is not evidence that rollback itself is
682        // broken.
683        if !postcondition_holds {
684            return RollbackProbe::NotApplicable;
685        }
686        self.ptr.store(core::ptr::null_mut(), Ordering::Release);
687
688        RollbackProbe::Proven
689    }
690}
691
692impl<T> Default for OncePtrCell<T> {
693    fn default() -> Self {
694        Self::new()
695    }
696}
697
698impl<T> core::fmt::Debug for OncePtrCell<T> {
699    /// Diagnostic-only classification of the cell's current state — never
700    /// dereferences the pointee, so no `T: Debug` bound is needed (`T` never
701    /// appears in the output). `Relaxed` is enough here: unlike `get`, this
702    /// never hands the pointer back to the caller to dereference, so there is
703    /// no happens-before edge to establish. Like any concurrent type's
704    /// `Debug` impl (`OnceLock`'s included), the state printed can be stale
705    /// the instant after this call returns.
706    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
707        let p = self.ptr.load(Ordering::Relaxed);
708        f.write_str("OncePtrCell(")?;
709        match p.addr() {
710            0 => f.write_str("Uninit")?,
711            SENTINEL_INITIALIZING => f.write_str("Initializing")?,
712            _ => write!(f, "Ready({p:p})")?,
713        }
714        f.write_str(")")
715    }
716}