Skip to content

Performance [P3]: Lazily load sandbox runtime API exports - #208

Merged
berndverst merged 5 commits into
mainfrom
berndverst-lazy-sandbox-exports
Jul 27, 2026
Merged

Performance [P3]: Lazily load sandbox runtime API exports#208
berndverst merged 5 commits into
mainfrom
berndverst-lazy-sandbox-exports

Conversation

@berndverst

@berndverst berndverst commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Importing durabletask.azuremanaged.preview.sandboxes eagerly imported both the sandbox
client and the sandbox worker. The worker module pulls in ManagedIdentityCredential from
azure-identity plus the core worker graph, so merely touching the sandboxes namespace — even
to declare a worker profile or reference a type — paid that entire cost at process startup.

This replaces the eager re-exports in the package initializer with PEP 562 lazy exports: a
module-level __getattr__ resolves each public name from its defining submodule on first
access and caches it in the module globals. TYPE_CHECKING imports keep type checkers, IDEs,
and pyright --strict fully aware of the names.

Backwards compatibility is preserved exactly:

  • Every exported name and its import path is unchanged.
  • __all__ is byte-for-byte identical, so from ... import * still exports the same six names.
  • dir() still lists the public names (via a module-level __dir__).
  • Submodules (client, helpers, worker, worker_profiles, ...) remain reachable as package
    attributes, matching the attribute binding the eager imports used to produce as a side effect.
  • Unknown attributes still raise a normal AttributeError.

Before / after

Measured on the same machine with durabletask and durabletask.azuremanaged installed
editable, median of 9 runs each, HEAD~1 vs HEAD:

Metric Before (eager) After (lazy) Change
-X importtime cumulative for durabletask.azuremanaged.preview.sandboxes 965.7 ms 376.0 ms -61.1%
Wall clock, full python -c "import ...sandboxes" process 1144.1 ms 532.5 ms -53.5%
len(sys.modules) after import 548 296 -252 modules

Raw -X importtime tail lines:

before: import time:       852 |    965731 | durabletask.azuremanaged.preview.sandboxes
after:  import time:       845 |    375998 | durabletask.azuremanaged.preview.sandboxes

azure.identity alone accounts for ~890 ms of cumulative import time on this machine, and it is
no longer loaded unless SandboxWorker is actually used:

before: worker loaded True  | azure.identity loaded True
after:  worker loaded False | azure.identity loaded False

Interaction with #196

#196 makes the analogous lazy-export change to the core durabletask/__init__.py. Because the
sandboxes package is reached through durabletask, there are two independent doors to the
worker graph, and closing only one leaves the other open. Measured here as a full 2x2 on
import durabletask.azuremanaged.preview.sandboxes — module counts are deterministic, times are
the median of 7 runs of the summed -X importtime self-times (which include the ~87 ms
interpreter floor):

Configuration Modules Median import time
Neither (main) 548 1079.9 ms
#196 alone 548 — no change 1037.6 ms
#197 alone (this PR) 296 452.7 ms
Both 84 92.6 ms

Two honest conclusions from that table:

The two PRs are therefore complementary rather than additive, and they touch disjoint files
(verified: no path overlap between the branches). These figures were reproduced independently in
this worktree; the module counts match the #196 author's measurements exactly.

