Skip to content

fix: reuse TLS contexts for Core and social login requests - #651

Open
mgLTF wants to merge 1 commit into
supertokens:devfrom
mgLTF:fix/reuse-tls-context
Open

mgLTF wants to merge 1 commit into
supertokens:devfrom
mgLTF:fix/reuse-tls-context

Conversation

@mgLTF

@mgLTF mgLTF commented Sep 24, 2026 •

Copy link
Copy Markdown

Summary of change

Core requests and third-party token/user-info/signing-key requests currently construct an HTTPX client for each request. Each default client rebuilds TLS configuration and reloads trusted certificates, including when the destination is a plain-HTTP Core.

Reuse a lazily created, verified SSL context across these requests. HTTPX's public create_ssl_context(verify=True, trust_env=True) retains the installed version's TLS defaults. The single-entry cache responds to changes in SSL_CERT_FILE, SSL_CERT_DIR, and SSLKEYLOGFILE, and is cleared by the test reset helper. Clients retain their existing per-request lifetimes and cleanup.

Replacing certificate contents at the same path requires a worker restart/cache reset. In particular, a removed trusted CA can remain trusted by an existing worker until then; operators who rotate trust bundles in place need to restart workers. Concurrent cold calls may construct separate contexts; subsequent calls reuse the cached result. This does not introduce connection pooling, a public configuration API, or a new dependency.

The included scripts/benchmark_tls.py compares existing behavior, this patch, and a client reused within one event loop, using real local HTTP/HTTPS connections. Warm request medians (60 measured requests after one cold request, concurrency 1):

Environment Protocol Fresh clients Reused TLS context Reused client (comparison only)
Windows / Python 3.12.14 HTTP 14.243 ms 1.859 ms 0.535 ms
Windows / Python 3.12.14 HTTPS 16.942 ms 4.430 ms 0.647 ms
Linux / Python 3.13.15 HTTP 6.545 ms 1.096 ms 0.573 ms
Linux / Python 3.13.15 HTTPS 9.319 ms 3.153 ms 0.569 ms

Both use HTTPX 0.28.1 and certifi plus a temporary local CA. Context creation falls from 61 to 1; connections remain at 61 for this patch, versus 1 for full client reuse. These are local microbenchmarks, not measurements of a live Google sign-in. Full client reuse offers further savings but needs async-runtime ownership, shutdown, and request-state isolation across the SDK's sync/async integrations.

Related issues

  • Related Go SDK implementation: fix(performance): reuse http client supertokens-golang#438. That PR reuses the entire Core client and adds an override; Go's old empty clients already shared its default transport. On Unix, Go's standard crypto/x509 loads file-based system roots once per process, so replacing a CA bundle at the same path can also require a restart there. That behavior predates Go PR fix: JWT lifetime setting issue #438 and does not apply identically to every platform or custom client. This Python patch targets the separately measured TLS setup cost and also covers third-party requests.

Test Plan

make dev-install
docker compose up --wait  # dedicated local test instances only
python -m pytest -q tests/test_ssl_utils.py
python -m pytest -q tests --junit-xml=test-results/junit.xml
python scripts/benchmark_tls.py
make lint
  • Eight new regression tests pass on Windows/Python 3.12 and Linux/Python 3.13: reuse across the four request paths; verified local HTTPS; untrusted certificates and hostname mismatches rejected; certificate/environment selection; invalid configuration; reset; concurrent initialization; sync calls and separate event loops.
  • Isolated helper compatibility checks pass with Python 3.8.20 / HTTPX 0.15.0 and Python 3.13.15 / HTTPX 0.27.2, including real TLS success/rejection checks and separate loops.
  • Complete Linux suite: 665 passed, 1 failed. The failure is tests/sessions/test_jwks.py::test_that_jwks_are_refresh_if_kid_is_unknown; it also fails on untouched upstream 4bfa86337d1f0d49ebca6f8453dd6c373aaec04d against the same Docker Core.
  • Complete Windows suite: 660 passed, 6 failed. All six failures reproduce on untouched upstream: the same JWKS test, a logger path assertion, and four pyfakefs/filelock tests.
  • Full strict Pyright reports one error in unchanged tests/webauthn/soft_authenticator.py:57, reproduced on untouched upstream. No added type errors.
  • All pre-commit hooks pass on the committed changes, including Ruff, formatting, and branch/version checks.
  • Full-repository hooks also find existing formatting issues in .changes/v0.31.2.md, .changes/v0.31.3.md, tests/oauth2provider/test_frontend_redirect.py, and tests/webauthn/test_sign_in_double_verify.py. Their automatic edits are excluded from this PR.

Documentation changes

Added a reproducible benchmark and documented cache lifecycle in the internal helper. No public API documentation change is required.

Checklist for important updates

  • Added a Fixed changie fragment using changie new.
  • CDI/FDI, version, framework, recipe-user, and access-token changes: not applicable.
  • Sync/async behavior covered by regression tests.
  • Installed and ran the repository pre-commit hook successfully.
  • Targeting dev, following the current dev-sync release workflow.
  • Confirm upstream CI after a maintainer approves the fork workflow runs and applies run-tests.

Remaining TODOs for this PR

  • This is a draft because full upstream validation is not green on the baseline either. All six workflows on this PR currently report action_required with no jobs. An upstream maintainer needs to approve the fork workflow runs and apply run-tests; the fork author cannot add that label (AddLabelsToLabelable permission denied). After the jobs run, evaluate the existing test/lint failures independently of this change.

@mgLTF
mgLTF force-pushed the fix/reuse-tls-context branch from 7e4aee0 to 504d16b Compare September 24, 2026 19:38
@mgLTF
mgLTF marked this pull request as ready for review September 24, 2026 20:18

This branch has not been deployed

No deployments
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.

1 participant