In RealWorkflowLoop#runWorkflowLoop, the order of operations after an output for the root workflow's runWorkflowLoop is:
output = select { .. }
doRender
output?.let { onOutput }
However, child workflows have their outputs immediately executed when the WorkflowAction is run.
- From
WorkflowNode#tick we call WorkflowNode#applyAction, but notice that we call output?.let(emitOutputToParent) immediately.
This difference in behavior manifests in the root workflow performing an extra render pass before the root workflow runner can receive an output and potentially end the workflow.
This extra render pass can cause a crash if the workflow does not expect to have render called after emitting an output (because the presumption is that the output stop the workflow from running).
To work around this, I can wrap my root workflow R in another workflow P that has 2 states: Running and CancelledAfterOutput. Initially, P will be in the Running state, and call renderChild on R. But after 1 output from R, P transitions to the CancelledAfterOutput state, which doesn't call renderChild on R. This "fixes" the errant extra render pass.