Skip to content

Conversation

@rhamzeh
Copy link
Member

@rhamzeh rhamzeh commented Sep 12, 2025

This brings ssl context handling in line with the async client. Importantly, openssl has a pretty signifigant performance regression in creating ssl contexts v3.0+ that is mitigated by paying the context creation tax once, instead of for every request.

Based on testing, this reduces the openssl v3 performance penalty from ~20ms per connection to 9ms per connection.

Original PR: openfga/sdk-generator#607 by @wadells

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Improved TLS/SSL configuration with consistent context handling, supporting custom CA certificates, client certificates, and proxy setups.
    • Option to disable SSL verification now behaves consistently across all requests.
    • Performance improvement by reusing the SSL context across connections.
  • Tests

    • Added comprehensive tests covering CA and client certificate usage, verification toggling, proxy scenarios, and context reuse.
  • Refactor

    • Consolidated per-request TLS settings into a single reusable configuration for more reliable and predictable network behavior.

This brings ssl context handling in line with the async client.
Importantly, openssl has a pretty signifigant performance regression
in creating ssl contexts v3.0+ that is mitigated by paying the context
creation tax once, instead of for every request.

Based on testing, this reduces the openssl v3 performance penalty
from ~200ms per connection to 9ms per connection.

Original PR: openfga/sdk-generator#607
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 12, 2025

Walkthrough

Replaces per-request TLS parameters with a single reusable SSLContext in the sync REST client and updates PoolManager/ProxyManager initialization to use it. Adds unit tests covering CA loading, client certs, verification toggling, proxy usage, and SSLContext reuse behavior.

Changes

Cohort / File(s) Summary of changes
TLS context integration in REST client
openfga_sdk/sync/rest.py
Construct a single ssl.SSLContext via ssl.create_default_context(cafile=ssl_ca_cert); optionally load client cert/key; disable verification when verify_ssl is False (hostname_checking off, CERT_NONE); pass ssl_context to urllib3 PoolManager/ProxyManager; centralize and reuse TLS configuration; remove per-call cert_reqs/ca_certs/cert_file/key_file usage.
SSL behavior tests
test/sync/rest_test.py
Add tests mocking ssl.create_default_context and urllib3 managers to verify: CA file usage, client cert loading, verification disabling, proxy integration, SSLContext reuse across requests, and configuration with all SSL options.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant RESTClient as RESTClientObject
  participant SSL as ssl.create_default_context
  participant C as SSLContext
  participant Pool as urllib3.PoolManager
  participant Proxy as urllib3.ProxyManager

  App->>RESTClient: __init__(configuration)
  RESTClient->>SSL: create_default_context(cafile=ssl_ca_cert)
  SSL-->>RESTClient: C
  alt cert_file provided
    RESTClient->>C: load_cert_chain(cert_file, key_file)
  end
  alt verify_ssl is False
    RESTClient->>C: check_hostname = False
    RESTClient->>C: verify_mode = CERT_NONE
  end
  alt proxy configured
    RESTClient->>Proxy: new(proxy_url, proxy_headers, ssl_context=C, maxsize=...)
  else no proxy
    RESTClient->>Pool: new(ssl_context=C, maxsize=...)
  end
  note over RESTClient,C: Reuse C for subsequent requests
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ssl-context-reuse

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: reuse ssl context in the sync client" is concise and accurately captures the primary change—updating the synchronous REST client to reuse a single SSLContext to avoid per-request OpenSSL v3.0 performance penalties; it directly corresponds to the modifications in openfga_sdk/sync/rest.py and the added SSL tests, so reviewers scanning history will understand the intent.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

@codecov-commenter
Copy link

codecov-commenter commented Sep 12, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.73%. Comparing base (89a39d1) to head (bb3acca).

