-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Problem
WhenAllTask and WhenAnyTask do not call this._parent?.onChildCompleted(this) when they complete. This means nested composite tasks hang forever — the outer composite is never notified that the inner one completed.
For example, whenAny([whenAll([a, b]), whenAll([c, d])]) — a natural "wait for the first group to finish" pattern — will never resolve, even after all tasks in one group complete.
Files: packages/durabletask-js/src/task/when-all-task.ts (lines 28-46), packages/durabletask-js/src/task/when-any-task.ts (lines 18-23)
Root Cause
CompletableTask (the base for leaf tasks like activities, timers, sub-orchestrations) correctly calls this._parent?.onChildCompleted(this) in both complete() and fail(). However, composite tasks (WhenAllTask and WhenAnyTask) set this._isComplete = true without notifying their own parent.
When a leaf task completes, the notification chain goes: leafTask.complete() → innerComposite.onChildCompleted() → inner composite marks itself complete → stops here. The outer composite never learns that the inner one finished.
The orchestration executor calls ctx.resume() after each event, which checks _previousTask.isComplete. Since the outer composite is never notified, isComplete remains false, and the generator is never resumed.
Proposed Fix
Add this._parent?.onChildCompleted(this) to both WhenAllTask.onChildCompleted() (both the success and fail-fast paths) and WhenAnyTask.onChildCompleted(), matching the pattern established by CompletableTask.
Impact
Severity: High — Any orchestration using nested whenAll/whenAny patterns will hang indefinitely. While simple flat composite usage is unaffected, the nested pattern (whenAny([whenAll(...), whenAll(...)])) is a natural way to express "wait for the first group of tasks to complete" and would be expected to work.