mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-23 00:56:29 +00:00
Avoid aiodns resolver memory leak (#5941)
* Avoid aiodns resolver memory leak In certain cases, the aiodns resolver can leak memory. This also leads to Fatal `Python error… ffi.from_handle()`. This addresses the issue by ensuring that the resolver is properly closed when it is no longer needed. * Address coderabbitai feedback * Fix pytest * Fix pytest
This commit is contained in:
parent
d5b5a328d7
commit
bdbd09733a
@ -15,6 +15,24 @@ from ..const import DNS_CHECK_HOST, ContextType, IssueType
|
||||
from .base import CheckBase
|
||||
|
||||
|
||||
async def check_server(
|
||||
loop: asyncio.AbstractEventLoop, server: str, qtype: str
|
||||
) -> None:
|
||||
"""Check a DNS server and report issues."""
|
||||
ip_addr = server[6:] if server.startswith("dns://") else server
|
||||
resolver = DNSResolver(loop=loop, nameservers=[ip_addr])
|
||||
try:
|
||||
await resolver.query(DNS_CHECK_HOST, qtype)
|
||||
finally:
|
||||
|
||||
def _delete_resolver():
|
||||
"""Close resolver to avoid memory leaks."""
|
||||
nonlocal resolver
|
||||
del resolver
|
||||
|
||||
loop.call_later(1, _delete_resolver)
|
||||
|
||||
|
||||
def setup(coresys: CoreSys) -> CheckBase:
|
||||
"""Check setup function."""
|
||||
return CheckDNSServer(coresys)
|
||||
@ -33,7 +51,7 @@ class CheckDNSServer(CheckBase):
|
||||
"""Run check if not affected by issue."""
|
||||
dns_servers = self.dns_servers
|
||||
results = await asyncio.gather(
|
||||
*[self._check_server(server) for server in dns_servers],
|
||||
*[check_server(self.sys_loop, server, "A") for server in dns_servers],
|
||||
return_exceptions=True,
|
||||
)
|
||||
for i in (r for r in range(len(results)) if isinstance(results[r], DNSError)):
|
||||
@ -51,18 +69,12 @@ class CheckDNSServer(CheckBase):
|
||||
return False
|
||||
|
||||
try:
|
||||
await self._check_server(reference)
|
||||
await check_server(self.sys_loop, reference, "A")
|
||||
except DNSError:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _check_server(self, server: str):
|
||||
"""Check a DNS server and report issues."""
|
||||
ip_addr = server[6:] if server.startswith("dns://") else server
|
||||
resolver = DNSResolver(nameservers=[ip_addr])
|
||||
await resolver.query(DNS_CHECK_HOST, "A")
|
||||
|
||||
@property
|
||||
def dns_servers(self) -> list[str]:
|
||||
"""All user and system provided dns servers."""
|
||||
|
@ -3,15 +3,16 @@
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
|
||||
from aiodns import DNSResolver
|
||||
from aiodns.error import DNSError
|
||||
|
||||
from supervisor.resolution.checks.dns_server import check_server
|
||||
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ...jobs.const import JobCondition, JobExecutionLimit
|
||||
from ...jobs.decorator import Job
|
||||
from ...utils.sentry import async_capture_exception
|
||||
from ..const import DNS_CHECK_HOST, DNS_ERROR_NO_DATA, ContextType, IssueType
|
||||
from ..const import DNS_ERROR_NO_DATA, ContextType, IssueType
|
||||
from .base import CheckBase
|
||||
|
||||
|
||||
@ -33,7 +34,7 @@ class CheckDNSServerIPv6(CheckBase):
|
||||
"""Run check if not affected by issue."""
|
||||
dns_servers = self.dns_servers
|
||||
results = await asyncio.gather(
|
||||
*[self._check_server(server) for server in dns_servers],
|
||||
*[check_server(self.sys_loop, server, "AAAA") for server in dns_servers],
|
||||
return_exceptions=True,
|
||||
)
|
||||
for i in (
|
||||
@ -58,19 +59,13 @@ class CheckDNSServerIPv6(CheckBase):
|
||||
return False
|
||||
|
||||
try:
|
||||
await self._check_server(reference)
|
||||
await check_server(self.sys_loop, reference, "AAAA")
|
||||
except DNSError as dns_error:
|
||||
if dns_error.args[0] != DNS_ERROR_NO_DATA:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _check_server(self, server: str):
|
||||
"""Check a DNS server and report issues."""
|
||||
ip_addr = server[6:] if server.startswith("dns://") else server
|
||||
resolver = DNSResolver(nameservers=[ip_addr])
|
||||
await resolver.query(DNS_CHECK_HOST, "AAAA")
|
||||
|
||||
@property
|
||||
def dns_servers(self) -> list[str]:
|
||||
"""All user and system provided dns servers."""
|
||||
|
@ -21,10 +21,6 @@ def fixture_mock_dns_query():
|
||||
"supervisor.resolution.checks.dns_server.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
),
|
||||
patch(
|
||||
"supervisor.resolution.checks.dns_server_ipv6.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
@ -15,7 +15,7 @@ from supervisor.resolution.const import ContextType, IssueType
|
||||
async def fixture_dns_query() -> AsyncMock:
|
||||
"""Mock aiodns query."""
|
||||
with patch(
|
||||
"supervisor.resolution.checks.dns_server_ipv6.DNSResolver.query",
|
||||
"supervisor.resolution.checks.dns_server.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
) as dns_query:
|
||||
yield dns_query
|
||||
|
Loading…
x
Reference in New Issue
Block a user