use std::{
marker::PhantomData,
ptr::NonNull,
sync::{
Condvar, Mutex,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
};
use moirai_core::{
ExecutorError,
error::{ExecutorResult, TaskError},
};
use super::worker::lock_mutex;
pub(super) struct SchedulerScopeState<'scheduler> {
pub(super) pending_tasks: AtomicUsize,
pub(super) failed_tasks: AtomicBool,
unrun_jobs: AtomicUsize,
pub(super) wait_lock: &'scheduler Mutex<()>,
pub(super) wait_signal: &'scheduler Condvar,
}
impl<'scheduler> SchedulerScopeState<'scheduler> {
pub(super) fn new(wait_lock: &'scheduler Mutex<()>, wait_signal: &'scheduler Condvar) -> Self {
Self {
pending_tasks: AtomicUsize::new(0),
failed_tasks: AtomicBool::new(false),
unrun_jobs: AtomicUsize::new(0),
wait_lock,
wait_signal,
}
}
pub(super) fn register_task(&self) {
self.pending_tasks.fetch_add(1, Ordering::AcqRel);
}
pub(super) unsafe fn complete_task(this: NonNull<Self>) {
let state = this.as_ptr();
let pending_tasks = unsafe { &(*state).pending_tasks };
loop {
let pending = pending_tasks.load(Ordering::Acquire);
debug_assert!(pending > 0, "scoped completion count must not underflow");
if pending == 1 {
let _guard = lock_mutex(unsafe { (*state).wait_lock });
if pending_tasks
.compare_exchange(1, 0, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
unsafe { (*state).wait_signal }.notify_all();
return;
}
continue;
}
if pending_tasks
.compare_exchange_weak(pending, pending - 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return;
}
}
}
pub(super) fn wait(&self) {
for _ in 0..131_072 {
if self.pending_tasks.load(Ordering::Acquire) == 0 {
break;
}
core::hint::spin_loop();
}
let mut guard = lock_mutex(self.wait_lock);
while self.pending_tasks.load(Ordering::Acquire) != 0 {
guard = self
.wait_signal
.wait(guard)
.unwrap_or_else(|poisoned| poisoned.into_inner());
}
}
pub(super) fn mark_failed(&self) {
self.failed_tasks.store(true, Ordering::Release);
}
pub(super) fn has_panicked(&self) -> bool {
self.failed_tasks.load(Ordering::Acquire)
}
pub(super) fn has_unrun_job(&self) -> bool {
self.unrun_jobs.load(Ordering::Acquire) != 0
}
pub(super) fn unrun_job_result(&self) -> ExecutorResult<()> {
if self.has_unrun_job() {
Err(ExecutorError::SpawnFailed(TaskError::Cancelled))
} else {
Ok(())
}
}
pub(super) fn forgive_refused_job(&self) {
self.unrun_jobs.fetch_sub(1, Ordering::AcqRel);
}
}
pub(super) struct ScopedTaskCompletion<'scope> {
state: NonNull<SchedulerScopeState<'scope>>,
ran: bool,
_scope: PhantomData<&'scope SchedulerScopeState<'scope>>,
}
unsafe impl Send for ScopedTaskCompletion<'_> {}
impl<'scope> ScopedTaskCompletion<'scope> {
pub(super) fn new(state: &'scope SchedulerScopeState<'scope>) -> Self {
Self {
state: NonNull::from(state),
ran: false,
_scope: PhantomData,
}
}
pub(super) fn finish(mut self, succeeded: bool) {
if !succeeded {
self.state().mark_failed();
}
self.ran = true;
}
fn state(&self) -> &SchedulerScopeState<'scope> {
unsafe { self.state.as_ref() }
}
}
impl Drop for ScopedTaskCompletion<'_> {
fn drop(&mut self) {
if !self.ran {
self.state().unrun_jobs.fetch_add(1, Ordering::AcqRel);
}
unsafe { SchedulerScopeState::complete_task(self.state) };
}
}