Skip to content

Commit 0df6a30

Browse files
tomer-wCopilot
andcommitted
Fix boot race condition: catch NotConnectedError and stop leaked paho threads
When HA and Victron boot simultaneously after a power outage, several failure paths can occur: 1. NotConnectedError escapes Hub.start() uncaught — HA treats this as a permanent failure (no retry), so the integration never recovers. Fix: catch NotConnectedError in Hub.start() and raise ConfigEntryNotReady so HA retries with exponential backoff. 2. Paho MQTT thread leak — connect() calls loop_start() early, but if any subsequent step fails (timeout, connection drop, etc.), loop_stop() is never called. Each HA retry creates a new Hub, leaking the old thread. Fix: wrap post-loop_start() code in try/except that calls loop_stop() on failure. 3. disconnect() doesn't call loop_stop() — the background thread started by loop_start() is never explicitly stopped. Fix: add loop_stop() call in disconnect(). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6374e2f1-1fb3-453d-a1c2-dddf69a62f61
1 parent ac92079 commit 0df6a30

2 files changed

Lines changed: 21 additions & 8 deletions

File tree

  • custom_components/victron_mqtt

custom_components/victron_mqtt/_vendor/victron_mqtt/hub.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,21 @@ async def connect(self) -> None:
368368

369369
_LOGGER.info("Starting paho mqtt")
370370
self._client.loop_start()
371-
_LOGGER.info("Connecting")
372-
self._client.connect_async(self.host, self.port)
373-
_LOGGER.info("Waiting for connection event")
374-
await self._wait_for_connect()
375-
if self._connect_failed_reason is not None:
376-
reraise_same_exception(self._connect_failed_reason)
377-
_LOGGER.info("Successfully connected to MQTT broker at %s:%d", self.host, self.port)
378-
await self._wait_for_installation_id(expected_id=self._expected_installation_id)
371+
try:
372+
_LOGGER.info("Connecting")
373+
self._client.connect_async(self.host, self.port)
374+
_LOGGER.info("Waiting for connection event")
375+
await self._wait_for_connect()
376+
if self._connect_failed_reason is not None:
377+
reraise_same_exception(self._connect_failed_reason)
378+
_LOGGER.info("Successfully connected to MQTT broker at %s:%d", self.host, self.port)
379+
await self._wait_for_installation_id(expected_id=self._expected_installation_id)
380+
except Exception:
381+
# If anything fails after loop_start(), stop the paho thread to avoid leaking it.
382+
# On HA retry a new Hub and paho client will be created.
383+
_LOGGER.info("Connection setup failed, stopping paho mqtt loop")
384+
self._client.loop_stop()
385+
raise
379386
assert self._installation_id is not None
380387
# First we need to replace the installation ID in the subscription topics
381388
new_list: list[str] = []
@@ -839,6 +846,7 @@ async def disconnect(self) -> None:
839846
self._stop_keepalive_loop()
840847
await asyncio.sleep(0.1)
841848
self._client.disconnect() # need to call disconnect so the paho thread will terminate
849+
self._client.loop_stop() # stop the background thread started by loop_start()
842850
_LOGGER.info("Disconnected from MQTT broker")
843851
# Give a small delay to allow any pending MQTT messages to be processed
844852
await asyncio.sleep(0.1)

custom_components/victron_mqtt/hub.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Hub as VictronVenusHub,
1212
Metric as VictronVenusMetric,
1313
MetricKind,
14+
NotConnectedError,
1415
OperationMode,
1516
)
1617

@@ -122,6 +123,10 @@ async def start(self) -> None:
122123
raise ConfigEntryNotReady(
123124
f"Cannot connect to the hub: {connect_error}"
124125
) from connect_error
126+
except NotConnectedError as connect_error:
127+
raise ConfigEntryNotReady(
128+
f"Connection lost during setup (possible boot race): {connect_error}"
129+
) from connect_error
125130

126131
async def stop(self) -> None:
127132
"""Stop the Victron MQTT hub."""

0 commit comments

Comments
 (0)