From cf2a4bf649bfff74737e3ae0b10e10f76cb3a5c9 Mon Sep 17 00:00:00 2001 From: Vergil Zhao Date: Thu, 24 Sep 2026 12:17:12 +0800 Subject: [PATCH] fix: drain stale futures in __get_prop_handler on batch failure (#1723) When get_props_async raises (cloud timeout, network error, etc.) the pending futures registered in self._get_prop_list stay unresolved forever. Subsequent get_prop_async calls with the same (did, siid, piid) key reuse the dead future via the cache lookup at the top of get_prop_async, so the integration never recovers from a single batch failure until Home Assistant is restarted. This was reproducible whenever the cloud HTTP request hung past the caller's wait_for timeout: every later poll for that key returned the same cancelled-but-still-pending future. Property entities driven by async_update silently stopped updating with no error in the logs. Wrap the await in try/except and fall through with results=[]. The existing fallback loop then drains every key still in props_req with set_result(None), unblocking awaiters and freeing the cache slot so the next aggregation cycle starts fresh. --- custom_components/xiaomi_home/miot/miot_cloud.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index ed672a0..2da33a2 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -755,7 +755,18 @@ class MIoTHttpClient: if not props_buffer: _LOGGER.error('get prop error, empty request list') return False - results = await self.get_props_async(props_buffer) + try: + results = await self.get_props_async(props_buffer) + except Exception as err: # pylint: disable=broad-exception-caught + # If the batch request fails (timeout, network error, etc.), the + # pending futures in self._get_prop_list would otherwise stay + # unresolved forever. Subsequent get_prop_async calls with the + # same key reuse the dead future via the cache lookup at the top + # of get_prop_async, so the integration would never recover until + # restart. Fall through with empty results so the loop below + # drains every requested key with set_result(None). + _LOGGER.error('get prop batch failed, %s', err) + results = [] for result in results: if not all(