use std::any::Any;
use std::cell::{Cell, RefCell};
use std::fmt;
use std::future::{poll_fn, Future};
use std::panic::{self, AssertUnwindSafe};
use std::pin::{pin, Pin};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use crate::runtime::{self, Inject, Worker, WorkerShared};
pub(crate) struct TaskSlot {
task: Option<Task>,
uid: u64,
abort: bool,
}
struct Task {
waker: Waker,
state: Arc<TaskWaker>,
fut: Pin<Box<dyn Future<Output = ()>>>,
}
pub(crate) struct TaskWaker {
shared: Arc<WorkerShared>,
id: usize,
queued: AtomicBool,
}
pub(crate) const MAIN_TASK: usize = usize::MAX;
impl TaskWaker {
pub(crate) fn main(shared: Arc<WorkerShared>) -> Arc<Self> {
Arc::new(Self {
shared,
id: MAIN_TASK,
queued: AtomicBool::new(true),
})
}
pub(crate) fn clear_queued(&self) {
self.queued.store(false, Ordering::Release);
}
}
impl Wake for TaskWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
if self.queued.swap(true, Ordering::AcqRel) {
return;
}
if runtime::current_uid() == self.shared.uid {
runtime::try_with_worker(|w| w.queue.borrow_mut().push_back(self.id));
} else {
self.shared.inject(Inject::Wake(self.id));
}
}
}
pub enum JoinError {
Cancelled,
Panic(Box<dyn Any + Send + 'static>),
}
impl JoinError {
pub fn is_panic(&self) -> bool {
matches!(self, JoinError::Panic(_))
}
pub fn is_cancelled(&self) -> bool {
matches!(self, JoinError::Cancelled)
}
pub fn try_into_panic(self) -> Result<Box<dyn Any + Send + 'static>, JoinError> {
match self {
JoinError::Panic(payload) => Ok(payload),
other => Err(other),
}
}
}
impl fmt::Debug for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JoinError::Cancelled => f.write_str("JoinError::Cancelled"),
JoinError::Panic(_) => f.write_str("JoinError::Panic(..)"),
}
}
}
impl fmt::Display for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JoinError::Cancelled => f.write_str("task was cancelled"),
JoinError::Panic(_) => f.write_str("task panicked"),
}
}
}
impl std::error::Error for JoinError {}
struct JoinState<T> {
result: RefCell<Option<Result<T, JoinError>>>,
waker: RefCell<Option<Waker>>,
finished: Cell<bool>,
}
impl<T> JoinState<T> {
fn complete(&self, result: Result<T, JoinError>) {
if self.finished.replace(true) {
return;
}
*self.result.borrow_mut() = Some(result);
let waker = self.waker.borrow_mut().take();
if let Some(waker) = waker {
waker.wake();
}
}
fn is_finished(&self) -> bool {
self.finished.get()
}
fn take(&self) -> Option<Result<T, JoinError>> {
self.result.borrow_mut().take()
}
}
struct CancelOnDrop<T>(Rc<JoinState<T>>);
impl<T> Drop for CancelOnDrop<T> {
fn drop(&mut self) {
self.0.complete(Err(JoinError::Cancelled));
}
}
pub struct JoinHandle<T> {
state: Rc<JoinState<T>>,
slot: usize,
uid: u64,
worker_uid: usize,
}
impl<T> JoinHandle<T> {
pub fn abort(&self) {
if self.state.is_finished() {
return;
}
let task = runtime::try_with_worker(|w| {
if w.shared.uid != self.worker_uid {
return None;
}
let mut tasks = w.tasks.borrow_mut();
match tasks.get_mut(self.slot) {
Some(slot) if slot.uid == self.uid => match slot.task.take() {
Some(task) => {
tasks.remove(self.slot);
Some(task)
}
None => {
slot.abort = true;
None
}
},
_ => None,
}
})
.flatten();
drop_quietly(task);
}
pub fn is_finished(&self) -> bool {
self.state.is_finished()
}
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, JoinError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(result) = self.state.take() {
return Poll::Ready(result);
}
assert!(
!self.state.is_finished(),
"JoinHandle polled after completion"
);
let mut waker = self.state.waker.borrow_mut();
match &*waker {
Some(w) if w.will_wake(cx.waker()) => {}
_ => *waker = Some(cx.waker().clone()),
}
Poll::Pending
}
}
impl<T> fmt::Debug for JoinHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JoinHandle")
.field("finished", &self.is_finished())
.finish()
}
}
fn drop_quietly<T>(value: T) {
let _ = panic::catch_unwind(AssertUnwindSafe(move || drop(value)));
}
impl Worker {
pub(crate) fn spawn_local<F>(&self, fut: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
{
let state = Rc::new(JoinState {
result: RefCell::new(None),
waker: RefCell::new(None),
finished: Cell::new(false),
});
let guard = CancelOnDrop(state.clone());
let wrapped = Box::pin(async move {
let guard = guard;
let mut fut = pin!(fut);
let result = poll_fn(|cx| {
match panic::catch_unwind(AssertUnwindSafe(|| fut.as_mut().poll(cx))) {
Ok(Poll::Pending) => Poll::Pending,
Ok(Poll::Ready(value)) => Poll::Ready(Ok(value)),
Err(payload) => Poll::Ready(Err(JoinError::Panic(payload))),
}
})
.await;
guard.0.complete(result);
});
let uid = self.next_task_uid.get();
self.next_task_uid.set(uid + 1);
let mut tasks = self.tasks.borrow_mut();
let slot = tasks.insert(TaskSlot {
task: None,
uid,
abort: false,
});
let waker_state = Arc::new(TaskWaker {
shared: self.shared.clone(),
id: slot,
queued: AtomicBool::new(true),
});
let task = Task {
waker: Waker::from(waker_state.clone()),
state: waker_state,
fut: wrapped,
};
tasks.get_mut(slot).expect("slot just inserted").task = Some(task);
drop(tasks);
self.queue.borrow_mut().push_back(slot);
JoinHandle {
state,
slot,
uid,
worker_uid: self.shared.uid,
}
}
pub(crate) fn run_tasks(&self, poll_main: &mut dyn FnMut()) {
let budget = self.queue.borrow().len();
for _ in 0..budget {
let Some(id) = self.queue.borrow_mut().pop_front() else {
break;
};
if id == MAIN_TASK {
poll_main();
continue;
}
let task = {
let mut tasks = self.tasks.borrow_mut();
tasks.get_mut(id).and_then(|slot| slot.task.take())
};
let Some(mut task) = task else { continue };
task.state.queued.store(false, Ordering::Release);
let finished = {
let mut cx = Context::from_waker(&task.waker);
match panic::catch_unwind(AssertUnwindSafe(|| task.fut.as_mut().poll(&mut cx))) {
Ok(Poll::Pending) => false,
Ok(Poll::Ready(())) | Err(_) => true,
}
};
let mut tasks = self.tasks.borrow_mut();
let abort = tasks.get(id).map(|slot| slot.abort).unwrap_or(true);
if finished || abort {
tasks.remove(id);
drop(tasks);
drop_quietly(task);
} else if let Some(slot) = tasks.get_mut(id) {
slot.task = Some(task);
}
}
}
pub(crate) fn drop_all_tasks(&self) {
loop {
let slots = self.tasks.borrow_mut().take_all();
if slots.is_empty() {
break;
}
for slot in slots {
drop_quietly(slot.task);
}
}
self.queue.borrow_mut().clear();
}
}
pub fn spawn<F>(fut: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
{
runtime::with_worker(|w| w.spawn_local(fut))
}
pub async fn yield_now() {
let mut yielded = false;
poll_fn(|cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await
}