Concurrency
Flux-Lang has two concurrency nodes. parallel fans out work and waits for every branch. race
takes the first successful branch and cancels the rest. Both keep the run trace deterministic, and
every operation in every branch still crosses the safety envelope.
parallel — concurrent fan-out
Each branch is introduced by a branch name arm; the branch body's last expression becomes the
value bound to that name after the join:
parallel
branch readme
readme = read("README.md")
branch todos
todos = grep(glob: "*.rs", pattern: "TODO")
report = fmt("""readme: {readme}
todos: {todos}""")
After the block, readme and todos are ordinary bound symbols.
Deterministic output. Branches execute concurrently, but each writes to a buffering sink; after the join, results and events are merged in declaration order. Output never interleaves, so the run trace of a parallel block reads the same way every time.
Failure. When a branch fails, the completed branches' buffered output and steps are still merged — a deterministic prefix — before the error propagates.
Scoping and constraints (enforced by the analyzer):
- Branch names must be unique.
returninside a branch is rejected — bind in the branch, return after the join.- Two branches binding the same symbol (including inner binds) are rejected: cross-branch binds would race.
- A symbol bound inside a branch body (other than the branch's own result) is not visible outside that branch.
A parallel with one branch is valid and degenerates to a sequential bind.
race — first success wins
race starts all branches together and completes as soon as one branch succeeds. The header
reads race <duration> [-> result], followed by the same branch arms as parallel:
race 5s -> result
branch fast
bash("fast-path.sh")
branch slow
bash("slow-path.sh")
Semantics:
- First success wins and its result binds to
bind. A failing branch does not win, but it does not abort the race either — the others keep running. timeout_msis required. If the deadline expires before any branch succeeds, the node errors with a timeout.- All branches failed is a joined branch error — reported distinctly from a timeout, so you can tell "everything broke" from "nothing was fast enough".
- Losing branches stay on the books. Their already-dispatched steps remain in the step
count and the trace — audit parity with the event log, and an enclosing
budgetcounts them. - Branch names must be unique.
Choosing between them
| You want | Use |
|---|---|
| All results, combined afterwards | parallel |
| The fastest of several equivalent paths | race |
| Alternatives tried in order, not concurrently | fallback — see Control flow |
Concurrency does not weaken the envelope: a branch cannot dispatch anything the same plan could not dispatch sequentially, and approvals still gate risky steps.
Related docs
- Control flow — sequential alternatives and list iteration.
- Reliability & guard rails — timeouts, retries, and failure handling.
- Execution model — cancellation and error behavior.