From ca8ed250c23d55842def835ad69548bf44bc7f48 Mon Sep 17 00:00:00 2001 From: Alexander Tihoniuk Date: Fri, 12 Jun 2026 10:54:56 +0300 Subject: [PATCH] fix(thread-safety): marshal pyhon MQTT push updates onto the event loop PR #43 (cherry-picked in the previous commit) only swaps async_write_ha_state() for schedule_update_ha_state() inside entity command methods, which run on the event loop anyway. It does NOT address the actual root cause of mmalolepszy/hon-revived#44. pyhon-revived delivers MQTT push notifications from the awscrt network thread: MQTTClient._on_publish_received -> Hon.notify -> self._notify_function(None), with no loop marshalling. The integration registered coordinator.async_set_updated_data directly as that subscriber, so the loop-affine @callback (which fans out to listeners and arms loop timers via _schedule_refresh) was being invoked from a foreign thread. HA Core 2026.5.x enforces thread safety and raises on this, breaking live state updates on 2026.5.4 even with PR #43 applied. Wrap the subscriber in a @callback that re-dispatches via hass.loop.call_soon_threadsafe, so the coordinator update always runs on the event loop. The None payload is preserved (entities read state from self._device, not coordinator.data, so it is only a listener trigger). Refs: https://developers.home-assistant.io/docs/asyncio_thread_safety/ Fixes mmalolepszy/hon-revived#44 Co-Authored-By: Claude Fable 5 --- custom_components/hon/__init__.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/custom_components/hon/__init__.py b/custom_components/hon/__init__.py index e29abd7..d76fae7 100644 --- a/custom_components/hon/__init__.py +++ b/custom_components/hon/__init__.py @@ -6,7 +6,7 @@ import voluptuous as vol # type: ignore[import-untyped] from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.helpers import config_validation as cv, aiohttp_client -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from pyhon import Hon @@ -48,7 +48,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator: DataUpdateCoordinator[dict[str, Any]] = DataUpdateCoordinator( hass, _LOGGER, name=DOMAIN ) - hon.subscribe_updates(coordinator.async_set_updated_data) + + @callback + def _push_update(data: Any) -> None: + """Apply a pyhon push update on the event loop. + + pyhon-revived delivers MQTT push notifications from the awscrt + network thread and invokes this subscriber synchronously (see + pyhon.connection.mqtt.MQTTClient._on_publish_received -> + Hon.notify). Calling the loop-affine ``async_set_updated_data`` + directly from that foreign thread raises a thread-safety error on + HA Core 2026.5.x, so we marshal it onto the event loop. + """ + hass.loop.call_soon_threadsafe(coordinator.async_set_updated_data, data) + + hon.subscribe_updates(_push_update) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = {"hon": hon, "coordinator": coordinator}