Skip to main content

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.
  • return inside 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_ms is 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 budget counts them.
  • Branch names must be unique.

Choosing between them

You wantUse
All results, combined afterwardsparallel
The fastest of several equivalent pathsrace
Alternatives tried in order, not concurrentlyfallback — 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.