feat: graceful shutdown with store persistence and restart overlay
Lint & Test / test (push) Failing after 29s
Lint & Test / test (push) Failing after 29s
- Add /api/v1/system/shutdown endpoint that triggers clean uvicorn exit - Persist all 15 stores to disk during shutdown via _save_all_stores() - Add force parameter to BaseJsonStore._save() to bypass restore freeze - Restart script now requests graceful shutdown via API (15s timeout), falls back to force-kill only if server doesn't exit in time - Broadcast server_restarting event over WebSocket before shutdown - Frontend shows "Server restarting..." overlay instantly on WS event, replacing the old dynamically-created overlay from settings.ts - Add server_ref module to share uvicorn Server + TrayManager refs - Add i18n keys for restart overlay (en/ru/zh)
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""Module-level holder for the uvicorn Server and TrayManager references.
|
||||
|
||||
Allows the shutdown API endpoint to trigger graceful shutdown via
|
||||
``server.should_exit = True`` + ``tray.stop()``, which is the same
|
||||
mechanism the system tray "Shutdown" menu item uses.
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
_server: Optional[Any] = None # uvicorn.Server
|
||||
_tray: Optional[Any] = None # TrayManager
|
||||
|
||||
|
||||
def set_server(server: Any) -> None:
|
||||
"""Store the uvicorn Server instance (called from __main__)."""
|
||||
global _server
|
||||
_server = server
|
||||
|
||||
|
||||
def set_tray(tray: Any) -> None:
|
||||
"""Store the TrayManager instance (called from __main__)."""
|
||||
global _tray
|
||||
_tray = tray
|
||||
|
||||
|
||||
def request_shutdown() -> None:
|
||||
"""Signal uvicorn + tray to perform a graceful shutdown.
|
||||
|
||||
Broadcasts a ``server_restarting`` event so the frontend can show
|
||||
a restart indicator, persists all stores to disk, then sets
|
||||
``should_exit = True`` on the uvicorn Server and stops the tray.
|
||||
"""
|
||||
# Notify connected clients that a restart is in progress
|
||||
_broadcast_restarting()
|
||||
|
||||
# Persist stores before signaling shutdown.
|
||||
# The lifespan shutdown handler also saves, but it may not run
|
||||
# reliably when uvicorn is in a daemon thread.
|
||||
try:
|
||||
from wled_controller.main import _save_all_stores
|
||||
_save_all_stores()
|
||||
except Exception:
|
||||
pass # best-effort; lifespan handler is the backup
|
||||
|
||||
if _server is not None:
|
||||
_server.should_exit = True
|
||||
if _tray is not None:
|
||||
_tray.stop()
|
||||
|
||||
|
||||
def _broadcast_restarting() -> None:
|
||||
"""Push a server_restarting event to all connected WebSocket clients."""
|
||||
try:
|
||||
from wled_controller.api.dependencies import _deps
|
||||
pm = _deps.get("processor_manager")
|
||||
if pm is not None:
|
||||
pm.fire_event({"type": "server_restarting"})
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user