Skip to content

fix: cancel pending timers on continue-as-new in InMemoryOrchestrationBackend#328

Open
YunchuWang wants to merge 1 commit into
mainfrom
copilot-finds/bug/fix-continue-as-new-timer-leak
Open

fix: cancel pending timers on continue-as-new in InMemoryOrchestrationBackend#328
YunchuWang wants to merge 1 commit into
mainfrom
copilot-finds/bug/fix-continue-as-new-timer-leak

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Fixes #207.

InMemoryOrchestrationBackend continue-as-new did not cancel pending timers, leaking stale timer events into the new iteration. This cancels pending timers on continue-as-new.


Opens the existing fix branch copilot-finds/bug/fix-continue-as-new-timer-leak as a PR (automated triage of active [copilot-finds] issues that had a ready fix branch but no open PR).

…nBackend

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

Because the new iteration resets the sequence counter, stale timer IDs can
collide with task IDs in the new iteration, potentially completing the wrong
task or causing unexpected-event warnings.

This commit:
- Adds per-instance timer tracking (instanceTimers map)
- Cancels all pending timers for an instance on continue-as-new
- Fixes carryover event ordering so OrchestratorStarted and
  ExecutionStarted come before carryover events, matching real sidecar
  behavior
- Adds tests for both the timer cancellation and event ordering fixes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 23, 2026 16:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a correctness issue in the in-memory testing backend where continueAsNew() did not cancel timers scheduled in the prior iteration, allowing stale TimerFired events to leak into the new execution. It also adjusts carryover external event ordering to align with real sidecar behavior.

Changes:

  • Track timer handles per orchestration instance and cancel them when processing continue-as-new.
  • Reorder continue-as-new “carryover events” so OrchestratorStarted / ExecutionStarted precede carried-over events.
  • Add regression tests covering timer cancellation and carryover event ordering scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/durabletask-js/src/testing/in-memory-backend.ts Adds per-instance timer tracking/cancellation and fixes carryover event ordering for continue-as-new.
packages/durabletask-js/test/in-memory-backend.spec.ts Adds tests intended to validate timer cancellation and carryover ordering after continue-as-new.

Comment on lines +388 to +399
const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, input: number): any {
if (input === 1) {
// First iteration: create a long timer, then immediately continue-as-new.
// The timer should be cancelled and NOT fire into the second iteration.
ctx.createTimer(60); // 60-second timer — should be cancelled
ctx.continueAsNew(2, false);
} else {
// Second iteration: create a short timer and wait for it.
// If the stale timer from iteration 1 leaks, it could complete
// the wrong task or cause unexpected events.
yield ctx.createTimer(0.05); // 50ms timer
iteration2TimerFired = true;
Comment on lines +442 to +445
expect(state).toBeDefined();
expect(state?.runtimeStatus).toEqual(OrchestrationStatus.COMPLETED);
expect(state?.serializedOutput).toEqual(JSON.stringify("carried-over"));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants