Problem/Motivation

A matching join decides which branches to wait for by evaluating the conditions on its incoming flows, in the view of the token arriving. The documentation lists three rules for that to work ("Wiring a conditional join correctly" in docs/joins-splits.md), and each is a way to get it wrong:

  1. The condition must be repeated on the join's incoming flow. An incoming flow left without one always holds, so the join waits for a branch that never started, and the instance stays stuck there.
  2. The variable the condition reads must be visible from every branch: instance-scoped, or token-scoped at or above the split. A value a branch sets for itself cannot be seen from the others.
  3. It must not change between the split and the join, or the join waits for the wrong branches.

The discriminator joins (threshold, quorum, timeout) already know which branches are still on their way without reading any condition: JoinCoordinator::findRivalTokens() finds the live tokens in the join's scope from which the join can still be reached, and getArcsStillReachable() gives the incoming flows they can come down. The matching join is the only one that still guesses from conditions.

Proposed resolution

Let the matching join wait for an incoming flow when a token has arrived on it, or when a live token can still come down it, the way the discriminator joins count the branches on their way:

  • An incoming flow nothing can reach any more is not waited for, whether or not it carries a condition. The join's incoming flows can then be left without conditions, and rules 1 to 3 above no longer apply to them.
  • An incoming flow whose condition is present and false stays out, as today, so a workflow that repeats the split's conditions on the join keeps working unchanged, and keeps rules 2 and 3 for those conditions.
  • The matching join still never cancels anything: the tokens on their way only make it wait. It must not count them as an early fire ($early_fire in evaluate()), which is what tears the rest down after a discriminator fire.
  • wait_all keeps waiting for every incoming flow, and is then the strict one: the step after it never runs on a partial result. A branch that never started, or was canceled, blocks it, and the stalled-instance recovery reports it, where matching goes on with what arrived. It also reads no tokens. The two joins, identical today when the incoming flows carry no condition, then differ only when a branch can no longer arrive, and the documentation says when to use which.

Cost: one query and one load of the instance's live tokens per arrival at a matching join, what the discriminator joins pay today. Reachability is already memoized per definition.

A known limit, shared with the inclusive gateway of BPMN: a token upstream in a loop that can come back to the join counts as on its way, so the join waits for it. Tokens are looked for in the join's scope only, as for the discriminators.

Remaining tasks

  • Use the tokens on their way for the matching join in JoinCoordinator::evaluate(), without counting them as an early fire.
  • Rewrite "Wiring a conditional join correctly" and "When to use matching" in docs/joins-splits.md.
  • Kernel tests: an inclusive split with incoming flows left without conditions fires once the started branches have arrived; with incoming flows left without conditions, the routing variable changed by a branch after the split does not change what the join waits for; a workflow repeating the split's conditions still works; a matching join never cancels a branch on its way; wait_all unchanged.

User interface changes

None. The join's description in the Join select may say it waits for the branches that can still arrive.

API changes

None.

Data model changes

None.

AI-Generated: Yes (Claude Code was used to help draft this issue summary. I reviewed it.)

Issue fork orchestra-3629950

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review

  • mably committed a32f44fc on 1.x
    feat: #3629950 Let a matching join wait for the branches still on their...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.