Skip to content

[copilot-finds] Bug: handleExecutionResumed iterates _suspendedEvents while modifying it, causing infinite loop #202

@github-actions

Description

@github-actions

Problem

OrchestrationExecutor.handleExecutionResumed() in packages/durabletask-js/src/worker/orchestration-executor.ts iterates _suspendedEvents using for..of while processEvent() can push new entries onto the same array during iteration.

If the suspended-event buffer contains an ExecutionSuspended event (e.g. from a double-suspend sequence), processing it sets _isSuspended = true. Any subsequent suspendable event in the buffer is then re-pushed onto the same array being iterated, and JavaScript's for..of on arrays iterates dynamically-added elements — creating an infinite loop that hangs the worker process.

Before the fix:

// orchestration-executor.ts — handleExecutionResumed
this._isSuspended = false;
for (const e of this._suspendedEvents) {   // ← iterates the live array
  await this.processEvent(ctx, e);          // ← can push back to the same array
}
this._suspendedEvents = [];                 // ← never reached during infinite loop

Additionally, isSuspendable() in worker/index.ts does not exclude EXECUTIONSUSPENDED, allowing suspend events to be buffered. It also uses loose equality (== -1) instead of strict equality (=== -1).

Root Cause

  1. for..of on a JavaScript array uses an iterator that checks length dynamically. Pushing to the array during iteration causes the new elements to be iterated, enabling the infinite loop.
  2. isSuspendable() does not exclude EXECUTIONSUSPENDED events, so a second suspend event received while already suspended gets buffered rather than processed immediately (as a no-op).

Proposed Fix

  1. Snapshot the buffer before iterating: Assign _suspendedEvents to a local variable and replace it with an empty array before the loop. Events buffered during processing are correctly held for the next Resume.
  2. Exclude EXECUTIONSUSPENDED from isSuspendable() so suspend events are never buffered (defense-in-depth).
  3. Use strict equality (===) in the indexOf check.

Impact

  • Severity: High — can hang the worker process indefinitely, causing the orchestration to never complete.
  • Affected scenarios: Any orchestration that receives duplicate or back-to-back ExecutionSuspended events from the sidecar, or during history replay of such sequences.
  • Likelihood: Low in normal operation (the sidecar typically does not send consecutive suspend events), but the code should be defensive against it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    copilot-findsFindings from daily automated code review agent

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions