The Fix
Fixes a hang when reusing a connection that was previously used for a streaming download and returned to the pool in paused state.
Based on closed aio-libs/aiohttp issue #10169 · PR/commit linked
Production note: This tends to surface only under concurrency. Reproduce with load tests and watch for lock contention/cancellation paths.
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+Fixed a hang where a connection previously used for a streaming
+download could be returned to the pool in a paused state.
+-- by :user:`javitonino`.
import asyncio
import aiohttp
async def run():
for _ in range(10):
async with aiohttp.ClientSession() as session:
response = await session.get(
"http://localhost:4566/bucket/ae4bbfcdc47f450aa8557abefeba4a5ct",
)
i = 0
async for chunk in response.content.iter_chunked(1024):
i += 1
print(f"chunk {i}")
# This process only fails near the end of the download, this condition is just here to speed up the testing
# by skipping some uploads but the bug reproduces without it, it just takes more time.
if i >= 900:
print("Streamed, time to upload")
# It hangs awaiting this, no timeout is raised
await session.put(
"http://localhost:4566/bucket/output/some_file",
data=b""
)
print("Uploaded")
asyncio.run(run())
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Apply the official fix\nFixes a hang when reusing a connection that was previously used for a streaming download and returned to the pool in paused state.\nWhen NOT to use: Do not use this fix if connection reuse is not a concern in your application.\n\n
Why This Fix Works in Production
- Trigger: message, payload = await protocol.read() # type: ignore[union-attr]
- Mechanism: The connection was returned to the pool in a paused state after a streaming download
- If left unfixed, failures can be intermittent under concurrency (hard to reproduce; shows up as sporadic 5xx/timeouts).
Why This Breaks in Prod
- Shows up under Python 3.12 in real deployments (not just unit tests).
- The connection was returned to the pool in a paused state after a streaming download
- Surfaces as: Traceback (most recent call last):
Proof / Evidence
- GitHub issue: #10169
- Fix PR: https://github.com/aio-libs/aiohttp/pull/10171
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.70
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.34
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“> I think the problem is that when the connection is released to the pool (on download eof) while the iteration is still ongoing, the…”
“Does it happen when using async with properly? I think I saw something similar recently that looked like some kind of race condition that wasn't…”
“Do you mean the request context manager like so: It still happens when using it like this.”
“Right, that's not related then to this issue.”
Failure Signature (Search String)
- message, payload = await protocol.read() # type: ignore[union-attr]
Error Message
Stack trace
Error Message
-------------
Traceback (most recent call last):
File "venv/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 1055, in start
message, payload = await protocol.read() # type: ignore[union-attr]
^^^^^^^^^^^^^^^^^^^^^
File "venv/lib/python3.12/site-packages/aiohttp/streams.py", line 668, in read
await self._waiter
asyncio.exceptions.CancelledError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "replicate.py", line 24, in <module>
asyncio.run(run())
File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "replicate.py", line 18, in run
await session.put(
File "venv/lib/python3.12/site-packages/aiohttp/client.py", line 728, in _request
await resp.start(conn)
File "venv/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 1050, in start
with self._timer:
^^^^^^^^^^^
File "venv/lib/python3.12/site-packages/aiohttp/helpers.py", line 671, in __exit__
raise asyncio.TimeoutError from
... (truncated) ...
Minimal Reproduction
import asyncio
import aiohttp
async def run():
for _ in range(10):
async with aiohttp.ClientSession() as session:
response = await session.get(
"http://localhost:4566/bucket/ae4bbfcdc47f450aa8557abefeba4a5ct",
)
i = 0
async for chunk in response.content.iter_chunked(1024):
i += 1
print(f"chunk {i}")
# This process only fails near the end of the download, this condition is just here to speed up the testing
# by skipping some uploads but the bug reproduces without it, it just takes more time.
if i >= 900:
print("Streamed, time to upload")
# It hangs awaiting this, no timeout is raised
await session.put(
"http://localhost:4566/bucket/output/some_file",
data=b""
)
print("Uploaded")
asyncio.run(run())
Environment
- Python: 3.12
What Broke
The upload process hangs intermittently without raising exceptions.
Why It Broke
The connection was returned to the pool in a paused state after a streaming download
Fix Options (Details)
Option A — Apply the official fix
Fixes a hang when reusing a connection that was previously used for a streaming download and returned to the pool in paused state.
Fix reference: https://github.com/aio-libs/aiohttp/pull/10171
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- Do not use this fix if connection reuse is not a concern in your application.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a stress test that runs high-concurrency workloads and fails on thread dumps / blocked locks.
- Enable watchdog dumps in prod (faulthandler, thread dump endpoint) to capture deadlocks quickly.
- Make timeouts explicit and test them (unit + integration) to avoid silent behavior changes.
- Instrument retries (attempt count + reason) and alert on spikes to catch dependency slowdowns.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.