Skip to content

Commit

Permalink
Fix resolve_host "Task was destroyed but it is pending" errors (#8967) (
Browse files Browse the repository at this point in the history
#8980)

(cherry picked from commit cd761a3)
  • Loading branch information
Dreamsorcerer authored Sep 2, 2024
1 parent 553e311 commit ea31633
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES/8967.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed resolve_host() 'Task was destroyed but is pending' errors -- by :user:`Dreamsorcerer`.
6 changes: 6 additions & 0 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,12 +824,16 @@ def __init__(
self._local_addr_infos = aiohappyeyeballs.addr_to_addr_infos(local_addr)
self._happy_eyeballs_delay = happy_eyeballs_delay
self._interleave = interleave
self._resolve_host_tasks: Set["asyncio.Task[List[ResolveResult]]"] = set()

def close(self) -> Awaitable[None]:
"""Close all ongoing DNS calls."""
for ev in self._throttle_dns_events.values():
ev.cancel()

for t in self._resolve_host_tasks:
t.cancel()

return super().close()

@property
Expand Down Expand Up @@ -907,6 +911,8 @@ async def _resolve_host(
resolved_host_task = asyncio.create_task(
self._resolve_host_with_throttle(key, host, port, traces)
)
self._resolve_host_tasks.add(resolved_host_task)
resolved_host_task.add_done_callback(self._resolve_host_tasks.discard)
try:
return await asyncio.shield(resolved_host_task)
except asyncio.CancelledError:
Expand Down
38 changes: 36 additions & 2 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys
import uuid
from collections import deque
from contextlib import closing
from contextlib import closing, suppress
from typing import Any, List, Optional, Type
from unittest import mock

Expand Down Expand Up @@ -1667,7 +1667,41 @@ async def test_close_cancels_cleanup_handle(loop) -> None:
assert conn._cleanup_handle is None


async def test_close_abort_closed_transports(loop) -> None:
async def test_close_cancels_resolve_host(loop: asyncio.AbstractEventLoop) -> None:
cancelled = False

async def delay_resolve_host(*args: object) -> None:
"""Delay _resolve_host() task in order to test cancellation."""
nonlocal cancelled
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
cancelled = True
raise

conn = aiohttp.TCPConnector()
req = ClientRequest(
"GET", URL("http://localhost:80"), loop=loop, response_class=mock.Mock()
)
with mock.patch.object(conn, "_resolve_host_with_throttle", delay_resolve_host):
t = asyncio.create_task(conn.connect(req, [], ClientTimeout()))
# Let it create the internal task
await asyncio.sleep(0)
# Let that task start running
await asyncio.sleep(0)

# We now have a task being tracked and can ensure that .close() cancels it.
assert len(conn._resolve_host_tasks) == 1
await conn.close()
await asyncio.sleep(0.01)
assert cancelled
assert len(conn._resolve_host_tasks) == 0

with suppress(asyncio.CancelledError):
await t


async def test_close_abort_closed_transports(loop: asyncio.AbstractEventLoop) -> None:
tr = mock.Mock()

conn = aiohttp.BaseConnector(loop=loop)
Expand Down

0 comments on commit ea31633

Please sign in to comment.