From 5279535046a10fe36385618abaab0a157ad55b52 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 17 Jan 2023 16:06:37 -1000 Subject: [PATCH] Fix live logbook stalling when there are no historical events with a high commit interval (#86110) * Force live logbook to send an empty message to indicate no results Since the sync task can take a while if the recorder is busy, the logbook will appear to hang if we do not send the first partial message even if its empty. This work is in preparation for a higher database commit interval where this issue is most obvious. The historical only path did not have this issue because it never had to wait for the db sync. * update tests --- .../components/logbook/websocket_api.py | 10 ++++++++-- tests/components/logbook/test_websocket_api.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py index 8d0dd49bff8..04b288d523b 100644 --- a/homeassistant/components/logbook/websocket_api.py +++ b/homeassistant/components/logbook/websocket_api.py @@ -83,6 +83,7 @@ async def _async_send_historical_events( formatter: Callable[[int, Any], dict[str, Any]], event_processor: EventProcessor, partial: bool, + force_send: bool = False, ) -> dt | None: """Select historical data from the database and deliver it to the websocket. @@ -116,7 +117,7 @@ async def _async_send_historical_events( # if its the last one (not partial) so # consumers of the api know their request was # answered but there were no results - if last_event_time or not partial: + if last_event_time or not partial or force_send: connection.send_message(message) return last_event_time @@ -150,7 +151,7 @@ async def _async_send_historical_events( # if its the last one (not partial) so # consumers of the api know their request was # answered but there were no results - if older_query_last_event_time or not partial: + if older_query_last_event_time or not partial or force_send: connection.send_message(older_message) # Returns the time of the newest event @@ -384,6 +385,11 @@ async def ws_event_stream( messages.event_message, event_processor, partial=True, + # Force a send since the wait for the sync task + # can take a a while if the recorder is busy and + # we want to make sure the client is not still spinning + # because it is waiting for the first message + force_send=True, ) live_stream.task = asyncio.create_task( diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index 5b16c98998c..91d1a95f75b 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -1817,6 +1817,7 @@ async def test_subscribe_unsubscribe_logbook_stream_device( assert msg["id"] == 7 assert msg["type"] == TYPE_RESULT assert msg["success"] + await async_wait_recording_done(hass) # There are no answers to our initial query # so we get an empty reply. This is to ensure @@ -1828,6 +1829,15 @@ async def test_subscribe_unsubscribe_logbook_stream_device( assert msg["id"] == 7 assert msg["type"] == "event" assert msg["event"]["events"] == [] + assert "partial" in msg["event"] + await async_wait_recording_done(hass) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + assert "partial" not in msg["event"] + await async_wait_recording_done(hass) hass.states.async_set("binary_sensor.should_not_appear", STATE_ON) hass.states.async_set("binary_sensor.should_not_appear", STATE_OFF) @@ -1942,6 +1952,14 @@ async def test_logbook_stream_match_multiple_entities( assert msg["id"] == 7 assert msg["type"] == "event" assert msg["event"]["events"] == [] + assert "partial" in msg["event"] + await async_wait_recording_done(hass) + + msg = await asyncio.wait_for(websocket_client.receive_json(), 2) + assert msg["id"] == 7 + assert msg["type"] == "event" + assert msg["event"]["events"] == [] + assert "partial" not in msg["event"] await async_wait_recording_done(hass) hass.states.async_set("binary_sensor.should_not_appear", STATE_ON)