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 <Task pending name=websocket_api.async:ws_info coro=<_handle_async_response() running at /usr/src/homeassistant/homeassistant/components/websocket_api/decorators.py:26> 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
This commit is contained in:
J. Nick Koston 2024-02-16 08:11:09 -06:00 committed by GitHub
parent 95015fbb40
commit f9dc92a9a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 39 additions and 16 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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: