use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
pub fn join_all<F: Future>(futures: impl IntoIterator<Item = F>) -> JoinAll<F> {
JoinAll {
pending: futures.into_iter().map(|f| Some(Box::pin(f))).collect(),
outputs: Vec::new(),
}
}
#[must_use = "a future does nothing unless awaited"]
pub struct JoinAll<F: Future> {
pending: Vec<Option<Pin<Box<F>>>>,
outputs: Vec<Option<F::Output>>,
}
impl<F: Future> Unpin for JoinAll<F> {}
impl<F: Future> Future for JoinAll<F> {
type Output = Vec<F::Output>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if this.outputs.is_empty() {
this.outputs = (0..this.pending.len()).map(|_| None).collect();
}
let mut all_done = true;
for (index, slot) in this.pending.iter_mut().enumerate() {
let Some(future) = slot else {
continue;
};
match future.as_mut().poll(context) {
Poll::Ready(output) => {
this.outputs[index] = Some(output);
*slot = None;
}
Poll::Pending => all_done = false,
}
}
match all_done {
true => Poll::Ready(
std::mem::take(&mut this.outputs)
.into_iter()
.map(|output| output.expect("every future resolved"))
.collect(),
),
false => Poll::Pending,
}
}
}
#[cfg(test)]
mod tests {
use std::pin::pin;
use std::sync::{Arc, Mutex};
use std::task::Waker;
use super::*;
fn poll_once<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
future.poll(&mut Context::from_waker(Waker::noop()))
}
#[test]
fn outputs_come_back_in_input_order() {
let mut future = pin!(join_all([
std::future::ready("a"),
std::future::ready("b"),
std::future::ready("c"),
]));
assert_eq!(poll_once(future.as_mut()), Poll::Ready(vec!["a", "b", "c"]));
}
#[test]
fn an_empty_batch_is_immediately_ready() {
let mut future = pin!(join_all(Vec::<std::future::Ready<u8>>::new()));
assert_eq!(poll_once(future.as_mut()), Poll::Ready(Vec::new()));
}
#[test]
fn every_future_makes_progress_before_any_of_them_finishes() {
let log: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
let make = |name: &'static str, log: Arc<Mutex<Vec<&'static str>>>| {
let mut polls = 0;
std::future::poll_fn(move |_context| {
polls += 1;
if polls == 1 {
log.lock().unwrap().push(name);
return Poll::Pending;
}
Poll::Ready(name)
})
};
let mut future = pin!(join_all(vec![
make("first", log.clone()),
make("second", log.clone()),
]));
assert_eq!(poll_once(future.as_mut()), Poll::Pending);
assert_eq!(*log.lock().unwrap(), vec!["first", "second"]);
assert_eq!(
poll_once(future.as_mut()),
Poll::Ready(vec!["first", "second"])
);
}
#[test]
fn a_finished_future_is_dropped_before_its_siblings_complete() {
let dropped = Arc::new(Mutex::new(false));
struct Flag(Arc<Mutex<bool>>);
impl Drop for Flag {
fn drop(&mut self) {
*self.0.lock().unwrap() = true;
}
}
let flag = Flag(dropped.clone());
let quick = async move {
let _flag = flag;
1u8
};
let slow = std::future::poll_fn(|_context| Poll::<u8>::Pending);
let mut future = pin!(join_all(vec![
Box::pin(quick) as Pin<Box<dyn Future<Output = u8>>>,
Box::pin(slow),
]));
assert_eq!(poll_once(future.as_mut()), Poll::Pending);
assert!(
*dropped.lock().unwrap(),
"the resolved future must be released, not held until the batch ends"
);
}
}