Skip to content

fix: preserve trailing events after continue-as-new - #386

Merged
wangbill (YunchuWang) merged 1 commit into
mainfrom
yunchuwang-rollover-event-loss
Oct 9, 2026
Merged

wangbill (YunchuWang) merged 1 commit into
mainfrom
yunchuwang-rollover-event-loss

Conversation

@YunchuWang

@YunchuWang wangbill (YunchuWang) commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Preserve external messages delivered after continueAsNew(..., true) in the same work item instead of completing an abandoned listener in the previous execution.

What changed?

  • Gate generic external-event listener completion on the orchestration still being active, reusing the existing carryover buffer.
  • Add public client/worker regressions with real backend batches and supplementary executor action/error controls.
  • Document trailing-event retention, consumed-event behavior, and the explicit saveEvents=false opt-out in the API, README, and changelog.

Why is this change needed?

  • An ordinary whenAny([rotate, message]) race could request continue-as-new when rotate arrived, then lose a trailing message by completing the old listener without resuming the completed generator or buffering the payload.
  • The new execution now receives that unconsumed payload exactly once. Messages consumed before rotation, case-insensitive matching, per-name FIFO, falsy payloads, and explicit malformed-payload failures remain covered.

Issues / work items

  • Resolves: N/A; no issue was created for this standalone fix.
  • Related: accepted post-continue-as-new event retention in Azure/durabletask#251.

Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to CHANGELOG.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR (not requested; maintainer decision pending)
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact: Corrects previously lost trailing messages when event retention is requested; no public API or protocol changes.
      • Migration guidance: Already-discarded events cannot be recovered by upgrading the worker. Backport and release decisions remain for maintainers.

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot.
  • AI-assisted areas/files: The executor listener guard, external-event and executor regressions, API JSDoc, README, changelog, and this description.
  • What you changed after AI output: No human edits yet; human review and attestations are pending.

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed. Before the production change, the new tests produced three expected missing-carryover failures with eight passing controls. After the fix, the affected tests and adjacent controls passed: 53 tests across three suites. The owning core suite passed all 1,726 tests across 86 suites.
  • The test commands below show the exact runner, paths, selectors, and scope; only reporting-only --json --outputFile=<local artifact> flags are omitted to avoid publishing internal paths.
  • npm run test:unit --workspace @microsoft/durabletask-js -- --runTestsByPath test\external-event-routing.spec.ts test\orchestration_executor.spec.ts --testNamePattern "continue-as-new trailing events|routes trailing events|malformed trailing event" — failed as expected before the fix: 3 failures, 8 passes.
  • npm run test:unit --workspace @microsoft/durabletask-js -- --runTestsByPath test\external-event-routing.spec.ts test\orchestration_executor.spec.ts test\in-memory-backend.spec.ts --testNamePattern "External event name routing|trailing event|continue-as-new|EVENTRAISED|EVENTSENT" — passed: 53 tests, 118 intentionally skipped by the selector.
  • npm run test:unit --workspace @microsoft/durabletask-js — passed: 86 suites, 1,726 tests, no skipped tests.
  • npm run build:core — passed, including core TypeScript compilation.
  • npm exec --no -- eslint packages\durabletask-js\src\worker\orchestration-executor.ts packages\durabletask-js\src\task\context\orchestration-context.ts packages\durabletask-js\test\external-event-routing.spec.ts packages\durabletask-js\test\orchestration_executor.spec.ts — passed.
  • npm exec --no -- prettier --check --end-of-line auto CHANGELOG.md packages\durabletask-js\src\task\context\orchestration-context.ts packages\durabletask-js\test\external-event-routing.spec.ts — passed. Added ranges in the remaining changed files were checked separately against existing Prettier; unrelated baseline style debt and CRLF endings were preserved.
  • git diff --check origin/main...HEAD — passed.
  • Not run locally: all-workspace aggregates, Azure-managed sample lanes, Go-sidecar/DTS/emulator/cloud E2E, and hosted CI. The Functions-host workflow does not match these changed paths.

Manual validation (only if runtime/behavior changed)

  • Environment (OS, Node.js version, components): Agent-run local runtime exercise, not a human manual attestation. Windows, Node.js v24.14.0, freshly compiled core SDK, public TestOrchestrationClient and TestOrchestrationWorker, real repository in-memory backend and executor.
  • Steps + observed results:
    1. Start the ordinary rotation/message race, wait for committed running state, stop the worker, enqueue both public events, and start a replacement worker. Existing executor logs confirmed one ordered two-event batch.
    2. Verify a new execution ID and committed input/history. All three fixed primary repetitions completed with the original payload, with one carried message and no rescue or fresh message required.
    3. Run no-old-listener, separately delivered message, mixed-case/falsy FIFO plus later replay, consumed-before-rotation, and both listener variants of saveEvents=false controls. All nine runtime cases passed; workers and clients were stopped, backends reset, and no runtime timers remained.
  • Evidence (optional): Committed public regressions and local runtime logs/JSON preserve exact batch boundaries, payloads, execution IDs, histories, source/compiled hashes, and cleanup. Scratch artifacts are not committed.

Notes for reviewers

  • The entity-response branch remains unchanged and still precedes generic external-event routing. No backend, Functions adapter, protocol, generated bindings, manifests, or dependencies changed.
  • This is a local real-core reproduction and correction, not a claim about a production incident or a passed service-backed deployment test.

Buffer external events arriving after continue-as-new instead of completing abandoned listeners. Add real-batch public regressions and document opt-out and consumed-event behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 03e10c9a-ff91-410a-bf7c-84b1ca56e0f6
Copilot AI balanced review requested due to automatic review settings October 9, 2026 15:49

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.

🟢 Approval recommended

The focused fix is consistent with carryover semantics and has comprehensive regression coverage.

0 open findings

What changed in this PR

Preserves trailing external events across continueAsNew(..., true) while maintaining existing routing semantics.

Changes:

  • Prevents abandoned listeners from consuming post-continuation events.
  • Adds executor and in-memory backend regression coverage.
  • Documents carryover behavior and opt-out semantics.
File Description
README.md Documents trailing-event retention.
CHANGELOG.md Records the bug fix.
orchestration-context.ts Expands API documentation.
orchestration-executor.ts Buffers events after continuation.
orchestration_executor.spec.ts Adds executor-level regressions.
external-event-routing.spec.ts Adds public client/worker integration coverage.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@YunchuWang
wangbill (YunchuWang) merged commit b7f8bd6 into main Oct 9, 2026
31 checks passed
@YunchuWang
wangbill (YunchuWang) deleted the yunchuwang-rollover-event-loss branch October 9, 2026 18:15
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.

4 participants