Skip to content

Commit 3c88f81

Browse files
authored
Ensure AsyncResolver.close() can be called multiple times (#10946)
1 parent b1e9462 commit 3c88f81

File tree

3 files changed

+40
-1
lines changed

3 files changed

+40
-1
lines changed

CHANGES/10946.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
10847.feature.rst

aiohttp/resolver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ async def close(self) -> None:
160160
self._resolver = None # type: ignore[assignment] # Clear reference to resolver
161161
return
162162
# Otherwise cancel our dedicated resolver
163-
self._resolver.cancel()
163+
if self._resolver is not None:
164+
self._resolver.cancel()
164165
self._resolver = None # type: ignore[assignment] # Clear reference
165166

166167

tests/test_resolver.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,40 @@ async def test_dns_resolver_manager_missing_loop_data() -> None:
660660

661661
# Verify no exception was raised
662662
assert loop not in manager._loop_data
663+
664+
665+
@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required")
666+
@pytest.mark.usefixtures("check_no_lingering_resolvers")
667+
async def test_async_resolver_close_multiple_times() -> None:
668+
"""Test that AsyncResolver.close() can be called multiple times without error."""
669+
with patch("aiodns.DNSResolver") as mock_dns_resolver:
670+
mock_resolver = Mock()
671+
mock_resolver.cancel = Mock()
672+
mock_dns_resolver.return_value = mock_resolver
673+
674+
# Create a resolver with custom args (dedicated resolver)
675+
resolver = AsyncResolver(nameservers=["8.8.8.8"])
676+
677+
# Close it once
678+
await resolver.close()
679+
mock_resolver.cancel.assert_called_once()
680+
681+
# Close it again - should not raise AttributeError
682+
await resolver.close()
683+
# cancel should still only be called once
684+
mock_resolver.cancel.assert_called_once()
685+
686+
687+
@pytest.mark.skipif(not getaddrinfo, reason="aiodns >=3.2.0 required")
688+
@pytest.mark.usefixtures("check_no_lingering_resolvers")
689+
async def test_async_resolver_close_with_none_resolver() -> None:
690+
"""Test that AsyncResolver.close() handles None resolver gracefully."""
691+
with patch("aiodns.DNSResolver"):
692+
# Create a resolver with custom args (dedicated resolver)
693+
resolver = AsyncResolver(nameservers=["8.8.8.8"])
694+
695+
# Manually set resolver to None to simulate edge case
696+
resolver._resolver = None # type: ignore[assignment]
697+
698+
# This should not raise AttributeError
699+
await resolver.close()

0 commit comments

Comments
 (0)