mod future_wrappers;
use crate::future_wrappers::{SendPtr, WriteOutput};
use std::{
any::Any, error::Error, fmt, future::Future, marker::PhantomData, mem::MaybeUninit,
process::abort, ptr::NonNull,
};
use tokio::task::JoinSet;
type TokioJoinError = tokio::task::JoinError;
#[derive(Default)]
pub struct ScopedJoinSet<'scope, T>
where
T: 'scope + Send,
{
join_set: JoinSet<SendPtr>,
_marker: PhantomData<&'scope T>,
_safe_to_drop: bool,
}
impl<'scope, T> ScopedJoinSet<'scope, T>
where
T: 'scope + Send,
{
pub fn new() -> Self {
Self {
join_set: JoinSet::new(),
_marker: PhantomData,
_safe_to_drop: false,
}
}
pub fn spawn<F>(&mut self, task: F)
where
F: Future<Output = T> + Send + 'scope,
T: Send,
{
let output: NonNull<MaybeUninit<T>> = Box::leak(Box::new(MaybeUninit::uninit())).into();
let task = WriteOutput::new(task, output.cast());
let task = unsafe { task.into_static() };
self.join_set.spawn(task);
}
pub fn is_empty(&self) -> bool {
self.join_set.is_empty()
}
pub fn len(&self) -> usize {
self.join_set.len()
}
pub async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
match self.join_set.join_next().await? {
Ok(output) => {
let output: NonNull<T> = output.cast();
let output = unsafe { *Box::from_raw(output.as_ptr()) };
Some(Ok(output))
}
Err(error) => Some(Err(error.into())),
}
}
pub fn try_join_next(&mut self) -> Option<Result<T, JoinError>> {
match self.join_set.try_join_next()? {
Ok(output) => {
let output: NonNull<T> = output.cast();
let output = unsafe { *Box::from_raw(output.as_ptr()) };
Some(Ok(output))
}
Err(error) => Some(Err(error.into())),
}
}
pub async fn join_all(mut self) -> Vec<Result<T, JoinError>> {
let mut results = Vec::with_capacity(self.len());
while let Some(r) = self.join_next().await {
results.push(r);
}
self._safe_to_drop = true;
results
}
pub fn abort_all(&mut self) {
self.join_set.abort_all();
}
pub async fn shutdown(mut self) -> Vec<Result<T, JoinError>> {
self.abort_all();
self.join_all().await
}
}
unsafe impl<'scope, T: Send> Send for ScopedJoinSet<'scope, T> {}
impl<'scope, T> Drop for ScopedJoinSet<'scope, T>
where
T: Send,
{
fn drop(&mut self) {
if !self._safe_to_drop {
eprintln!("ScopedJoinSet cannot be dropped implicitly. Use `.join_all().await` or `.shutdown().await`.");
abort();
}
}
}
#[derive(Debug)]
pub enum JoinError {
Cancelled,
Panicked(Box<dyn Any + Send + 'static>),
}
impl fmt::Display for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JoinError::Cancelled => write!(f, "task was cancelled"),
JoinError::Panicked(_) => write!(f, "task panicked"),
}
}
}
impl Error for JoinError {}
impl JoinError {
pub fn is_cancelled(&self) -> bool {
matches!(self, JoinError::Cancelled)
}
pub fn is_panic(&self) -> bool {
matches!(self, JoinError::Panicked(_))
}
}
impl From<TokioJoinError> for JoinError {
fn from(err: TokioJoinError) -> Self {
if err.is_cancelled() {
JoinError::Cancelled
} else if err.is_panic() {
JoinError::Panicked(err.into_panic())
} else {
JoinError::Cancelled
}
}
}