conquer_once/cell.rs
1//! Generic definition and implementation of the [`OnceCell`] type.
2
3use core::{
4 cell::UnsafeCell,
5 fmt,
6 marker::PhantomData,
7 mem::{self, MaybeUninit},
8 ptr,
9 sync::atomic::Ordering,
10};
11
12use crate::{
13 state::{AtomicOnceState, BlockedState, OnceState, SwapState, TryBlockError},
14 POISON_PANIC_MSG,
15};
16
17/// An internal (sealed) trait specifying the unblocking mechanism of a cell
18/// locking strategy.
19pub trait Unblock {
20 /// Unblocks all waiting threads after setting the cell state to `READY`.
21 ///
22 /// # Safety
23 ///
24 /// Must only be called after swapping the cell with `READY` with the state
25 /// returned by the swap operation.
26 unsafe fn on_unblock(state: BlockedState);
27}
28
29/// An internal (sealed) trait specifying the blocking mechanism of a cell
30/// locking strategy.
31///
32/// # Safety
33///
34/// The implementation of `block` must adhere to its documentation or otherwise
35/// data-races could occur in other code.
36pub unsafe trait Block: Unblock {
37 /// Blocks the current thread until `state` is either `READY` or `POISONED`.
38 ///
39 /// # Panics
40 ///
41 /// Panics if the `state` becomes poisoned.
42 fn block(state: &AtomicOnceState);
43}
44
45/// An interior mutability cell type which allows synchronized one-time
46/// initialization and read-only access exclusively after initialization.
47///
48/// # Poisoning
49///
50/// A thread that panics in the course of executing its `init` function or
51/// closure **poisons** the cell.
52/// All subsequent accesses to a poisoned cell will propagate this and panic
53/// themselves.
54pub struct OnceCell<T, B> {
55 /// The current initialization status.
56 state: AtomicOnceState,
57 /// The internal and potentially uninitialized value.
58 inner: UnsafeCell<MaybeUninit<T>>,
59 /// A marker for the blocking strategy (i.e. OS-level block or spin-lock)
60 _marker: PhantomData<B>,
61}
62
63unsafe impl<T, B> Send for OnceCell<T, B> where T: Send {}
64unsafe impl<T, B> Sync for OnceCell<T, B> where T: Send + Sync {}
65
66impl<T, B> OnceCell<T, B> {
67 /// Creates a new uninitialized [`OnceCell`].
68 #[inline]
69 pub const fn uninit() -> Self {
70 Self {
71 state: AtomicOnceState::new(),
72 inner: UnsafeCell::new(MaybeUninit::uninit()),
73 _marker: PhantomData,
74 }
75 }
76
77 /// Creates a new [`OnceCell`] pre-initialized with `value`.
78 #[inline]
79 pub const fn new(value: T) -> Self {
80 Self {
81 state: AtomicOnceState::ready(),
82 inner: UnsafeCell::new(MaybeUninit::new(value)),
83 _marker: PhantomData,
84 }
85 }
86
87 /// Consumes `self` and returns a [`Some(T)`](Some) if the [`OnceCell`] has
88 /// previously been successfully initialized or [`None`] otherwise.
89 ///
90 /// # Panics
91 ///
92 /// This method panics if the [`OnceCell`] has been poisoned.
93 ///
94 /// # Examples
95 ///
96 /// ```
97 /// # use conquer_once::spin::OnceCell;
98 ///
99 /// let uninit: OnceCell<i32> = OnceCell::uninit();
100 /// assert!(uninit.into_inner().is_none());
101 ///
102 /// let once = OnceCell::uninit();
103 /// once.init_once(|| "initialized");
104 /// assert_eq!(once.into_inner(), Some("initialized"));
105 /// ```
106 #[inline]
107 pub fn into_inner(mut self) -> Option<T> {
108 // SAFETY: `take_inner` can not be called again after this, since we
109 // forget `self` right after calling it
110 let res = unsafe { self.take_inner(false) };
111 mem::forget(self);
112 res
113 }
114
115 /// Returns true if the [`OnceCell`] has been successfully initialized.
116 ///
117 /// This method will never panic or block.
118 #[inline]
119 pub fn is_initialized(&self) -> bool {
120 // (cell:1) this acquire load syncs-with the acq-rel swap (guard:2)
121 self.state.load(Ordering::Acquire) == Ok(OnceState::Ready)
122 }
123
124 /// Returns true if the [`OnceCell`] has been poisoned during
125 /// initialization.
126 ///
127 /// This method will never panic or block.
128 ///
129 /// Once this method has returned `true` all other means of accessing the
130 /// [`OnceCell`] except for further calls to
131 /// [`is_initialized`][OnceCell::is_initialized] or
132 /// [`is_poisoned`][OnceCell::is_poisoned] will panic.
133 #[inline]
134 pub fn is_poisoned(&self) -> bool {
135 self.state.load(Ordering::Relaxed).is_err()
136 }
137
138 /// Returns a reference to the [`OnceCell`]'s initialized inner state or
139 /// an [`Err`].
140 ///
141 /// This method will never blocks.
142 ///
143 /// When this function returns with an [`Ok`] result, it is guaranteed that
144 /// some initialization closure has run and completed.
145 /// It is also guaranteed that any memory writes performed by the executed
146 /// closure can be reliably observed by other threads at this point (there
147 /// is a happens-before relation between the closure and code executing
148 /// after the return).
149 ///
150 /// # Errors
151 ///
152 /// This method fails if the [`OnceCell`] is either not initialized
153 /// ([`Uninit`][TryGetError::Uninit]) or is currently being
154 /// initialized by some other thread
155 /// ([`WouldBlock`][TryGetError::WouldBlock]).
156 ///
157 /// # Panics
158 ///
159 /// This method panics if the [`OnceCell`] has been poisoned.
160 #[inline]
161 pub fn try_get(&self) -> Result<&T, TryGetError> {
162 // (cell:2) this acquire load syncs-with the acq-rel swap (guard:2)
163 match self.state.load(Ordering::Acquire).expect(POISON_PANIC_MSG) {
164 OnceState::Ready => Ok(unsafe { self.get_unchecked() }),
165 OnceState::Uninit => Err(TryGetError::Uninit),
166 OnceState::WouldBlock(_) => Err(TryGetError::WouldBlock),
167 }
168 }
169
170 /// Returns a reference to the inner value without checking whether the
171 /// [`OnceCell`] is actually initialized.
172 ///
173 /// # Safety
174 ///
175 /// The caller has to ensure that the cell has been successfully
176 /// initialized, otherwise uninitialized memory will be read.
177 ///
178 /// # Examples
179 ///
180 /// This is one safe way to use this method, although
181 /// [`try_get`][OnceCell::try_get] is the better alternative:
182 ///
183 /// ```
184 /// # #[cfg(feature = "std")]
185 /// use conquer_once::OnceCell;
186 /// # #[cfg(not(feature = "std"))]
187 /// # use conquer_once::spin::OnceCell;
188 ///
189 /// // let cell = ...
190 /// # let cell = OnceCell::uninit();
191 /// # cell.init_once(|| 0);
192 ///
193 /// let res = if cell.is_initialized() {
194 /// Some(unsafe { cell.get_unchecked() })
195 /// } else {
196 /// None
197 /// };
198 ///
199 /// # assert_eq!(res, Some(&0));
200 /// ```
201 #[inline]
202 pub unsafe fn get_unchecked(&self) -> &T {
203 let inner = &*self.inner.get();
204 &*inner.as_ptr()
205 }
206
207 /// Moves the inner cell value out of the [`OnceCell`] and returns it
208 /// wrapped in [`Some`], if it has been successfully initialized.
209 ///
210 /// # Safety
211 ///
212 /// If the cell is initialized, this must be called at most once.
213 ///
214 /// # Panics
215 ///
216 /// If `ignore_poisoning` is `false`, this method will panic if the
217 /// [`OnceCell`] has been poisoned, otherwise it will simply return
218 /// [`None`].
219 #[inline]
220 unsafe fn take_inner(&mut self, ignore_poisoning: bool) -> Option<T> {
221 #[allow(clippy::match_wild_err_arm)]
222 match self.state.load(Ordering::Relaxed) {
223 Err(_) if !ignore_poisoning => panic!("{}", POISON_PANIC_MSG),
224 // SAFETY: the mutable reference guarantees there can be no aliased
225 // reference and since the state is `Ready`
226 Ok(OnceState::Ready) =>
227 {
228 #[allow(unused_unsafe)]
229 Some(unsafe { ptr::read(self.get_unchecked()) })
230 }
231 _ => None,
232 }
233 }
234}
235
236impl<T, B: Unblock> OnceCell<T, B> {
237 /// Attempts to initialize the [`OnceCell`] with `func` if is is
238 /// uninitialized and returns [`Ok(())`](Ok) only if `func` is successfully
239 /// executed.
240 ///
241 /// This method will never block.
242 ///
243 /// When this function returns with an [`Ok`] or
244 /// [`Err(AlreadyInit)`][TryInitError::AlreadyInit] result, it is guaranteed
245 /// that *some* initialization closure has run and completed (it may not be
246 /// the closure specified).
247 /// It is also guaranteed that any memory writes performed by the executed
248 /// closure can be reliably observed by other threads at this point (there
249 /// is a *happens-before* relation between the closure and code executing
250 /// after the return).
251 ///
252 /// # Errors
253 ///
254 /// This method fails, if the initialization of [`OnceCell`] has already
255 /// been completed previously, in which case an
256 /// [`AlreadyInit`][TryInitError::AlreadyInit] error is returned.
257 /// If another thread is concurrently in the process of initializing it and
258 /// this thread would have to block, a
259 /// [`WouldBlock`][TryInitError::WouldBlock] error is returned.
260 ///
261 /// # Panics
262 ///
263 /// This method panics if the [`OnceCell`] has been poisoned.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// # #[cfg(feature = "std")]
269 /// use conquer_once::{OnceCell, TryInitError};
270 /// # #[cfg(not(feature = "std"))]
271 /// # use conquer_once::{spin::OnceCell, TryInitError};
272 ///
273 /// let cell = OnceCell::uninit();
274 ///
275 /// // .. in thread 1
276 /// let res = cell.try_init_once(|| {
277 /// 1
278 /// });
279 /// assert!(res.is_ok());
280 ///
281 /// // .. in thread 2
282 /// let res = cell.try_init_once(|| {
283 /// 2
284 /// });
285 /// assert_eq!(res, Err(TryInitError::AlreadyInit));
286 ///
287 /// # assert_eq!(cell.get().copied(), Some(1));
288 /// ```
289 #[inline]
290 pub fn try_init_once(&self, func: impl FnOnce() -> T) -> Result<(), TryInitError> {
291 // (cell:3) this acq load syncs-with the acq-rel swap (guard:2)
292 match self.state.load(Ordering::Acquire).expect(POISON_PANIC_MSG) {
293 OnceState::Ready => Err(TryInitError::AlreadyInit),
294 OnceState::WouldBlock(_) => Err(TryInitError::WouldBlock),
295 OnceState::Uninit => {
296 let mut func = Some(func);
297 self.try_init_inner(&mut || func.take().unwrap()())?;
298 Ok(())
299 }
300 }
301 }
302
303 /// This method is annotated with `#[cold]` in order to keep it out of the
304 /// fast path.
305 #[inline(never)]
306 #[cold]
307 fn try_init_inner(&self, func: &mut dyn FnMut() -> T) -> Result<&T, TryBlockError> {
308 // sets the state to blocked (i.e. guarantees mutual exclusion) or
309 // returns with an error.
310 let guard = PanicGuard::<B>::try_block(&self.state)?;
311 // SAFETY: `try_block` ensures mutual exclusion, so no aliasing of the
312 // cell is possible and the raw pointer write is unproblematic (pointer
313 // is trivially aligned and valid)
314 unsafe {
315 let inner = &mut *self.inner.get();
316 inner.as_mut_ptr().write(func());
317 }
318 guard.disarm();
319
320 // SAFETY: the cell was just initialized so no check is required here
321 Ok(unsafe { self.get_unchecked() })
322 }
323
324 /// Returns a reference to the [`OnceCell`]'s initialized inner state or
325 /// otherwise attempts to initialize it with `func` and return the result.
326 ///
327 /// This method never blocks.
328 ///
329 /// When this function returns with an [`Ok`] result, it is guaranteed that
330 /// some initialization closure has run and completed (it may not be the
331 /// closure specified).
332 /// It is also guaranteed that any memory writes performed by the executed
333 /// closure can be reliably observed by other threads at this point (there
334 /// is a happens-before relation between the closure and code executing
335 /// after the return).
336 ///
337 /// # Errors
338 ///
339 /// This method only fails if the calling thread would have to block in case
340 /// another thread is concurrently initializing the [`OnceCell`].
341 ///
342 /// # Panics
343 ///
344 /// This method panics if the [`OnceCell`] has been poisoned.
345 #[inline]
346 pub fn try_get_or_init(&self, func: impl FnOnce() -> T) -> Result<&T, WouldBlockError> {
347 match self.try_get() {
348 Ok(res) => Ok(res),
349 Err(TryGetError::WouldBlock) => Err(WouldBlockError(())),
350 Err(TryGetError::Uninit) => {
351 let mut func = Some(func);
352 let res = self.try_init_inner(&mut || func.take().unwrap()())?;
353 Ok(res)
354 }
355 }
356 }
357}
358
359impl<T, B: Block> OnceCell<T, B> {
360 /// Returns a reference to the [`OnceCell`]'s initialized inner state or
361 /// [`None`].
362 ///
363 /// This method **blocks** if another thread has already begun initializing
364 /// the [`OnceCell`] concurrently.
365 /// See [`try_get`][OnceCell::try_get] for a non-blocking alternative.
366 ///
367 /// When this function returns with [`Some`], it is guaranteed that some
368 /// initialization closure has run and completed.
369 /// It is also guaranteed that any memory writes performed by the executed
370 /// closure can be reliably observed by other threads at this point (there
371 /// is a happens-before relation between the closure and code executing
372 /// after the return).
373 ///
374 /// # Panics
375 ///
376 /// This method panics if the [`OnceCell`] has been poisoned.
377 ///
378 /// # Examples
379 ///
380 /// ```
381 /// # #[cfg(feature = "std")]
382 /// use conquer_once::OnceCell;
383 /// # #[cfg(not(feature = "std"))]
384 /// # use conquer_once::spin::OnceCell;
385 ///
386 /// let cell = OnceCell::uninit();
387 /// assert_eq!(cell.get(), None);
388 /// cell.init_once(|| {
389 /// 1
390 /// });
391 /// assert_eq!(cell.get(), Some(&1));
392 /// ```
393 #[inline]
394 pub fn get(&self) -> Option<&T> {
395 match self.try_get() {
396 Ok(res) => Some(res),
397 Err(TryGetError::WouldBlock) => {
398 B::block(&self.state);
399 // SAFETY: `block` only returns when the state is set to
400 // `INITIALIZED` and acts as an acquire barrier (initialization
401 // happens-before block returns)
402 Some(unsafe { self.get_unchecked() })
403 }
404 Err(TryGetError::Uninit) => None,
405 }
406 }
407
408 /// Attempts to initialize the [`OnceCell`] with `func` if it is
409 /// uninitialized.
410 ///
411 /// This method **blocks** if another thread has already begun initializing
412 /// the [`OnceCell`] concurrently.
413 ///
414 /// If the initialization of the [`OnceCell`] has already been
415 /// completed previously, this method returns early with minimal
416 /// overhead.
417 ///
418 /// When this function returns, it is guaranteed that some initialization
419 /// closure has run and completed (it may not be the closure specified).
420 /// It is also guaranteed that any memory writes performed by the executed
421 /// closure can be reliably observed by other threads at this point (there
422 /// is a happens-before relation between the closure and code executing
423 /// after the return).
424 ///
425 /// # Panics
426 ///
427 /// This method panics if the [`OnceCell`] has been poisoned.
428 ///
429 /// # Examples
430 ///
431 /// ```
432 /// # #[cfg(feature = "std")]
433 /// use conquer_once::OnceCell;
434 /// # #[cfg(not(feature = "std"))]
435 /// # use conquer_once::spin::OnceCell;
436 ///
437 /// let cell = OnceCell::uninit();
438 /// cell.init_once(|| {
439 /// // expensive calculation
440 /// (0..1_000).map(|i| i * i).sum::<usize>()
441 /// });
442 ///
443 /// cell.init_once(|| {
444 /// // any further or concurrent calls to `init_once` will do
445 /// // nothing and return immediately with almost no overhead.
446 /// # 0
447 /// });
448 ///
449 /// # let exp = (0..1_000).map(|i| i * i).sum::<usize>();
450 /// # assert_eq!(cell.get().copied(), Some(exp));
451 /// ```
452 #[inline]
453 pub fn init_once(&self, func: impl FnOnce() -> T) {
454 if let Err(TryInitError::WouldBlock) = self.try_init_once(func) {
455 // block the current thread if the cell is currently being
456 // initialized
457 B::block(&self.state);
458 }
459 }
460
461 /// Returns a reference to the [`OnceCell`]'s initialized inner state or
462 /// otherwise attempts to initialize it with `func` and return the result.
463 ///
464 /// This method **blocks** if another thread has already begun
465 /// initializing the [`OnceCell`] concurrently.
466 /// See [`try_get_or_init`][OnceCell::try_get_or_init] for a non-blocking
467 /// alternative.
468 ///
469 /// When this function returns, it is guaranteed that some initialization
470 /// closure has run and completed (it may not be the closure specified).
471 /// It is also guaranteed that any memory writes performed by the executed
472 /// closure can be reliably observed by other threads at this point (there
473 /// is a happens-before relation between the closure and code executing
474 /// after the return).
475 ///
476 /// # Panics
477 ///
478 /// This method panics if the [`OnceCell`] has been poisoned.
479 #[inline]
480 pub fn get_or_init(&self, func: impl FnOnce() -> T) -> &T {
481 match self.try_get_or_init(func) {
482 Ok(res) => res,
483 Err(_) => {
484 B::block(&self.state);
485 // SAFETY: `block` only returns when the state is set to
486 // `INITIALIZED` and acts as an acquire barrier (initialization
487 // happens-before block returns)
488 unsafe { self.get_unchecked() }
489 }
490 }
491 }
492}
493
494impl<T: fmt::Debug, B> fmt::Debug for OnceCell<T, B> {
495 #[inline]
496 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497 f.debug_struct("OnceCell").field("inner", &self.try_get().ok()).finish()
498 }
499}
500
501impl<T, B> Drop for OnceCell<T, B> {
502 #[inline]
503 fn drop(&mut self) {
504 // drop must never panic, so poisoning is ignored
505 // SAFETY: take_inner cannot be called again after drop has been called
506 mem::drop(unsafe { self.take_inner(true) })
507 }
508}
509
510const UNINIT_MSG: &str = "the `OnceCell` is uninitialized";
511const ALREADY_INIT_MSG: &str = "the `OnceCell` has already been initialized";
512const WOULD_BLOCK_MSG: &str = "the `OnceCell` is currently being initialized";
513
514/// Possible error variants of non-blocking initialization calls.
515#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
516pub enum TryInitError {
517 /// The [`OnceCell`] is already initialized and the initialization procedure
518 /// was not called.
519 AlreadyInit,
520 /// The [`OnceCell`] is currently being initialized by another thread and
521 /// the current thread would have to block.
522 WouldBlock,
523}
524
525impl fmt::Display for TryInitError {
526 #[inline]
527 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
528 match self {
529 TryInitError::AlreadyInit => write!(f, "{}", ALREADY_INIT_MSG),
530 TryInitError::WouldBlock => write!(f, "{}", WOULD_BLOCK_MSG),
531 }
532 }
533}
534
535impl From<TryBlockError> for TryInitError {
536 #[inline]
537 fn from(err: TryBlockError) -> Self {
538 match err {
539 TryBlockError::AlreadyInit => TryInitError::AlreadyInit,
540 TryBlockError::WouldBlock(_) => TryInitError::WouldBlock,
541 }
542 }
543}
544
545#[cfg(feature = "std")]
546impl std::error::Error for TryInitError {}
547
548/// Possible error variants of non-blocking fallible get calls.
549#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
550pub enum TryGetError {
551 /// The [`OnceCell`] is currently not initialized.
552 Uninit,
553 /// The [`OnceCell`] is currently being initialized by another thread and
554 /// the current thread would have to block.
555 WouldBlock,
556}
557
558impl fmt::Display for TryGetError {
559 #[inline]
560 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561 match self {
562 TryGetError::Uninit => write!(f, "{}", UNINIT_MSG),
563 TryGetError::WouldBlock => write!(f, "{}", WOULD_BLOCK_MSG),
564 }
565 }
566}
567
568#[cfg(feature = "std")]
569impl std::error::Error for TryGetError {}
570
571/// An error indicating that a [`OnceCell`] would have to block.
572#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
573pub struct WouldBlockError(());
574
575impl fmt::Display for WouldBlockError {
576 #[inline]
577 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
578 write!(f, "{}", WOULD_BLOCK_MSG)
579 }
580}
581
582impl From<TryBlockError> for WouldBlockError {
583 #[inline]
584 fn from(err: TryBlockError) -> Self {
585 match err {
586 TryBlockError::AlreadyInit => unreachable!(),
587 TryBlockError::WouldBlock(_) => Self(()),
588 }
589 }
590}
591
592#[cfg(feature = "std")]
593impl std::error::Error for WouldBlockError {}
594
595/// A guard for catching panics during the execution of the initialization
596/// closure.
597#[derive(Debug)]
598struct PanicGuard<'a, B: Unblock> {
599 /// The state of the associated [`OnceCell`].
600 state: &'a AtomicOnceState,
601 /// Flag for indicating if a panic has occurred during the caller supplied
602 /// arbitrary closure.
603 poison: bool,
604 /// A marker for the [`OnceCell`]'s blocking strategy.
605 _marker: PhantomData<B>,
606}
607
608impl<'a, B: Unblock> PanicGuard<'a, B> {
609 /// Attempts to block the [`OnceCell`] and return a guard on success.
610 #[inline]
611 fn try_block(state: &'a AtomicOnceState) -> Result<Self, TryBlockError> {
612 // (guard:1) this acquire CAS syncs-with the acq-rel swap (guard:2) and the acq-rel CAS
613 // (wait:2)
614 state.try_block(Ordering::Acquire)?;
615 Ok(Self { state, poison: true, _marker: PhantomData })
616 }
617
618 /// Consumes the guard and assures that no panic has occurred.
619 #[inline]
620 fn disarm(mut self) {
621 self.poison = false;
622 mem::drop(self);
623 }
624}
625
626impl<B: Unblock> Drop for PanicGuard<'_, B> {
627 #[inline]
628 fn drop(&mut self) {
629 let swap = if self.poison { SwapState::Poisoned } else { SwapState::Ready };
630 unsafe {
631 // (guard:2) this acq-rel swap syncs-with the acq-rel CAS (wait:2)
632 // and the acquire loads (cell:1), (cell:2), (wait:1) and the
633 // acquire CAS (guard:1)
634 let prev = self.state.unblock(swap, Ordering::AcqRel);
635 B::on_unblock(prev);
636 }
637 }
638}