Skip to content

fix: host race condition on hci acl data packets#935

Open
klow68 wants to merge 3 commits into
google:mainfrom
klow68:fix/acl-race-condition
Open

fix: host race condition on hci acl data packets#935
klow68 wants to merge 3 commits into
google:mainfrom
klow68:fix/acl-race-condition

Conversation

@klow68
Copy link
Copy Markdown
Contributor

@klow68 klow68 commented Jun 2, 2026

After some analysis, we found that we had some race condition on acl data packets which arrived before the connection complete

This PR add a fix for the race condition where ACL data can arrive before the corresponding Connection Complete event.
Use a buffer to store packets by handle and replaying them once the connection is established.
And buffered packets are removed after a delay of 100ms

Analysis:
When a BLE central connects to the nRF52840 dongle and immediately sends an SMP Pairing Request, the ACL data packet is delivered to Bumble before the LE Connection Complete event due to USB endpoint ordering, causing Bumble to drop the pairing request for an unknown connection handle and leaving the pairing stuck until the connection times out.

@klow68 klow68 force-pushed the fix/acl-race-condition branch from 2c3cfea to 4864e7e Compare June 8, 2026 11:59
Comment thread bumble/host.py
self.pairing_io_capability_provider = None # Classic only
self.snooper: Snooper | None = None
self._pending_acl: dict[int, list[tuple[float, hci.HCI_AclDataPacket]]] = {}
self._cleanup_task: asyncio.Task[None] | None = None
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.

It's probably not necessary to have an async cleanup task here. It should be sufficient to queue the packets that arrive before the connection event, and remove "stale" packets in the queue at that time. The queue will get emptied when the connection event is handled (after delivering the packets).

Comment thread bumble/host.py
else acl_timed_out
).append((ts, packet))
for ts, _ in acl_timed_out:
logger.info(
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.

Logging for each stale packet seems overly verbose (definitely too verbose at info level)

Comment thread bumble/host.py
for handle in list(self._pending_acl):
acl_to_keep: list[tuple[float, hci.HCI_AclDataPacket]] = []
acl_timed_out: list[tuple[float, hci.HCI_AclDataPacket]] = []
for ts, packet in self._pending_acl[handle]:
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.

Keeping a list of stale packets seems unnecessary. A simple list comprehension with a condition, or a filter, should be enough.

Comment thread bumble/host.py

async def _drain_buffered_acl(self, handle: int) -> None:
"""Replay all buffered ACL packet"""
await asyncio.sleep(0.1)
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.

What is the purpose of this?

Comment thread bumble/host.py
queued = self._pending_acl.pop(handle, [])
if not queued:
return
for ts, packet in queued:
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.

It might be a good idea to insert a sleep(0) to allow other tasks to do some work in between two packets (in a normal situation, there should be some task switching between ACL packet arrivals)

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