From df6985c994f07b6f4604b579464a0d36ae803927 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Sep 2022 11:21:39 -0500 Subject: [PATCH] Fix race with _disconnect_monitor event in BlueZ (#999) 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 ``` --- CHANGELOG.rst | 1 + bleak/backends/bluezdbus/client.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d519eb16..1644c665 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ------- diff --git a/bleak/backends/bluezdbus/client.py b/bleak/backends/bluezdbus/client.py index 94067b13..41c24e2c 100644 --- a/bleak/backends/bluezdbus/client.py +++ b/bleak/backends/bluezdbus/client.py @@ -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` @@ -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 @@ -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", )