Bug
A long timer that loses a when_any race can schedule another chunk after the orchestration has completed when its current chunk fires later in the same history batch.
Confirmed on Python commit ec9af14. This corresponds to the long-timer portion of microsoft/durabletask-go#164, not its Go-specific forced coroutine unload/defer mechanism.
Reproduction
def orchestrator(ctx, _):
event = ctx.wait_for_external_event("done")
timer = ctx.create_timer(timedelta(days=10))
yield task.when_any([event, timer])
return "done"
With the default three-day timer chunk, replay the initial execution and timer creation. Process a new batch containing eventRaised("done", true) followed by the first chunk's timerFired.
Actual: executor emits [completeOrchestration, createTimer]; the new chunk is emitted after completion.
Expected: no further long-timer chunk is scheduled after terminal completion.
Relevant code and scope
durabletask/worker.py: _OrchestrationExecutor.process_event, especially the timerFired path and _handle_timer_fired result handling. Shared by core, Azure Managed, and this repository's Azure Functions provider.
Fix lifecycle gating precisely and preserve ordinary multi-chunk progression, cancellation, replay, retries, and legitimate pre-completion work. Add regression cases for relevant terminal states. Do not port Go coroutine-unwind machinery: Python's execution model differs.
Add applicable Unreleased changelog entries. No emulator/live-service dispatch claim is made; the invalid post-completion action output was reproduced locally.
Bug
A long timer that loses a
when_anyrace can schedule another chunk after the orchestration has completed when its current chunk fires later in the same history batch.Confirmed on Python commit ec9af14. This corresponds to the long-timer portion of microsoft/durabletask-go#164, not its Go-specific forced coroutine unload/defer mechanism.
Reproduction
With the default three-day timer chunk, replay the initial execution and timer creation. Process a new batch containing
eventRaised("done", true)followed by the first chunk'stimerFired.Actual: executor emits
[completeOrchestration, createTimer]; the new chunk is emitted after completion.Expected: no further long-timer chunk is scheduled after terminal completion.
Relevant code and scope
durabletask/worker.py:_OrchestrationExecutor.process_event, especially thetimerFiredpath and_handle_timer_firedresult handling. Shared by core, Azure Managed, and this repository's Azure Functions provider.Fix lifecycle gating precisely and preserve ordinary multi-chunk progression, cancellation, replay, retries, and legitimate pre-completion work. Add regression cases for relevant terminal states. Do not port Go coroutine-unwind machinery: Python's execution model differs.
Add applicable Unreleased changelog entries. No emulator/live-service dispatch claim is made; the invalid post-completion action output was reproduced locally.