mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Fix recorder setup hanging if non live schema migration fails (#122242)
This commit is contained in:
parent
293ad99dae
commit
2f47312eeb
@ -488,7 +488,7 @@ class Recorder(threading.Thread):
|
|||||||
async_at_started(self.hass, self._async_hass_started)
|
async_at_started(self.hass, self._async_hass_started)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_startup_failed(self) -> None:
|
def _async_startup_done(self, startup_failed: bool) -> None:
|
||||||
"""Report startup failure."""
|
"""Report startup failure."""
|
||||||
# If a live migration failed, we were able to connect (async_db_connected
|
# If a live migration failed, we were able to connect (async_db_connected
|
||||||
# marked True), the database was marked ready (async_db_ready marked
|
# marked True), the database was marked ready (async_db_ready marked
|
||||||
@ -499,11 +499,12 @@ class Recorder(threading.Thread):
|
|||||||
self.async_db_connected.set_result(False)
|
self.async_db_connected.set_result(False)
|
||||||
if not self.async_db_ready.done():
|
if not self.async_db_ready.done():
|
||||||
self.async_db_ready.set_result(False)
|
self.async_db_ready.set_result(False)
|
||||||
persistent_notification.async_create(
|
if startup_failed:
|
||||||
self.hass,
|
persistent_notification.async_create(
|
||||||
"The recorder could not start, check [the logs](/config/logs)",
|
self.hass,
|
||||||
"Recorder",
|
"The recorder could not start, check [the logs](/config/logs)",
|
||||||
)
|
"Recorder",
|
||||||
|
)
|
||||||
self._async_stop_listeners()
|
self._async_stop_listeners()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -1484,16 +1485,15 @@ class Recorder(threading.Thread):
|
|||||||
def _shutdown(self) -> None:
|
def _shutdown(self) -> None:
|
||||||
"""Save end time for current run."""
|
"""Save end time for current run."""
|
||||||
_LOGGER.debug("Shutting down recorder")
|
_LOGGER.debug("Shutting down recorder")
|
||||||
if not self.schema_version or self.schema_version != SCHEMA_VERSION:
|
|
||||||
# If the schema version is not set, we never had a working
|
# If the schema version is not set, we never had a working
|
||||||
# connection to the database or the schema never reached a
|
# connection to the database or the schema never reached a
|
||||||
# good state.
|
# good state.
|
||||||
#
|
# In either case, we want to mark startup as failed.
|
||||||
# In either case, we want to mark startup as failed.
|
startup_failed = (
|
||||||
#
|
not self.schema_version or self.schema_version != SCHEMA_VERSION
|
||||||
self.hass.add_job(self._async_startup_failed)
|
)
|
||||||
else:
|
self.hass.add_job(self._async_startup_done, startup_failed)
|
||||||
self.hass.add_job(self._async_stop_listeners)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._end_session()
|
self._end_session()
|
||||||
|
@ -200,8 +200,14 @@ async def test_database_migration_encounters_corruption(
|
|||||||
assert move_away.called
|
assert move_away.called
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("live_migration", "expected_setup_result"), [(True, True), (False, False)]
|
||||||
|
)
|
||||||
async def test_database_migration_encounters_corruption_not_sqlite(
|
async def test_database_migration_encounters_corruption_not_sqlite(
|
||||||
hass: HomeAssistant, async_setup_recorder_instance: RecorderInstanceGenerator
|
hass: HomeAssistant,
|
||||||
|
async_setup_recorder_instance: RecorderInstanceGenerator,
|
||||||
|
live_migration: bool,
|
||||||
|
expected_setup_result: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test we fail on database error when we cannot recover."""
|
"""Test we fail on database error when we cannot recover."""
|
||||||
assert recorder.util.async_migration_in_progress(hass) is False
|
assert recorder.util.async_migration_in_progress(hass) is False
|
||||||
@ -226,8 +232,14 @@ async def test_database_migration_encounters_corruption_not_sqlite(
|
|||||||
"homeassistant.components.persistent_notification.dismiss",
|
"homeassistant.components.persistent_notification.dismiss",
|
||||||
side_effect=pn.dismiss,
|
side_effect=pn.dismiss,
|
||||||
) as mock_dismiss,
|
) as mock_dismiss,
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.recorder.core.migration.live_migration",
|
||||||
|
return_value=live_migration,
|
||||||
|
),
|
||||||
):
|
):
|
||||||
await async_setup_recorder_instance(hass, wait_recorder=False)
|
await async_setup_recorder_instance(
|
||||||
|
hass, wait_recorder=False, expected_setup_result=expected_setup_result
|
||||||
|
)
|
||||||
hass.states.async_set("my.entity", "on", {})
|
hass.states.async_set("my.entity", "on", {})
|
||||||
hass.states.async_set("my.entity", "off", {})
|
hass.states.async_set("my.entity", "off", {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -1394,6 +1394,8 @@ async def _async_init_recorder_component(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
add_config: dict[str, Any] | None = None,
|
add_config: dict[str, Any] | None = None,
|
||||||
db_url: str | None = None,
|
db_url: str | None = None,
|
||||||
|
*,
|
||||||
|
expected_setup_result: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the recorder asynchronously."""
|
"""Initialize the recorder asynchronously."""
|
||||||
# pylint: disable-next=import-outside-toplevel
|
# pylint: disable-next=import-outside-toplevel
|
||||||
@ -1408,10 +1410,13 @@ async def _async_init_recorder_component(
|
|||||||
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True):
|
with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True):
|
||||||
if recorder.DOMAIN not in hass.data:
|
if recorder.DOMAIN not in hass.data:
|
||||||
recorder_helper.async_initialize_recorder(hass)
|
recorder_helper.async_initialize_recorder(hass)
|
||||||
assert await async_setup_component(
|
setup_task = asyncio.ensure_future(
|
||||||
hass, recorder.DOMAIN, {recorder.DOMAIN: config}
|
async_setup_component(hass, recorder.DOMAIN, {recorder.DOMAIN: config})
|
||||||
)
|
)
|
||||||
assert recorder.DOMAIN in hass.config.components
|
# Wait for recorder integration to setup
|
||||||
|
setup_result = await setup_task
|
||||||
|
assert setup_result == expected_setup_result
|
||||||
|
assert (recorder.DOMAIN in hass.config.components) == expected_setup_result
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Test recorder successfully started, database location: %s",
|
"Test recorder successfully started, database location: %s",
|
||||||
config[recorder.CONF_DB_URL],
|
config[recorder.CONF_DB_URL],
|
||||||
@ -1527,10 +1532,16 @@ async def async_test_recorder(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType | None = None,
|
config: ConfigType | None = None,
|
||||||
*,
|
*,
|
||||||
|
expected_setup_result: bool = True,
|
||||||
wait_recorder: bool = True,
|
wait_recorder: bool = True,
|
||||||
) -> AsyncGenerator[recorder.Recorder]:
|
) -> AsyncGenerator[recorder.Recorder]:
|
||||||
"""Setup and return recorder instance.""" # noqa: D401
|
"""Setup and return recorder instance.""" # noqa: D401
|
||||||
await _async_init_recorder_component(hass, config, recorder_db_url)
|
await _async_init_recorder_component(
|
||||||
|
hass,
|
||||||
|
config,
|
||||||
|
recorder_db_url,
|
||||||
|
expected_setup_result=expected_setup_result,
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
instance = hass.data[recorder.DATA_INSTANCE]
|
instance = hass.data[recorder.DATA_INSTANCE]
|
||||||
# The recorder's worker is not started until Home Assistant is running
|
# The recorder's worker is not started until Home Assistant is running
|
||||||
@ -1557,12 +1568,18 @@ async def async_setup_recorder_instance(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType | None = None,
|
config: ConfigType | None = None,
|
||||||
*,
|
*,
|
||||||
|
expected_setup_result: bool = True,
|
||||||
wait_recorder: bool = True,
|
wait_recorder: bool = True,
|
||||||
) -> AsyncGenerator[recorder.Recorder]:
|
) -> AsyncGenerator[recorder.Recorder]:
|
||||||
"""Set up and return recorder instance."""
|
"""Set up and return recorder instance."""
|
||||||
|
|
||||||
return await stack.enter_async_context(
|
return await stack.enter_async_context(
|
||||||
async_test_recorder(hass, config, wait_recorder=wait_recorder)
|
async_test_recorder(
|
||||||
|
hass,
|
||||||
|
config,
|
||||||
|
expected_setup_result=expected_setup_result,
|
||||||
|
wait_recorder=wait_recorder,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
yield async_setup_recorder
|
yield async_setup_recorder
|
||||||
|
Loading…
x
Reference in New Issue
Block a user