use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(feature = "std")]
use parking_lot::{Condvar, Mutex};
const UNINIT: u8 = 0;
const BUSY: u8 = 1;
const INIT: u8 = 2;
const POISONED: u8 = 3;
struct PoisonGuard<'a, T> {
slot: &'a OnceSlot<T>,
done: bool,
}
impl<T> Drop for PoisonGuard<'_, T> {
fn drop(&mut self) {
if !self.done {
self.slot.finish(POISONED);
}
}
}
pub struct OnceSlot<T> {
state: AtomicU8,
#[cfg(feature = "std")]
lock: Mutex<()>,
#[cfg(feature = "std")]
cvar: Condvar,
value: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T> bytemuck::Zeroable for OnceSlot<T> {}
unsafe impl<T: Send + Sync> Sync for OnceSlot<T> {}
unsafe impl<T: Send> Send for OnceSlot<T> {}
impl<T> OnceSlot<T> {
#[must_use]
pub const fn new() -> Self {
Self {
state: AtomicU8::new(UNINIT),
#[cfg(feature = "std")]
lock: Mutex::new(()),
#[cfg(feature = "std")]
cvar: Condvar::new(),
value: UnsafeCell::new(MaybeUninit::uninit()),
}
}
fn finish(&self, next: u8) {
debug_assert!(next == INIT || next == POISONED);
#[cfg(feature = "std")]
{
let _guard = self.lock.lock();
self.state.store(next, Ordering::Release);
self.cvar.notify_all();
}
#[cfg(not(feature = "std"))]
{
self.state.store(next, Ordering::Release);
}
}
#[must_use]
pub fn get(&'static self) -> Option<&'static T> {
if self.state.load(Ordering::Acquire) == INIT {
Some(unsafe { (*self.value.get()).assume_init_ref() })
} else {
None
}
}
#[must_use]
pub fn is_completed(&'static self) -> bool {
self.state.load(Ordering::Acquire) == INIT
}
#[must_use]
pub fn is_poisoned(&'static self) -> bool {
self.state.load(Ordering::Acquire) == POISONED
}
fn wait(&'static self) -> &'static T {
#[cfg(feature = "std")]
{
let mut guard = self.lock.lock();
loop {
match self.state.load(Ordering::Acquire) {
INIT => {
return unsafe { (*self.value.get()).assume_init_ref() };
}
POISONED => panic!("OnceSlot has previously been poisoned"),
_ => self.cvar.wait(&mut guard),
}
}
}
#[cfg(not(feature = "std"))]
{
loop {
match self.state.load(Ordering::Acquire) {
INIT => {
return unsafe { (*self.value.get()).assume_init_ref() };
}
POISONED => panic!("OnceSlot has previously been poisoned"),
_ => core::hint::spin_loop(),
}
}
}
}
pub fn get_or_init(&'static self, init: impl FnOnce() -> T) -> &'static T {
if let Some(value) = self.get() {
return value;
}
self.initialize(init)
}
#[cold]
#[inline(never)]
fn initialize(&'static self, init: impl FnOnce() -> T) -> &'static T {
match self
.state
.compare_exchange(UNINIT, BUSY, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => {
let mut guard = PoisonGuard {
slot: self,
done: false,
};
let value = init();
unsafe {
(*self.value.get()).write(value);
}
self.finish(INIT);
guard.done = true;
unsafe { (*self.value.get()).assume_init_ref() }
}
Err(INIT) => {
unsafe { (*self.value.get()).assume_init_ref() }
}
Err(POISONED) => panic!("OnceSlot has previously been poisoned"),
Err(_) => self.wait(),
}
}
}
impl<T> Default for OnceSlot<T> {
fn default() -> Self {
Self::new()
}
}