From f9dc92a9a0822a0a93d674f1bb8ebd4069c57d4a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Feb 2024 08:11:09 -0600 Subject: [PATCH] Fix recorder ws_info blocking the event loop (#110657) * Fix recorder ws_info blocking the event loop Fixes ``` 2024-02-15 06:37:55.423 WARNING (MainThread) [asyncio] Executing wait_for=<_GatheringFuture pending cb=[Task.task_wakeup()] created at /usr/local/lib/python3.12/asyncio/tasks.py:712> cb=[set.remove()] created at /usr/src/homeassistant/homeassistant/core.py:653> took 0.332 seconds ``` * no instance did not actually work --- homeassistant/components/recorder/core.py | 3 ++ .../components/recorder/websocket_api.py | 35 ++++++++++--------- .../components/recorder/test_websocket_api.py | 17 +++++++++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index ce539b7f0c8..8885116dbfd 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -187,6 +187,7 @@ class Recorder(threading.Thread): self.auto_purge = auto_purge self.auto_repack = auto_repack self.keep_days = keep_days + self.is_running: bool = False self._hass_started: asyncio.Future[object] = hass.loop.create_future() self.commit_interval = commit_interval self._queue: queue.SimpleQueue[RecorderTask | Event] = queue.SimpleQueue() @@ -694,6 +695,7 @@ class Recorder(threading.Thread): def run(self) -> None: """Run the recorder thread.""" + self.is_running = True try: self._run() except Exception: # pylint: disable=broad-exception-caught @@ -703,6 +705,7 @@ class Recorder(threading.Thread): finally: # Ensure shutdown happens cleanly if # anything goes wrong in the run loop + self.is_running = False self._shutdown() def _add_to_session(self, session: Session, obj: object) -> None: diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 39821cb9699..af61faf921e 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -44,13 +44,7 @@ from .statistics import ( statistics_during_period, validate_statistics, ) -from .util import ( - PERIOD_SCHEMA, - async_migration_in_progress, - async_migration_is_live, - get_instance, - resolve_period, -) +from .util import PERIOD_SCHEMA, get_instance, resolve_period _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -497,21 +491,30 @@ def ws_info( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] ) -> None: """Return status of the recorder.""" - instance = get_instance(hass) - - backlog = instance.backlog if instance else None - migration_in_progress = async_migration_in_progress(hass) - migration_is_live = async_migration_is_live(hass) - recording = instance.recording if instance else False - thread_alive = instance.is_alive() if instance else False + if instance := get_instance(hass): + backlog = instance.backlog + migration_in_progress = instance.migration_in_progress + migration_is_live = instance.migration_is_live + recording = instance.recording + # We avoid calling is_alive() as it can block waiting + # for the thread state lock which will block the event loop. + is_running = instance.is_running + max_backlog = instance.max_backlog + else: + backlog = None + migration_in_progress = False + migration_is_live = False + recording = False + is_running = False + max_backlog = None recorder_info = { "backlog": backlog, - "max_backlog": instance.max_backlog, + "max_backlog": max_backlog, "migration_in_progress": migration_in_progress, "migration_is_live": migration_is_live, "recording": recording, - "thread_running": thread_alive, + "thread_running": is_running, } connection.send_result(msg["id"], recorder_info) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index e902dd49020..fff8daa14f4 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -2132,6 +2132,23 @@ async def test_recorder_info_bad_recorder_config( assert response["result"]["thread_running"] is False +async def test_recorder_info_no_instance( + recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator +) -> None: + """Test getting recorder when there is no instance.""" + client = await hass_ws_client() + + with patch( + "homeassistant.components.recorder.websocket_api.get_instance", + return_value=None, + ): + await client.send_json_auto_id({"type": "recorder/info"}) + response = await client.receive_json() + assert response["success"] + assert response["result"]["recording"] is False + assert response["result"]["thread_running"] is False + + async def test_recorder_info_migration_queue_exhausted( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: