fix: reuse HTTP connections in the default clients - #252
Open
mayankbohradev wants to merge 1 commit into
Open
Conversation
RequestsClient called requests.request() and HTTPXClient opened an httpx.AsyncClient inside a context manager on every call. Both build and discard a connection pool per request, so each API call paid a new TCP and TLS handshake. RequestsClient now holds a requests.Session. HTTPXClient holds an httpx.AsyncClient created lazily on first use, because the class is instantiated at import time when no event loop is running and the pool binds to the running loop. A new client is created if the loop changes, so repeated asyncio.run() calls keep working. Ten sequential sends against a local server open one connection instead of ten, for both the sync and async clients. Tests in request_test.py patched requests.request at module level. They now patch requests.Session.request; the assertions are unchanged.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #252 +/- ##
===========================================
+ Coverage 82.66% 96.65% +13.98%
===========================================
Files 4 70 +66
Lines 75 3736 +3661
===========================================
+ Hits 62 3611 +3549
- Misses 13 125 +112 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Both default HTTP clients now hold a single underlying client, so connections are
reused across requests instead of a pool being built and discarded per call.
Why
RequestsClient.request()calledrequests.request(), which creates a newSessionper call.HTTPXClient.request()openedasync with httpx.AsyncClient(...)per call. Every API call therefore paid a fresh TCPconnect and TLS handshake. It shows up most in loops (batch sends, contact syncs,
paginated reads) and in async code, where pooling is a main reason to be async.
Ten sequential
Emails.sendcalls, against a local server counting accepted TCPconnections:
RequestsClientHTTPXClientNotes for review
HTTPXClientcreates itsAsyncClientlazily rather than in__init__.resend/__init__.pyinstantiates the class at import time, when no event loop isrunning, and an
AsyncClientpool binds to the running loop. If the loop changes,for example a second
asyncio.run(...), a client is created for the new loop; theprevious one is dropped rather than closed, since its connections died with its
loop.
close()andaclose()are available for explicit cleanup.requests.Sessionis not thread safe in general, because concurrent mutation ofsession attributes is not safe. This client never mutates session state after
construction: method, headers, body and timeout are all passed per call. A test
sends 40 requests across 8 threads through one shared client and asserts each
caller gets its own response.
A shared
Sessionalso persists cookies across requests where a per-call sessiondid not. The API does not rely on cookies, but the difference is real.
tests/request_test.pypatchedrequests.requestat module level, which a sharedsession bypasses. Those seven decorators now patch
requests.Session.request. Noassertion changed.
Testing
tox -e linttox -e mypytox -e py(568 passed)tests/http_client_connection_reuse_test.pycovers connection reuse for bothclients,
close()/aclose(), concurrent use of the shared session, and thechanged-loop path.
mainwhile keeping the newtests makes six of the seven fail. The seventh covers the changed-loop path,
which the previous per-request client handled by construction; it is included as
a regression guard for the lazy client added here.
Summary by cubic
Reuse HTTP connections in the default HTTP clients to avoid per-request TCP/TLS handshakes. Previously each call created a new
requests.Session/httpx.AsyncClient; now a single shared client reuses connections and cuts latency, especially in loops and async flows.RequestsClient: holds onerequests.Session; supports context manager; call close() when disposing a long‑lived client. Cookies now persist across requests.HTTPXClient: holds onehttpx.AsyncClientcreated on first use and bound to the current event loop; a new client is created if the loop changes (e.g., multiple asyncio.run calls). Call aclose() when disposing.requests.Session.requestinstead ofrequests.request(assertions unchanged).Written for commit 2962256. Summary will update on new commits.