Files
supervisor/tests/api/middleware
f8ba166ed2 Enter STOPPING state before Supervisor restart API response (#7085)
* Enter STOPPING state before Supervisor restart API response

The Supervisor accepts new API requests in the window after responding
to /supervisor/restart: restart() schedules Core.stop() as a task and
returns immediately, so the state only changes to stopping after the
restart response has been sent. A request accepted in that window keeps
running while the Supervisor shuts down, and when stop() tears down the
API server after the 10 second stage 1 timeout the connection is
dropped without a response - the client sees EOF instead of an error.
This is what made the CI restore step flaky (see #7084 for the CI-side
fix): the restore request landed on the old, dying instance.

Make restart() transition to STOPPING before returning, via a new
Core.begin_stop() which contains the transition part previously at the
start of Core.stop(). The system validation middleware already rejects
requests outside of STARTUP/RUNNING/FREEZE, so any request arriving
after the restart response now gets a clear "System is not ready" error
instead of possibly being accepted and killed. Since the state can now
already be STOPPING when stop() runs, its re-entry guard is changed
from a state check to an explicit flag.

Supervisor.update() schedules the same stop task but is left unchanged:
entering STOPPING before update() returns would suppress the final job
progress event to Home Assistant (WebSocket messages are dropped in
CLOSING_STATES), regressing update progress reporting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Allow stop() retry when begin_stop() fails

Address review feedback: stop() set _stop_initiated before awaiting
begin_stop(), so an exception or cancellation during the state
transition (e.g. a failing config write in _update_last_boot) would
leave the flag set and turn every later stop() call into a no-op,
with no way to retry the teardown.

Reset the flag and re-raise when begin_stop() fails. Nothing has been
torn down at that point, so a retry is safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Use a stopping_complete event instead of begin_stop()

Address review feedback questioning the extra infrastructure: instead
of splitting the state transition out of stop() (begin_stop()) and
guarding re-entry with an explicit flag, have stop() take an optional
stopping_complete event which is set once the STOPPING state is
entered, following the same pattern as backup/restore's
validation_complete. Core.stop() keeps its original structure and
re-entry semantics, and restart() waits for the event before
returning.

Should the stop task fail ahead of the state transition (only
possible through a failing config write in _update_last_boot()), the
event is never set and the restart request runs into the client
timeout - an accepted trade-off to keep restart() simple.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 13:14:55 +02:00
..