feat(durabletask): implement timer origin and backwards-compatible optional timers#1733
Conversation
…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>
There was a problem hiding this comment.
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
CreateTimerActionwith the new protobuforiginoneof 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
taskExecutionIdvalues (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 (passnulltotaskScheduled) or discover the emittedtaskExecutionIdin 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.
… 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>
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
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.
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: