-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Problem
RuntimeOrchestrationContext.createTimer() uses Date.now() when converting a relative seconds value to an absolute Date for the timer fire-at time:
// packages/durabletask-js/src/worker/runtime-orchestration-context.ts, line 297
if (!(fireAt instanceof Date)) {
fireAt = new Date(Date.now() + fireAt * 1000);
}Date.now() is explicitly listed as a non-deterministic call that must not be used in orchestrator code (see .github/copilot-instructions.md). The SDK provides context.currentUtcDateTime for this purpose, which returns the deterministic orchestration time set by OrchestratorStarted events.
Root Cause
The createTimer method was implemented with Date.now() as a convenience shorthand, but this violates the fundamental determinism rule that all orchestrator code must follow. During replay, Date.now() returns the current wall clock time (which could be minutes or hours after the original execution), producing a different timer fire-at value than the original execution.
While the replay mechanism currently tolerates this (the timer action is validated by ID and type only, not by fire-at time), it violates the SDK's documented contract and is inconsistent with how other Durable Task SDKs handle relative timer delays.
Proposed Fix
Replace Date.now() with this._currentUtcDatetime.getTime() so the timer fire-at is computed deterministically from the orchestration time.
Impact
Severity: Low-Medium. The timer fire-at is computed from a non-deterministic source, which is a determinism contract violation. In practice, the replay mechanism tolerates the different value because it only validates the action type and ID. However, this could become a breaking issue if the sidecar ever validates action content, and it is inconsistent with cross-SDK behavior.