Description
Describe the bug
async_solipsism is an event loop that uses fake time - good for unit tests. The keepalive logic in aiohttp assumes that loop.call_at will always call the handler strictly later than the requested time, but that doesn't hold for the idealised time source in async_solipsism. The net result is that tests using async_solipsism can get stuck in an infinite loop where aiohttp keeps re-submitting the keepalive handler.
To Reproduce
- pip install aiohttp==3.11.10 async-solipsism==0.7 pytest==8.2.2 pytest-asyncio==0.24.0
- Put the following in pytest.ini:
[pytest]
asyncio_mode = auto
asyncio_default_fixture_loop_scope = function
- Put the following in test_stuff.py:
import asyncio
import async_solipsism
import pytest
from aiohttp import web, test_utils
@pytest.fixture
def event_loop_policy():
return async_solipsism.EventLoopPolicy()
@pytest.fixture(autouse=True)
def mock_start_connection(monkeypatch):
monkeypatch.setattr("aiohappyeyeballs.start_connection",
async_solipsism.aiohappyeyeballs_start_connection)
def socket_factory(host, port, family):
return async_solipsism.ListenSocket((host, port))
async def test_integration():
app = web.Application()
async with test_utils.TestServer(app, socket_factory=socket_factory) as server:
async with test_utils.TestClient(server) as client:
resp = await client.post("/hey", json={})
assert resp.status == 404
await asyncio.sleep(10000)
resp = await client.post("/hey", json={})
assert resp.status == 404
- Run pytest. The test hangs.
Expected behavior
The test should succeed.
Logs/tracebacks
======================================= test session starts ========================================
platform linux -- Python 3.12.3, pytest-8.2.2, pluggy-1.5.0
rootdir: /home/bmerry/work/bugs/aiohttp-solipsism
configfile: pytest.ini
plugins: mock-3.12.0, timeout-2.3.1, reportlog-0.4.0, asyncio-0.24.0, repeat-0.9.3, xdist-3.5.0, cov-4.1.0, anyio-4.3.0, check-2.2.2, custom-exit-code-0.3.0
asyncio: mode=Mode.AUTO, default_loop_scope=function
collected 1 item
test_stuff.pyPython Version
$ python --version
Python 3.12.3aiohttp Version
3.11.10multidict Version
6.0.5propcache Version
0.2.0yarl Version
1.18.0OS
Ubuntu 24.04
Related component
Server
Additional context
The issue is in this line. If the handler fires exactly on time, this check treats it as too early, and it is queued to run again. It will keep getting re-submitted until the clock ticks past close_time. With a real system that's likely to happen immediately (since the number of instructions that can run per clock tick is pretty small), but with the artificial time in async_solipsism (and probably any similar source of mock time), time doesn't flow until there is something to sleep for, and this becomes an infinite loop.
I'm happy to supply a patch. The fix itself is easy (change <= to <), but let me know what you recommend for regression testing. Would it be reasonable to make async_solipsism a dev dependency of aiohttp so that it can be used in a regression test?
Code of Conduct
- I agree to follow the aio-libs Code of Conduct