Skip to content

Guard against InvalidStateError from stale BLE notifications in DeviceReader._notification_handler - #78

Open
shbatm wants to merge 1 commit into
Patrick762:mainfrom
shbatm:fix/notification-invalid-state-error
Open

Guard against InvalidStateError from stale BLE notifications in DeviceReader._notification_handler#78
shbatm wants to merge 1 commit into
Patrick762:mainfrom
shbatm:fix/notification-invalid-state-error

Conversation

@shbatm

@shbatm shbatm commented Jul 16, 2026

Copy link
Copy Markdown

Fixes #64

Problem

DeviceReader._notification_handler calls self.notify_future.set_result(...) unconditionally. If the pending response future is already done — resolved by an earlier notification, or cancelled by the 5s asyncio.wait_for timeout in _async_send_command — a late or duplicate BLE notification raises asyncio.InvalidStateError inside the notification task, which surfaces as an unhandled task exception.

Seen in the wild via the hassio-bluetti-bt custom integration (v0.2.2, pinning bluetti-bt-lib==0.1.7) on Home Assistant / Python 3.14, flooding the logs:

<class 'asyncio.exceptions.InvalidStateError'>: invalid state
future: <Task finished name='Task-XXXXXX'
  coro=<DeviceReader._notification_handler() done,
  defined at .../site-packages/bluetti_bt_lib/bluetooth/device_reader.py:252>
  exception=InvalidStateError('invalid state')>

Same root cause as the traceback reported in #64 (line 280 in 0.1.6 is the same set_result call).

Fix

Guard the resolution with notify_future.done() (which also covers cancellation) and drop the stale notification with a debug log so it stays diagnosable. The guard runs before notify_response.extend(data) so a late fragment can't mutate a bytearray result already delivered to the waiter in _async_send_command.

Dropping is safe: the first response wins, and the existing consumer-side timeout already handles genuinely missed responses.

Testing

  • Added test_notification_with_done_future covering both the duplicate-notification and cancelled-future (timeout) paths; it fails with InvalidStateError before the fix and passes after.
  • Full suite from Dockerfile.test passes (67 tests across tests/, fields/, registers/, base_devices/, devices/).
  • Changed files checked with black --target-version py310 (the remaining reformat hints in device_reader.py are on pre-existing lines, left untouched to keep the diff minimal).

No version bump included — left to the maintainer since setup.py takes the version from LIB_VERSION at release time.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GcnWB3thiiJUoaHHJDS9r5

A BLE notification arriving after the pending response future is
already done (resolved by an earlier notification, or cancelled by
the asyncio.wait_for timeout in _async_send_command) called
set_result on the done future, raising asyncio.InvalidStateError
inside the notification task.

Drop such late/duplicate notifications with a debug log instead.

Fixes Patrick762#64

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GcnWB3thiiJUoaHHJDS9r5
@Patrick762

Copy link
Copy Markdown
Owner

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

Labels

AI generated bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InvalidStateError: notify_future.set_result called on done Future in _notification_handler

2 participants