❌ Your project status has failed because the head coverage (70.73%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #222      +/-   ##
==========================================
+ Coverage   70.70%   70.73%   +0.03%     
==========================================
  Files         134      134              
  Lines       10881    10884       +3     
==========================================
+ Hits         7693     7699       +6     
+ Misses       3188     3185       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rhamzeh rhamzeh force-pushed the fix/ssl-context-reuse branch from 0ad931b to bb3acca Compare September 12, 2025 20:29
@rhamzeh rhamzeh marked this pull request as ready for review September 12, 2025 20:33
@rhamzeh rhamzeh requested a review from a team as a code owner September 12, 2025 20:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openfga_sdk/sync/rest.py (1)

18-18: Bug: urllib.parse not imported (AttributeError risk when query_params is used)

urllib.parse isn’t imported; import urllib alone won’t expose the parse submodule.

Apply:

-import urllib
+from urllib.parse import urlencode
-            encoded_qs = urllib.parse.urlencode(query_params)
+            encoded_qs = urlencode(query_params)

Also applies to: 287-290

🧹 Nitpick comments (4)
openfga_sdk/sync/rest.py (1)

512-514: Don’t clear the entire connection pool on every request

Clearing the pool defeats HTTP/TLS connection reuse and undermines the perf win (you’ll still avoid context creation, but lose keep-alive/TLS session resumption). Release only the connection you used; keep the pool alive.

Apply:

-        # Release the connection back to the pool
-        self.close()
+        # Release only this connection; keep pools for reuse
+        if _preload_content:
+            raw_response.release_conn()
test/sync/rest_test.py (3)

538-541: Patch the symbols where they’re used (module-under-test) for robustness

Patching urllib3.* and ssl.create_default_context at their global modules works, but patching via the module-under-test prevents interference from other imports.

Apply representative changes (repeat for all occurrences):

-@patch('ssl.create_default_context')
-@patch('urllib3.PoolManager')
+@patch('openfga_sdk.sync.rest.ssl.create_default_context')
+@patch('openfga_sdk.sync.rest.urllib3.PoolManager')
-@patch('ssl.create_default_context')
-@patch('urllib3.ProxyManager')
+@patch('openfga_sdk.sync.rest.ssl.create_default_context')
+@patch('openfga_sdk.sync.rest.urllib3.ProxyManager')

Also applies to: 566-567, 597-599, 628-630, 664-665, 700-702


560-564: Use identity checks for the SSLContext mock

Compare the exact context instance instead of equality (MagicMock equality is permissive).

Apply:

-    assert call_kwargs['ssl_context'] == mock_ssl_context
+    assert call_kwargs['ssl_context'] is mock_ssl_context

Also applies to: 592-595, 734-735


726-730: Make this assertion resilient (MagicMock truthiness is always True)

hasattr(...)/truthiness on MagicMock will pass even if the code modified the values. Seed explicit defaults and assert they remain unchanged.

Apply:

-    # Verify SSL verification settings were NOT modified (verify_ssl=True)
-    # check_hostname and verify_mode should remain at their default secure values
-    assert not hasattr(mock_ssl_context, 'check_hostname') or mock_ssl_context.check_hostname
-    assert not hasattr(mock_ssl_context, 'verify_mode') or mock_ssl_context.verify_mode != ssl.CERT_NONE
+    # Seed secure defaults and ensure they weren't changed
+    mock_ssl_context.check_hostname = True
+    mock_ssl_context.verify_mode = ssl.CERT_REQUIRED
+    RESTClientObject(configuration=mock_config)
+    assert mock_ssl_context.check_hostname is True
+    assert mock_ssl_context.verify_mode == ssl.CERT_REQUIRED
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89a39d1 and bb3acca.

📒 Files selected for processing (2)
  • openfga_sdk/sync/rest.py (3 hunks)
  • test/sync/rest_test.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/sync/rest_test.py (2)
openfga_sdk/configuration.py (2)
  • timeout_millisec (731-735)
  • timeout_millisec (738-742)
openfga_sdk/sync/rest.py (2)
  • RESTClientObject (140-514)
  • build_request (226-320)
🔇 Additional comments (2)
openfga_sdk/sync/rest.py (1)

158-169: Good move: single SSLContext reused and passed to Pool/Proxy managers

  • Correct ordering when disabling verification (check_hostname before verify_mode).
  • Passing the same context to both ProxyManager and PoolManager is the right alignment with urllib3 2.x.

Also applies to: 200-209, 213-218

test/sync/rest_test.py (1)

537-736: Nice coverage of SSLContext creation and reuse

Tests exercise CA loading, client certs, verify off, proxy path, and reuse behavior.

@wadells
Copy link
Contributor

wadells commented Sep 12, 2025

👍 My only note would be in the description. The ssl context creation difference is more like 30-40ms better per call, not 200ms better.

@rhamzeh
Copy link
Member Author

rhamzeh commented Sep 12, 2025

Ah that was a typo - apologies.

I wonder if one of the changes suggested by the AI bot above might help too:

# openfga_sdk/sync/rest.py
512-514: Don’t clear the entire connection pool on every request

Clearing the pool defeats HTTP/TLS connection reuse and undermines the perf win (you’ll still avoid context creation, but lose keep-alive/TLS session resumption). Release only the connection you used; keep the pool alive.

Apply:

-        # Release the connection back to the pool
-        self.close()
+        # Release only this connection; keep pools for reuse
+        if _preload_content:
+            raw_response.release_conn()

I didn't want to include that in this same PR as we have not tested that, so maybe Monday we'll push out a release with this ssl context fix, then we can have a follow up release with that change after testing.

@rhamzeh rhamzeh added this pull request to the merge queue Sep 12, 2025
Merged via the queue into main with commit 20a302c Sep 12, 2025
29 checks passed
@rhamzeh rhamzeh deleted the fix/ssl-context-reuse branch September 12, 2025 22:50
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.

5 participants