From b7f7bed46cf38c1fbe1906c64715b12d98630cdf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 09:05:18 -1000 Subject: [PATCH] Import and create pyudev for usb in the executor (#113478) --- homeassistant/components/usb/__init__.py | 34 +++++++++++++++++------- tests/components/hassio/test_issues.py | 1 + tests/components/usb/test_init.py | 4 +-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index f91603ce158..959a8f5894c 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -35,7 +35,7 @@ from .models import USBDevice from .utils import usb_device_from_port if TYPE_CHECKING: - from pyudev import Device + from pyudev import Device, MonitorObserver _LOGGER = logging.getLogger(__name__) @@ -228,6 +228,25 @@ class USBDiscovery: if info.get("docker"): return + if not ( + observer := await self.hass.async_add_executor_job( + self._get_monitor_observer + ) + ): + return + + def _stop_observer(event: Event) -> None: + observer.stop() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_observer) + self.observer_active = True + + def _get_monitor_observer(self) -> MonitorObserver | None: + """Get the monitor observer. + + This runs in the executor because the import + does blocking I/O. + """ from pyudev import ( # pylint: disable=import-outside-toplevel Context, Monitor, @@ -237,7 +256,7 @@ class USBDiscovery: try: context = Context() except (ImportError, OSError): - return + return None monitor = Monitor.from_netlink(context) try: @@ -246,17 +265,14 @@ class USBDiscovery: _LOGGER.debug( "Unable to setup pyudev filtering; This is expected on WSL: %s", ex ) - return + return None + observer = MonitorObserver( monitor, callback=self._device_discovered, name="usb-observer" ) + observer.start() - - def _stop_observer(event: Event) -> None: - observer.stop() - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_observer) - self.observer_active = True + return observer def _device_discovered(self, device: Device) -> None: """Call when the observer discovers a new usb tty device.""" diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 6b0b2e5fdc4..6ff3df65908 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -590,6 +590,7 @@ async def test_supervisor_issues_initial_failure( with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1): result = await async_setup_component(hass, "hassio", {}) + await hass.async_block_till_done() assert result client = await hass_ws_client(hass) diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index dd6d9fff874..6d04516a26c 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -85,7 +85,7 @@ async def test_observer_discovery( def _create_mock_monitor_observer(monitor, callback, name): nonlocal mock_observer - hass.async_create_task(_mock_monitor_observer_callback(callback)) + hass.create_task(_mock_monitor_observer_callback(callback)) mock_observer = MagicMock() return mock_observer @@ -107,7 +107,7 @@ async def test_observer_discovery( hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() - assert mock_observer.mock_calls == [call.start(), call.stop()] + assert mock_observer.mock_calls == [call.start(), call.__bool__(), call.stop()] @pytest.mark.skipif(