Repository navigation
Add deterministic async/await orchestrators across all three Python packages - #293
Draft
andystaples wants to merge 18 commits into
Draft
andystaples wants to merge 18 commits into
andystaples wants to merge 18 commits into
Conversation
Make durable tasks awaitable, drive native coroutines through the existing replay engine, and preserve generator compatibility. Include provider smoke coverage, prototype documentation, and coordinated-release notes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
6 tasks
6 tasks
* feat(functions): add opt-in Unicode JSON serialization Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54e13dcd-4b0d-4c0b-ab5b-bbb958fd5792 * chore(functions): adopt released Unicode codec API Require azure-functions 2.4.0b1 and exercise its released opt-in codec directly. Align documentation and samples with the available shared-library API. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54e13dcd-4b0d-4c0b-ab5b-bbb958fd5792 --------- Co-authored-by: Bernd Verst <beverst@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 54e13dcd-4b0d-4c0b-ab5b-bbb958fd5792
This was referenced Oct 20, 2026
Give each replay dispatch explicit ownership of its orchestrator generator: close it once in an outer finally after the response is built, so cleanup in finally blocks runs deterministically and cannot alter the returned actions or custom status. Ordinary teardown failures are logged without replacing dispatch results or abandonment. Async bridges such as Pydantic AI otherwise retain AnyIO task scopes, event loops, and orchestration contexts indefinitely. Also relevant to the async/await work in #293 and #294. Fixes #299 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35371373-86cf-4897-bf7c-460fa3c88d1f
…s CancelledError (#303) Catch asyncio.CancelledError raised by orchestrator generator teardown alongside ordinary cleanup exceptions, so a cancelled async bridge cannot replace an already-built dispatch response or a version-rejection abandonment. SystemExit and KeyboardInterrupt still propagate. Document the per-dispatch finalizer contract in docs/supported-patterns.md and clarify the three Unreleased changelog entries. Closes #301 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35371373-86cf-4897-bf7c-460fa3c88d1f
Fixes #298 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c95a41ba-3f18-427d-8a6c-a7438e7ec782
* fix: complete permanent payload failures and release entity locks Fixes #297 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845 * fix: preserve historical entity release order during replay Retain legacy shared-list ordering while tracking pending lock release obligations independently. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845 * test: explain handled entity failure in replay regression Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845 * fix: keep normal unlock emission separate from terminal cleanup Preserve pending-call legacy release and subsequent action IDs while retaining missing obligations for permanent payload failure snapshots. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845 * test: make payload failure regression return explicit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845 --------- Copilot-Session: 064f1284-f637-4ba9-8406-8c094ce9f845
Closes #222. - Adds ask_hub_names: list[str] | None = None to OrchestrationQuery. - Serializes ask_hub_names into InstanceQuery.taskHubNames as epeated google.protobuf.StringValue in �uild_query_instances_req(). - Updates unit tests covering ask_hub_names serialization independently, combined with instance_id_prefix, and verifying positional argument order preservation. - Documents the addition in CHANGELOG.md under Unreleased.
* fix(worker): recover unexpected orchestration dispatch exits Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * fix(worker): preserve recovery signals and stacked lock-release paths Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * test: simplify dispatch recovery parameter IDs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * test: preserve post-call release order in teardown failures Regression for the pending replay-compatible #306 dependency update; not published until the verified replacement base is integrated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * test: verify scheduler-visible teardown failure over local gRPC Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * test: cover compatible release order for pending entity calls Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * fix(worker): recover output serialization exits before completion Keep terminal callbacks non-reentrant while restoring failure conversion after incomplete output serialization. Treat serialization StopIteration as failure, not generator completion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 * fix(worker): retain pending releases for unexpected terminal failures Reuse the existing failed-result conversion after the full pre-close snapshot; preserve historical normal timeout actions and original failure details. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1 --------- Copilot-Session: 974213a5-9101-41b4-9bde-aa86a72f25e1
…309) * fix(entities): implement __hash__ and __repr__ for EntityInstanceId * fix(entities): enforce immutability on EntityInstanceId for hash stability * fix(entities): preserve subclass type when pickling
Bumps the github-actions group with 4 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action), [actions/setup-node](https://github.com/actions/setup-node) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `github/codeql-action/init` from 4.38.2 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@2892aa5...6f2ac81) Updates `github/codeql-action/analyze` from 4.38.2 to 4.39.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@2892aa5...6f2ac81) Updates `actions/setup-node` from 7.0.0 to 7.1.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@8207627...949feb2) Updates `actions/upload-artifact` from 7.0.1 to 7.0.2 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@043fb46...cf430e0) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: 7.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.38.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/init dependency-version: 4.38.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…sonDataConverter (#310) * fix(serialization): support set and frozenset generic containers in JsonDataConverter * fix(serialization): deterministic set ordering, opt-in set reconstruction, and pyright typing - Preserve replay compatibility by gating generic set reconstruction behind enable_generic_sets=False default on JsonDataConverter - Ensure deterministic set serialization with fallback sort key on recursive encoded values, qualname, and repr - Retain primitive set element annotations in type discovery when set reconstruction is enabled - Resolve strict pyright errors and update tests * fix(serialization): stabilize nested set ordering * fix(serialization): simplify set serialization by emitting sorted canonical JSON strings
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve generator dispatch recovery and snapshots; resume coroutine branches per history completion, order cancellation wakeups, and define the bounded async contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Handle closure tasks created during cleanup, retaining warnings for ordinary errors and terminal recovery for unexpected BaseException exits. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise TestModel parallel tools and recorded failures without a trampoline, retain adapter serialization and replay-stable metadata, and cover Windows Python versions in CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| timer = ctx.create_timer(timedelta(days=1)) | ||
|
|
||
| async def waiter() -> None: | ||
| await timer |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| if not future.done(): | ||
| try: | ||
| future.get_coro().close() | ||
| except BaseException as error: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the coordinated async authoring contract discussed in #294 across
durabletask,durabletask.azuremanaged, andazure-functions-durable. Existing synchronous generator orchestrators remain supported. This stays one draft PR and one customer-facing release unit; it must not be merged as a partial feature.Shared runtime and supported contract
async deforchestrators and nested helpers await existing durable activities, timers, external events, sub-orchestrations, combinators, and supported entity operations. Generator/coroutine parents and children interoperate. Functions supports native two-argument and compatibility single-argument entry points.AbstractEventLoopsupplies native asyncio Task/Future semantics and FIFO scheduling on top of the existing history/retry/payload/entity engine, not a separate provider runtime. Each history completion resumes its ready branches immediately, rather than waiting for a whole batch.asyncio.gather, local tasks/futures, ready callbacks, andsleep(0)are supported.loop.time()uses the durable clock;call_at/call_latercreate durable timers, includingasyncio.sleep,wait_for, and Python 3.11+timeout/timeout_at.TaskGroup, and the actual AnyIO asyncio task-group/cancel-scope/shielded-cleanup/fail_afterpaths, use native task semantics. Task-group cancellation wakeups are sorted by task creation rather than object hashes.gatherfails fast without cancelling siblings; durablewhen_allretains wait-all behavior.TaskCancelledError; scheduler termination remains a client operation.The full supported contract and migration guidance are in
docs/async-orchestrators.md.Lifecycle and current-main integration
The branch integrates current main, including the dispatch-finalization/exceptional-exit behavior from #300, #303, #306 and #307. The response and entity-release obligations are captured before teardown. Local tasks are cancelled/drained, nested cooperative finalizers and async context managers run, and tracked async generators are closed. New durable work is rejected during teardown. Ordinary cleanup errors and unobserved background-task errors are reported without changing the decided response; unexpected
BaseExceptionexits retain the existing failure/recovery policy. Cleanup is bounded, and one cleanup failure does not hide other task errors.Tests exercise suspension, termination, continue-as-new, history failure, cancellation, action ordering, retries, long/tiny timers, shared waiters, outer event-loop restoration, payload behavior, and generator regressions. Cleanup at a dispatch boundary is explicitly not workflow cancellation or a durable compensation boundary.
Real framework proof
Checked Tamir's integration branch; its head still matches reviewed snapshot
8e9ae67a492171b7839a94f72ff44238f03d597a. The SDK proof uses an actual Pydantic AIAgent.run, deterministicTestModel, and its parallel-tool AnyIO path, covering success, recorded tool failure, and repeated identical-history replay without paid model calls. The agent runs directly inside the orchestrator without an adapter event loop, trampoline, or batch-wide join.The small proof adapter deliberately retains ordinary model/tool activities, registration, JSON codecs, stable IDs/deadline clock, and canonicalization of volatile framework message metadata. This is not a production Pydantic adapter or blanket compatibility promise. The test-only framework dependency is conditional on Python 3.11+; no framework dependency was added to the shipped SDK.
Deliberate exclusions and limitations
is_replayingis not result recording.asyncio.waitandas_completedwere explicitly investigated and are not deterministic supported composition APIs because of unordered sets/hash-dependent task creation and tie ordering. Successful execution alone is not support.orchestrator_generator_wrapperhandles direct durable awaits, not simulation of the asyncio scheduler.Validation
Final implementation head:
8c050a7c1ca11b0372151592151f8776796e4485. Local evidence (overlapping suites, not an additive total):All final-head CI checks pass, including:
Deployed Azure cloud-service validation has not been performed. Reported skips remain visible in the test results; green CI is not a blanket claim of arbitrary library or migration compatibility.
Release coordination
All three affected changelogs and entry-point documentation are updated. Package versions and dependency minimums are unchanged. The PR remains draft for review as requested. Nothing has been merged or released. Once the full contract is approved, publish core first through the coordinated release process and update provider minimums/versions in the dedicated release PR so resolvers cannot select an incompatible core.