Skip to content

Add rewind support#296

Merged
YunchuWang merged 3 commits into
mainfrom
yunchuwang-add-rewind-support
Jul 13, 2026
Merged

Add rewind support#296
YunchuWang merged 3 commits into
mainfrom
yunchuwang-add-rewind-support

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Summary

Adds rewind support to the durabletask-js SDK, reaching parity with the Python reference implementation in microsoft/durabletask-python#121. Rewind resets a failed orchestration to a runnable state so that failed activities re-run while previously successful results are preserved.

Before this change, durabletask-js had only the client entry point (client.rewindInstance(instanceId, reason)); the worker-side history rewrite and the in-memory backend support were missing, so rewind was a no-op end-to-end.

Architecture

Rewind spans three layers, split exactly as in the Python reference:

  1. Client (already existed): rewindInstance(instanceId, reason) sends a RewindInstanceRequest over gRPC.
  2. Backend (in-memory, new here): validates the instance is FAILED, appends an ExecutionRewoundEvent, flips status to RUNNING, and re-dispatches the work item. On terminal completion it now appends an executionCompleted "bookend" event — this is the precondition the worker uses to detect a rewind.
  3. Worker (new here): when it sees executionRewound in the new events and executionCompleted in the committed history, it short-circuits normal replay and calls buildRewindResult(), which rewrites history — dropping taskFailed, the matching failed taskScheduled, subOrchestrationInstanceFailed, and executionCompleted; assigning a fresh execution id to executionStarted; keeping successful results and the executionRewound event — wrapped in a RewindOrchestrationAction. The backend then applies the new history and recursively rewinds failed sub-orchestrations.

Note: The history rewrite in buildRewindResult is a backend contract — the in-memory and DTS backends rely on it. Any change to how history is rewritten must be mirrored in those backends.

Changes

  • Proto: re-synced orchestrator_service.proto from durabletask-protobuf main and regenerated the JS/TS stubs. Adds RewindOrchestrationAction and OrchestratorAction.rewindOrchestration = 9. The re-sync also pulls in benign, purely-additive upstream drift: ActivityRequest.tags, PurgeInstanceFilter.timeout, and [deprecated=true] annotations on OrchestratorResponse.isPartial/chunkIndex. No fields were renumbered or removed.
  • Worker: new worker/rewind.ts (buildRewindResult); orchestration-executor.ts gains the rewind short-circuit and an informational EXECUTIONREWOUND no-op case.
  • In-memory backend: rewindInstance, prepareRewind, and processRewindOrchestrationAction (including recursive and purged sub-orchestration handling), the terminal executionCompleted bookend, and a failureDetails presence guard. New helpers newExecutionCompletedEvent / newExecutionRewoundEvent; new rewindOrchestration() test-client method.

Tests

  • 14 new unit cases for buildRewindResult (test/build-rewind-result.spec.ts).
  • 8 new in-memory e2e cases (test/rewind-e2e.spec.ts): failed activity, preserves successful results, not-found, non-failed instance, sub-orchestration, purged sub-orchestration, no reason, and rewind-twice.
  • Full suite: 1119 passing / 63 suites. build:core and lint are green.

Known limitation

Consistent with the Core/.NET/Python implementations: timer events emitted between retry attempts of an activity scheduled with a RetryPolicy are not removed during the history rewrite, so rewinding a retry-policy activity is not currently supported (documented in worker/rewind.ts).

Reference

Ports andystaples/durabletask-python@d5052d7 (worker._build_rewind_result, testing/in_memory_backend.py, internal/helpers.new_execution_completed_event, and the corresponding unit + e2e tests).

Implement full orchestration rewind in the core SDK so a failed
orchestration can be replayed from the point of failure. The client-side
`rewindInstance` already existed but was a no-op without the worker and
backend pieces; this wires up the whole path.

Backends do not compute the rewound history themselves: they append an
`ExecutionRewoundEvent`, flip the instance to RUNNING, and re-dispatch.
The worker detects the rewind, rewrites the history (dropping failed
task/sub-orchestration results and the terminal `executionCompleted`),
and returns a `RewindOrchestrationAction { newHistory }` that the
backend applies.

Changes:
- proto: re-sync orchestrator_service.proto from durabletask-protobuf main,
  adding `RewindOrchestrationAction` and `OrchestratorAction.rewindOrchestration = 9`;
  regenerate the JS/TS stubs.
- worker: new `buildRewindResult` (worker/rewind.ts) plus an executor
  trigger before replay and an informational `EXECUTIONREWOUND` no-op case.
