use core::mem::ManuallyDrop;
use std::sync::Arc;
use super::caught::{self, Panic};
use super::context::{self, Bracketed};
use super::handoff::home::Home;
use super::shard::Shared;
use super::task::list::Links;
use super::task::{Runnable, Schedule, ScheduleInfo};
use crate::sync::internal::Ordering;
pub(crate) struct Sched<const BOUND: bool> {
shard: ManuallyDrop<Arc<Shared>>,
home: ManuallyDrop<Option<Arc<Home>>>,
hooks: ManuallyDrop<Option<Arc<super::tuning::TaskHooks>>>,
kind: Kind,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Counted,
Uncounted,
Visitor,
}
impl<const BOUND: bool> Sched<BOUND> {
pub(crate) fn counted(
shard: Arc<Shared>,
home: Option<Arc<Home>>,
hooks: Option<Arc<super::tuning::TaskHooks>>,
) -> Self {
debug_assert_eq!(home.is_some(), BOUND);
shard.load.fetch_add(1, Ordering::Relaxed);
Self {
shard: ManuallyDrop::new(shard),
home: ManuallyDrop::new(home),
hooks: ManuallyDrop::new(hooks),
kind: Kind::Counted,
}
}
#[cold]
#[inline(never)]
fn visited_runtime(&self) -> Option<context::RuntimeSet> {
let visiting = self.hooks.as_ref().as_ref()?.visiting.as_deref()?;
Some(context::set_runtime(&visiting.inner))
}
}
impl Sched<false> {
pub(crate) fn uncounted(shard: Arc<Shared>) -> Self {
Self {
shard: ManuallyDrop::new(shard),
home: ManuallyDrop::new(None),
hooks: ManuallyDrop::new(None),
kind: Kind::Uncounted,
}
}
pub(crate) fn visitor(shard: Arc<Shared>, hooks: Arc<super::tuning::TaskHooks>) -> Self {
debug_assert!(hooks.visiting.is_some());
let mut sched = Self::counted(shard, None, Some(hooks));
sched.kind = Kind::Visitor;
sched
}
}
impl<const BOUND: bool> Drop for Sched<BOUND> {
#[inline(always)]
fn drop(&mut self) {
unsafe {
ManuallyDrop::drop(&mut self.shard);
ManuallyDrop::drop(&mut self.home);
ManuallyDrop::drop(&mut self.hooks);
}
}
}
unsafe impl<const BOUND: bool> Schedule for Sched<BOUND> {
#[inline(always)]
fn schedule(&self, runnable: Runnable, info: ScheduleInfo) {
let left = match &*self.home {
None => queue_here(&self.shard, runnable, info),
Some(home) => queue_home_here(home, runnable, info),
};
if let Some(runnable) = left {
self.schedule_elsewhere(runnable);
}
}
#[inline(always)]
fn pending(&self, links: &Links) {
if !BOUND && !links.is_listed() {
self.list(links);
}
}
#[inline(always)]
fn drop_future(&self, links: &Links, drop: impl FnOnce()) {
if BOUND {
unsafe { links.unlink() };
if let Some(home) = &*self.home {
unsafe { home.task_ended() };
}
} else if links.is_listed() {
self.unlist(links);
}
{
let _runtime = match self.kind {
Kind::Counted => self.shard.enter_runtime(),
Kind::Visitor => self.visited_runtime(),
Kind::Uncounted => return drop(),
};
drop();
}
if self.shard.load.fetch_sub(1, Ordering::Release) == 1 {
self.shard.last_task_done();
}
}
#[inline]
fn before_poll(&self, id: super::task::Id) {
if let Some(hooks) = &*self.hooks {
if let Some(visiting) = &hooks.visiting {
visiting.enter();
}
super::tuning::call(hooks.before_poll.as_ref(), id);
}
}
#[inline(always)]
fn has_hooks(&self) -> bool {
self.hooks.is_some()
}
#[inline]
fn after_poll(&self, id: super::task::Id) -> Option<Panic> {
match &*self.hooks {
Some(hooks) => {
let panic = super::tuning::call_task_caught(hooks.after_poll.as_ref(), id);
if let Some(visiting) = &hooks.visiting {
visiting.leave();
}
panic
}
None => None,
}
}
#[inline]
fn terminated(&self, id: super::task::Id) -> Option<Panic> {
match &*self.hooks {
Some(hooks) => super::tuning::call_task_caught(hooks.terminate.as_ref(), id),
None => None,
}
}
fn panicked(&self, _id: super::task::Id) {
let shut = caught::catch(|| {
let runtime = self
.hooks
.as_ref()
.and_then(|hooks| hooks.shutdown.as_ref());
if let Some(runtime) = runtime.and_then(std::sync::Weak::upgrade) {
runtime.shut_down_on_panic();
}
});
if let Some(panic) = shut {
caught::lost(
format_args!("the runtime's shutdown after a task's panic panicked"),
panic,
);
}
}
}
#[inline(always)]
pub(crate) fn queue_here(
shard: &Arc<Shared>,
runnable: Runnable,
info: ScheduleInfo,
) -> Option<Runnable> {
let shard = Arc::as_ptr(shard) as usize;
match context::unbracketed(context::local()) {
Ok(current) => queue_on(current, shard, runnable, info),
Err(Bracketed) => queue_here_bracketed(shard, runnable, info),
}
}
#[cold]
#[inline(never)]
fn queue_here_bracketed(shard: usize, runnable: Runnable, info: ScheduleInfo) -> Option<Runnable> {
match context::push_lifo_marked(runnable, info, |current| current.shared_addr() == shard) {
Ok(()) => None,
Err(runnable) => queue_here_held(shard, runnable, info),
}
}
#[cold]
#[inline(never)]
fn queue_here_held(shard: usize, runnable: Runnable, info: ScheduleInfo) -> Option<Runnable> {
let held = context::bracketed();
queue_on(held.shard(), shard, runnable, info)
}
#[inline(always)]
fn queue_on(
current: Option<&super::shard::Shard>,
shard: usize,
runnable: Runnable,
info: ScheduleInfo,
) -> Option<Runnable> {
match current {
Some(current) if current.shared_addr() == shard => {
current.push_local_from(runnable, info, context::in_task());
None
}
_ => Some(runnable),
}
}
#[inline(always)]
pub(crate) fn queue_home_here(
home: &Home,
runnable: Runnable,
info: ScheduleInfo,
) -> Option<Runnable> {
match context::unbracketed(context::local()) {
Ok(current) => queue_home_on(current, home, runnable, info),
Err(Bracketed) => queue_home_here_bracketed(home, runnable, info),
}
}
#[cold]
#[inline(never)]
fn queue_home_here_bracketed(
home: &Home,
runnable: Runnable,
info: ScheduleInfo,
) -> Option<Runnable> {
let held = context::bracketed();
queue_home_on(held.shard(), home, runnable, info)
}
#[inline(always)]
fn queue_home_on(
current: Option<&super::shard::Shard>,
home: &Home,
runnable: Runnable,
info: ScheduleInfo,
) -> Option<Runnable> {
match current {
Some(current) if context::is_home(home) || current.owns_home(home) => {
current.push_local_from(runnable, info, context::in_task());
None
}
_ => Some(runnable),
}
}
impl<const BOUND: bool> Sched<BOUND> {
#[cold]
#[inline(never)]
fn list(&self, links: &Links) {
unsafe { self.shard.tasks.push(links) }
}
#[cold]
#[inline(never)]
fn unlist(&self, links: &Links) {
unsafe { links.unlink() };
}
#[inline(never)]
fn schedule_elsewhere(&self, runnable: Runnable) {
let keep = runnable.waker();
match &*self.home {
Some(home) => home.push(runnable),
None => self.shard.push_remote(runnable),
}
drop(keep);
}
}
pub(crate) struct Visiting {
inner: Arc<super::runtime::Inner>,
found: core::sync::atomic::AtomicUsize,
marked: core::sync::atomic::AtomicBool,
}
const OUT: usize = usize::MAX;
impl Visiting {
pub(crate) fn new(inner: Arc<super::runtime::Inner>) -> Self {
Self {
inner,
found: core::sync::atomic::AtomicUsize::new(OUT),
marked: core::sync::atomic::AtomicBool::new(false),
}
}
fn enter(&self) {
if let Some(found) = context::swap_runtime(&self.inner) {
let marked = found.as_ref().is_some_and(context::mark_own);
self.marked
.store(marked, core::sync::atomic::Ordering::Relaxed);
let raw = found.map_or(0, |found| Arc::into_raw(found) as usize);
self.found.store(raw, core::sync::atomic::Ordering::Relaxed);
}
}
fn leave(&self) {
if self
.marked
.swap(false, core::sync::atomic::Ordering::Relaxed)
{
context::unmark_own();
}
let raw = self.found.swap(OUT, core::sync::atomic::Ordering::Relaxed);
if raw != OUT {
context::put_runtime(Self::taken(raw));
}
}
fn taken(raw: usize) -> Option<Arc<super::runtime::Inner>> {
(raw != 0).then(|| unsafe { Arc::from_raw(raw as *const super::runtime::Inner) })
}
}
impl Drop for Visiting {
fn drop(&mut self) {
let raw = *self.found.get_mut();
if raw != OUT {
drop(Self::taken(raw));
}
}
}