Skip to content

Fix coordinator stall on transient API errors (retry backoff unit + raise UpdateFailed) - #120

Open
QuintinHumphreys wants to merge 1 commit into
radical-squared:Custom-componentfrom
QuintinHumphreys:fix/coordinator-stall-retry-backoff
Open

QuintinHumphreys wants to merge 1 commit into
radical-squared:Custom-componentfrom
QuintinHumphreys:fix/coordinator-stall-retry-backoff

Conversation

@QuintinHumphreys

Copy link
Copy Markdown

Problem

After a transient AquaTemp cloud/API error (or a failed poll around an HA restart), the climate + sensor entities stop updating and stay frozen until the config entry is reloaded. HA's DataUpdateCoordinator is meant to auto-retry on its interval, so a persistent stall points at the integration breaking that contract.

There are two root causes in managers/aqua_temp_api.py.

1. Retry backoff uses the wrong time unit (1000 s instead of 1 s)

asyncio.sleep takes seconds, but the retry delay is await sleep(1000) in both _internal_update and _perform_action:

if attempt < API_MAX_ATTEMPTS:
    await sleep(1000)   # 1000 seconds = ~16.7 minutes, not 1000 ms
    await self._internal_update(attempt + 1)

_async_update_data awaits _internal_update, so a single transient error blocks the coordinator's update method for 1000 s per retry - up to ~33 minutes across API_MAX_ATTEMPTS = 3. During that window the entities look frozen. The value was evidently intended as a short (1 s) backoff.

2. _internal_update swallows the error after exhausting retries

After the final attempt, _internal_update only logs and returns normally, so _async_update_data returns stale data as a success. The coordinator sets last_update_success = True, never raises UpdateFailed, and the entities never go unavailable - the failure is completely hidden. This is also inconsistent with _perform_action, which correctly re-raises after max attempts.

Fix

  • Change both retry delays from await sleep(1000) to await sleep(1).
  • Re-raise the captured error in _internal_update after the last attempt, so the coordinator's existing try/except wraps it in UpdateFailed and HA's normal retry/backoff engages.
if error is not None:
    if attempt < API_MAX_ATTEMPTS:
        await sleep(1)
        await self._internal_update(attempt + 1)
    else:
        _LOGGER.error(
            f"Failed to update (Attempt #{attempt}), Error: {error}, Line: {line_number}"
        )
        raise error

Testing

Applied to a live install (single AquaTemp heat pump). After ha core check + restart the integration loads cleanly (config entry state loaded), the API-status binary sensor is on, and temperature sensors report fresh live values on the normal poll interval. No UpdateFailed or login errors in the log.

🤖 Generated with Claude Code

Two defects in AquaTempAPI caused the coordinator to appear "stalled"
(entities stop updating) after a transient cloud/API error, requiring a
config-entry reload to recover:

1. Retry backoff used the wrong unit. `asyncio.sleep` takes SECONDS, but
   the retry delay was `await sleep(1000)` in both `_internal_update` and
   `_perform_action`. On any transient error the coordinator's update
   method blocked for 1000s per retry (up to ~33 min across
   API_MAX_ATTEMPTS=3), so entities looked frozen. Changed to
   `await sleep(1)` (1s backoff, the evidently intended value).

2. `_internal_update` swallowed the error after exhausting retries: it
   logged and returned normally, so `_async_update_data` returned stale
   data as a success. The DataUpdateCoordinator therefore never raised
   UpdateFailed, entities never went `unavailable`, and the failure was
   hidden. This is inconsistent with `_perform_action`, which re-raises
   after max attempts. Now re-raise so the coordinator reports
   UpdateFailed and HA's normal retry/backoff engages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant