Parallel Steps
Zart supports running multiple steps in parallel via zart::schedule() and zart::wait(). Each scheduled step becomes an independent task in the scheduler — it can run on a different worker node and is fully durable.
Basic Usage
Section titled “Basic Usage”use zart::prelude::*;use zart::{zart_durable, zart_step};
#[zart_step("send-email")]async fn send_email(email: String) -> Result<(), StepError> { send_welcome_email(&email).await}
#[zart_step("setup-billing")]async fn setup_billing(email: String) -> Result<String, StepError> { setup_stripe_customer(&email).await}
#[zart_step("provision-resources")]async fn provision_resources(user_id: String) -> Result<String, StepError> { provision_aws(&user_id).await}
#[zart_step("notify-ready")]async fn notify_ready(email: String) -> Result<(), StepError> { send_ready_notification(&email).await}
#[zart_durable("onboarding")]async fn onboarding(data: OnboardingData) -> Result<(), TaskError> { // Schedule three parallel steps let h1 = zart::schedule(send_email(data.email.clone())); let h2 = zart::schedule(setup_billing(data.email.clone())); let h3 = zart::schedule(provision_resources(data.user_id.clone()));
// Durably wait for all three to complete let _results = zart::wait(vec![h1, h2, h3]).await?;
// This line only runs after all three succeed notify_ready(data.email).await?;
Ok(())}How It Works
Section titled “How It Works”Each zart::schedule() call:
- Inserts a new child task into
zart_executionswith a reference back to the parent. - Returns a
StepHandle— a token identifying that scheduled task. - The child task can be picked up by any available worker, potentially running on a different machine.
zart::wait(handles):
- Marks the parent execution as waiting in the database.
- Returns the parent to the queue — no worker thread is blocked.
- When all child tasks complete, the scheduler re-queues the parent.
- On resumption,
waitreturns the collected results.
Getting Results
Section titled “Getting Results”Each scheduled step returns its output through the zart::wait result vector, in the same order as the handles:
let h1 = zart::schedule(send_email(data.email.clone()));let h2 = zart::schedule(setup_billing(data.email.clone()));
let results = zart::wait(vec![h1, h2]).await?;// results[0] = output of send_email// results[1] = output of setup_billing (the new customer_id String)Error Handling
Section titled “Error Handling”If any child task fails (returns Err), zart::wait returns that error. Other in-flight tasks are not cancelled — they run to completion (or failure), but their results are not returned to the parent.
let results = zart::wait(vec![h1, h2, h3]).await;match results { Ok(outputs) => { /* all succeeded */ } Err(e) => { // At least one child failed // The parent can retry — already-completed children won't re-run return Err(e); }}Dynamic Fan-Out
Section titled “Dynamic Fan-Out”Schedule a variable number of parallel tasks based on runtime data:
#[zart_step("process-{region}")]async fn process_region(region: String) -> Result<RegionResult, StepError> { run_regional_job(®ion).await}
#[zart_durable("multi-region")]async fn multi_region(data: MultiRegionJob) -> Result<Vec<RegionResult>, TaskError> { // Schedule one task per region dynamically let handles: Vec<_> = data.regions .iter() .map(|region| zart::schedule(process_region(region.clone()))) .collect();
let raw_results = zart::wait(handles).await?; let results: Vec<RegionResult> = raw_results .into_iter() .map(|r| r.map_err(|e| TaskError::StepFailed { step: "dynamic".into(), source: e })) .collect::<Result<_, _>>()?;
Ok(results)}Nesting Parallel Steps
Section titled “Nesting Parallel Steps”Parallel steps can themselves schedule further parallel steps, creating a tree of concurrent work. Each level is fully durable and can span multiple worker nodes.
Sequential vs Parallel
Section titled “Sequential vs Parallel”Sequential (.await) | Parallel (schedule + wait) | |
|---|---|---|
| Execution | In-process, one after another | Potentially cross-worker, concurrent |
| Durability | Yes | Yes |
| Result storage | Yes | Yes |
| Best for | Steps that depend on each other | Independent, parallelizable work |
// Sequential — each step waits for the previous one to finishlet a = step_a().await?;let b = step_b().await?;let c = step_c().await?;
// Parallel — all three are registered first, then collected togetherlet h1 = zart::schedule(step_a());let h2 = zart::schedule(step_b());let h3 = zart::schedule(step_c());let results = zart::wait(vec![h1, h2, h3]).await?;See it in action
Section titled “See it in action”The Parallel Steps example runs three concurrent service health checks and aggregates the results — a complete, runnable demonstration of zart::schedule and zart::wait.