use std::any::Any;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll};
use tokio::sync::oneshot;
use tokio_util::sync::CancellationToken;
pub(super) fn joinable<F>(
future: F,
) -> (
impl Future<Output = ()> + Send + 'static,
JoinHandle<F::Output>,
)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let shared = Arc::new(Shared::default());
let (report, result) = oneshot::channel();
let mut running = Running {
body: Some(Box::pin(future)),
shared: Arc::clone(&shared),
report: Some(report),
};
let task = async move {
let outcome = {
let mut aborted = std::pin::pin!(running.shared.abort.cancelled());
let body = &mut running.body;
std::future::poll_fn(|cx| {
if aborted.as_mut().poll(cx).is_ready() {
return Poll::Ready(Err(JoinError(Repr::Cancelled)));
}
let body = body.as_mut().expect("polled until it ends").as_mut();
match std::panic::catch_unwind(AssertUnwindSafe(|| body.poll(cx))) {
Ok(poll) => poll.map(Ok),
Err(payload) => Poll::Ready(Err(JoinError(Repr::Panic(payload)))),
}
})
.await
};
running.finish(outcome);
};
(
task,
JoinHandle {
result,
abort: AbortHandle(shared),
},
)
}
struct Running<F: Future> {
body: Option<Pin<Box<F>>>,
shared: Arc<Shared>,
report: Option<oneshot::Sender<Result<F::Output, JoinError>>>,
}
impl<F: Future> Running<F> {
fn finish(&mut self, outcome: Result<F::Output, JoinError>) {
let outcome = match self.body.take() {
Some(body) => match std::panic::catch_unwind(AssertUnwindSafe(|| drop(body))) {
Ok(()) => outcome,
Err(payload) => Err(JoinError(Repr::Panic(payload))),
},
None => outcome,
};
self.shared.finished.store(true, Ordering::Release);
if let Some(report) = self.report.take() {
let _ = report.send(outcome);
}
}
}
impl<F: Future> Drop for Running<F> {
fn drop(&mut self) {
if self.report.is_some() {
self.finish(Err(JoinError(Repr::Cancelled)));
}
}
}
#[derive(Default)]
struct Shared {
abort: CancellationToken,
finished: AtomicBool,
}
pub struct JoinHandle<T> {
result: oneshot::Receiver<Result<T, JoinError>>,
abort: AbortHandle,
}
impl<T> JoinHandle<T> {
pub fn abort(&self) {
self.abort.abort();
}
pub fn abort_handle(&self) -> AbortHandle {
self.abort.clone()
}
pub fn is_finished(&self) -> bool {
self.abort.is_finished()
}
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, JoinError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.result)
.poll(cx)
.map(|received| received.unwrap_or(Err(JoinError(Repr::Cancelled))))
}
}
#[derive(Clone)]
pub struct AbortHandle(Arc<Shared>);
impl AbortHandle {
pub fn abort(&self) {
self.0.abort.cancel();
}
pub fn is_finished(&self) -> bool {
self.0.finished.load(Ordering::Acquire)
}
}
pub struct JoinError(Repr);
enum Repr {
Cancelled,
Panic(Box<dyn Any + Send + 'static>),
}
impl JoinError {
pub fn is_cancelled(&self) -> bool {
matches!(self.0, Repr::Cancelled)
}
pub fn is_panic(&self) -> bool {
matches!(self.0, Repr::Panic(_))
}
#[track_caller]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
match self.0 {
Repr::Panic(payload) => payload,
Repr::Cancelled => panic!("`into_panic` on a task that was cancelled"),
}
}
}
impl std::fmt::Debug for JoinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Repr::Cancelled => f.write_str("JoinError::Cancelled"),
Repr::Panic(_) => f.write_str("JoinError::Panic(..)"),
}
}
}
impl std::fmt::Display for JoinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Repr::Cancelled => f.write_str("task was cancelled"),
Repr::Panic(_) => f.write_str("task panicked"),
}
}
}
impl std::error::Error for JoinError {}