Skip to content

Bugfix - Keep HCI Number Of Completed Packets enabled in the host event mask#946

Merged
barbibulle merged 2 commits into
google:mainfrom
greateggsgreg:hci-num-completed-packets-enabled
Jun 27, 2026
Merged

Bugfix - Keep HCI Number Of Completed Packets enabled in the host event mask#946
barbibulle merged 2 commits into
google:mainfrom
greateggsgreg:hci-num-completed-packets-enabled

Conversation

@greateggsgreg

Copy link
Copy Markdown
Contributor

Summary

On controllers with a small LE ACL transmit buffer, Bumble can silently stop sending host-to-controller ACL data after the first controller-credit window is used. We reproduced this with an nRF52840 running Zephyr hci_usb, which reported total_num_le_acl_data_packets=3: the first writes reached the controller, then later ATT/L2CAP data stayed queued in Bumble and never reached HCI. No error is raised.

To reproduce: Use a controller that reports a small LE ACL buffer, such as Zephyr hci_usb on nRF52840 with total_num_le_acl_data_packets=3. Power on Bumble and perform a workflow that sends more than three outbound LE ACL packets. Without this fix, the first three packets leave Bumble, later packets remain queued forever, and no Python exception is raised. With this fix, completed-packets events restore credits and queued ACL data continues flowing.

Root Cause

Device.power_on() resets the host and explicitly sends HCI_Set_Event_Mask. That explicit mask omitted HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT, even though the controller default mask enables it. That disables HCI_Number_Of_Completed_Packets events, which are the only path that returns ACL transmit credits to Bumble's data queue:

  • The event-mask list is in bumble/host.py:401-bumble/host.py:421.
  • LE buffer sizing reads HCI_LE_Read_Buffer_Size[_V2] and stores total_num_le_acl_data_packets in the queue limit at bumble/host.py:552- bumble/host.py:590.
  • DataPacketQueue sends only while _in_flight < max_in_flight at bumble/host.py:140-bumble/host.py:147.
  • Completed-packets credits are consumed by on_hci_number_of_completed_packets_event() at bumble/host.py:1169-
    bumble/host.py:1175, which calls DataPacketQueue.on_packets_completed().
  • DataPacketQueue.on_packets_completed() decrements _in_flight and drains queued packets at bumble/host.py:149-bumble/host.py:180.

With HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT masked off, _in_flight reaches max_in_flight and never decreases, so later ACL packets enqueue indefinitely.

`Device.power_on()` resets the host and explicitly sends `HCI_Set_Event_Mask`. That explicit mask omitted `HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT`, even though the controller default mask enables it. That disables `HCI_Number_Of_Completed_Packets` events, which are the only path that returns ACL transmit credits to Bumble's data queue:

- The event-mask list is in `bumble/host.py:401`-`bumble/host.py:421`.
- LE buffer sizing reads `HCI_LE_Read_Buffer_Size[_V2]` and stores
  `total_num_le_acl_data_packets` in the queue limit at `bumble/host.py:552`-
  `bumble/host.py:590`.
- `DataPacketQueue` sends only while `_in_flight < max_in_flight` at
  `bumble/host.py:140`-`bumble/host.py:147`.
- Completed-packets credits are consumed by
  `on_hci_number_of_completed_packets_event()` at `bumble/host.py:1169`-
  `bumble/host.py:1175`, which calls `DataPacketQueue.on_packets_completed()`.
- `DataPacketQueue.on_packets_completed()` decrements `_in_flight` and drains
  queued packets at `bumble/host.py:149`-`bumble/host.py:180`.

With `HCI_NUMBER_OF_COMPLETED_PACKETS_EVENT` masked off, `_in_flight` reaches `max_in_flight` and never decreases, so later ACL packets enqueue indefinitely.

@barbibulle barbibulle left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed analysis and fix.

@barbibulle barbibulle merged commit 82a5acc into google:main Jun 27, 2026
60 checks passed
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