use async_task::{Runnable, Task};
use std::any::Any;
use std::future::Future;
use std::marker::PhantomData;
use std::mem;
use std::panic;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use std::thread;
use crate::Threadpool;
type Inner<R> = Task<Result<R, Box<dyn Any + Send + 'static>>>;
enum State<R> {
Pending {
runnable: Runnable,
task: Inner<R>,
},
Scheduled(Inner<R>),
Done,
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SpawnFuture<'pool, R> {
state: State<R>,
_pool: PhantomData<&'pool Threadpool>,
}
impl<'pool, R> SpawnFuture<'pool, R> {
#[inline]
pub(crate) fn new(runnable: Runnable, task: Inner<R>) -> Self {
Self {
state: State::Pending {
runnable,
task,
},
_pool: PhantomData,
}
}
}
impl<R> Future for SpawnFuture<'_, R> {
type Output = R;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.as_mut().get_unchecked_mut() };
if matches!(this.state, State::Pending { .. }) {
match mem::replace(&mut this.state, State::Done) {
State::Pending {
runnable,
task,
} => {
runnable.schedule();
this.state = State::Scheduled(task);
}
_ => unreachable!(),
}
}
match &mut this.state {
State::Scheduled(task) => match Pin::new(task).poll(cx) {
Poll::Ready(result) => {
this.state = State::Done;
match result {
Ok(value) => Poll::Ready(value),
Err(payload) => panic::resume_unwind(payload),
}
}
Poll::Pending => Poll::Pending,
},
State::Done => panic!("SpawnFuture polled after completion"),
State::Pending {
..
} => unreachable!("Pending handled above"),
}
}
}
impl<R> Drop for SpawnFuture<'_, R> {
fn drop(&mut self) {
match mem::replace(&mut self.state, State::Done) {
State::Pending {
runnable,
task,
} => {
drop(runnable);
drop(task);
}
State::Scheduled(task) => {
block_on_cancel(task);
}
State::Done => {}
}
}
}
fn block_on_cancel<R>(task: Inner<R>) {
struct ParkWaker(thread::Thread);
impl Wake for ParkWaker {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.unpark();
}
}
let waker: Waker = Arc::new(ParkWaker(thread::current())).into();
let mut cx = Context::from_waker(&waker);
let mut fut = task.cancel();
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(_) => return,
Poll::Pending => thread::park(),
}
}
}