#![feature(ptr_metadata)]
mod future_wrappers;
use crate::future_wrappers::{SendPtr, WriteOutput};
use std::{
any::Any, error::Error, fmt, future::Future, hint::spin_loop, marker::PhantomData,
mem::MaybeUninit, ptr::NonNull,
};
use tokio::{
runtime::{Handle, RuntimeFlavor},
task::{block_in_place, 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>,
}
impl<'scope, T> ScopedJoinSet<'scope, T>
where
T: 'scope + Send,
{
pub fn new() -> Self {
Self {
join_set: JoinSet::new(),
_marker: PhantomData,
}
}
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);
}
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.is_empty() {
return;
}
self.abort_all();
if Handle::current().runtime_flavor() == RuntimeFlavor::MultiThread {
block_in_place(|| {
while !self.is_empty() {
self.try_join_next();
spin_loop();
}
});
}
}
}
#[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
}
}
}