use super::budget::DispatchKind;
#[cfg(not(target_arch = "wasm32"))]
use super::cancel::race;
use super::cancel::{Limits, Stop};
use super::interp::RunEnv;
use super::policy::{self, Flow};
use super::record::{self, Completed, Stamp, TaskIds};
use crate::base::{Change, Error, ErrorKind, ErrorRecord, Result, SiteKind, TaskIx};
use crate::expr::Path;
use crate::function::{BoundCall, CallInfo, CallLayer, CallStart, Ctx, Next, Outcome, Overlay};
use crate::program::{CallSite, CompiledWorkflow};
use crate::state::{Reader, RunContext};
use datavalue::OwnedDataValue;
use std::borrow::Cow;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone, Copy)]
pub(crate) struct Layers<'a> {
pub(crate) run: &'a [Arc<dyn CallLayer>],
pub(crate) registry: &'a [Arc<dyn CallLayer>],
}
pub(crate) fn call_start<'c, 'r: 'c, 'p: 'c>(
env: &RunEnv<'r, 'p>,
reader: Reader<'c>,
wf: &'c CompiledWorkflow,
site: &CallSite,
) -> (CallStart<'c>, Layers<'c>) {
let start = CallStart {
reader,
binding: None,
payload: env.payload,
exprs: env.exprs,
budget: env.budget,
info: env.call_info(wf, site.task, Stamp::sweep(env)),
deadline: env.limits.deadline,
cancel: env.limits.cancel,
capture: env.capture.captures(),
};
let layers = Layers {
run: env.layers,
registry: wf.registry.layers(),
};
(start, layers)
}
impl<'r> RunEnv<'r, '_> {
pub(crate) fn call_info<'c>(
&self,
wf: &'c CompiledWorkflow,
task: u32,
stamp: Stamp,
) -> CallInfo<'c>
where
'r: 'c,
{
let meta = &wf.tasks[task as usize];
CallInfo {
program: self.program,
workflow: self.workflow_ix,
task: TaskIx::new(self.task_base + task),
function: &meta.function,
workflow_id: &wf.id,
task_id: &meta.id,
channel: self.channel,
routing_bucket: self.routing_bucket,
loop_counter: stamp.loop_counter,
element_index: stamp.element_index,
}
}
}
pub(crate) enum CallResult {
Returned(Result<Outcome>),
CutShort(Stop, Error),
}
impl CallResult {
pub(crate) fn is_ok(&self) -> bool {
matches!(self, Self::Returned(Ok(_)))
}
pub(crate) fn into_result(self) -> Result<Outcome> {
match self {
Self::Returned(result) => result,
Self::CutShort(_, error) => Err(error),
}
}
}
pub(crate) struct Invoked {
pub(crate) result: CallResult,
pub(crate) overlay: Overlay,
pub(crate) changes: Vec<Change>,
pub(crate) errors: Vec<ErrorRecord>,
pub(crate) collected: Option<OwnedDataValue>,
pub(crate) elapsed: Option<Duration>,
}
pub(crate) fn invoke<'r>(
start: CallStart<'r>,
bound: &'r dyn BoundCall,
layers: Layers<'r>,
collect: Option<&Path>,
timers: bool,
) -> impl Future<Output = Invoked> {
let limits = Limits {
deadline: start.deadline,
cancel: start.cancel,
timers,
};
let function = start.info.function;
let mut ctx = Ctx::new(start, bound);
async move {
let chain = Next::new(layers.run, layers.registry, ctx.callee());
let ran = match limits.stop() {
Some(stop) => Err(stop),
#[cfg(not(target_arch = "wasm32"))]
None if limits.is_set() => Box::pin(race(chain.run(&mut ctx), limits)).await,
None => Ok(chain.run(&mut ctx).await),
};
let result = match ran {
Ok(Err(e)) => match limits.stop() {
Some(stop) => CallResult::CutShort(stop, stop.during(function)),
None => CallResult::Returned(Err(e)),
},
Ok(result) => CallResult::Returned(result),
Err(stop) => CallResult::CutShort(stop, stop.during(function)),
};
let collected = match collect {
Some(path) if result.is_ok() => ctx.get(path).map(Cow::into_owned),
_ => None,
};
let (overlay, changes, errors) = ctx.settle();
Invoked {
result,
overlay,
changes,
errors,
collected,
elapsed: None,
}
}
}
pub(crate) fn keep_records(
ids: TaskIds<'_>,
now: chrono::DateTime<chrono::Utc>,
now_text: &mut Option<Arc<str>>,
out: &mut Vec<ErrorRecord>,
records: Vec<ErrorRecord>,
element_index: Option<usize>,
) {
if records.is_empty() {
return;
}
let now = now_text.get_or_insert_with(|| Arc::from(now.to_rfc3339()));
for mut record in records {
record.stamp(ids.workflow, Some(ids.task), now, element_index);
out.push(record);
}
}
pub(crate) struct Settled {
pub(crate) result: CallResult,
pub(crate) collected: Option<OwnedDataValue>,
pub(crate) elapsed: Option<Duration>,
pub(crate) errors_before: usize,
pub(crate) marked: bool,
}
pub(crate) fn apply_writes(
rc: &mut RunContext<'_, '_>,
ok: bool,
overlay: Overlay,
changes: Vec<Change>,
) -> Vec<Change> {
if ok {
overlay.apply(rc);
changes
} else {
Vec::new()
}
}
pub(crate) fn settle(
rc: &mut RunContext<'_, '_>,
env: &mut RunEnv<'_, '_>,
task: u32,
invoked: Invoked,
element_index: Option<usize>,
) -> Settled {
let Invoked {
result,
overlay,
changes,
errors,
collected,
elapsed,
} = invoked;
let errors_before = env.errors.len();
let ids = env.ids(task);
keep_records(
ids,
env.now,
env.now_text,
env.errors,
errors,
element_index,
);
env.changes = apply_writes(rc, result.is_ok(), overlay, changes);
let marked = replaces_a_root(&env.changes);
Settled {
result,
collected,
elapsed,
errors_before,
marked,
}
}
pub(crate) async fn call_host<'p>(
rc: &mut RunContext<'_, 'p>,
env: &mut RunEnv<'_, 'p>,
wf: &'p CompiledWorkflow,
site: &'p CallSite,
bound: &'p dyn BoundCall,
) -> Flow {
let meta = &wf.tasks[site.task as usize];
let guard = match env.budget.charge_dispatch(DispatchKind::HostTask {
workflow_id: &wf.id,
task_id: &meta.id,
function: &meta.function,
}) {
Ok(guard) => guard,
Err(e) => {
let errors_before = env.errors.len();
let e = e.with_site(env.task_site(SiteKind::Task, site.task, meta.path.clone()));
let task_id = env.ids(site.task).task;
let decision = policy::forced_abort(task_id, e);
let done = Completed::new(env, site.task, None, None, errors_before, false);
return record::record(rc, env, wf, done, decision);
}
};
let started = env.watch.timed.then(record::now);
let invoked = {
let reader = rc.reader();
let (start, layers) = call_start(env, reader, wf, site);
invoke(start, bound, layers, None, env.limits.timers).await
};
drop(guard);
let elapsed = started.map(record::since);
let Settled {
result,
errors_before,
marked,
..
} = settle(rc, env, site.task, invoked, None);
let task_id = env.ids(site.task).task;
let decision = match result {
CallResult::CutShort(stop, error) => policy::cut_short(task_id, stop, &error),
CallResult::Returned(Err(mut e)) => {
if e.site().is_none() {
e = e.with_site(env.task_site(SiteKind::Task, site.task, meta.path.clone()));
}
if *e.kind() == ErrorKind::Budget && env.budget.is_run_limited() {
policy::forced_abort(task_id, e)
} else {
policy::decide(site.policy, task_id, Err(e))
}
}
CallResult::Returned(Ok(outcome)) => policy::decide(site.policy, task_id, Ok(outcome)),
};
let done = Completed::new(env, site.task, None, elapsed, errors_before, marked);
record::record(rc, env, wf, done, decision)
}
fn replaces_a_root(changes: &[Change]) -> bool {
changes.iter().any(|c| c.replaced)
}