mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-02-15 07:27:13 +00:00
Disable Home Assistant watchdog during system shutdown (#6512)
During system shutdown (reboot/poweroff), the watchdog was incorrectly detecting the Home Assistant Core container as failed and attempting to restart it. This occurred because Docker was stopping all containers in parallel with Supervisor's own shutdown sequence, causing the watchdog to trigger while add-ons were still being stopped. This led to an abrupt termination of Core before it could cleanly shut down its SQLite database, resulting in a warning on the next startup: "The system could not validate that the sqlite3 database was shutdown cleanly". The fix registers a supervisor state change listener that unregisters the watchdog when entering any shutdown state (SHUTDOWN, STOPPING, or CLOSE). This prevents restart attempts during both user-initiated reboots (via API) and external shutdown signals (Docker SIGTERM, console reboot commands). Since SHUTDOWN, STOPPING, and CLOSE are terminal states with no reverse transition back to RUNNING, no re-registration logic is needed. Fixes #6511 Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,8 @@ from awesomeversion import AwesomeVersion
|
||||
|
||||
from supervisor.utils import remove_colors
|
||||
|
||||
from ..const import ATTR_HOMEASSISTANT, BusEvent
|
||||
from ..bus import EventListener
|
||||
from ..const import ATTR_HOMEASSISTANT, BusEvent, CoreState
|
||||
from ..coresys import CoreSys
|
||||
from ..docker.const import ContainerState
|
||||
from ..docker.homeassistant import DockerHomeAssistant
|
||||
@@ -75,6 +76,7 @@ class HomeAssistantCore(JobGroup):
|
||||
super().__init__(coresys, JOB_GROUP_HOME_ASSISTANT_CORE)
|
||||
self.instance: DockerHomeAssistant = DockerHomeAssistant(coresys)
|
||||
self._error_state: bool = False
|
||||
self._watchdog_listener: EventListener | None = None
|
||||
|
||||
@property
|
||||
def error_state(self) -> bool:
|
||||
@@ -83,9 +85,12 @@ class HomeAssistantCore(JobGroup):
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Prepare Home Assistant object."""
|
||||
self.sys_bus.register_event(
|
||||
self._watchdog_listener = self.sys_bus.register_event(
|
||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, self.watchdog_container
|
||||
)
|
||||
self.sys_bus.register_event(
|
||||
BusEvent.SUPERVISOR_STATE_CHANGE, self._supervisor_state_changed
|
||||
)
|
||||
|
||||
try:
|
||||
# Evaluate Version if we lost this information
|
||||
@@ -558,6 +563,16 @@ class HomeAssistantCore(JobGroup):
|
||||
if event.state in [ContainerState.FAILED, ContainerState.UNHEALTHY]:
|
||||
await self._restart_after_problem(event.state)
|
||||
|
||||
async def _supervisor_state_changed(self, state: CoreState) -> None:
|
||||
"""Handle supervisor state changes to disable watchdog during shutdown."""
|
||||
if state in (CoreState.SHUTDOWN, CoreState.STOPPING, CoreState.CLOSE):
|
||||
if self._watchdog_listener:
|
||||
_LOGGER.debug(
|
||||
"Unregistering Home Assistant watchdog due to system shutdown"
|
||||
)
|
||||
self.sys_bus.remove_listener(self._watchdog_listener)
|
||||
self._watchdog_listener = None
|
||||
|
||||
@Job(
|
||||
name="home_assistant_core_restart_after_problem",
|
||||
throttle_period=WATCHDOG_THROTTLE_PERIOD,
|
||||
|
||||
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, PropertyMock, patch
|
||||
from aiodocker.containers import DockerContainer
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from supervisor.const import BusEvent
|
||||
from supervisor.const import BusEvent, CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.docker.const import ContainerState
|
||||
from supervisor.docker.monitor import DockerContainerStateEvent
|
||||
@@ -175,3 +175,73 @@ async def test_home_assistant_watchdog_skip_on_load(
|
||||
events.assert_not_called()
|
||||
restart.assert_not_called()
|
||||
start.assert_not_called()
|
||||
|
||||
|
||||
async def test_home_assistant_watchdog_unregisters_on_shutdown(
|
||||
coresys: CoreSys,
|
||||
) -> None:
|
||||
"""Test home assistant watchdog unregisters when entering shutdown states."""
|
||||
coresys.homeassistant.version = AwesomeVersion("2022.7.3")
|
||||
with (
|
||||
patch(
|
||||
"supervisor.docker.interface.DockerInterface.version",
|
||||
new=PropertyMock(return_value=AwesomeVersion("2022.7.3")),
|
||||
),
|
||||
patch.object(type(coresys.homeassistant.core.instance), "attach"),
|
||||
):
|
||||
await coresys.homeassistant.core.load()
|
||||
|
||||
coresys.homeassistant.core.watchdog = True
|
||||
|
||||
# Verify watchdog listener is registered
|
||||
assert coresys.homeassistant.core._watchdog_listener is not None
|
||||
watchdog_listener = coresys.homeassistant.core._watchdog_listener
|
||||
|
||||
with (
|
||||
patch.object(type(coresys.homeassistant.core), "restart") as restart,
|
||||
patch.object(type(coresys.homeassistant.core), "start") as start,
|
||||
patch.object(
|
||||
type(coresys.homeassistant.core.instance),
|
||||
"current_state",
|
||||
return_value=ContainerState.FAILED,
|
||||
),
|
||||
):
|
||||
# Watchdog should respond to events before shutdown
|
||||
coresys.bus.fire_event(
|
||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
||||
DockerContainerStateEvent(
|
||||
name="homeassistant",
|
||||
state=ContainerState.FAILED,
|
||||
id="abc123",
|
||||
time=1,
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
start.assert_called_once()
|
||||
start.reset_mock()
|
||||
|
||||
# Test each shutdown state
|
||||
for shutdown_state in (CoreState.SHUTDOWN, CoreState.STOPPING, CoreState.CLOSE):
|
||||
# Reload to reset listener
|
||||
coresys.homeassistant.core._watchdog_listener = watchdog_listener
|
||||
|
||||
# Fire shutdown state change
|
||||
coresys.bus.fire_event(BusEvent.SUPERVISOR_STATE_CHANGE, shutdown_state)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
# Verify watchdog listener is unregistered
|
||||
assert coresys.homeassistant.core._watchdog_listener is None
|
||||
|
||||
# Watchdog should not respond to events after shutdown
|
||||
coresys.bus.fire_event(
|
||||
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
||||
DockerContainerStateEvent(
|
||||
name="homeassistant",
|
||||
state=ContainerState.FAILED,
|
||||
id="abc123",
|
||||
time=1,
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
start.assert_not_called()
|
||||
restart.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user