diff --git a/CHANGES/10529.bugfix.rst b/CHANGES/10529.bugfix.rst new file mode 100644 index 00000000000..d6714ffd043 --- /dev/null +++ b/CHANGES/10529.bugfix.rst @@ -0,0 +1,2 @@ +Fixed an issue where dns queries were delayed indefinitely when an exception occurred in a ``trace.send_dns_cache_miss`` +-- by :user:`logioniz`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3004ee5cd18..e3ddd3e3d6a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -31,6 +31,7 @@ Alexandru Mihai Alexey Firsov Alexey Nikitin Alexey Popravka +Alexey Stavrov Alexey Stepanov Almaz Salakhov Amin Etesamian diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 8a3f1bcbf2b..3bdcc7d1c14 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1018,11 +1018,11 @@ async def _resolve_host_with_throttle( This method must be run in a task and shielded from cancellation to avoid cancelling the underlying lookup. """ - if traces: - for trace in traces: - await trace.send_dns_cache_miss(host) try: if traces: + for trace in traces: + await trace.send_dns_cache_miss(host) + for trace in traces: await trace.send_dns_resolvehost_start(host) diff --git a/tests/test_connector.py b/tests/test_connector.py index 076ed556971..5ff88256dce 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -3683,6 +3683,61 @@ async def send_dns_cache_hit(self, *args: object, **kwargs: object) -> None: await connector.close() +async def test_connector_resolve_in_case_of_trace_cache_miss_exception( + loop: asyncio.AbstractEventLoop, +) -> None: + token: ResolveResult = { + "hostname": "localhost", + "host": "127.0.0.1", + "port": 80, + "family": socket.AF_INET, + "proto": 0, + "flags": socket.AI_NUMERICHOST, + } + + request_count = 0 + + class DummyTracer(Trace): + def __init__(self) -> None: + """Dummy""" + + async def send_dns_cache_hit(self, *args: object, **kwargs: object) -> None: + """Dummy send_dns_cache_hit""" + + async def send_dns_resolvehost_start( + self, *args: object, **kwargs: object + ) -> None: + """Dummy send_dns_resolvehost_start""" + + async def send_dns_resolvehost_end( + self, *args: object, **kwargs: object + ) -> None: + """Dummy send_dns_resolvehost_end""" + + async def send_dns_cache_miss(self, *args: object, **kwargs: object) -> None: + nonlocal request_count + request_count += 1 + if request_count <= 1: + raise Exception("first attempt") + + async def resolve_response() -> List[ResolveResult]: + await asyncio.sleep(0) + return [token] + + with mock.patch("aiohttp.connector.DefaultResolver") as m_resolver: + m_resolver().resolve.return_value = resolve_response() + + connector = TCPConnector() + traces = [DummyTracer()] + + with pytest.raises(Exception): + await connector._resolve_host("", 0, traces) + + await connector._resolve_host("", 0, traces) == [token] + + await connector.close() + + async def test_connector_does_not_remove_needed_waiters( loop: asyncio.AbstractEventLoop, key: ConnectionKey ) -> None: