Bugfix: Ignore libusb callback scheduling after the asyncio loop is closed#947
Merged
barbibulle merged 2 commits intoJun 27, 2026
Merged
Conversation
barbibulle
approved these changes
Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
During an unclean USB transport shutdown, the asyncio loop can be closed while a USB transfer is still in flight. When libusb later invokes the transfer completion callback on its C callback thread, Bumble calls
loop.call_soon_threadsafe(...)on the closed loop. That raisesRuntimeError('Event loop is closed')on the C callback thread and can escalateinside libusb to a mutex assertion and process abort (
SIGABRT).To reproduce: Start a USB HCI transport, leave an IN or OUT transfer in flight, and close the asyncio loop before libusb delivers the transfer callback. Without this fix, the late callback raises
RuntimeError('Event loop is closed')on the libusb callback thread and can abort the process. With this fix, the late callback is ignored.Root Cause
The libusb event thread schedules back onto the asyncio loop at seven sites. Before this fix, each site called
loop.call_soon_threadsafe(...)directly, so a callback that arrived after loop close raised on the callback thread.The fix adds
_safe_call_soon()atbumble/transport/usb.py:41-bumble/transport/usb.py:51, which catches the closed-loopRuntimeErrorand turns late callbacks into no-ops. All libusb-event-thread scheduling sites now use it.