Skip to content

InvalidStateError: notify_future.set_result called on done Future in _notification_handler #64

Description

@shbatm

Summary

device_reader.py:280 calls self.notify_future.set_result(self.notify_response) on a future that may already be in the done state, raising asyncio.exceptions.InvalidStateError: invalid state.

The traceback floods Home Assistant logs (we're seeing 50+ occurrences over a single ~16 minute window) and shows up as Task exception was never retrieved because the resulting exception is never awaited.

Traceback

ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved (task: None)
Traceback (most recent call last):
  File "/usr/local/lib/python3.14/site-packages/bluetti_bt_lib/bluetooth/device_reader.py", line 280, in _notification_handler
    self.notify_future.set_result(self.notify_response)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
asyncio.exceptions.InvalidStateError: invalid state

Environment

  • bluetti-bt-lib: 0.1.6
  • Consumer: Patrick762/hassio-bluetti-bt v0.2.1 (Home Assistant custom integration, pins this lib at 0.1.6 in manifest.json)
  • Home Assistant: 2026.4.3 on HA OS 17.2 / Python 3.14
  • Device: Bluetti AC300

Likely cause

_notification_handler is invoked by bleak whenever a BLE notification arrives on the characteristic. If a second notification arrives before the awaiting consumer has cleared self.notify_future, the second call to set_result hits an already-completed future.

The same race likely fires when:

  • the BLE peer sends spurious / duplicated notifications
  • the read coroutine is cancelled after set_result is queued but before the future is consumed and reset

Suggested fix

Guard the set_result against the already-done state, e.g.:

def _notification_handler(self, ...):
    ...
    if self.notify_future is not None and not self.notify_future.done():
        self.notify_future.set_result(self.notify_response)

This keeps the first response and silently drops late-arriving duplicates — the consumer-side timeout already handles missed responses, so dropping is safe.

Acceptance

  • No InvalidStateError / Task exception was never retrieved traces in HA logs from device_reader.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions