Parallel Execution
Ironflow supports running multiple steps in parallel within a workflow.
Using ctx.parallel()
Pass a list of step configurations to ctx.parallel(). All steps run concurrently and the method returns when all complete:
//! Demo workflow showcasing parallel execution and conditional branching.
use ironflow_engine::config::{ShellConfig, StepConfig};
use ironflow_engine::context::WorkflowContext;
use ironflow_engine::handler::{HandlerFuture, WorkflowHandler};
/// Simulated CI pipeline that demonstrates DAG features:
///
/// 1. **build** (sequential)
/// 2. **test-unit + test-integration + lint** (parallel)
/// 3. **deploy** or **notify-failure** (conditional branch on test results)
pub struct CiPipeline;
impl WorkflowHandler for CiPipeline {
fn name(&self) -> &str {
"ci-pipeline"
}
fn description(&self) -> &str {
"Simulated CI pipeline with parallel tests and conditional deploy. \
Demonstrates ctx.parallel() and native Rust if/else branching."
}
fn source_code(&self) -> Option<&str> {
Some(include_str!("ci_pipeline.rs"))
}
fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move {
// Step 1: Build
let build = ctx
.shell(
"build",
ShellConfig::new(
"echo 'Compiling project...' && sleep 0.2 && echo 'Build successful'",
),
)
.await?;
if !build.is_success() {
ctx.shell(
"notify-build-failure",
ShellConfig::new("echo 'BUILD FAILED - notifying team'"),
)
.await?;
return Ok(());
}
// Step 2: Parallel tests + lint
let results = ctx
.parallel(
vec![
(
"test-unit",
StepConfig::Shell(ShellConfig::new(
"echo 'Running unit tests...' && sleep 0.3 && echo '42 tests passed'",
)),
),
(
"test-integration",
StepConfig::Shell(ShellConfig::new(
"echo 'Running integration tests...' && sleep 0.5 && echo '12 tests passed'",
)),
),
(
"lint",
StepConfig::Shell(ShellConfig::new(
"echo 'Running linter...' && sleep 0.1 && echo 'No warnings'",
)),
),
],
true,
)
.await?;
// Step 3: Conditional deploy
let all_passed = results.iter().all(|r| r.output.is_success());
if all_passed {
ctx.shell(
"deploy",
ShellConfig::new("echo 'Deploying to production...' && sleep 0.2 && echo 'Deployed successfully'"),
)
.await?;
} else {
ctx.shell(
"notify-test-failure",
ShellConfig::new("echo 'TESTS FAILED - deployment skipped'"),
)
.await?;
}
Ok(())
})
}
}
How it works
- All steps in a
parallel()call start at the same time - The method returns a
Vec<ParallelResult>with outputs in the same order as the input - If
fail_fastistrue(the second argument), the remaining steps are cancelled when one fails - If
fail_fastisfalse, all steps run to completion regardless of individual failures
Conditional branching
Since workflows are Rust code, conditional logic is just if/else:
let results = ctx.parallel(steps, true).await?;
let all_passed = results.iter().all(|r| r.output.is_success());
if all_passed {
ctx.shell("deploy", ShellConfig::new("echo 'Deploying'")).await?;
} else {
ctx.shell("notify", ShellConfig::new("echo 'Tests failed'")).await?;
}
No special DSL for branching – Rust control flow works directly.