Host._send_command does not release command_semaphore on timeout, causing subsequent commands to hang
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- python
- Domain
- embedded-iot
Research direction
Start at Host._send_command and run the reproduction script from the issue to observe the timeout and the subsequent command hang. Trace the finally-block semaphore handling, then add or update coverage for a timed-out command followed by another command. Done means the second command no longer blocks indefinitely after the first timeout.
Written by the indexing model from the issue text.
Description
When Host._send_command times out waiting for an HCI response, the command_semaphore is never released. Every subsequent call to _send_command then blocks indefinitely at await self.command_semaphore.acquire(), making the host permanently unresponsive with no error or exception.
The release in the finally block is gated on response is not None. On a timeout, response remains None, so the semaphore is never released:
response = None
try:
...
response = await asyncio.wait_for(self.pending_response, timeout=response_timeout)
return response
finally:
if (
response is not None # <-- None on timeout
and response.num_hci_command_packets
and self.command_semaphore.locked()
):
self.command_semaphore.release() # <-- never reached
I'm wondering if this is intentional, to perhaps indicate that the controller is unresponsive/pending? The controller I'm using definitely isn't the most reliable (Raspberry Pi 3B+), but I'm not sure how to detect / recover from this, other than wrapping all bumble calls in a timeout.
There was about 3 minutes between when I issued the command that timed out (!!! Command HCI_LE_CLEAR_FILTER_ACCEPT_LIST_COMMAND timed out) to when I tried issuing the next command.
I haven't been able to capture any proper logs of this happening on real hardware, but this script reproduces what I've seen (reproduction code generated with Claude).
import asyncio
from bumble import hci
from bumble.controller import Controller
from bumble.device import Device
from bumble.host import Host
from bumble.link import LocalLink
from bumble.transport.common import AsyncPipeSink
def make_device() -> Device:
link = LocalLink()
controller = Controller("test-controller", link=link)
device = Device(host=Host())
device.host.set_packet_sink(controller)
controller.host = AsyncPipeSink(device.host)
return device
async def main():
device = make_device()
await device.power_on()
print("Step 1: Send a command with a near-zero timeout to force a timeout...")
try:
await device.host._send_command(
hci.HCI_LE_Clear_Filter_Accept_List_Command(),
response_timeout=0.00001,
)
except TimeoutError:
print(" -> Timed out as expected.")
print("Step 2: Send a normal command — hangs forever if semaphore was leaked...")
try:
await asyncio.wait_for(
device.host._send_command(
hci.HCI_LE_Clear_Filter_Accept_List_Command(),
),
timeout=3.0,
)
print(" -> Completed (bug is fixed).")
except asyncio.TimeoutError:
print(
" -> HUNG for 3 seconds and timed out: semaphore was leaked. Bug confirmed."
)
asyncio.run(main())
- Dominant language
- Python
- Stars
- 556
- Forks
- 138
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 9
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from google/bumble
-
Difficulty 4/5 3-5 days Newbie friendliness 58/100
-
Difficulty 4/5 3-5 days Newbie friendliness 38/100
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
Difficulty 3/5 1-2 days Newbie friendliness 72/100
-
cannot download Open
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100