Skip to content

Commit

Permalink
Fix race with _disconnect_monitor event in BlueZ (#999)
Browse files Browse the repository at this point in the history
If the device disconnects right away for any reason, the
on_connected_changed callback fires and clears the
disconnect_monitor_event which results in the following
exception:

```
  File "/usr/local/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 182, in connect
    asyncio.ensure_future(self._disconnect_monitor())
  File "/usr/local/lib/python3.10/asyncio/tasks.py", line 615, in ensure_future
    return _ensure_future(coro_or_future, loop=loop)
  File "/usr/local/lib/python3.10/asyncio/tasks.py", line 636, in _ensure_future
    return loop.create_task(coro_or_future)
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 240, in _disconnect_monitor
    await self._disconnect_monitor_event.wait()
AttributeError: NoneType object has no attribute wait
```
  • Loading branch information
bdraco authored Sep 12, 2022
1 parent 21e72cb commit df6985c
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Fixed
* Fixed inconsistent return types for ``properties`` and ``descriptors`` properties of ``BleakGATTCharacteristic``.
* Handle device being removed before GetManagedObjects returns in BlueZ backend. Fixes #996.
* Fixed crash in ``max_pdu_size_changed_handler`` in WinRT backend. Fixes #998.
* Fixes a race in the BlueZ D-Bus backend where the disconnect monitor would be removed before it could be awaited.

Removed
-------
Expand Down
17 changes: 12 additions & 5 deletions bleak/backends/bluezdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ def on_value_changed(char_path: str, value: bytes) -> None:

# Create a task that runs until the device is disconnected.
self._disconnect_monitor_event = asyncio.Event()
asyncio.ensure_future(self._disconnect_monitor())
asyncio.ensure_future(
self._disconnect_monitor(
self._bus, self._device_path, self._disconnect_monitor_event
)
)

#
# We will try to use the cache if it exists and `dangerous_use_bleak_cache`
Expand Down Expand Up @@ -226,7 +230,10 @@ def on_value_changed(char_path: str, value: bytes) -> None:
self._cleanup_all()
raise

async def _disconnect_monitor(self) -> None:
@staticmethod
async def _disconnect_monitor(
bus: MessageBus, device_path: str, disconnect_monitor_event: asyncio.Event
) -> None:
# This task runs until the device is disconnected. If the task is
# cancelled, it probably means that the event loop crashed so we
# try to disconnected the device. Otherwise BlueZ will keep the device
Expand All @@ -237,16 +244,16 @@ async def _disconnect_monitor(self) -> None:
# task will still be running and asyncio complains if a loop with running
# tasks is stopped.
try:
await self._disconnect_monitor_event.wait()
await disconnect_monitor_event.wait()
except asyncio.CancelledError:
try:
# by using send() instead of call(), we ensure that the message
# gets sent, but we don't wait for a reply, which could take
# over one second while the device disconnects.
await self._bus.send(
await bus.send(
Message(
destination=defs.BLUEZ_SERVICE,
path=self._device_path,
path=device_path,
interface=defs.DEVICE_INTERFACE,
member="Disconnect",
)
Expand Down

0 comments on commit df6985c

Please sign in to comment.