v1.45.2: hardening from the v1.45.1 review — R4-1, R4-2

R4-1: collecting superseded plan files runs after the configuration is already
durable, but an error listing the directory propagated out of config/set. The
client saw a failure for a revision the server had committed, and its retry
came back as a conflict. collect_plans now reports 0 instead of raising, and
config/set logs and proceeds — the event fires, the revision is returned.

R4-2: the pending set was cleared when a batch went out, not when it came back,
so every render during an in-flight content/sign queued another request: six
calls where one was needed, and unbounded on a socket that is slow rather than
busy. Queued and in-flight are separate states now; a failure backs off (2 s
doubling to 60 s) instead of retrying on the next frame; an in-flight entry
expires after 15 s so a promise that never settles cannot wedge retries; a late
answer after dispose() no longer renders.

Tests: test/signing.test.mjs — eight cases with hand-settled promises, verified
against a v1.45.1 checkout where four of them fail (2 sign calls instead of 1,
no backoff, a late answer rendering after teardown). Backend: a broken
collector still yields a successful save whose revision the next CAS accepts.
Pure collector: a disappearing directory returns 0.
Docs: CHANGELOG.md + CHANGELOG.ru.md + ARCHITECTURE.md + TESTING.md + STATUS.md.
This commit is contained in:
Matysh
2026-07-28 00:13:45 +03:00
parent c749b52a0d
commit 2e2d353b04
21 changed files with 434 additions and 72 deletions
+12 -5
View File
@@ -363,11 +363,18 @@ async def ws_config_set(hass: HomeAssistant, connection, msg: dict[str, Any]) ->
return
new_rev = current_rev + 1
await rt.config_store.async_save({"config": msg["config"], "rev": new_rev})
# still holding the lock: the file system is not part of the store's
# transaction, so collection has to be pinned to this commit (R3-1)
await hass.async_add_executor_job(
collect_plans, Path(hass.config.path(PLANS_DIR)), data.get("config"), msg["config"]
)
# Still holding the lock: the file system is not part of the store's
# transaction, so collection has to be pinned to this commit (R3-1).
# It is best-effort housekeeping behind an already durable write — a
# failure here must not withhold the event and the success response,
# or the client retries an edit the server has already accepted and
# gets a conflict for its trouble (R4-1).
try:
await hass.async_add_executor_job(
collect_plans, Path(hass.config.path(PLANS_DIR)), data.get("config"), msg["config"]
)
except Exception: # noqa: BLE001 — see above: the commit stands regardless
_LOGGER.exception("House Plan: collecting superseded plan files failed")
hass.bus.async_fire("houseplan_config_updated", {"rev": new_rev})
# refresh repair issues (broken plan references) without waiting for a restart
entry = get_entry(hass)