Skip to content

Commit

Permalink
[PR #10151/7c12b1a9 backport][3.12] Fix infinite callback loop when t…
Browse files Browse the repository at this point in the history
…ime is not moving forward (#10174)

Co-authored-by: Bruce Merry <1963944+bmerry@users.noreply.github.com>
Fixes #123'). -->
Fixes #10149.
  • Loading branch information
patchback[bot] authored Dec 17, 2024
1 parent 99a3737 commit 5afac55
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGES/10149.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed an infinite loop that can occur when using aiohttp in combination
with `async-solipsism`_ -- by :user:`bmerry`.

.. _async-solipsism: https://github.com/bmerry/async-solipsism
2 changes: 1 addition & 1 deletion aiohttp/web_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def _process_keepalive(self) -> None:
loop = self._loop
now = loop.time()
close_time = self._next_keepalive_close_time
if now <= close_time:
if now < close_time:
# Keep alive close check fired too early, reschedule
self._keepalive_handle = loop.call_at(close_time, self._process_keepalive)
return
Expand Down
38 changes: 38 additions & 0 deletions tests/test_web_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -2324,3 +2324,41 @@ async def handler(request: web.Request) -> web.Response:
# Make 2nd request which will hit the race condition.
async with client.get("/") as resp:
assert resp.status == 200


async def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:
"""Test that the keepalive handle expires on time."""

async def handler(request: web.Request) -> web.Response:
body = await request.read()
assert b"" == body
return web.Response(body=b"OK")

app = web.Application()
app.router.add_route("GET", "/", handler)

connector = aiohttp.TCPConnector(limit=1)
client = await aiohttp_client(app, connector=connector)

loop = asyncio.get_running_loop()
now = loop.time()

# Patch loop time so we can control when the keepalive timeout is processed
with mock.patch.object(loop, "time") as loop_time_mock:
loop_time_mock.return_value = now
resp1 = await client.get("/")
await resp1.read()
request_handler = client.server.handler.connections[0]

# Ensure the keep alive handle is set
assert request_handler._keepalive_handle is not None

# Set the loop time to exactly the keepalive timeout
loop_time_mock.return_value = request_handler._next_keepalive_close_time

# sleep twice to ensure the keep alive timeout is processed
await asyncio.sleep(0)
await asyncio.sleep(0)

# Ensure the keep alive handle expires
assert request_handler._keepalive_handle is None

0 comments on commit 5afac55

Please sign in to comment.