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.
|
||||
_save_all_stores()
|
||||
|
||||
# Stop Home Assistant manager
|
||||
try:
|
||||
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)
|
||||
# Stop automation engine first so it can no longer activate scenes that
|
||||
# would talk to processors mid-shutdown.
|
||||
try:
|
||||
await automation_engine.stop()
|
||||
logger.info("Stopped automation engine")
|
||||
except Exception as 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:
|
||||
try:
|
||||
await discovery_watcher.stop()
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping discovery watcher: {e}")
|
||||
|
||||
# Stop OS notification listener
|
||||
try:
|
||||
os_notif_listener.stop()
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping OS notification listener: {e}")
|
||||
|
||||
# Stop all processing.
|
||||
# The shutdown action setting controls whether per-device restore
|
||||
# Stop all processing BEFORE tearing down ha_manager / mqtt_manager /
|
||||
# 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
|
||||
# sequence; "nothing" cancels capture tasks so the LEDs freeze on
|
||||
# their last frame.
|
||||
@@ -458,18 +440,38 @@ async def lifespan(app: FastAPI):
|
||||
except Exception as 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:
|
||||
await mqtt_manager.shutdown()
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping MQTT manager: {e}")
|
||||
|
||||
# Stop MQTT service (legacy global connection)
|
||||
try:
|
||||
await mqtt_service.stop()
|
||||
except Exception as 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
|
||||
app = FastAPI(
|
||||
|
||||
Reference in New Issue
Block a user