use std::future::Future;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::Ordering::{Acquire, Relaxed};
use crate::sync::{AsyncOnce, CallOnceResult, OnceState};
use crossbeam::utils::CachePadded;
pub struct Once {
state: CachePadded<AtomicIsize>,
}
impl Once {
pub const fn new() -> Self {
Self {
state: CachePadded::new(AtomicIsize::new(OnceState::not_called())),
}
}
}
impl AsyncOnce for Once {
#[inline(always)]
#[allow(
clippy::future_not_send,
reason = "It is not `Send` only when Fut is not `Send`, it is fine"
)]
async fn call_once<Fut: Future<Output = ()>>(&self, f: Fut) -> CallOnceResult {
if self
.state
.compare_exchange(
OnceState::NotCalled.into(),
OnceState::Called.into(),
Acquire,
Relaxed,
)
.is_ok()
{
f.await;
CallOnceResult::Called
} else {
CallOnceResult::WasAlreadyCompleted
}
}
#[inline(always)]
fn call_once_sync<F: FnOnce()>(&self, f: F) -> CallOnceResult {
if self
.state
.compare_exchange(
OnceState::NotCalled.into(),
OnceState::Called.into(),
Acquire,
Relaxed,
)
.is_ok()
{
f();
CallOnceResult::Called
} else {
CallOnceResult::WasAlreadyCompleted
}
}
#[inline(always)]
fn state(&self) -> OnceState {
#[cfg(debug_assertions)]
{
use crate::bug_message::BUG_MESSAGE;
OnceState::try_from(self.state.load(Acquire)).expect(BUG_MESSAGE)
}
#[cfg(not(debug_assertions))]
unsafe {
OnceState::try_from(self.state.load(Acquire)).unwrap_unchecked()
}
}
}
impl Default for Once {
fn default() -> Self {
Self::new()
}
}
unsafe impl Sync for Once {}
unsafe impl Send for Once {}
#[allow(dead_code, reason = "It is used only in compile tests")]
fn test_compile_shared_once() {}
#[cfg(test)]
mod tests {
use super::*;
use crate as orengine;
use crate::sleep;
use crate::sync::{AsyncWaitGroup, WaitGroup};
use crate::test::sched_future_to_another_thread;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::Arc;
use std::time::Duration;
#[orengine::test::test_shared]
fn test_async_shared_once() {
let a = Arc::new(AtomicBool::new(false));
let wg = Arc::new(WaitGroup::new());
let once = Arc::new(Once::new());
assert_eq!(once.state(), OnceState::NotCalled);
assert!(!once.is_completed());
for _ in 0..10 {
let a = a.clone();
let wg = wg.clone();
wg.add(1);
let once = once.clone();
sched_future_to_another_thread(async move {
let _ = once
.call_once(async move {
sleep(Duration::from_millis(1)).await;
assert!(!a.load(SeqCst));
a.store(true, SeqCst);
})
.await;
wg.done();
});
}
wg.wait().await;
assert!(once.is_completed());
assert_eq!(
once.call_once(async {}).await,
CallOnceResult::WasAlreadyCompleted
);
}
#[orengine::test::test_shared]
fn test_sync_shared_once() {
let a = Arc::new(AtomicBool::new(false));
let wg = Arc::new(WaitGroup::new());
let once = Arc::new(Once::new());
assert_eq!(once.state(), OnceState::NotCalled);
assert!(!once.is_completed());
for _ in 0..10 {
let a = a.clone();
let wg = wg.clone();
wg.add(1);
let once = once.clone();
sched_future_to_another_thread(async move {
let _ = once.call_once_sync(|| {
assert!(!a.load(SeqCst));
a.store(true, SeqCst);
});
wg.done();
});
}
wg.wait().await;
assert!(once.is_completed());
assert_eq!(
once.call_once_sync(|| ()),
CallOnceResult::WasAlreadyCompleted
);
}
}