From baad839c588cdda1da1bff7cc2d62809d2e5e373 Mon Sep 17 00:00:00 2001 From: imonlinux Date: Fri, 2 Oct 2026 21:26:01 -0700 Subject: [PATCH] Tray: MQTT auto-retry for boot-time network race + visible systemctl failures Two related issues on workstation installs: - At login the tray can start before the network is up; the synchronous connect failed (Errno 101) with no retry, leaving the tray MQTT-dead for the whole session (mute mirroring and state colors silent). Use connect_async + loop_start with reconnect backoff (1-30s) so the connection establishes whenever the network comes up. - The service start/stop/restart actions ran systemctl with check=False and no output capture, so failed starts left no trace in the journal. Route them through a helper that logs the return code and stderr. --- linux_voice_assistant/tray_client/client.py | 46 ++++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/linux_voice_assistant/tray_client/client.py b/linux_voice_assistant/tray_client/client.py index 496e0f8f..79de9db3 100644 --- a/linux_voice_assistant/tray_client/client.py +++ b/linux_voice_assistant/tray_client/client.py @@ -127,10 +127,14 @@ def __init__(self, app: QtWidgets.QApplication, config: Config): _LOGGER.debug("MQTT connecting to %s:%s", self._mqtt_host, self._mqtt_port) try: - self._client.connect(self._mqtt_host, self._mqtt_port, 60) + # Async connect + network loop: at login the tray can start before + # the network is up, so the loop must keep retrying the connection + # (with backoff) until the broker is reachable. + self._client.reconnect_delay_set(min_delay=1, max_delay=30) + self._client.connect_async(self._mqtt_host, self._mqtt_port, 60) self._client.loop_start() except Exception: # noqa: BLE001 - _LOGGER.exception("Failed to connect to MQTT broker") + _LOGGER.exception("Failed to start MQTT loop") # Show tray icon self.setVisible(True) @@ -179,26 +183,36 @@ def _build_menu(self) -> None: self.setContextMenu(menu) - def _start_lva(self) -> None: - _LOGGER.info("Running: systemctl --user start %s", self._systemd_service_name) - subprocess.run( - ["systemctl", "--user", "start", self._systemd_service_name], + def _run_systemctl(self, action: str) -> None: + """Run a systemctl --user action on the LVA service, logging failures. + + Failures were previously swallowed (check=False, no output), which made + tray-initiated starts undiagnosable from the journal. + """ + _LOGGER.info("Running: systemctl --user %s %s", action, self._systemd_service_name) + result = subprocess.run( + ["systemctl", "--user", action, self._systemd_service_name], check=False, + capture_output=True, + text=True, ) + if result.returncode != 0: + _LOGGER.error( + "systemctl --user %s failed (rc=%d): %s %s", + action, + result.returncode, + result.stdout.strip(), + result.stderr.strip(), + ) + + def _start_lva(self) -> None: + self._run_systemctl("start") def _stop_lva(self) -> None: - _LOGGER.info("Running: systemctl --user stop %s", self._systemd_service_name) - subprocess.run( - ["systemctl", "--user", "stop", self._systemd_service_name], - check=False, - ) + self._run_systemctl("stop") def _restart_lva(self) -> None: - _LOGGER.info("Running: systemctl --user restart %s", self._systemd_service_name) - subprocess.run( - ["systemctl", "--user", "restart", self._systemd_service_name], - check=False, - ) + self._run_systemctl("restart") def _toggle_mute(self, checked: bool) -> None: self._mute_action.setChecked(checked)