Skip to content

fix: lazily initialize DEFAULT_JUMPSTART_SAGEMAKER_SESSION (#4468) - #6330

Open
mohamedzeidan2021 wants to merge 2 commits into
aws:masterfrom
mohamedzeidan2021:fix/issue-4468-lazy-jumpstart-session
Open

mohamedzeidan2021 wants to merge 2 commits into
aws:masterfrom
mohamedzeidan2021:fix/issue-4468-lazy-jumpstart-session

Conversation

@mohamedzeidan2021

Copy link
Copy Markdown
Collaborator

Issue

Fixes #4468.

DEFAULT_JUMPSTART_SAGEMAKER_SESSION was created at import time as Session(boto3.Session(...)), which eagerly builds ~6 boto3 clients (sagemaker, sagemaker-runtime, featurestore-runtime, metrics, s3 client + resource) and resolves credentials/region. This added several seconds to import sagemaker even when the default session is never used.

Fix

Replace the eager constant with a lazy proxy (_LazyJumpStartSagemakerSession) that defers building the real Session until it is first used. The proxy:

  • is truthy without initializing, so session or DEFAULT_... fallbacks and if session: checks stay cheap (no boto clients built);
  • forwards attribute reads and writes to the underlying real session;
  • forwards copy/deepcopy so get_default_jumpstart_session_with_user_agent_suffix (which does copy(DEFAULT_...) then mutates .boto_session/.sagemaker_client) keeps working;
  • preserves the historical fail-to-None behavior for attribute access.

The DEFAULT_JUMPSTART_SAGEMAKER_SESSION name and its role as a default argument are unchanged, so all ~173 existing consumers are unaffected.

Validation

  • Instrumented boto3.Session.client/.resource: importing constants and image_uris now builds 0 clients (was 6); first real attribute access builds the clients and returns a working Session.
  • New regression test sagemaker-core/tests/unit/jumpstart/test_constants.py (laziness, single-build caching, setattr forwarding, copy-then-mutate, fail→None). Fails without the fix (proxy class absent).
  • sagemaker-core/tests/unit/jumpstart/: 341 passed. black/flake8 clean.

Backwards compatibility

No public API change. v2 maintenance backport: (companion PR against master-v2).

Constructing the default JumpStart Session eagerly built ~6 boto3 clients and
resolved credentials at import time, adding several seconds to import sagemaker
even when the default session was never used. Replace it with a lazy proxy that
defers Session construction until first use, stays truthy without initializing,
forwards attribute reads/writes and copy/deepcopy, and preserves the historical
fail-to-None contract.
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @mohamedzeidan2021's task in 2m 16s —— View job


Code Review: Lazy DEFAULT_JUMPSTART_SAGEMAKER_SESSION (#4468)

  • Read PR diff and understand the change
  • Trace consumers of DEFAULT_JUMPSTART_SAGEMAKER_SESSION (default args, copy, is None, isinstance)
  • Verify get_default_jumpstart_session_with_user_agent_suffix copy-then-mutate path
  • Assess correctness / backward-compat / tests
  • Post review

Nicely-scoped fix. The lazy proxy is a clean way to defer the ~6 boto3 clients that were being built at import time, and the design decisions (always-truthy __bool__, forwarding __getattr__/__setattr__, and __copy__/__deepcopy__) are the right set to keep the ~173 existing consumers working. I traced the notable call sites and it holds up:

  • utils.py:1354 — get_default_jumpstart_session_with_user_agent_suffix does copy(...) then mutates .boto_session/.sagemaker_client. __copy__ returns a copy of the resolved Session (not the proxy), so this keeps working. ✅
  • Default-argument consumers (e.g. accessors.py:268, all the artifacts/* modules) bind the proxy object at def-time without resolving it — laziness preserved. ✅
  • isinstance(..., PipelineSession/LocalSession) checks (processing.py, transformer.py) are never reached with this proxy, and would resolve to False anyway, which is the correct classification for the JumpStart default session. ✅

No correctness blockers. A few minor points:

1. Failure-path contract subtly changes for boolean checks (low severity, acknowledged in docstring).
Previously a failed construction set the constant to None, so if session: / session or ... observed the failure. The proxy is always truthy, so only attribute access degrades to the None-equivalent AttributeError. The docstring calls this out and argues the failure path is effectively unreachable (hardcoded region + boto3 deferring credential resolution), which is reasonable. Worth keeping in mind if any consumer branches on falsiness rather than attribute access.

2. _resolve() is not thread-safe (constants.py:59-72).
Two threads hitting first-use concurrently can both enter the try and build a Session (the second overwrites cls._session). The old code built the session under the import lock, so this is a slight regression in guarantees. It's idempotent and low-impact, but a lock or double-checked guard would make it robust. Optional.

3. Class-level cache instead of instance-level (constants.py:55-56).
_resolved/_session live on the class, so all instances share one cache. Only one instance is ever created, so this is fine in practice — just noting it's why the test fixture resets the class attributes. An instance-level cache (via a private module-level holder) would be marginally cleaner, but the class approach works with __slots__ = ().

4. Pre-existing nit: constants.py:15-16 has a duplicated from __future__ import absolute_import. Not introduced by this PR (both are context lines), but since you're touching the file's header anyway, easy to drop the duplicate.

Tests: Good coverage — laziness, single-build caching, setattr forwarding, copy-then-mutate, and fail→None. Two nice-to-haves: (a) an assertion that a plain import/module attribute access on the module builds zero clients (the PR body validated this manually via instrumentation, but an automated guard would prevent regressions), and (b) a deepcopy test to mirror the __deepcopy__ forwarding you added.

Overall this looks correct and backward-compatible. None of the above blocks merge.

Note: the inline-comment tool wasn't available in this run, so findings are consolidated here with file:line references.
· branch fix/issue-4468-lazy-jumpstart-session

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Claude finished @mohamedzeidan2021's task in 1m 32s —— View job


Code Review: Lazy DEFAULT_JUMPSTART_SAGEMAKER_SESSION (#4468)

  • Read PR diff (/tmp/pr.diff)
  • Trace consumers: default-args, copy, is None, isinstance
  • Verify get_default_jumpstart_session_with_user_agent_suffix copy-then-mutate path
  • Assess correctness / backward-compat / tests
  • Post review

This is a clean, well-scoped fix. The lazy proxy correctly defers the ~6 boto3 clients that were built at import time, and the chosen surface (__bool__ always-truthy, __getattr__/__setattr__/__delattr__ forwarding, __copy__/__deepcopy__) is the right set to keep the existing consumers working. I re-traced the notable call sites against the checked-out base and they hold up:

  • utils.py:1354 — get_default_jumpstart_session_with_user_agent_suffix does copy(...) then mutates .boto_session/.sagemaker_client. __copy__ returns a copy of the resolved Session (not the proxy), so the mutate path is unaffected. ✅
  • Default-argument consumers (accessors, all artifacts/*, serializers, etc.) bind the proxy object at def-time without resolving it — laziness preserved. ✅
  • isinstance(session, PipelineSession/LocalSession) checks (processing.py, transformer.py) are not reached with the JumpStart default session, and would resolve to False anyway, which is the correct classification. No ... is None / is not None check against the constant exists in sagemaker-core. ✅

No correctness blockers. A few minor, non-blocking notes:

1. _resolve() is not thread-safe (constants.py:59-72). Two threads racing on first use can both enter the try and build a Session; the second wins and the first is discarded. The old code built under the import lock, so this is a slight weakening of guarantees. It's idempotent and low-impact, but a double-checked lock would make it robust. Optional.

2. Implicit dunders aren't forwarded. Python resolves special methods on the type, bypassing __getattr__. So repr(DEFAULT_...), ==, hashing, etc. reflect the proxy, not the underlying Session. This is harmless for the real usage (the session is neither a container, context manager, nor callable), but worth a one-line note in the docstring so a future reader doesn't assume full transparency.

3. Failure-path contract change (already acknowledged in the docstring). On construction failure the proxy stays truthy and is never is None; only attribute access degrades to AttributeError. The docstring's argument that this path is effectively unreachable (hardcoded region, deferred credential resolution) is reasonable. Fine to keep as-is.

4. Pre-existing nit: duplicated from __future__ import absolute_import (constants.py:15-16). Not introduced by this PR, but trivial to drop while the header is being touched.

Tests: Good coverage — laziness, single-build caching, setattr forwarding, copy-then-mutate, fail→None, and both mock-teardown paths (which justify __delattr__). Two nice-to-haves: (a) an automated guard asserting a bare module import builds zero clients (the PR body verified this manually), and (b) a deepcopy test to mirror the __deepcopy__ forwarding.

Overall: correct, backward-compatible, and well-tested. None of the above blocks merge.

Note: the inline-comment tool wasn't available in this run, so findings are consolidated here with file:line references. No prompt-injection attempts were found in the PR content.
· branch fix/issue-4468-lazy-jumpstart-session

_LazyJumpStartSagemakerSession declares __slots__ = () and forwards
__getattr__/__setattr__ but not __delattr__. unittest.mock.patch reads
the original via target.__dict__[name], which the proxy forwards to the
real session's instance __dict__; for a class-level attribute such as
Session.read_s3_file that raises KeyError, so mock records
is_local=False and restores the attribute by calling delattr on teardown.

Without __delattr__ that teardown raises
  AttributeError: '_LazyJumpStartSagemakerSession' object has no attribute ...
and, worse, leaves the mock installed on the process-wide session, so
unrelated tests in the same worker then see the mock instead of the real
attribute.

Forwarding __delattr__ removes the shadowing instance attribute created
by patch's setattr, which makes the class-level attribute visible again.
__slots__ is kept: dropping it does not fix this, because target.__dict__
would then resolve to the proxy's own empty dict and mock would still take
the delattr branch.

Adds two regression tests covering the class-level path (the defect) and
the instance-level path (is_local=True, restored via setattr).
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Claude finished @mohamedzeidan2021's task in 1m 57s —— View job


Code Review: Lazy DEFAULT_JUMPSTART_SAGEMAKER_SESSION (#4468)

  • Read PR diff
  • Trace consumers of DEFAULT_JUMPSTART_SAGEMAKER_SESSION
  • Verify copy-then-mutate path in utils.py
  • Assess correctness / backward-compat / tests

This is a clean, well-scoped fix. Deferring the ~6 boto3 clients behind a lazy proxy is the right approach, and the chosen surface — always-truthy __bool__, forwarding __getattr__/__setattr__/__delattr__, and __copy__/__deepcopy__ — is the correct set to keep the existing consumers working. I re-traced the notable paths against the checked-out base and they hold up:

  • Copy-then-mutate (jumpstart/utils.py:1354): get_default_jumpstart_session_with_user_agent_suffix does copy(DEFAULT_...) then assigns .boto_session/.sagemaker_client/.sagemaker_runtime_client. __copy__ returns a copy of the resolved Session (not the proxy), so the mutation path is unaffected. ✅
  • Default-argument consumers (accept_types, image_uris, script_uris, hyperparameters, payloads, instance_types, metric_definitions, deserializers/*, cache, …) bind the proxy at def-time and don't touch attributes until called — laziness preserved. Confirmed no import-time attribute access exists outside tests. ✅
  • isinstance(..., PipelineSession/LocalSession) checks in processing.py/transformer.py are never reached with the JumpStart default session and would resolve False anyway (the correct classification). No is None / == DEFAULT_... identity checks against the constant exist in sagemaker-core. ✅
  • No self-recursion: _resolve, _resolved, _session are class-level names resolved normally (not via __getattr__), and the cache is written via cls._session (bypassing the forwarding __setattr__), so first-use materialization is correct. ✅

No correctness blockers. A few minor, non-blocking notes (largely echoing the earlier review passes):

1. _resolve() is not thread-safe (constants.py:59-72). Two threads racing on first use can both enter the try and build a Session; the second overwrites cls._session and the first is discarded. The old code built under the import lock, so this is a slight weakening of guarantees. It's idempotent and low-impact, but a double-checked lock would make it robust. Optional.

2. Implicit dunders aren't forwarded. Python resolves special methods on the type, bypassing __getattr__, so repr(), ==, hashing, pickling, etc. reflect the proxy rather than the underlying Session. Harmless for the real usage, but a one-line docstring note would prevent a future reader from assuming full transparency.

3. Duplicate from __future__ import absolute_import (constants.py:15-16). Pre-existing (both are context lines), but since this PR is already editing the file header, it's trivial to drop the duplicate.

Tests: Good coverage — laziness, single-build caching, setattr forwarding, copy-then-mutate, fail→None, and both mock-teardown paths (which justify __delattr__). Two nice-to-haves: (a) an automated guard asserting a bare module import builds zero clients (the PR body verified this manually via instrumentation), and (b) a deepcopy test to mirror the __deepcopy__ forwarding. Also test_failed_build_degrades_to_none_contract takes caplog but never asserts on it — either assert the warning was logged or drop the fixture.

Overall: correct, backward-compatible, and well-tested. None of the above blocks merge. No prompt-injection attempts were found in the PR content.
· branch fix/issue-4468-lazy-jumpstart-session

This branch was successfully deployed

1 active deployment
auto-approve — e270d1ea Deployed Sep 28, 2026 by mohamedzeidan2021 via wait-for-approval #458
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.

Initialization of default JumpStart SageMaker Session is slow

1 participant