1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-02-14 23:19:37 +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:
Stefan Agner
2026-01-31 17:01:05 +01:00
committed by GitHub
parent 96d0593af2
commit 77f3da7014
2 changed files with 88 additions and 3 deletions

View File

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

View File

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