Skip to content

Commit b00739b

Browse files
authored
[PR #10949/06e3b36 backport][3.12] Improve connection reuse test coverage (#10950)
1 parent 31d3638 commit b00739b

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

tests/test_client_functional.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,3 +4311,128 @@ async def handler(request: web.Request) -> web.Response:
43114311
response.raise_for_status()
43124312

43134313
assert len(client._session.connector._conns) == 1
4314+
4315+
4316+
async def test_post_content_exception_connection_kept(
4317+
aiohttp_client: AiohttpClient,
4318+
) -> None:
4319+
"""Test that connections are kept after content.set_exception() with POST."""
4320+
4321+
async def handler(request: web.Request) -> web.Response:
4322+
await request.read()
4323+
return web.Response(
4324+
body=b"x" * 1000
4325+
) # Larger response to ensure it's not pre-buffered
4326+
4327+
app = web.Application()
4328+
app.router.add_post("/", handler)
4329+
client = await aiohttp_client(app)
4330+
4331+
# POST request with body - connection should be closed after content exception
4332+
resp = await client.post("/", data=b"request body")
4333+
4334+
with pytest.raises(RuntimeError):
4335+
async with resp:
4336+
assert resp.status == 200
4337+
resp.content.set_exception(RuntimeError("Simulated error"))
4338+
await resp.read()
4339+
4340+
assert resp.closed
4341+
4342+
# Wait for any pending operations to complete
4343+
await resp.wait_for_close()
4344+
4345+
assert client._session.connector is not None
4346+
# Connection is kept because content.set_exception() is a client-side operation
4347+
# that doesn't affect the underlying connection state
4348+
assert len(client._session.connector._conns) == 1
4349+
4350+
4351+
async def test_network_error_connection_closed(
4352+
aiohttp_client: AiohttpClient,
4353+
) -> None:
4354+
"""Test that connections are closed after network errors."""
4355+
4356+
async def handler(request: web.Request) -> NoReturn:
4357+
# Read the request body
4358+
await request.read()
4359+
4360+
# Start sending response but close connection before completing
4361+
response = web.StreamResponse()
4362+
response.content_length = 1000 # Promise 1000 bytes
4363+
await response.prepare(request)
4364+
4365+
# Send partial data then force close the connection
4366+
await response.write(b"x" * 100) # Only send 100 bytes
4367+
# Force close the transport to simulate network error
4368+
assert request.transport is not None
4369+
request.transport.close()
4370+
assert False, "Will not return"
4371+
4372+
app = web.Application()
4373+
app.router.add_post("/", handler)
4374+
client = await aiohttp_client(app)
4375+
4376+
# POST request that will fail due to network error
4377+
with pytest.raises(aiohttp.ClientPayloadError):
4378+
resp = await client.post("/", data=b"request body")
4379+
async with resp:
4380+
await resp.read() # This should fail
4381+
4382+
# Give event loop a chance to process connection cleanup
4383+
await asyncio.sleep(0)
4384+
4385+
assert client._session.connector is not None
4386+
# Connection should be closed due to network error
4387+
assert len(client._session.connector._conns) == 0
4388+
4389+
4390+
async def test_client_side_network_error_connection_closed(
4391+
aiohttp_client: AiohttpClient,
4392+
) -> None:
4393+
"""Test that connections are closed after client-side network errors."""
4394+
handler_done = asyncio.Event()
4395+
4396+
async def handler(request: web.Request) -> NoReturn:
4397+
# Read the request body
4398+
await request.read()
4399+
4400+
# Start sending a large response
4401+
response = web.StreamResponse()
4402+
response.content_length = 10000 # Promise 10KB
4403+
await response.prepare(request)
4404+
4405+
# Send some data
4406+
await response.write(b"x" * 1000)
4407+
4408+
# Keep the response open - we'll interrupt from client side
4409+
await asyncio.wait_for(handler_done.wait(), timeout=5.0)
4410+
assert False, "Will not return"
4411+
4412+
app = web.Application()
4413+
app.router.add_post("/", handler)
4414+
client = await aiohttp_client(app)
4415+
4416+
# POST request that will fail due to client-side network error
4417+
with pytest.raises(aiohttp.ClientPayloadError):
4418+
resp = await client.post("/", data=b"request body")
4419+
async with resp:
4420+
# Simulate client-side network error by closing the transport
4421+
# This simulates connection reset, network failure, etc.
4422+
assert resp.connection is not None
4423+
assert resp.connection.protocol is not None
4424+
assert resp.connection.protocol.transport is not None
4425+
resp.connection.protocol.transport.close()
4426+
4427+
# This should fail with connection error
4428+
await resp.read()
4429+
4430+
# Signal handler to finish
4431+
handler_done.set()
4432+
4433+
# Give event loop a chance to process connection cleanup
4434+
await asyncio.sleep(0)
4435+
4436+
assert client._session.connector is not None
4437+
# Connection should be closed due to client-side network error
4438+
assert len(client._session.connector._conns) == 0

tests/test_web_functional.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,10 @@ async def handler(request):
19561956
await resp.read()
19571957
assert resp.closed
19581958

1959+
# Wait for any pending operations to complete
1960+
await resp.wait_for_close()
1961+
1962+
assert session._connector is not None
19591963
assert len(session._connector._conns) == 1
19601964

19611965
await session.close()

0 commit comments

Comments
 (0)