Run interface poll on the event loop (fix async_dispatcher_send off-thread)
The interface-status poll was registered as a plain lambda, so async_track_time_interval ran it in an executor thread, where its _push_state()->async_dispatcher_send() call tripped HA's thread-safety guard. Make refresh_interface_status a @callback (it only reads fast in-memory interface attributes) and pass it to async_track_time_interval directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,10 +114,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
await _safe_announce(manager)
|
await _safe_announce(manager)
|
||||||
_schedule_periodic(hass, entry, runtime)
|
_schedule_periodic(hass, entry, runtime)
|
||||||
|
|
||||||
# Poll interface status.
|
# Poll interface status. refresh_interface_status is a @callback, so pass it
|
||||||
|
# directly (a lambda would be a plain sync job and get run in an executor
|
||||||
|
# thread, where its async_dispatcher_send call is not thread-safe).
|
||||||
runtime.unsubs.append(
|
runtime.unsubs.append(
|
||||||
async_track_time_interval(
|
async_track_time_interval(
|
||||||
hass, lambda _now: manager.refresh_interface_status(), INTERFACE_POLL
|
hass, manager.refresh_interface_status, INTERFACE_POLL
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,5 +12,5 @@
|
|||||||
"loggers": ["RNS", "LXMF"],
|
"loggers": ["RNS", "LXMF"],
|
||||||
"requirements": ["rns>=0.9.0", "lxmf>=0.6.0"],
|
"requirements": ["rns>=0.9.0", "lxmf>=0.6.0"],
|
||||||
"single_config_entry": true,
|
"single_config_entry": true,
|
||||||
"version": "1.0.4"
|
"version": "1.0.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -612,10 +612,13 @@ class ReticulumManager:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
def refresh_interface_status(self) -> None:
|
@callback
|
||||||
|
def refresh_interface_status(self, now: Any = None) -> None:
|
||||||
"""Recompute interface online status and collect telemetry.
|
"""Recompute interface online status and collect telemetry.
|
||||||
|
|
||||||
Called on the event loop from a periodic timer. RX/TX byte counters and
|
Decorated with @callback and used directly as the async_track_time_interval
|
||||||
|
action so it runs on the event loop (only fast in-memory interface
|
||||||
|
attribute reads happen here, no blocking I/O). RX/TX byte counters and
|
||||||
bitrate are available for the TCP client interface; RSSI/SNR/quality are
|
bitrate are available for the TCP client interface; RSSI/SNR/quality are
|
||||||
only populated when the underlying interface is a physical one (e.g. an
|
only populated when the underlying interface is a physical one (e.g. an
|
||||||
RNode/LoRa interface) and stay ``None`` for a plain TCP link.
|
RNode/LoRa interface) and stay ``None`` for a plain TCP link.
|
||||||
|
|||||||
Reference in New Issue
Block a user