Skip to content

Performance [P3]: Cache Azure managed SDK version lookup - #203

Merged
berndverst merged 4 commits into
mainfrom
berndverst-cache-azure-managed-sdk-version
Jul 27, 2026
Merged

Performance [P3]: Cache Azure managed SDK version lookup#203
berndverst merged 4 commits into
mainfrom
berndverst-cache-azure-managed-sdk-version

Conversation

@berndverst

Copy link
Copy Markdown
Member

Summary

Both the sync (DTSDefaultClientInterceptorImpl) and async
(DTSAsyncDefaultClientInterceptorImpl) interceptor constructors called
importlib.metadata.version('durabletask-azuremanaged') directly. That call
walks distribution metadata on disk, so the work was repeated on every
interceptor construction — i.e. on every client and worker construction.

This change resolves the version once in a module-level functools.lru_cached
helper (_get_sdk_version()) and reuses it for every interceptor.

Preserved exactly

  • The "unknown" fallback when the version cannot be determined (the helper
    keeps the original broad except Exception, which still covers
    importlib.metadata.PackageNotFoundError).
  • The user-agent string format, durabletask-python/{version}, verbatim.
  • The metadata key names and their order:
    taskhub, x-user-agent, and workerid (sync worker case only).

No public API signature changes, no renames, no removed symbols, no changed
import paths. The only observable difference is the removed repeat filesystem
work.

Note

No changelog entry was added. This is an internal performance change with no
user-visible behavior difference, which the repo guidelines say should not be
documented in the changelog.

Verification

Ran against a session-local virtualenv with both packages installed in
editable mode.

Effectiveness (the actual fix). A probe patched the module-level version
symbol and counted calls across three interceptor constructions (sync client,
sync worker, async):

Variant version() calls
Before (uncached) 3
After (cached) 1

Tests.

  • New TestSdkVersionCaching::test_version_resolved_once_across_interceptor_constructions
    asserts the lookup happens exactly once across three constructions, and pins
    the full metadata list (keys and order) for all three cases.
  • New TestSdkVersionCaching::test_unknown_fallback_when_version_cannot_be_determined
    asserts PackageNotFoundError still yields durabletask-python/unknown for
    both the sync and async interceptors.
  • Both new tests clear the cache in setUp/tearDown so no patched value
    leaks into other tests.
  • Existing test_worker_includes_workerid_header and
    test_user_agent_metadata_passed_in_request still pass unchanged.

Suites.

  • Provider unit tests (test_durabletask_grpc_interceptor.py,
    test_sandboxes_extension.py, test_azuremanaged_grpc_resiliency.py):
    47 passed.
  • Core unit tests (tests/durabletask, excluding *_e2e.py):
    681 passed, 7 skipped.
  • flake8 clean on both changed files.
  • pyright (repo pyrightconfig.json, strict): 441 errors before and after,
    none in the changed file — unchanged from baseline.

*_e2e.py suites and test_dts_activity_sequence.py need a live Durable Task
Scheduler endpoint and were not run.

Fixes #195

Both the sync and async DTS interceptor constructors called
importlib.metadata.version('durabletask-azuremanaged') directly, which walks
distribution metadata on disk. That work was repeated on every interceptor
construction, i.e. on every client and worker construction.

Resolve the version once in a module-level lru_cache'd helper and reuse it.
The "unknown" fallback, the user-agent string format, and the metadata key
names and their order are all unchanged.

Verified with a probe that the number of importlib.metadata.version() calls
across three interceptor constructions drops from 3 to 1.

Fixes #195

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e7499a54-f262-49a6-bf91-274af28543ae
Copilot AI review requested due to automatic review settings July 27, 2026 06:13

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 improves startup performance for the durabletask.azuremanaged provider by caching the importlib.metadata.version('durabletask-azuremanaged') lookup in a module-level lru_cache helper and reusing the resolved value across sync and async interceptors.

Changes:

  • Added _get_sdk_version() with @lru_cache(maxsize=1) and updated both interceptors to use it.
  • Added unit tests that verify the version lookup happens once across multiple interceptor constructions, including the "unknown" fallback behavior.

