From f79e5e002bc5c4a691682c5894f0e649ab943d33 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 1 Jun 2022 19:13:09 -1000 Subject: [PATCH] Ensure recorder shuts down when its startup future is canceled out from under it (#72866) --- homeassistant/components/recorder/core.py | 14 +++++++++++--- tests/components/recorder/test_init.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 7df4cf57e56..7a096a9c404 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio from collections.abc import Callable, Iterable +from concurrent.futures import CancelledError import contextlib from datetime import datetime, timedelta import logging @@ -518,9 +519,16 @@ class Recorder(threading.Thread): def _wait_startup_or_shutdown(self) -> object | None: """Wait for startup or shutdown before starting.""" - return asyncio.run_coroutine_threadsafe( - self._async_wait_for_started(), self.hass.loop - ).result() + try: + return asyncio.run_coroutine_threadsafe( + self._async_wait_for_started(), self.hass.loop + ).result() + except CancelledError as ex: + _LOGGER.warning( + "Recorder startup was externally canceled before it could complete: %s", + ex, + ) + return SHUTDOWN_TASK def run(self) -> None: """Start processing events to save.""" diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 41c4428ee5e..87dbce3ba3b 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -118,6 +118,26 @@ async def test_shutdown_before_startup_finishes( assert run_info.end is not None +async def test_canceled_before_startup_finishes( + hass: HomeAssistant, + async_setup_recorder_instance: SetupRecorderInstanceT, + caplog: pytest.LogCaptureFixture, +): + """Test recorder shuts down when its startup future is canceled out from under it.""" + hass.state = CoreState.not_running + await async_setup_recorder_instance(hass) + instance = get_instance(hass) + await instance.async_db_ready + instance._hass_started.cancel() + with patch.object(instance, "engine"): + await hass.async_block_till_done() + await hass.async_add_executor_job(instance.join) + assert ( + "Recorder startup was externally canceled before it could complete" + in caplog.text + ) + + async def test_shutdown_closes_connections(hass, recorder_mock): """Test shutdown closes connections."""