From 29f4931a490fcc57b6e7ddd67d0b6ebff3858a21 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Feb 2023 12:14:21 -0600 Subject: [PATCH] Use PidfdChildWatcher by default when available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a backport from cpython 3.12 https://docs.python.org/3/library/asyncio-policy.html > PidfdChildWatcher is a “Goldilocks” child watcher implementation. It doesn’t require signals or threads, doesn’t interfere with any processes launched outside the event loop, and scales linearly with the number of subprocesses launched by the event loop. The main disadvantage is that pidfds are specific to Linux, and only work on recent (5.3+) kernels. https://github.com/python/cpython/pull/98024 There are some additional fixes in cpython 3.12 in https://github.com/python/cpython/pull/94184 when there is no event loop running in the main thread but this is not a problem we have --- homeassistant/runner.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 702a5c0450138..d3c2633bc7bd8 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -2,8 +2,10 @@ from __future__ import annotations import asyncio +from asyncio import events import dataclasses import logging +import os import threading import traceback from typing import Any @@ -49,6 +51,22 @@ class RuntimeConfig: open_ui: bool = False +def can_use_pidfd() -> bool: + """Check if pidfd_open is available. + + Back ported from cpython 3.12 + """ + if not hasattr(os, "pidfd_open"): + return False + try: + pid = os.getpid() + os.close(os.pidfd_open(pid, 0)) # pylint: disable=no-member + except OSError: + # blocked by security policy like SECCOMP + return False + return True + + class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): """Event loop policy for Home Assistant.""" @@ -56,6 +74,23 @@ def __init__(self, debug: bool) -> None: """Init the event loop policy.""" super().__init__() self.debug = debug + self._watcher: asyncio.AbstractChildWatcher | None = None + + def _init_watcher(self) -> None: + """Initialize the watcher for child processes. + + Back ported from cpython 3.12 + """ + with events._lock: # type: ignore[attr-defined] # pylint: disable=protected-access + if self._watcher is None: # pragma: no branch + if can_use_pidfd(): + self._watcher = asyncio.PidfdChildWatcher() + else: + self._watcher = asyncio.ThreadedChildWatcher() + if threading.current_thread() is threading.main_thread(): + self._watcher.attach_loop( + self._local._loop # type: ignore[attr-defined] # pylint: disable=protected-access + ) @property def loop_name(self) -> str: