use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::Poll;
use crate::{JoinHandle, spawn};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum JoinError {
Cancelled,
Aborted,
Panicked,
}
impl JoinError {
pub fn is_aborted(&self) -> bool {
matches!(self, JoinError::Aborted)
}
pub fn is_cancelled(&self) -> bool {
matches!(self, JoinError::Cancelled)
}
pub fn is_panicked(&self) -> bool {
matches!(self, JoinError::Panicked)
}
}
impl fmt::Display for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JoinError::Cancelled => f.write_str("blocking task was cancelled"),
JoinError::Aborted => f.write_str("task was aborted"),
JoinError::Panicked => f.write_str("task panicked"),
}
}
}
impl std::error::Error for JoinError {}
pub struct JoinSet<T> {
handles: Vec<JoinHandle<T>>,
}
impl<T> JoinSet<T> {
pub fn new() -> Self {
Self {
handles: Vec::new(),
}
}
pub fn spawn<F>(&mut self, future: F)
where
F: Future<Output = T> + 'static,
T: 'static,
{
self.handles.push(spawn(future));
}
pub fn join_next(&mut self) -> impl Future<Output = Option<Result<T, JoinError>>> + '_ {
std::future::poll_fn(|cx| {
if self.handles.is_empty() {
return Poll::Ready(None);
}
for index in 0..self.handles.len() {
match Pin::new(&mut self.handles[index]).poll(cx) {
Poll::Ready(result) => {
self.handles.swap_remove(index);
return Poll::Ready(Some(result));
}
Poll::Pending => {}
}
}
Poll::Pending
})
}
pub fn abort_all(&mut self) {
for handle in &self.handles {
handle.abort();
}
}
pub fn detach_all(&mut self) {
self.handles.clear();
}
pub fn len(&self) -> usize {
self.handles.len()
}
pub fn is_empty(&self) -> bool {
self.handles.is_empty()
}
}
impl<T> Default for JoinSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Drop for JoinSet<T> {
fn drop(&mut self) {
self.abort_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{run, spawn};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[test]
fn join_next_returns_results_as_tasks_complete() {
let results = Rc::new(RefCell::new(Vec::new()));
let results_task = Rc::clone(&results);
spawn(async move {
let mut set = JoinSet::new();
set.spawn(async { 3 });
set.spawn(async { 1 });
set.spawn(async { 2 });
while let Some(result) = set.join_next().await {
results_task
.borrow_mut()
.push(result.expect("task should complete"));
}
});
run();
results.borrow_mut().sort_unstable();
assert_eq!(&*results.borrow(), &[1, 2, 3]);
}
#[test]
fn join_next_returns_none_when_empty() {
let observed = Rc::new(Cell::new(false));
let observed_task = Rc::clone(&observed);
spawn(async move {
let mut set = JoinSet::<usize>::new();
observed_task.set(set.join_next().await.is_none());
});
run();
assert!(observed.get());
}
#[test]
fn abort_all_surfaces_aborted_join_errors() {
let aborted = Rc::new(Cell::new(0));
let aborted_task = Rc::clone(&aborted);
spawn(async move {
let mut set = JoinSet::<usize>::new();
set.spawn(async { std::future::pending::<usize>().await });
set.spawn(async { std::future::pending::<usize>().await });
set.abort_all();
let mut count = 0;
while let Some(result) = set.join_next().await {
assert!(result.expect_err("task should be aborted").is_aborted());
count += 1;
}
aborted_task.set(count);
});
run();
assert_eq!(aborted.get(), 2);
}
#[test]
fn drop_aborts_running_tasks() {
let completed = Rc::new(Cell::new(false));
let completed_task = Rc::clone(&completed);
spawn(async move {
let mut set = JoinSet::new();
set.spawn(async move {
completed_task.set(true);
});
drop(set);
});
run();
assert!(!completed.get());
}
}