Skip to content

Make AIOHTTPTransport.connect() reentrant for shared client instances - #612

Closed
magicmark wants to merge 2 commits into
graphql-python:masterfrom
magicmark:fix/reentrant-aiohttp-transport-connect
Closed

Make AIOHTTPTransport.connect() reentrant for shared client instances#612
magicmark wants to merge 2 commits into
graphql-python:masterfrom
magicmark:fix/reentrant-aiohttp-transport-connect

Conversation

@magicmark

Copy link
Copy Markdown
Contributor

When a Client is cached and reused across requests (e.g. wrapped in lru_cache for connection pooling, as aiohttp recommends), concurrent async with client blocks race on transport.connect() — the second caller hits TransportAlreadyConnected because the session from the first caller is still active.

This also bites when a downstream consumer disconnects abruptly (e.g. a mobile client crashes mid-stream): __aexit__ may never fire, the session leaks, and the next request on the same transport fails permanently.

The fix adds a reference count to connect()/close(). If the session already exists, connect() reuses it instead of raising. close() only tears it down when the last caller exits. This matches aiohttp's own guidance that a single ClientSession should be long-lived and shared.

The first commit adds an xfail'd minimal repro (two concurrent asyncio.gather callers on the same Client). The second commit fixes the behavior and un-xfails it.

Draft PR: This PR was sent by Claude and may not have yet been reviewed by mark.

magicmark and others added 2 commits July 28, 2026 12:23
Demonstrates that AIOHTTPTransport.connect() raises
TransportAlreadyConnected when two async-with blocks enter the same
Client concurrently — a common pattern when the Client is cached
(e.g. via lru_cache) for connection pooling.

Co-Authored-By: Claude <noreply@anthropic.com>
A cached Client instance (common when wrapping with lru_cache for
connection pooling) would raise TransportAlreadyConnected if entered
concurrently from multiple async-with blocks, or if a prior session
wasn't cleaned up due to an abrupt disconnection.

connect() now reuses the existing aiohttp.ClientSession when one is
already active (incrementing a reference count), and close() only
tears it down when the last caller exits. This matches aiohttp's own
recommendation of a single long-lived session per application.

Co-Authored-By: Claude <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7fb869a) to head (b3a66e6).
⚠️ Report is 76 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##            master      #612    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           38        40     +2     
  Lines         2908      3333   +425     
==========================================
+ Hits          2908      3333   +425     

☔ View full report in Codecov by Harness.
📢 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.

@magicmark magicmark closed this Jul 28, 2026
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