Verification

  • New tests in tests/durabletask-azuremanaged/test_sandboxes_extension.py. Because that
    module imports the sandbox worker at module scope, the lazy-import assertions run in a
    subprocess:
    • test_sandbox_package_defers_worker_runtime_imports — asserts ...sandboxes.worker,
      ...sandboxes.client, ...sandboxes.transport, and azure.identity are absent from
      sys.modules after importing the package, then asserts __all__, dir(), that every
      exported name resolves to the same object as a direct import, and that submodule attributes
      still work.
    • test_sandbox_package_star_import_matches_all — asserts from ... import * yields exactly
      the __all__ names and does not leak the lazy-import plumbing.
    • test_sandbox_package_raises_attribute_error_for_unknown_attributes — asserts a proper
      AttributeError and correct hasattr behavior.
    • Confirmed these fail against the pre-change initializer (the lazy-import probe reports
      client, transport, worker, and azure.identity as eagerly imported).
  • Provider unit tests: test_sandboxes_extension.py + test_durabletask_grpc_interceptor.py
    — 45 passed (42 before this change, plus the 3 new tests).
  • Core unit tests: pytest tests/durabletask --ignore-glob="*_e2e.py" — 681 passed,
    7 skipped, no regressions against the recorded baseline.
  • flake8: clean on both changed Python files. The TYPE_CHECKING imports are resolved
    properly rather than suppressed — pyflakes treats names listed in __all__ as used, so no
    noqa was needed.
  • pyright (repo pyrightconfig.json, strict): zero errors in the changed file and zero
    anywhere under durabletask-azuremanaged/durabletask.
  • pymarkdownlnt: the changelog addition introduces no new findings.
  • autopep8: --diff is empty for both changed files.

Fixes #197

Importing durabletask.azuremanaged.preview.sandboxes eagerly imported both
the sandbox client and the sandbox worker. The worker module pulls in
ManagedIdentityCredential from azure-identity plus the core worker graph,
so merely touching the sandboxes namespace paid that cost at startup.

Resolve the package's public names through a PEP 562 module __getattr__
instead, with TYPE_CHECKING imports so type checkers and IDEs still see
them. __all__, dir(), star-imports, submodule attributes, and every
import path are unchanged.

Fixes #197

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9df32033-0d12-4be2-ad8c-3fea7890376f
Copilot AI review requested due to automatic review settings July 27, 2026 06:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces import-time overhead for the Azure Managed sandbox preview surface by switching durabletask.azuremanaged.preview.sandboxes from eager re-exports to PEP 562 lazy exports, so heavy dependencies (notably azure-identity via the sandbox worker runtime) are only imported when actually needed.

Changes:

  • Replaced eager symbol imports in ...preview.sandboxes.__init__ with lazy __getattr__ + caching and a __dir__ implementation to preserve discoverability.
  • Added subprocess-based tests to validate that importing the package does not eagerly import the worker/runtime modules while preserving __all__, dir(), star-import behavior, and attribute errors.
  • Documented the user-visible import-time improvement in the Azure Managed changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/durabletask-azuremanaged/test_sandboxes_extension.py Adds isolated subprocess tests validating lazy-import behavior and API compatibility for the sandboxes package.
durabletask-azuremanaged/durabletask/azuremanaged/preview/sandboxes/init.py Implements PEP 562 lazy exports for sandbox preview public names while preserving __all__ and submodule attribute access.
durabletask-azuremanaged/CHANGELOG.md Notes the user-facing reduction in eager imports and improved import performance under ## Unreleased.

Comment thread tests/durabletask-azuremanaged/test_sandboxes_extension.py

@andystaples andystaples left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs discussion, see my review on #209

Bernd Verst and others added 2 commits July 27, 2026 09:40
…ox-exports

# Conflicts:
#	durabletask-azuremanaged/CHANGELOG.md
The subprocess-isolated lazy-import probes ran with no timeout, so a hung
child (import deadlock or environment issue) would stall CI indefinitely.
Pass an explicit 60s bound - generous for an import-time probe - and turn
subprocess.TimeoutExpired into a readable pytest failure that reports the
bound and any partial output, rather than letting it propagate raw.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9df32033-0d12-4be2-ad8c-3fea7890376f
Copilot AI review requested due to automatic review settings July 27, 2026 16:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

…ox-exports

# Conflicts:
#	tests/durabletask-azuremanaged/test_sandboxes_extension.py
Copilot AI review requested due to automatic review settings July 27, 2026 17:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

…ox-exports

# Conflicts:
#	tests/durabletask-azuremanaged/test_sandboxes_extension.py
Copilot AI review requested due to automatic review settings July 27, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@berndverst
berndverst merged commit 6a52e1d into main Jul 27, 2026
25 checks passed
@berndverst
berndverst deleted the berndverst-lazy-sandbox-exports branch July 27, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance [P3]: Lazily load sandbox runtime API exports

3 participants