use super::TryJoin as TryJoinTrait;
use crate::utils::{FutureVec, OutputVec, PollVec, WakerVec};
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::vec::Vec;
use core::fmt;
use core::future::{Future, IntoFuture};
use core::mem::ManuallyDrop;
use core::ops::DerefMut;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project::{pin_project, pinned_drop};
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project(PinnedDrop)]
pub struct TryJoin<Fut, T, E>
where
Fut: Future<Output = Result<T, E>>,
{
consumed: bool,
pending: usize,
items: OutputVec<T>,
wakers: WakerVec,
state: PollVec,
#[pin]
futures: FutureVec<Fut>,
}
impl<Fut, T, E> TryJoin<Fut, T, E>
where
Fut: Future<Output = Result<T, E>>,
{
#[inline]
pub(crate) fn new(futures: Vec<Fut>) -> Self {
let len = futures.len();
Self {
consumed: false,
pending: len,
items: OutputVec::uninit(len),
wakers: WakerVec::new(len),
state: PollVec::new_pending(len),
futures: FutureVec::new(futures),
}
}
}
impl<Fut, T, E> TryJoinTrait for Vec<Fut>
where
Fut: IntoFuture<Output = Result<T, E>>,
{
type Output = Vec<T>;
type Error = E;
type Future = TryJoin<Fut::IntoFuture, T, E>;
fn try_join(self) -> Self::Future {
TryJoin::new(self.into_iter().map(IntoFuture::into_future).collect())
}
}
impl<Fut, T, E> fmt::Debug for TryJoin<Fut, T, E>
where
Fut: Future<Output = Result<T, E>> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.state.iter()).finish()
}
}
impl<Fut, T, E> Future for TryJoin<Fut, T, E>
where
Fut: Future<Output = Result<T, E>>,
{
type Output = Result<Vec<T>, E>;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
assert!(
!*this.consumed,
"Futures must not be polled after completing"
);
let mut readiness = this.wakers.readiness();
readiness.set_waker(cx.waker());
if *this.pending != 0 && !readiness.any_ready() {
return Poll::Pending;
}
for (i, mut fut) in this.futures.iter().enumerate() {
if this.state[i].is_pending() && readiness.clear_ready(i) {
#[allow(clippy::drop_non_drop)]
drop(readiness);
let mut cx = Context::from_waker(this.wakers.get(i).unwrap());
if let Poll::Ready(value) = unsafe {
fut.as_mut()
.map_unchecked_mut(|t| t.deref_mut())
.poll(&mut cx)
} {
*this.pending -= 1;
match value {
Ok(value) => {
this.items.write(i, value);
this.state[i].set_ready();
unsafe { ManuallyDrop::drop(fut.get_unchecked_mut()) };
}
Err(err) => {
*this.consumed = true;
this.state[i].set_none();
unsafe { ManuallyDrop::drop(fut.get_unchecked_mut()) };
return Poll::Ready(Err(err));
}
}
}
readiness = this.wakers.readiness();
}
}
if *this.pending == 0 {
*this.consumed = true;
for state in this.state.iter_mut() {
debug_assert!(
state.is_ready(),
"Future should have reached a `Ready` state"
);
state.set_none();
}
Poll::Ready(Ok(unsafe { this.items.take() }))
} else {
Poll::Pending
}
}
}
#[pinned_drop]
impl<Fut, T, E> PinnedDrop for TryJoin<Fut, T, E>
where
Fut: Future<Output = Result<T, E>>,
{
fn drop(self: Pin<&mut Self>) {
let mut this = self.project();
for i in this.state.ready_indexes() {
unsafe { this.items.drop(i) };
}
for i in this.state.pending_indexes() {
unsafe { this.futures.as_mut().drop(i) };
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::vec;
use core::future;
#[test]
fn all_ok() {
futures_lite::future::block_on(async {
let res: Result<_, ()> = vec![future::ready(Ok("hello")), future::ready(Ok("world"))]
.try_join()
.await;
assert_eq!(res.unwrap(), ["hello", "world"]);
})
}
#[test]
fn empty() {
futures_lite::future::block_on(async {
let data: Vec<future::Ready<Result<(), ()>>> = vec![];
let res = data.try_join().await;
assert_eq!(res.unwrap(), vec![]);
});
}
#[test]
fn one_err() {
futures_lite::future::block_on(async {
let res: Result<_, _> = vec![future::ready(Ok("hello")), future::ready(Err("oh no"))]
.try_join()
.await;
assert_eq!(res.unwrap_err(), "oh no");
});
}
}