use crate::concurrent_stream::ConsumerState;
use crate::future::FutureGroup;
use crate::private::Try;
use futures_lite::StreamExt;
use pin_project::pin_project;
use super::Consumer;
use alloc::sync::Arc;
use core::future::Future;
use core::marker::PhantomData;
use core::num::NonZeroUsize;
use core::ops::ControlFlow;
use core::pin::Pin;
use core::sync::atomic::{AtomicUsize, Ordering};
use core::task::{ready, Context, Poll};
#[pin_project]
pub(crate) struct TryForEachConsumer<FutT, T, F, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
count: Arc<AtomicUsize>,
#[pin]
group: FutureGroup<TryForEachFut<F, FutT, T, FutB, B>>,
limit: usize,
residual: Option<B::Residual>,
f: F,
_phantom: PhantomData<(T, FutB)>,
}
impl<FutT, T, F, FutB, B> TryForEachConsumer<FutT, T, F, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
pub(crate) fn new(limit: Option<NonZeroUsize>, f: F) -> Self {
let limit = match limit {
Some(n) => n.get(),
None => usize::MAX,
};
Self {
limit,
f,
residual: None,
count: Arc::new(AtomicUsize::new(0)),
group: FutureGroup::new(),
_phantom: PhantomData,
}
}
}
impl<FutT, T, F, FutB, B> Consumer<T, FutT> for TryForEachConsumer<FutT, T, F, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
type Output = B;
async fn send(self: Pin<&mut Self>, future: FutT) -> super::ConsumerState {
let mut this = self.project();
while this.count.load(Ordering::Relaxed) >= *this.limit {
match this.group.next().await {
None => break,
Some(res) => match res.branch() {
ControlFlow::Continue(_) => continue,
ControlFlow::Break(residual) => {
*this.residual = Some(residual);
return ConsumerState::Break;
}
},
}
}
this.count.fetch_add(1, Ordering::Relaxed);
let fut = TryForEachFut::new(this.f.clone(), future, this.count.clone());
this.group.as_mut().insert_pinned(fut);
ConsumerState::Continue
}
async fn progress(self: Pin<&mut Self>) -> super::ConsumerState {
let mut this = self.project();
while let Some(res) = this.group.next().await {
if let ControlFlow::Break(residual) = res.branch() {
*this.residual = Some(residual);
return ConsumerState::Break;
}
}
ConsumerState::Empty
}
async fn flush(self: Pin<&mut Self>) -> Self::Output {
let mut this = self.project();
if this.residual.is_some() {
return B::from_residual(this.residual.take().unwrap());
}
while let Some(res) = this.group.next().await {
if let ControlFlow::Break(residual) = res.branch() {
return B::from_residual(residual);
}
}
B::from_output(())
}
}
#[derive(Debug)]
pub struct TryForEachFut<F, FutT, T, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
done: bool,
count: Arc<AtomicUsize>,
f: F,
fut_t: Option<FutT>,
fut_b: Option<FutB>,
}
impl<F, FutT, T, FutB, B> TryForEachFut<F, FutT, T, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
fn new(f: F, fut_t: FutT, count: Arc<AtomicUsize>) -> Self {
Self {
done: false,
count,
f,
fut_t: Some(fut_t),
fut_b: None,
}
}
}
impl<F, FutT, T, FutB, B> Future for TryForEachFut<F, FutT, T, FutB, B>
where
FutT: Future<Output = T>,
F: Clone + Fn(T) -> FutB,
FutB: Future<Output = B>,
B: Try<Output = ()>,
{
type Output = B;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
if this.done {
panic!("future has already been polled to completion once");
}
if let Some(fut) = this.fut_t.as_mut() {
let t = ready!(unsafe { Pin::new_unchecked(fut) }.poll(cx));
let fut_b = (this.f)(t);
this.fut_t = None;
this.fut_b = Some(fut_b);
}
if let Some(fut) = this.fut_b.as_mut() {
let item = ready!(unsafe { Pin::new_unchecked(fut) }.poll(cx));
this.count.fetch_sub(1, Ordering::Relaxed);
this.done = true;
return Poll::Ready(item);
}
unreachable!("neither future `a` nor future `b` were ready");
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::prelude::*;
use futures_lite::stream;
use std::io;
#[test]
fn concurrency_one() {
futures_lite::future::block_on(async {
let count = Arc::new(AtomicUsize::new(0));
stream::repeat(1)
.take(2)
.co()
.limit(NonZeroUsize::new(1))
.try_for_each(|n| {
let count = count.clone();
async move {
count.fetch_add(n, Ordering::Relaxed);
std::io::Result::Ok(())
}
})
.await
.unwrap();
assert_eq!(count.load(Ordering::Relaxed), 2);
});
}
#[test]
fn concurrency_three() {
futures_lite::future::block_on(async {
let count = Arc::new(AtomicUsize::new(0));
stream::repeat(1)
.take(10)
.co()
.limit(NonZeroUsize::new(3))
.try_for_each(|n| {
let count = count.clone();
async move {
count.fetch_add(n, Ordering::Relaxed);
std::io::Result::Ok(())
}
})
.await
.unwrap();
assert_eq!(count.load(Ordering::Relaxed), 10);
});
}
#[test]
fn short_circuits() {
futures_lite::future::block_on(async {
let count = Arc::new(AtomicUsize::new(0));
let output = stream::repeat(10)
.take(2)
.co()
.limit(NonZeroUsize::new(1))
.try_for_each(|n| {
let count = count.clone();
async move {
count.fetch_add(n, Ordering::SeqCst);
std::io::Result::Err(io::ErrorKind::Other.into())
}
})
.await;
assert!(output.is_err());
});
}
}