apalis-workflow 1.0.0

A flexible and composable task workflow engine for rust
Documentation
use std::{
    collections::HashSet,
    marker::PhantomData,
    pin::Pin,
    sync::{Arc, atomic::Ordering},
    task::{Context, Poll},
};

use apalis_core::{
    backend::{TaskResult, memory::MemoryStorageError},
    task::task_id::TaskId,
};
use futures_util::Stream;
use serde::de::DeserializeOwned;

use crate::in_memory::result_store::ResultStore;

pub(crate) type WaitId = u64;

#[derive(Debug, Clone)]
pub struct WaitForStream<O> {
    task_ids: Vec<TaskId>,
    completed: HashSet<TaskId>,
    store: Arc<ResultStore>,
    id: WaitId,
    _marker: PhantomData<O>,
}

impl<O> WaitForStream<O> {
    pub(super) fn new(task_ids: impl IntoIterator<Item = TaskId>, store: Arc<ResultStore>) -> Self {
        let id = store.next_id.fetch_add(1, Ordering::Relaxed);
        Self {
            task_ids: task_ids.into_iter().collect(),
            completed: HashSet::new(),
            store,
            id,
            _marker: PhantomData,
        }
    }
}

impl<O> Stream for WaitForStream<O>
where
    O: DeserializeOwned + Unpin,
{
    type Item = Result<TaskResult<O>, MemoryStorageError>;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let this = self.get_mut();

        #[cfg(feature = "tracing")]
        tracing::trace!(
            completed = ?this.completed.len(),
            results = ?&this.store.results.len(),
            wakers = ?&this.store.wakers.len(),
            "Checking the results",
        );
        for task_id in &this.task_ids {
            if this.completed.contains(task_id) {
                continue;
            }

            if let Some(value) = this.store.get(task_id) {
                this.completed.insert(task_id.clone());
                let decoded = value
                    .result
                    .map(|a| O::deserialize(a).map_err(|e| e.to_string()))
                    .map_err(|e| MemoryStorageError::Other(e.into()))?;

                return Poll::Ready(Some(Ok(TaskResult {
                    task_id: value.task_id,
                    attempt: value.attempt,
                    status: value.status,
                    result: decoded,
                })));
            }
        }

        if this.completed.len() == this.task_ids.len() {
            return Poll::Ready(None);
        }
        this.store.register_waker(this.id, cx.waker());
        Poll::Pending
    }
}