- in-memory backend: `rewindInstance` / `prepareRewind` /
  `processRewindOrchestrationAction` (incl. recursive sub-orchestration
  rewind and purged-sub re-creation), an `executionCompleted` bookend on
  terminal completion, an explicit failureDetails presence guard, and a
  `rewindOrchestration` test-client method.
- helpers: `newExecutionCompletedEvent` and `newExecutionRewoundEvent`.
- tests: 14 buildRewindResult unit cases and 8 in-memory e2e cases.

Known limitation (shared with the .NET/Python/Go SDKs): timer events between
RetryPolicy retry attempts are not removed, so rewinding a retry-policy-wrapped
activity is unsupported.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 13, 2026 15:40
Comment thread packages/durabletask-js/src/proto/orchestrator_service_pb.js Dismissed

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

Adds end-to-end rewind support to the Durable Task JS SDK (worker + in-memory backend) so failed orchestrations can be reset to runnable state while preserving prior successful results, matching the durabletask-python reference behavior.

Changes:

  • Added worker-side history rewrite logic (buildRewindResult) and executor short-circuiting when a rewind is detected.
  • Implemented in-memory backend rewind handling, including terminal executionCompleted bookend and processing of RewindOrchestrationAction (with recursive sub-orchestration rewind).
  • Updated protocol/proto stubs for RewindOrchestrationAction and related upstream additive proto changes; added unit + e2e tests for rewind.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/durabletask-js/src/worker/rewind.ts Implements history rewrite to drop failed activity/sub-orchestration results and produce a RewindOrchestrationAction.
packages/durabletask-js/src/worker/orchestration-executor.ts Detects rewind work items and short-circuits to buildRewindResult; adds no-op handling for executionRewound events.
packages/durabletask-js/src/testing/in-memory-backend.ts Adds in-memory backend rewind entrypoint, applies rewind action, appends terminal executionCompleted bookend, and supports recursive sub-orchestration rewind.
packages/durabletask-js/src/testing/test-client.ts Exposes rewind operation from the test client to the in-memory backend.
packages/durabletask-js/src/utils/pb-helper.util.ts Adds helpers for creating executionCompleted and executionRewound history events.
packages/durabletask-js/test/build-rewind-result.spec.ts Unit tests for the worker-side history rewrite behavior.
packages/durabletask-js/test/rewind-e2e.spec.ts In-memory backend e2e tests covering rewind scenarios, including sub-orchestrations and purged sub-orchestrations.
internal/protocol/protos/orchestrator_service.proto Proto updates including RewindOrchestrationAction and other additive upstream drift.
packages/durabletask-js/src/proto/orchestrator_service_pb.js Regenerated JS proto stubs reflecting proto updates.
packages/durabletask-js/src/proto/orchestrator_service_pb.d.ts Regenerated TS declarations reflecting proto updates.
internal/protocol/SOURCE_COMMIT Updates the recorded upstream protocol source commit.

Comment thread packages/durabletask-js/src/testing/in-memory-backend.ts
@YunchuWang YunchuWang changed the title Add rewind support (parity with durabletask-python #121) Add rewind support Jul 13, 2026
@YunchuWang YunchuWang changed the title Add rewind support Add rewind support Jul 13, 2026
Comment thread packages/durabletask-js/src/utils/pb-helper.util.ts
YunchuWang and others added 2 commits July 13, 2026 13:32
The rewind e2e cases that exercise real backend rewind (fail -> rewind -> succeed, plus the running/terminated negative cases) were it.skip'd because earlier DTS emulator builds returned UNIMPLEMENTED. The current emulator (mcr.microsoft.com/dts/dts-emulator:latest) supports rewind and delegates the history rewrite to the SDK worker, so they now pass end-to-end against buildRewindResult.

- Un-skip the 4 backend-support cases and add per-test timeouts (the two positive cases run two 30s completion waits each, exceeding jest's 5s default).

- Fix waitingOrchestrator to the generator style (async function* + yield ctx.waitForExternalEvent), matching every other e2e test. The plain async/await form does not suspend in durabletask-js, so the orchestrator completed immediately instead of staying Running, preventing the terminated case from reaching TERMINATED.

All 7 rewind e2e cases pass against the emulator.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mirror processCreateSubOrchestrationAction: the purged-sub re-creation path in
processRewindOrchestrationAction now passes parentInstance metadata (name,
instanceId, taskScheduledId) so the re-created sub keeps its parent link and can
route its completion back to the parent orchestration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@YunchuWang YunchuWang merged commit 486ad4e into main Jul 13, 2026
28 checks passed
@YunchuWang YunchuWang deleted the yunchuwang-add-rewind-support branch July 13, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants