use core::sync::atomic::{AtomicU8,
Ordering::{Acquire, Relaxed, Release}};
#[derive(Debug)]
pub(crate) struct InitOnce(AtomicU8);
impl InitOnce {
const UNINITIALIZED: u8 = 0;
const PREPARING: u8 = 1;
const READY: u8 = 2;
pub(crate) const fn new() -> Self { Self(AtomicU8::new(Self::UNINITIALIZED)) }
pub(crate) fn call_once<T, E>(
&self,
f: impl FnOnce() -> Result<T, E>,
) -> Option<Result<T, E>> {
match self.0.compare_exchange(Self::UNINITIALIZED, Self::PREPARING, Relaxed, Relaxed) {
Ok(_) => {
let r = f();
if r.is_ok() {
self.0.store(Self::READY, Release);
}
Some(r)
},
Err(_) => None,
}
}
pub(crate) fn is_ready(&self) -> bool {
self.0.load(Acquire) == Self::READY
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unreachable, clippy::unwrap_used)]
use super::*;
use core::{hint, sync::atomic::AtomicI32};
extern crate std;
use std::thread;
#[test]
fn is_ready() {
let x = InitOnce::new();
assert!(!x.is_ready());
let r1 = x.call_once(|| Result::<_, ()>::Ok(1 + 1));
assert_eq!(r1, Some(Ok(2)));
assert!(x.is_ready());
let r2: Option<Result<(), bool>> = x.call_once(|| unreachable!());
assert!(r2.is_none());
assert!(x.is_ready());
let r3: Option<Result<bool, i32>> = x.call_once(|| unreachable!());
assert!(r3.is_none());
assert!(x.is_ready());
}
#[test]
fn failure_isnt_ready() {
let x = InitOnce::new();
let r1 = x.call_once(|| Result::<(), _>::Err(()));
assert_eq!(r1, Some(Err(())));
assert!(!x.is_ready());
assert_eq!(x.0.load(Relaxed), InitOnce::PREPARING);
let r2: Option<Result<(), ()>> = x.call_once(|| unreachable!());
assert!(r2.is_none());
assert!(!x.is_ready());
assert_eq!(x.0.load(Relaxed), InitOnce::PREPARING);
}
#[test]
fn synchronizes() {
static THING: AtomicI32 = AtomicI32::new(0);
static X: InitOnce = InitOnce::new();
let t = thread::spawn(|| {
X.call_once(|| {
THING.store(1, Relaxed);
Result::<_, ()>::Ok(()) })
.unwrap()
.unwrap();
});
while !X.is_ready() {
thread::yield_now();
hint::spin_loop();
}
assert_eq!(THING.load(Relaxed), 1);
t.join().unwrap(); }
}