use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::AppError;
use crate::workflow::ActionMode;
use crate::workflow::definition::CompiledDefinition;
use crate::workflow::run::RunRecord;
pub trait ChildExecutor: Send + Sync + 'static {
type Execution<'a, F>: Future<Output = Result<Vec<ChildCompletion>, AppError>> + 'a
where
Self: 'a,
F: Future<Output = Result<ChildCompletion, AppError>> + 'a;
fn execute<'a, F>(&'a self, batch: ChildBatch<F>) -> Self::Execution<'a, F>
where
F: Future<Output = Result<ChildCompletion, AppError>> + 'a;
}
pub struct ChildBatch<F> {
mode: ActionMode,
tasks: Vec<ChildSlot<F>>,
state: ChildBatchState,
}
enum ChildSlot<F> {
Pending(Pin<Box<F>>),
Completed(Result<ChildCompletion, AppError>),
}
enum ChildBatchState {
Unpolled,
Polling,
Finished,
}
pub struct ChildCompletion {
ordinal: usize,
record: Box<RunRecord>,
}
impl<F> ChildBatch<F> {
pub(super) fn new(tasks: Vec<F>, mode: ActionMode) -> Result<Self, AppError> {
if mode == ActionMode::Both {
return Err(AppError::InvalidInput(
"child execution requires an actual mode".into(),
));
}
Ok(Self {
mode,
tasks: tasks
.into_iter()
.map(|task| ChildSlot::Pending(Box::pin(task)))
.collect(),
state: ChildBatchState::Unpolled,
})
}
pub fn mode(&self) -> ActionMode {
self.mode
}
pub fn len(&self) -> usize {
self.tasks.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn into_tasks(self) -> Result<Vec<Pin<Box<F>>>, AppError> {
match self.state {
ChildBatchState::Unpolled => self
.tasks
.into_iter()
.map(|slot| match slot {
ChildSlot::Pending(task) => Ok(task),
ChildSlot::Completed(_) => Err(AppError::Conflict(
"child batch polling already began".into(),
)),
})
.collect(),
ChildBatchState::Polling | ChildBatchState::Finished => Err(AppError::Conflict(
"child batch polling already began".into(),
)),
}
}
}
impl<F: Future<Output = Result<ChildCompletion, AppError>>> Future for ChildBatch<F> {
type Output = Result<Vec<ChildCompletion>, AppError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let batch = self.get_mut();
if matches!(batch.state, ChildBatchState::Unpolled) {
batch.state = ChildBatchState::Polling;
}
let ChildBatchState::Polling = batch.state else {
return Poll::Ready(Err(AppError::Conflict(
"child batch already finished".into(),
)));
};
let mut pending = false;
for slot in &mut batch.tasks {
let ChildSlot::Pending(task) = slot else {
continue;
};
let result = match task.as_mut().poll(cx) {
Poll::Pending if batch.mode == ActionMode::Synchronous => return Poll::Pending,
Poll::Pending => {
pending = true;
continue;
}
Poll::Ready(result) => result,
};
match (batch.mode, result) {
(ActionMode::Synchronous, Err(error)) => {
batch.tasks.clear();
batch.state = ChildBatchState::Finished;
return Poll::Ready(Err(error));
}
(_, result) => *slot = ChildSlot::Completed(result),
}
}
if pending {
return Poll::Pending;
}
let results = std::mem::take(&mut batch.tasks)
.into_iter()
.map(|slot| match slot {
ChildSlot::Completed(result) => result,
ChildSlot::Pending(_) => Err(AppError::Integration(
"child completion is unavailable".into(),
)),
})
.collect();
batch.state = ChildBatchState::Finished;
Poll::Ready(results)
}
}
impl ChildCompletion {
pub(super) fn admitted(
record: RunRecord,
parent: &RunRecord,
definition: &CompiledDefinition,
) -> Result<Self, AppError> {
record.validate_child(parent, definition)?;
if !record.checkpoint.is_terminal() {
return Err(AppError::Conflict(
"child has no terminal completion".into(),
));
}
let ordinal = record
.parent
.as_ref()
.ok_or_else(|| AppError::Integration("child parent evidence unavailable".into()))?
.index;
Ok(Self {
ordinal,
record: Box::new(record),
})
}
pub fn ordinal(&self) -> usize {
self.ordinal
}
pub(super) fn record(&self) -> &RunRecord {
&self.record
}
pub(super) fn into_record(self) -> RunRecord {
*self.record
}
}
#[derive(Default)]
pub struct InlineExecutor;
impl ChildExecutor for InlineExecutor {
type Execution<'a, F>
= ChildBatch<F>
where
Self: 'a,
F: Future<Output = Result<ChildCompletion, AppError>> + 'a;
fn execute<'a, F>(&'a self, batch: ChildBatch<F>) -> Self::Execution<'a, F>
where
F: Future<Output = Result<ChildCompletion, AppError>> + 'a,
{
batch
}
}