parail 0.2.0

Easily switch between sequential and parallel.
Documentation
use std::{
    collections::BinaryHeap,
    pin::Pin,
    sync::{Arc, atomic},
    task::{Context, Poll},
};

use futures::{Stream, stream::StreamExt};

use crate::utils::heap::HeapItem;

pub struct ParFilterStream<S: Stream, F> {
    stream: Option<S>,
    filter_op: Option<F>,
    rx: Option<tokio::sync::mpsc::Receiver<(usize, Option<S::Item>)>>,
    done: Arc<atomic::AtomicBool>,
    heap: BinaryHeap<HeapItem<Option<S::Item>>>,
    next: usize,
}

impl<S, F, Fut> ParFilterStream<S, F>
where
    S: futures::stream::Stream + Send + 'static,
    S::Item: Send,
    F: FnOnce(&S::Item) -> Fut + Clone + Send + 'static,
    Fut: Future<Output = bool> + Send,
{
    #[inline]
    pub(crate) fn new(stream: S, filter_op: F) -> Self {
        ParFilterStream {
            stream: Some(stream),
            filter_op: Some(filter_op),
            rx: None,
            done: Arc::new(atomic::AtomicBool::new(false)),
            heap: BinaryHeap::new(),
            next: 0,
        }
    }

    #[inline]
    fn start(&mut self) {
        if self.stream.is_none() || self.filter_op.is_none() {
            return;
        }
        if let Some(stream) = self.stream.take() {
            if let Some(filter_op) = self.filter_op.take() {
                let handle = tokio::runtime::Handle::current();
                let buffer = handle.metrics().num_workers();
                let (tx, rx) = tokio::sync::mpsc::channel(buffer);
                self.rx = Some(rx);
                let done = self.done.clone();
                handle.spawn(async move {
                    let mut this = Box::pin(stream);
                    let mut sets = tokio::task::JoinSet::new();
                    let mut next = 0;
                    while let Some(item) = this.next().await {
                        if done.load(atomic::Ordering::Relaxed) {
                            break;
                        }
                        if sets.len() >= buffer {
                            if let Some(res) = sets.join_next().await {
                                match res {
                                    Ok(Err(_)) | Err(_) => return,
                                    _ => {}
                                }
                                while let Some(res) = sets.try_join_next() {
                                    match res {
                                        Ok(Err(_)) | Err(_) => return,
                                        _ => {}
                                    }
                                }
                            }
                        }
                        let tx = tx.clone();
                        let filter_op = filter_op.clone();
                        sets.spawn(async move {
                            let item = filter_op(&item).await.then(|| item);
                            tx.send((next, item)).await
                        });
                        next += 1;
                    }
                    while let Some(_) = sets.join_next().await {}
                });
            }
        }
    }
}

impl<S, F, Fut> futures::stream::Stream for ParFilterStream<S, F>
where
    Self: Unpin,
    S: futures::stream::Stream + Send + 'static,
    S::Item: Send,
    F: FnOnce(&S::Item) -> Fut + Clone + Send + 'static,
    Fut: Future<Output = bool> + Send,
{
    type Item = S::Item;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
        let this = self.get_mut();
        this.start();
        if let Some(HeapItem(i, _)) = this.heap.peek() {
            if *i == this.next {
                this.next += 1;
                if let Some(item) = this.heap.pop().map(|HeapItem(_, item)| item) {
                    if item.is_some() {
                        return Poll::Ready(item);
                    } else {
                        cx.waker().wake_by_ref();
                        return Poll::Pending;
                    }
                }
            }
        }
        if let Some(rx) = this.rx.as_mut() {
            return match rx.poll_recv(cx) {
                Poll::Ready(Some((i, Some(item)))) if i == this.next => {
                    this.next += 1;
                    Poll::Ready(Some(item))
                }
                Poll::Ready(Some((i, None))) if i == this.next => {
                    this.next += 1;
                    cx.waker().wake_by_ref();
                    Poll::Pending
                }
                Poll::Ready(Some((i, item))) => {
                    this.heap.push(HeapItem(i, item));
                    cx.waker().wake_by_ref();
                    Poll::Pending
                }
                Poll::Ready(None) => Poll::Ready(None),
                Poll::Pending => Poll::Pending,
            };
        }
        Poll::Pending
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        if let Some(stream) = self.stream.as_ref() {
            (0, stream.size_hint().1)
        } else {
            (0, None)
        }
    }
}

pub trait ParallelFilterStream: futures::stream::Stream {
    fn par_filter<F>(self, filter_op: F) -> impl futures::stream::Stream<Item = Self::Item>
    where
        F: Unpin + FnOnce(&Self::Item) -> bool + Clone + Send + 'static;

    fn par_filter_async<Fut, F>(
        self,
        filter_op: F,
    ) -> impl futures::stream::Stream<Item = Self::Item>
    where
        F: Unpin + FnOnce(&Self::Item) -> Fut + Clone + Send + 'static,
        Fut: Future<Output = bool> + Send;
}

impl<S> ParallelFilterStream for S
where
    S: Unpin + futures::stream::Stream + Send + 'static,
    S::Item: Unpin + Send,
{
    fn par_filter<F>(self, filter_op: F) -> impl futures::stream::Stream<Item = Self::Item>
    where
        F: Unpin + FnOnce(&Self::Item) -> bool + Clone + Send + 'static,
    {
        ParFilterStream::new(self, |item| futures::future::ready(filter_op(item)))
    }

    fn par_filter_async<Fut, F>(
        self,
        filter_op: F,
    ) -> impl futures::stream::Stream<Item = Self::Item>
    where
        F: Unpin + FnOnce(&Self::Item) -> Fut + Clone + Send + 'static,
        Fut: Future<Output = bool> + Send,
    {
        ParFilterStream::new(self, filter_op)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_par_filter_stream() {
        let mut stream = futures::stream::iter(0..100).par_filter(|i| i % 2 == 0);
        for i in (0..100).step_by(2) {
            assert_eq!(stream.next().await, Some(i));
        }
    }
}