Performance [P2]: Reuse sandbox registration transport on retries - #206
Merged
berndverst merged 3 commits intoJul 27, 2026
Merged
Conversation
The sandbox worker registration loop built a brand new SandboxActivitiesGrpcTransport on every attempt, so each transient failure paid for a fresh gRPC channel, stub, interceptor, and Entra token acquisition. That amplified startup and recovery latency exactly when the service was already degraded. The loop now creates the transport once and reuses it across retriable stream failures, replacing it only when a failure leaves the channel unusable (a shut-down channel or a non-gRPC failure such as an error raised while the channel is being created). The transport is closed once when the loop exits, so channels are not leaked. The retry backoff schedule, stop-signal responsiveness, thread join semantics, log messages, and exception types on terminal failure are unchanged. Fixes #188 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9a361145-b7cc-437d-895d-0bea3d19bf91
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves the Azure Managed sandbox worker’s resiliency and startup/recovery performance by reusing the sandbox registration gRPC transport across retriable registration failures, avoiding repeated channel + interceptor + token-acquisition setup during retry loops.
Changes:
- Reworked
SandboxWorker._run_sandbox_registration_loop()to lazily create and then reuse a singleSandboxActivitiesGrpcTransportacross retriable stream failures, closing it once when the loop exits. - Added
_requires_new_registration_transport()to decide when a failure implies the underlying channel/transport is no longer reusable (e.g., channel shutdown) and should be recreated. - Added targeted tests validating reuse vs rebuild behavior, correct backoff progression, and prompt loop shutdown on stop signal.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| durabletask-azuremanaged/durabletask/azuremanaged/preview/sandboxes/worker.py | Reuses registration transport across retries; adds transport-close helper and “reuse vs rebuild” predicate. |
| tests/durabletask-azuremanaged/test_sandboxes_extension.py | Adds regression tests covering transport reuse, rebuild-on-channel-shutdown, stop responsiveness, and predicate behavior. |
…box-registration-transport
grpc.StatusCode members are enum singletons, so `is not` and `!=` behave identically here. Every other grpc.StatusCode comparison in the repository uses ==/!= (durabletask/client.py, durabletask/worker.py, durabletask/internal/grpc_resiliency.py, durabletask/extensions/history_export/client.py), so match that style instead of introducing the only identity comparison. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9a361145-b7cc-437d-895d-0bea3d19bf91
andystaples
approved these changes
Jul 27, 2026
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
SandboxWorker._run_sandbox_registration_loop()constructed a brand newSandboxActivitiesGrpcTransporton every retry attempt. Each construction created anew gRPC channel, a new stub, and a new
DTSDefaultClientInterceptorImpl, which inturn acquires an Entra token in its constructor. A transient registration failure
therefore cost a full channel setup plus a token acquisition per retry, amplifying
startup and recovery latency exactly when the service was already degraded.
The registration loop now builds the transport once and reuses it across retriable
stream failures, closing it once when the loop exits so channels are not leaked.
What changed
durabletask-azuremanaged/durabletask/azuremanaged/preview/sandboxes/worker.pyattempts, including the reconnect path where the server ends the registration
stream while the worker is still running.
_requires_new_registration_transport(), which replaces the transport onlywhen a failure leaves the channel unusable: a channel that has been shut down
(
CANCELLEDwithChannel closed!) or any non-gRPC failure, such as an errorraised while the channel itself is being created. gRPC channels reconnect on their
own after transient RPC failures, so those reuse the existing transport.
_close_sandbox_registration_transport(), which closes the transportdefensively and is invoked from a
finallyblock covering the whole loop.Everything else is unchanged: the exponential backoff schedule, responsiveness to the
stop signal, thread join semantics, log messages, and the exception types and terminal
handling for non-retriable failures.
No public API, signature, import path, or observable behavior changes, so per the
repository changelog policy this internal performance fix does not get a changelog
entry.
Verification
tests/durabletask-azuremanaged/test_sandboxes_extension.py:test_sandbox_registration_reuses_one_transport_across_retriable_failuresuses afake transport factory to assert that 3 retriable
UNAVAILABLEfailures result inexactly 1 transport construction and 1 close, and that the backoff windows
are still
1.0, 2.0, 4.0.test_sandbox_registration_rebuilds_transport_after_channel_shutdownasserts ashut-down channel still produces a replacement transport, and that the discarded
transport is closed.
test_sandbox_registration_loop_exits_promptly_on_stop_signalruns the loop on athread and asserts it exits promptly once the stop event is set, with the transport
closed exactly once.
test_sandbox_registration_recreates_transport_only_when_channel_is_unusablecovers the reuse-versus-replace predicate directly.
confirming it guards the regression.
pytest tests/durabletask-azuremanaged/test_sandboxes_extension.py tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py -q-> 46 passed (42 before, plus the 4 new tests).pytest tests/durabletask -q --ignore-glob="*_e2e.py"-> 681 passed, 7 skipped.flake8clean on both changed files.pyrightreports 0 errors for the changed source file under the repository's strictconfiguration.
Out of scope
The issue's cold-start note about sharing channel or authentication state with the
parent
DurableTaskSchedulerWorkeris intentionally not addressed here; it overlapswith the deferred token acquisition work tracked in #187.
Fixes #188