mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-26 18:46: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
|
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:
|
def setup(coresys: CoreSys) -> CheckBase:
|
||||||
"""Check setup function."""
|
"""Check setup function."""
|
||||||
return CheckDNSServer(coresys)
|
return CheckDNSServer(coresys)
|
||||||
@ -33,7 +51,7 @@ class CheckDNSServer(CheckBase):
|
|||||||
"""Run check if not affected by issue."""
|
"""Run check if not affected by issue."""
|
||||||
dns_servers = self.dns_servers
|
dns_servers = self.dns_servers
|
||||||
results = await asyncio.gather(
|
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,
|
return_exceptions=True,
|
||||||
)
|
)
|
||||||
for i in (r for r in range(len(results)) if isinstance(results[r], DNSError)):
|
for i in (r for r in range(len(results)) if isinstance(results[r], DNSError)):
|
||||||
@ -51,18 +69,12 @@ class CheckDNSServer(CheckBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._check_server(reference)
|
await check_server(self.sys_loop, reference, "A")
|
||||||
except DNSError:
|
except DNSError:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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
|
@property
|
||||||
def dns_servers(self) -> list[str]:
|
def dns_servers(self) -> list[str]:
|
||||||
"""All user and system provided dns servers."""
|
"""All user and system provided dns servers."""
|
||||||
|
@ -3,15 +3,16 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from aiodns import DNSResolver
|
|
||||||
from aiodns.error import DNSError
|
from aiodns.error import DNSError
|
||||||
|
|
||||||
|
from supervisor.resolution.checks.dns_server import check_server
|
||||||
|
|
||||||
from ...const import CoreState
|
from ...const import CoreState
|
||||||
from ...coresys import CoreSys
|
from ...coresys import CoreSys
|
||||||
from ...jobs.const import JobCondition, JobExecutionLimit
|
from ...jobs.const import JobCondition, JobExecutionLimit
|
||||||
from ...jobs.decorator import Job
|
from ...jobs.decorator import Job
|
||||||
from ...utils.sentry import async_capture_exception
|
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
|
from .base import CheckBase
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ class CheckDNSServerIPv6(CheckBase):
|
|||||||
"""Run check if not affected by issue."""
|
"""Run check if not affected by issue."""
|
||||||
dns_servers = self.dns_servers
|
dns_servers = self.dns_servers
|
||||||
results = await asyncio.gather(
|
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,
|
return_exceptions=True,
|
||||||
)
|
)
|
||||||
for i in (
|
for i in (
|
||||||
@ -58,19 +59,13 @@ class CheckDNSServerIPv6(CheckBase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._check_server(reference)
|
await check_server(self.sys_loop, reference, "AAAA")
|
||||||
except DNSError as dns_error:
|
except DNSError as dns_error:
|
||||||
if dns_error.args[0] != DNS_ERROR_NO_DATA:
|
if dns_error.args[0] != DNS_ERROR_NO_DATA:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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
|
@property
|
||||||
def dns_servers(self) -> list[str]:
|
def dns_servers(self) -> list[str]:
|
||||||
"""All user and system provided dns servers."""
|
"""All user and system provided dns servers."""
|
||||||
|
@ -21,10 +21,6 @@ def fixture_mock_dns_query():
|
|||||||
"supervisor.resolution.checks.dns_server.DNSResolver.query",
|
"supervisor.resolution.checks.dns_server.DNSResolver.query",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
),
|
),
|
||||||
patch(
|
|
||||||
"supervisor.resolution.checks.dns_server_ipv6.DNSResolver.query",
|
|
||||||
new_callable=AsyncMock,
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from supervisor.resolution.const import ContextType, IssueType
|
|||||||
async def fixture_dns_query() -> AsyncMock:
|
async def fixture_dns_query() -> AsyncMock:
|
||||||
"""Mock aiodns query."""
|
"""Mock aiodns query."""
|
||||||
with patch(
|
with patch(
|
||||||
"supervisor.resolution.checks.dns_server_ipv6.DNSResolver.query",
|
"supervisor.resolution.checks.dns_server.DNSResolver.query",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
) as dns_query:
|
) as dns_query:
|
||||||
yield dns_query
|
yield dns_query
|
||||||
|
Loading…
x
Reference in New Issue
Block a user