Skip to content

fix: drain stale futures in __get_prop_handler on batch failure#1723

Open
vergil-zhao wants to merge 1 commit into
XiaoMi:mainfrom
vergil-zhao:fix/get-prop-handler-future-leak
Open

fix: drain stale futures in __get_prop_handler on batch failure#1723
vergil-zhao wants to merge 1 commit into
XiaoMi:mainfrom
vergil-zhao:fix/get-prop-handler-future-leak

Conversation

@vergil-zhao

@vergil-zhao vergil-zhao commented May 25, 2026

Copy link
Copy Markdown

Problem

When the cloud batch request inside __get_prop_handler raises (timeout, network error, transient HTTP failure), the pending futures registered in self._get_prop_list stay unresolved forever. Because get_prop_async looks up _get_prop_list[key] at the top and return await prop_obj['fut'] if found, every subsequent call with the same (did, siid, piid) reuses the dead future — the integration never recovers from a single batch failure until Home Assistant is restarted.

Reproduction

  1. Have any custom poller call get_prop_async periodically (e.g. an async_update override on a property entity, or homeassistant.update_entity triggered from an automation).
  2. Wrap the call in asyncio.wait_for(..., timeout=N) shorter than __mihome_api_post_async's 30 s timeout — common when working around HA's 10 s "entity update is taking over 10 seconds" warning.
  3. Make one batch hang past the caller's timeout (slow cloud response, proxy/DNS pollution, brief network blip).
  4. The caller's wait_for cancels, but the underlying future created at line 800 stays pending. From that moment on, every poll for that key resolves the same dead future → entity values silently freeze with no error in the logs.

In the affected integration the only path back to a working state is a HA restart that clears _get_prop_list.

Fix

Wrap the await self.get_props_async(...) call in try/except and fall through with results = []. The existing fallback loop right below already iterates every key still in props_req, pops it from _get_prop_list, and calls set_result(None) — so reusing that path drains all stale futures and frees the cache slots. The next aggregation cycle starts fresh.

The change is contained to one block in miot/miot_cloud.py::__get_prop_handler. No public surface, return value, or call site changes. The broad-except + lazy _LOGGER.error formatting follow the existing convention used elsewhere in miot_lan.py / miot_network.py.

Verification

  • pylint --rcfile=.pylintrc custom_components/xiaomi_home/miot/miot_cloud.py → 10.00/10
  • Manually verified in production: before the fix, a transient cloud hang caused property entities to permanently stop updating until HA restart; after the fix the next 30 s aggregation cycle recovers cleanly.

Possibly related issues

These open issues describe the exact symptom this fix addresses ("data stops updating, reload/restart restores it"), though they likely have multiple root causes — this PR only covers the cloud-batch-failure → future-leak path:

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.
@CLAassistant

CLAassistant commented May 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@vergil-zhao

Copy link
Copy Markdown
Author

Hit this in production today on a PTX 透明物联 three-phase breaker (cloud-poll path, exactly as described). Concrete numbers in case helpful for triage:

  • 7 sensor entities stuck for ~3.9 h — 465 consecutive async_update timed out warnings from a local wait_for(8s) wrapper around get_prop_async
  • During the entire hang, curl --max-time 10 https://ha.api.io.mi.com/ returned 204 in <200 ms — so the cloud was reachable, __get_prop_handler had simply raised once early on and the pending futures were dead
  • Recovery only via ha core restart (rebuilding MIoTHttp zeros _get_prop_list)
  • Symptom matches 设备状态不更新 #1334 / 花花草草植物检测仪状态不更新 #1698 exactly

Independently arrived at the same diff before finding this PR — confirming both the diagnosis and that this is the right fix. Any chance of a re-review ping? Happy to address feedback or sign whatever's needed if anything's blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants