1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-04-02 00:07:16 +01:00

Guard shutdown() for STOPPING/CLOSE and unload host early in stop()

Move host.unload() before Stage 1 in stop() so the shutdown monitor
task is cancelled before infrastructure teardown begins. This prevents
a race where the monitor could react to PrepareForShutdown after
stop() has already started tearing down.

For the remaining edge case where shutdown() is called while stop()
is running, log a warning and return immediately instead of awaiting
the shutdown event (which would deadlock since stop() never sets it).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Agner
2026-03-16 14:22:25 +01:00
parent 67ff4635eb
commit 58bb0c909d
2 changed files with 26 additions and 2 deletions

View File

@@ -315,6 +315,9 @@ class Core(CoreSysAttributes):
# don't process scheduler anymore
await self.set_state(CoreState.STOPPING)
# Cancel shutdown monitor task before tearing down infrastructure
await self.sys_host.unload()
# Stage 1
try:
async with asyncio.timeout(10):
@@ -341,7 +344,6 @@ class Core(CoreSysAttributes):
self.sys_websession.close(),
self.sys_ingress.unload(),
self.sys_hardware.unload(),
self.sys_host.unload(),
self.sys_dbus.unload(),
)
]
@@ -359,7 +361,13 @@ class Core(CoreSysAttributes):
Reentrant: if a shutdown is already in progress, subsequent calls
await completion of the existing shutdown rather than starting a second one.
"""
if self.state in (CoreState.SHUTDOWN, CoreState.STOPPING, CoreState.CLOSE):
# Supervisor is already tearing down, no point running shutdown
if self.state in (CoreState.STOPPING, CoreState.CLOSE):
_LOGGER.warning("Ignoring shutdown request, Supervisor is already stopping")
return
# Another shutdown is in progress, wait for it to complete
if self.state == CoreState.SHUTDOWN:
await self._shutdown_event.wait()
return

View File

@@ -211,3 +211,19 @@ async def test_shutdown_event_reset_between_cycles(coresys: CoreSys):
assert second_entered
assert coresys.core._shutdown_event.is_set()
@pytest.mark.parametrize(
"state", [CoreState.STOPPING, CoreState.CLOSE], ids=["stopping", "close"]
)
async def test_shutdown_ignored_during_stop(
coresys: CoreSys, caplog: pytest.LogCaptureFixture, state: CoreState
):
"""Test that shutdown is ignored when Supervisor is already stopping."""
await coresys.core.set_state(state)
with patch.object(coresys.addons, "shutdown") as mock_addon_shutdown:
await coresys.core.shutdown()
mock_addon_shutdown.assert_not_called()
assert "Ignoring shutdown request, Supervisor is already stopping" in caplog.text