fix(executor): let a branch loop back and re-run an earlier node - #2184
fix(executor): let a branch loop back and re-run an earlier node#2184joelorzet wants to merge 1 commit into
Conversation
A workflow could already be drawn with an edge pointing back at an earlier node, but nothing ran past it. The back edge raised that node's incoming-edge count, so the convergence barrier held it waiting for an arrival only its own execution could produce, and the branch stalled in front of the loop entry. Auto-layout broke the same way from the other side: the extra in-degree kept the loop entry out of the column sweep, so it and everything behind it landed in the disconnected pile. Classify back edges once per run with a depth-first pass and keep them out of every map that answers "what has to happen before this node runs": the convergence barrier, orphan detection, skip propagation and the For Each body scan all read the forward DAG now. Routing still sees them, so a node wired back to an ancestor re-enters it, clears the loop body's traversal state, and runs the entry plus everything below it again. Nothing in a graph bounds how often that happens, so two caps do: 100 passes per loop, 1000 loop traversals per run. Hitting either fails the run with a message naming the loop, rather than returning whichever pass happened to be last. A back edge crossing a For Each body is refused up front, since the body runner's dispatcher would ignore it in silence.
PR Environment DeployedYour PR environment has been deployed! Environment Details:
Components:
The environment will be automatically cleaned up when this PR is closed or merged. |
|
Heads up, I broke this PR environment and I am rebuilding it now. KEEP-1258 repaired the ECR lifecycle policies, which had never expired anything. The staging
I am removing and re-adding The real fix is to stop PR builds from sharing a tag family with the staging branch builds, so PR Sorry for the noise. |
|
Correction to my note above. I could not rebuild the environment, and nothing was deleted. This branch conflicts with So, concretely:
Rebase or merge The underlying problem is mine: PR builds share the |
Problem
Drawing an edge from a downstream node back to an earlier one produced a workflow that stopped at the node before the loop entry. The engine treats any node with more than one incoming edge as a fan-in join and holds it until every branch arrives; a back edge counts as one of those branches, and it can only arrive after the node it points at has run. The join waits forever.
Auto-layout had the mirror of the same bug: the loop entry never cleared its in-degree during the column sweep, so it and everything downstream were placed as disconnected nodes stacked below the graph.
What changes
Back edges are classified once per run by a depth-first pass and kept apart from the forward DAG. Everything that reasons about what must happen before a node runs (convergence barrier, orphan detection, condition skip propagation, For Each body identification, layout columns) reads the forward edges only.
Routing keeps the back edges, so reaching one re-enters the loop entry: the loop body's traversal state is cleared and the entry plus every node below it runs again. Outputs are overwritten per pass, so templates and the run panel read the newest one.
Guardrails
Coverage
33 new unit tests over back-edge classification, the caps, body-state reset, layout, and a harness that drives the executor's dispatch shape end to end. Full unit suite passes (21,719 tests).