Skip to content

feat(durabletask): implement timer origin and backwards-compatible optional timers#1733

Merged
dapr-bot merged 2 commits intodapr:masterfrom
javier-aliaga:feat/implement-timer-origin
Apr 25, 2026
Merged

feat(durabletask): implement timer origin and backwards-compatible optional timers#1733
dapr-bot merged 2 commits intodapr:masterfrom
javier-aliaga:feat/implement-timer-origin

Conversation

@javier-aliaga
Copy link
Copy Markdown
Contributor

@javier-aliaga javier-aliaga commented Apr 24, 2026

Description

Annotate every CreateTimerAction the SDK emits with the new origin oneof from durabletask-protobuf (CreateTimer / ExternalEvent / ActivityRetry / ChildWorkflowRetry), and emit a synthetic "optional" timer for waitForExternalEvent calls with a negative (indefinite) timeout.

  • CreateTimer: TimerOriginCreateTimer on every user-facing timer.
  • waitForExternalEvent: TimerOriginExternalEvent{name} on timeout timers; indefinite timeouts emit a single optional timer with fireAt = sentinel 9999-12-31T23:59:59.999999999Z so the backend has a record of the wait.
  • Activity retry delays: TimerOriginActivityRetry carrying a stable taskExecutionId shared with the ScheduleTaskAction.
  • Child-workflow retry delays: TimerOriginChildWorkflowRetry carrying the first child's instanceId.

Add dropOptionalExternalEventTimerAt(atId) and wire it into handleTaskScheduled, handleSubOrchestrationCreated, and handleTimerCreated so pre-upgrade histories (which lack the synthetic timer emitted by indefinite waitForExternalEvent calls) replay without non-determinism errors. Mirrors the shape of durabletask-go's task/orchestrator.go.

Includes 13 unit tests covering origin assignment across all four call sites and the optional-timer replay-compatibility machinery.

Issue reference

We strive to have all PR being opened based on an issue, where the problem or feature have been discussed prior to implementation.

Please reference the issue this PR will close: #1729

Checklist

Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • Code compiles correctly
  • Created/updated tests
  • Extended the documentation

…tional timers

Annotate every CreateTimerAction the SDK emits with the new origin oneof
from durabletask-protobuf (CreateTimer / ExternalEvent / ActivityRetry /
ChildWorkflowRetry), and emit a synthetic "optional" timer for
waitForExternalEvent calls with a negative (indefinite) timeout.

- CreateTimer: TimerOriginCreateTimer on every user-facing timer.
- waitForExternalEvent: TimerOriginExternalEvent{name} on timeout timers;
  indefinite timeouts emit a single optional timer with fireAt = sentinel
  9999-12-31T23:59:59.999999999Z so the backend has a record of the wait.
- Activity retry delays: TimerOriginActivityRetry carrying a stable
  taskExecutionId shared with the ScheduleTaskAction.
- Child-workflow retry delays: TimerOriginChildWorkflowRetry carrying
  the first child's instanceId.

Add dropOptionalExternalEventTimerAt(atId) and wire it into
handleTaskScheduled, handleSubOrchestrationCreated, and handleTimerCreated
so pre-upgrade histories (which lack the synthetic timer emitted by
indefinite waitForExternalEvent calls) replay without non-determinism
errors. Mirrors the shape of durabletask-go's task/orchestrator.go.

Includes 13 unit tests covering origin assignment across all four call
sites and the optional-timer replay-compatibility machinery.

Fixes dapr#1729

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Implements timer-origin annotation across all SDK-emitted timers and adds a backwards-compatible “optional” (synthetic) timer for indefinite waitForExternalEvent calls to improve backend observability and replay compatibility (fixes #1729).

Changes:

  • Annotate CreateTimerAction with the new protobuf origin oneof for user timers, external-event timeouts, activity retries, and child-workflow retries.
  • Emit a synthetic sentinel timer for indefinite external-event waits and add replay-compat “drop-and-shift” logic for pre-upgrade histories.
  • Add a comprehensive JUnit test suite covering origin assignment and replay compatibility scenarios.

Reviewed changes

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

File Description
durabletask-client/src/main/java/io/dapr/durabletask/TaskOrchestrationExecutor.java Adds origin setters for timers, synthetic optional external-event timer emission, and replay-compat drop/shift handling.
durabletask-client/src/test/java/io/dapr/durabletask/TimerOriginTest.java Adds unit tests validating origin assignment and optional-timer replay compatibility behavior.
Comments suppressed due to low confidence (1)

durabletask-client/src/test/java/io/dapr/durabletask/TimerOriginTest.java:1

  • Several pre-patch replay tests hardcode taskExecutionId values (e.g., exec-A, exec-B) even though those IDs are not the focus of the test and may become validated more strictly in the future. To make these tests more robust, consider omitting the field (pass null to taskScheduled) or discover the emitted taskExecutionId in a phase-1 run (as done in Tests 3/4) when the ID matters.
/*

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread durabletask-client/src/test/java/io/dapr/durabletask/TimerOriginTest.java Outdated
… drop unused taskExecutionId in tests

- Extract isSentinelFireAt(Timestamp) helper; use Timestamp.equals()
  instead of manual seconds/nanos comparison in both recognition
  predicates. Proto-generated equals() compares fields exactly, so
  nanosecond fidelity is preserved.
- Tests 10 and 13 exercise drop-and-shift replay-compat and do not
  verify taskExecutionId; pass null instead of hardcoded "exec-A" /
  "exec-B" filler values so the tests stay robust if the SDK ever adds
  stricter validation on that field.

Signed-off-by: Javier Aliaga <javier@diagrid.io>
@javier-aliaga javier-aliaga marked this pull request as ready for review April 24, 2026 15:01
@javier-aliaga javier-aliaga requested review from a team as code owners April 24, 2026 15:01
Copy link
Copy Markdown
Contributor

@siri-varma siri-varma left a comment

Choose a reason for hiding this comment

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

LGTM

@dapr-bot dapr-bot merged commit 4396215 into dapr:master Apr 25, 2026
12 checks passed
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.76%. Comparing base (4ec9b89) to head (f91fb27).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1733   +/-   ##
=========================================
  Coverage     76.76%   76.76%           
  Complexity     2260     2260           
=========================================
  Files           241      241           
  Lines          7066     7066           
  Branches        740      740           
=========================================
  Hits           5424     5424           
  Misses         1279     1279           
  Partials        363      363           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Implement timer origin

4 participants