Skip to content

[copilot-finds] Bug: InMemoryOrchestrationBackend continue-as-new does not cancel pending timers, causing stale events in new iteration #207

@github-actions

Description

@github-actions

Problem

When an orchestration calls continueAsNew() in the InMemoryOrchestrationBackend, the instance state (history, status, input) is reset, but pending setTimeout handles from the previous iteration are not cancelled. These stale timers continue to fire and deliver TimerFired events into the new iteration.

File: packages/durabletask-js/src/testing/in-memory-backend.ts, processCompleteOrchestrationAction() (line ~475)

Root Cause

processCompleteOrchestrationAction() resets the instance state for continue-as-new but does not cancel any timers created during the previous iteration. The pendingTimers set tracks all timers globally, with no per-instance mapping, making it impossible to cancel just the timers belonging to the restarting instance.

Additionally, carryover events were placed before OrchestratorStarted and ExecutionStarted events, which is inconsistent with the real sidecar's event ordering.

Impact

Because the RuntimeOrchestrationContext resets its sequence counter on each new execution, stale timer IDs from the old iteration can collide with task IDs in the new iteration. This can cause:

  1. Wrong task completion — a stale TimerFired event with timer ID N could match a completely different pending task (activity, sub-orchestration, or new timer) in the new iteration that happens to have the same sequence number.
  2. Unexpected-event warnings — if no matching task exists, the executor logs a warning and silently drops the event. While not a crash, this adds noise and wastes processing.
  3. Test behavior divergence — the in-memory backend's behavior for continue-as-new does not match the real sidecar, which cancels old timers on instance restart.

Proposed Fix

  1. Add per-instance timer tracking (instanceTimers: Map<string, Set<TimerHandle>>)
  2. Cancel all pending timers for an instance when continue-as-new is processed
  3. Fix carryover event ordering so OrchestratorStarted and ExecutionStarted come before carryover events
  4. Add tests for timer cancellation and correct event ordering

Severity

Medium — Affects correctness of the in-memory testing backend for orchestrations that use both timers and continue-as-new. Could cause intermittent, hard-to-diagnose test failures.

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