Skip to content

Commit c80be67

Browse files
[PR #10151/7c12b1a9 backport][3.11] Fix infinite callback loop when time is not moving forward (#10173)
Co-authored-by: Bruce Merry <[email protected]> Fixes #123'). --> Fixes #10149.
1 parent 3680479 commit c80be67

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

CHANGES/10149.misc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed an infinite loop that can occur when using aiohttp in combination
2+
with `async-solipsism`_ -- by :user:`bmerry`.
3+
4+
.. _async-solipsism: https://github.com/bmerry/async-solipsism

aiohttp/web_protocol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ def _process_keepalive(self) -> None:
458458
loop = self._loop
459459
now = loop.time()
460460
close_time = self._next_keepalive_close_time
461-
if now <= close_time:
461+
if now < close_time:
462462
# Keep alive close check fired too early, reschedule
463463
self._keepalive_handle = loop.call_at(close_time, self._process_keepalive)
464464
return

tests/test_web_functional.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2324,3 +2324,41 @@ async def handler(request: web.Request) -> web.Response:
23242324
# Make 2nd request which will hit the race condition.
23252325
async with client.get("/") as resp:
23262326
assert resp.status == 200
2327+
2328+
2329+
async def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:
2330+
"""Test that the keepalive handle expires on time."""
2331+
2332+
async def handler(request: web.Request) -> web.Response:
2333+
body = await request.read()
2334+
assert b"" == body
2335+
return web.Response(body=b"OK")
2336+
2337+
app = web.Application()
2338+
app.router.add_route("GET", "/", handler)
2339+
2340+
connector = aiohttp.TCPConnector(limit=1)
2341+
client = await aiohttp_client(app, connector=connector)
2342+
2343+
loop = asyncio.get_running_loop()
2344+
now = loop.time()
2345+
2346+
# Patch loop time so we can control when the keepalive timeout is processed
2347+
with mock.patch.object(loop, "time") as loop_time_mock:
2348+
loop_time_mock.return_value = now
2349+
resp1 = await client.get("/")
2350+
await resp1.read()
2351+
request_handler = client.server.handler.connections[0]
2352+
2353+
# Ensure the keep alive handle is set
2354+
assert request_handler._keepalive_handle is not None
2355+
2356+
# Set the loop time to exactly the keepalive timeout
2357+
loop_time_mock.return_value = request_handler._next_keepalive_close_time
2358+
2359+
# sleep twice to ensure the keep alive timeout is processed
2360+
await asyncio.sleep(0)
2361+
await asyncio.sleep(0)
2362+
2363+
# Ensure the keep alive handle expires
2364+
assert request_handler._keepalive_handle is None

0 commit comments

Comments
 (0)