Bug
When a timer wins when_any, the orchestrator calls continue_as_new(..., save_events=True) and returns, and matching external events occur later in the same history batch, those events can be lost instead of carried into the new execution.
Confirmed on Python commit ec9af14. Equivalent Go fix: microsoft/durabletask-go#163.
Reproduction
def orchestrator(ctx, _):
event = ctx.wait_for_external_event("event")
timer = ctx.create_timer(timedelta(seconds=1))
yield task.when_any([event, timer])
ctx.continue_as_new(None, save_events=True)
Replay the initial execution and timer creation, then process a new batch containing timerFired, eventRaised("event", 1), eventRaised("event", 2).
Actual: carryover contains only 2. The abandoned losing wait consumes 1.
Expected: both trailing, undelivered events carry forward, once each. Events delivered to live waits before the execution finishes must not be resurrected.
Relevant code and scope
durabletask/worker.py: _OrchestrationExecutor.process_event external-event dispatch and _RuntimeOrchestrationContext completion/carryover handling. This is shared by Azure Managed and the Azure Functions provider in this repository.
Preserve save_events=False behavior, cancellation semantics, entity-event routing, replay, and existing Python continue-as-new semantics (Python marks the context complete at the call, unlike Go's deferred intent). Cover meaningful successful/failed/terminated lifecycle boundaries without blindly porting Go semantics.
Add focused regression coverage and applicable Unreleased changelog entries. Global ordering across different event names is a separate defect and should remain separately scoped.
Bug
When a timer wins
when_any, the orchestrator callscontinue_as_new(..., save_events=True)and returns, and matching external events occur later in the same history batch, those events can be lost instead of carried into the new execution.Confirmed on Python commit ec9af14. Equivalent Go fix: microsoft/durabletask-go#163.
Reproduction
Replay the initial execution and timer creation, then process a new batch containing
timerFired,eventRaised("event", 1),eventRaised("event", 2).Actual: carryover contains only
2. The abandoned losing wait consumes1.Expected: both trailing, undelivered events carry forward, once each. Events delivered to live waits before the execution finishes must not be resurrected.
Relevant code and scope
durabletask/worker.py:_OrchestrationExecutor.process_eventexternal-event dispatch and_RuntimeOrchestrationContextcompletion/carryover handling. This is shared by Azure Managed and the Azure Functions provider in this repository.Preserve
save_events=Falsebehavior, cancellation semantics, entity-event routing, replay, and existing Python continue-as-new semantics (Python marks the context complete at the call, unlike Go's deferred intent). Cover meaningful successful/failed/terminated lifecycle boundaries without blindly porting Go semantics.Add focused regression coverage and applicable Unreleased changelog entries. Global ordering across different event names is a separate defect and should remain separately scoped.