Reviewed changes

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

File Description
durabletask-azuremanaged/durabletask/azuremanaged/internal/durabletask_grpc_interceptor.py Introduces cached SDK version helper and uses it to build consistent user-agent metadata without repeated filesystem metadata scans.
tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py Adds tests validating the caching behavior and the preserved "unknown" fallback behavior for both sync and async interceptors.
Comments suppressed due to low confidence (1)

tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py:190

  • This mock.patch.object block uses a nonstandard hanging indent and closing-paren placement that is inconsistent with the rest of the file and may fail flake8 indentation checks. Reformat to a conventional multi-line call layout.
        with mock.patch.object(
                durabletask_grpc_interceptor,
                "version",
                side_effect=PackageNotFoundError('durabletask-azuremanaged')) as mock_version:

Comment thread tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py
@berndverst

Copy link
Copy Markdown
Member Author

Thanks for the review. Declining the indentation change, because the stated reason turns out not to hold.

The automated comment flagged the hanging indentation (around L162-163 and L187-190) as something that "may fail flake8." I checked rather than assuming: python -m flake8 on the changed files exits 0 with no output. There is no lint violation.

The style in question is the standard PEP 8 form for a wrapped compound-statement header — the continuation lines are indented further than the block body specifically so the two are visually distinguishable:

with ...          <- column 8   (statement)
        ...       <- column 16  (continuation of the header)
    body          <- column 12  (block body)

PEP 8 calls this out explicitly as the recommended way to resolve the ambiguity, so changing it would move away from the convention rather than toward it.

Separately, this branch is being updated to merge latest main, which had made the PR conflicting — main added ~94 lines to test_durabletask_grpc_interceptor.py and modified durabletask_grpc_interceptor.py, both files this PR touches. The resolution keeps both sides: main's new coverage and this PR's cached SDK-version lookup.

Resolves conflicts in tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py
introduced by PR #199 (deferred synchronous token acquisition), keeping both sides:

- Imports: kept both main's `from typing import Any` and this branch's
  `from unittest import mock` / `PackageNotFoundError`.
- Test classes: kept main's relocated `_TestTokenCredential` and its new
  deferred-token tests verbatim; moved this branch's `TestSdkVersionCaching`
  to sit between `TestDurableTaskGrpcInterceptor` and
  `TestAccessTokenManagerThreadSafety`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e7499a54-f262-49a6-bf91-274af28543ae
Copilot AI review requested due to automatic review settings July 27, 2026 16:40

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 2 out of 2 changed files in this pull request and generated no new comments.

Bernd Verst and others added 2 commits July 27, 2026 10:53
Resolves the single conflict in
tests/durabletask-azuremanaged/test_durabletask_grpc_interceptor.py introduced by
PR #198 (single-flight async Azure token refresh), keeping both sides.

Both sides widened an adjacent import into parenthesized form, so git tangled
them around the shared closing parenthesis. Kept every import from both:

- main's `access_token_manager` import widened to `AccessTokenManager` plus
  `AsyncAccessTokenManager`.
- this branch's `durabletask_grpc_interceptor` module import and its
  `DTSAsyncDefaultClientInterceptorImpl` / `DTSDefaultClientInterceptorImpl`
  import.

No test class conflicted: #198 appended its async single-flight classes after
TestAccessTokenManagerThreadSafety, while TestSdkVersionCaching sits before it.
Verified by AST inventory that the merged symbol set equals the union of both
parents exactly - 38 symbols, 38 unique, 0 duplicates.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e7499a54-f262-49a6-bf91-274af28543ae
Copilot AI review requested due to automatic review settings July 27, 2026 17:55

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 2 out of 2 changed files in this pull request and generated no new comments.

@berndverst
berndverst merged commit 6a6c79a into main Jul 27, 2026
25 checks passed
@berndverst
berndverst deleted the berndverst-cache-azure-managed-sdk-version branch July 27, 2026 19:31
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]: Cache Azure managed SDK version lookup

3 participants