use std::{
cell::RefCell,
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll, Waker},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TaskId(pub(crate) u64);
impl TaskId {
pub fn as_u64(self) -> u64 {
self.0
}
}
impl std::fmt::Display for TaskId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TaskId({})", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JoinError {
Cancelled,
Panicked,
}
impl std::fmt::Display for JoinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cancelled => write!(f, "task was cancelled"),
Self::Panicked => write!(f, "task panicked"),
}
}
}
impl std::error::Error for JoinError {}
impl JoinError {
pub fn is_panic(&self) -> bool {
matches!(self, Self::Panicked)
}
pub fn is_cancelled(&self) -> bool {
matches!(self, Self::Cancelled)
}
}
pub(crate) enum TaskState<T> {
Running { join_waker: Option<Waker> },
Completed(T),
Panicked,
Cancelled,
Consumed,
}
pub(crate) struct TaskShared<T> {
pub(crate) state: RefCell<TaskState<T>>,
}
#[must_use = "tasks are detached if the handle is dropped"]
pub struct JoinHandle<T> {
pub(crate) task_id: TaskId,
pub(crate) shared: Rc<TaskShared<T>>,
pub(crate) is_cancelled: Rc<std::cell::Cell<bool>>,
}
impl<T> JoinHandle<T> {
pub fn id(&self) -> TaskId {
self.task_id
}
pub fn is_finished(&self) -> bool {
let state = self.shared.state.borrow();
!matches!(&*state, TaskState::Running { .. })
}
pub fn cancel(&self) {
self.abort();
}
pub fn abort(&self) {
self.is_cancelled.set(true);
let mut state = self.shared.state.borrow_mut();
if let TaskState::Running { join_waker } = &mut *state {
let w = join_waker.take();
*state = TaskState::Cancelled;
drop(state);
if let Some(w) = w {
w.wake();
}
super::cancel_task(self.task_id);
}
}
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, JoinError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut state = self.shared.state.borrow_mut();
match &mut *state {
TaskState::Running { join_waker } => {
*join_waker = Some(cx.waker().clone());
Poll::Pending
}
TaskState::Completed(_) => {
if let TaskState::Completed(val) =
std::mem::replace(&mut *state, TaskState::Consumed)
{
Poll::Ready(Ok(val))
} else {
unreachable!()
}
}
TaskState::Panicked => Poll::Ready(Err(JoinError::Panicked)),
TaskState::Cancelled => Poll::Ready(Err(JoinError::Cancelled)),
TaskState::Consumed => Poll::Ready(Err(JoinError::Cancelled)),
}
}
}
pub(crate) struct Task {
#[allow(dead_code)]
pub(crate) id: TaskId,
pub(crate) waker: Waker,
pub(crate) is_cancelled: Rc<std::cell::Cell<bool>>,
future: Pin<Box<dyn Future<Output = ()> + 'static>>,
on_panic: Option<Box<dyn FnOnce()>>,
}
impl Task {
pub(crate) fn new(
id: TaskId,
waker: Waker,
is_cancelled: Rc<std::cell::Cell<bool>>,
future: Pin<Box<dyn Future<Output = ()> + 'static>>,
on_panic: Box<dyn FnOnce()>,
) -> Self {
Self {
id,
waker,
is_cancelled,
future,
on_panic: Some(on_panic),
}
}
#[inline]
pub(crate) fn poll_own_waker(&mut self) -> Poll<()> {
let mut cx = Context::from_waker(&self.waker);
let future = self.future.as_mut();
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| future.poll(&mut cx)));
match res {
Ok(poll) => poll,
Err(_panic_payload) => {
if let Some(on_panic) = self.on_panic.take() {
on_panic();
}
Poll::Ready(())
}
}
}
}