fix(shutdown): apply target stop actions before tearing down HA/MQTT
Reorder the lifespan shutdown so processor_manager.stop_all() runs before ha_manager.shutdown(), mqtt_manager.shutdown(), and mqtt_service.stop(). HA-light targets check `_ha_runtime.is_connected` before applying their `stop_action` (turn_off / restore) and silently skip when HA is already disconnected; MQTT-output devices need the broker connection alive to send restore frames. The previous order tore those down first, turning "stop_targets" into a no-op for those targets — most visible when closing via the tray Shutdown button. Also moves automation_engine.stop(), discovery_watcher.stop(), and the OS notification listener stop ahead of processor stop so they can no longer fire events into a shutting-down processor manager. Independent services (weather, update checker, auto-backup) now run last, where their order does not matter. Bonus: if the daemon-thread join times out (10 s) and the rest of shutdown is cut short, the user-visible part — targets stopping — has already run.
This commit is contained in:
+33
-31
@@ -394,52 +394,34 @@ async def lifespan(app: FastAPI):
|
|||||||
# where no CRUD happened during the session.
|
# where no CRUD happened during the session.
|
||||||
_save_all_stores()
|
_save_all_stores()
|
||||||
|
|
||||||
# Stop Home Assistant manager
|
# Stop automation engine first so it can no longer activate scenes that
|
||||||
try:
|
# would talk to processors mid-shutdown.
|
||||||
await ha_manager.shutdown()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error stopping Home Assistant manager: {e}")
|
|
||||||
|
|
||||||
# Stop weather manager
|
|
||||||
try:
|
|
||||||
weather_manager.shutdown()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error stopping weather manager: {e}")
|
|
||||||
|
|
||||||
# Stop update checker
|
|
||||||
try:
|
|
||||||
await update_service.stop()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error stopping update checker: {e}")
|
|
||||||
|
|
||||||
# Stop auto-backup engine
|
|
||||||
try:
|
|
||||||
await auto_backup_engine.stop()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error stopping auto-backup engine: {e}")
|
|
||||||
|
|
||||||
# Stop automation engine first (deactivates automation-managed scenes)
|
|
||||||
try:
|
try:
|
||||||
await automation_engine.stop()
|
await automation_engine.stop()
|
||||||
logger.info("Stopped automation engine")
|
logger.info("Stopped automation engine")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping automation engine: {e}")
|
logger.error(f"Error stopping automation engine: {e}")
|
||||||
|
|
||||||
# Stop discovery watcher (before health monitor stop so events still flow)
|
# Stop discovery watcher and OS notification listener so they stop
|
||||||
|
# firing events into a shutting-down processor manager.
|
||||||
if discovery_watcher is not None:
|
if discovery_watcher is not None:
|
||||||
try:
|
try:
|
||||||
await discovery_watcher.stop()
|
await discovery_watcher.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping discovery watcher: {e}")
|
logger.error(f"Error stopping discovery watcher: {e}")
|
||||||
|
|
||||||
# Stop OS notification listener
|
|
||||||
try:
|
try:
|
||||||
os_notif_listener.stop()
|
os_notif_listener.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping OS notification listener: {e}")
|
logger.error(f"Error stopping OS notification listener: {e}")
|
||||||
|
|
||||||
# Stop all processing.
|
# Stop all processing BEFORE tearing down ha_manager / mqtt_manager /
|
||||||
# The shutdown action setting controls whether per-device restore
|
# mqtt_service. HA-light targets need a live HA runtime to apply their
|
||||||
|
# stop_action (turn_off / restore), and MQTT-output devices need a live
|
||||||
|
# MQTT broker connection to send restore frames. Shutting those down
|
||||||
|
# first silently turns "stop_targets" into a no-op for those targets.
|
||||||
|
#
|
||||||
|
# The shutdown_action setting controls whether per-device restore
|
||||||
# frames are sent: "stop_targets" (default) runs the normal stop
|
# frames are sent: "stop_targets" (default) runs the normal stop
|
||||||
# sequence; "nothing" cancels capture tasks so the LEDs freeze on
|
# sequence; "nothing" cancels capture tasks so the LEDs freeze on
|
||||||
# their last frame.
|
# their last frame.
|
||||||
@@ -458,18 +440,38 @@ async def lifespan(app: FastAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping processors: {e}")
|
logger.error(f"Error stopping processors: {e}")
|
||||||
|
|
||||||
# Stop MQTT manager (entity-based broker connections)
|
# Now safe to tear down the connections that processors depended on.
|
||||||
|
try:
|
||||||
|
await ha_manager.shutdown()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping Home Assistant manager: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await mqtt_manager.shutdown()
|
await mqtt_manager.shutdown()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping MQTT manager: {e}")
|
logger.error(f"Error stopping MQTT manager: {e}")
|
||||||
|
|
||||||
# Stop MQTT service (legacy global connection)
|
|
||||||
try:
|
try:
|
||||||
await mqtt_service.stop()
|
await mqtt_service.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping MQTT service: {e}")
|
logger.error(f"Error stopping MQTT service: {e}")
|
||||||
|
|
||||||
|
# Independent services — order doesn't matter relative to processors.
|
||||||
|
try:
|
||||||
|
weather_manager.shutdown()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping weather manager: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await update_service.stop()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping update checker: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await auto_backup_engine.stop()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping auto-backup engine: {e}")
|
||||||
|
|
||||||
|
|
||||||
# Create FastAPI application
|
# Create FastAPI application
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
|
|||||||
Reference in New Issue
Block a user