Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/10923.feature.rst
5 changes: 4 additions & 1 deletion aiohttp/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,14 @@ def release_resolver(
loop: The event loop the resolver was using.
"""
# Remove client from its loop's tracking
if loop not in self._loop_data:
return
resolver, client_set = self._loop_data[loop]
client_set.discard(client)
# If no more clients for this loop, cancel and remove its resolver
if not client_set:
resolver.cancel()
if resolver is not None:
resolver.cancel()
del self._loop_data[loop]


Expand Down
46 changes: 46 additions & 0 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,3 +614,49 @@ async def test_dns_resolver_manager_multiple_event_loops(
# Verify resolver cleanup
resolver1.cancel.assert_called_once()
resolver2.cancel.assert_called_once()


@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required")
async def test_dns_resolver_manager_weakref_garbage_collection() -> None:
"""Test that release_resolver handles None resolver due to weakref garbage collection."""
manager = _DNSResolverManager()

# Create a mock resolver that will be None when accessed
mock_resolver = Mock()
mock_resolver.cancel = Mock()

with patch("aiodns.DNSResolver", return_value=mock_resolver):
# Create an AsyncResolver to get a resolver from the manager
resolver = AsyncResolver()
loop = asyncio.get_running_loop()

# Manually corrupt the data to simulate garbage collection
# by setting the resolver to None
manager._loop_data[loop] = (None, manager._loop_data[loop][1]) # type: ignore[assignment]

# This should not raise an AttributeError: 'NoneType' object has no attribute 'cancel'
await resolver.close()

# Verify no exception was raised and the loop data was cleaned up properly
# Since we set resolver to None and there was one client, the entry should be removed
assert loop not in manager._loop_data


@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required")
async def test_dns_resolver_manager_missing_loop_data() -> None:
"""Test that release_resolver handles missing loop data gracefully."""
manager = _DNSResolverManager()

with patch("aiodns.DNSResolver"):
# Create an AsyncResolver
resolver = AsyncResolver()
loop = asyncio.get_running_loop()

# Manually remove the loop data to simulate race condition
manager._loop_data.clear()

# This should not raise a KeyError
await resolver.close()

# Verify no exception was raised
assert loop not in manager._loop_data
Loading