Preserve trailing external events after continue-as-new - #281
andystaples wants to merge 4 commits into
Conversation
Gate ordinary external-event delivery on the existing terminal context boundary while retaining entity routing and raw carryover buffering. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Track buffered event arrival indexes independently of task IDs and retain per-name FIFO consumption and raw payloads. Add replay, selective consumption, cancellation, converter, and save-events regression coverage. Fixes #277 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The implementation appears consistent, but the draft explicitly requires human agreement on cross-SDK event-delivery semantics.
Review effort: Balanced
Findings: None
What changed in this PR
Preserves trailing external events after terminal orchestration states, especially continue_as_new.
Changes:
- Gates delivery to abandoned event waits after completion.
- Documents event-consumption semantics.
- Adds comprehensive core/provider regression coverage and changelogs.
| File | Description |
|---|---|
durabletask/worker.py |
Buffers events after completion. |
durabletask/task.py |
Documents continue-as-new semantics. |
tests/durabletask/test_orchestration_executor.py |
Covers lifecycle and carryover cases. |
tests/azure-functions-durable/test_worker_compat.py |
Verifies serialized provider responses. |
CHANGELOG.md |
Records the core fix. |
durabletask-azuremanaged/CHANGELOG.md |
Records managed-provider impact. |
azure-functions-durable/CHANGELOG.md |
Records Functions-provider impact. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Guard timer continuation at the executor callback boundary while preserving pre-terminal actions and trailing history processing. Cover terminal states, replay, native/chunked retries, cancellation, and Functions compatibility. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve both event-delivery and global-order regression coverage, and verify carried events are consumed by the next execution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| save_events: bool, replay_completion: bool): | ||
| waits: list[task.CancellableTask[Any]] = [] | ||
|
|
||
| def orchestrator(ctx: task.OrchestrationContext, input: str | None): |
Bernd Verst (berndverst)
left a comment
There was a problem hiding this comment.
Re-reviewed the updated implementation and main integration. No actionable findings. The completion-state guard preserves trailing external events without restoring events already delivered to live waits, while retaining existing Python continue-as-new semantics, cancellation behavior, and entity-response routing.
The merge-base changed after approval.
Fixes #274
Summary
Preserve external events arriving after
continue_as_new()instead of delivering them to abandoned waits from the completed execution. This fixes the timer-winningwhen_anyscenario where trailing events in the same batch were lost despitesave_events=True.The event-delivery fix follows the finalized-execution approach in the merged microsoft/durabletask-go#163: undelivered events are buffered for carryover, while events already delivered to live waits are not restored.
Behavior
save_events=True, undelivered events are carried into the next execution once, preserving global arrival order across event names and retaining raw/null payloads. Withsave_events=False, they are omitted from carryover.when_anyrace. Losing waits are not automatically canceled.continue_as_new()makes the context terminal at the call; Go finalizes when the orchestrator function returns. Python orchestrators should return immediately after callingcontinue_as_new(). This change does not interrupt synchronous code, buffered-event consumption, orfinallycleanup, and does not alter exception-after-continue behavior.continue_as_new()does not override Python's already-terminal status.Main integration and coverage
Merged the latest
mainand resolved changelog and regression-test conflicts without dropping either set of changes. The PR now includes the global carryover-order fix from #278 and the terminal timer-callback guard from #280.Regression coverage includes both save modes, timer/event winners, replay and new-event processing, multiple same-name waits, explicit cancellation, case-insensitive names, raw/null payloads, already-delivered versus undelivered events, terminal lifecycle behavior, entity responses and unlock retention, and serialized Azure Functions worker responses. Added combined coverage for mixed-name trailing events and their consumption in the next execution.
Local validation: 407 affected executor/provider unit tests passed; changed core source passes Pyright; source and test lint passes for all three packages. External-service E2E tests were not run locally; GitHub CI runs against the updated head.
Purge discrimination (#276) remains out of scope.
Release coordination
Unreleased notes are included in all three package changelogs. No package versions or dependency minimums change here. Release the corrected core first, then coordinate provider minimum dependencies in a dedicated release PR.