Skip to content

Commit

Permalink
Index BlueZ advertisement_callbacks by adapter (#1632)
Browse files Browse the repository at this point in the history
Instead of doing a linear search of all the callbacks
to find the one for the adapter, store them in a dict
so the adapter path can be looked up for better
performance
  • Loading branch information
bdraco authored Aug 22, 2024
1 parent a065527 commit 7130c40
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0
Changed
-------
* In bleak.backends.winrt.util the SetTimer, KillTimer and CoGetApartmentType functions define their own prototype and don't change ctypes' global state anymore
* Improved performance of BlueZ backend when there are many adapters.

`0.22.2`_ (2024-06-01)
======================
Expand Down
51 changes: 20 additions & 31 deletions bleak/backends/bluezdbus/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import contextlib
import logging
import os
from collections import defaultdict
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -54,22 +55,6 @@
"""


class CallbackAndState(NamedTuple):
"""
Encapsulates an :data:`AdvertisementCallback` and some state.
"""

callback: AdvertisementCallback
"""
The callback.
"""

adapter_path: str
"""
The D-Bus object path of the adapter associated with the callback.
"""


DevicePropertiesChangedCallback = Callable[[Optional[Any]], None]
"""
A callback that is called when the properties of a device change in BlueZ.
Expand Down Expand Up @@ -193,7 +178,9 @@ def __init__(self):
# map of characteristic d-bus object paths to set of descriptor d-bus object paths
self._descriptor_map: Dict[str, Set[str]] = {}

self._advertisement_callbacks: List[CallbackAndState] = []
self._advertisement_callbacks: defaultdict[str, List[AdvertisementCallback]] = (
defaultdict(list)
)
self._device_removed_callbacks: List[DeviceRemovedCallbackAndState] = []
self._device_watchers: Dict[str, Set[DeviceWatcher]] = {}
self._condition_callbacks: Dict[str, Set[DeviceConditionCallback]] = {}
Expand Down Expand Up @@ -403,8 +390,7 @@ async def active_scan(
# error message.
self._check_adapter(adapter_path)

callback_and_state = CallbackAndState(advertisement_callback, adapter_path)
self._advertisement_callbacks.append(callback_and_state)
self._advertisement_callbacks[adapter_path].append(advertisement_callback)

device_removed_callback_and_state = DeviceRemovedCallbackAndState(
device_removed_callback, adapter_path
Expand Down Expand Up @@ -440,7 +426,9 @@ async def stop() -> None:
# need to remove callbacks first, otherwise we get TxPower
# and RSSI properties removed during stop which causes
# incorrect advertisement data callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(
device_removed_callback_and_state
)
Expand Down Expand Up @@ -477,7 +465,9 @@ async def stop() -> None:
return stop
except BaseException:
# if starting scanning failed, don't leak the callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(device_removed_callback_and_state)
raise

Expand Down Expand Up @@ -511,8 +501,7 @@ async def passive_scan(
# error message.
self._check_adapter(adapter_path)

callback_and_state = CallbackAndState(advertisement_callback, adapter_path)
self._advertisement_callbacks.append(callback_and_state)
self._advertisement_callbacks[adapter_path].append(advertisement_callback)

device_removed_callback_and_state = DeviceRemovedCallbackAndState(
device_removed_callback, adapter_path
Expand Down Expand Up @@ -555,7 +544,9 @@ async def stop() -> None:
# need to remove callbacks first, otherwise we get TxPower
# and RSSI properties removed during stop which causes
# incorrect advertisement data callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(
device_removed_callback_and_state
)
Expand All @@ -579,7 +570,9 @@ async def stop() -> None:

except BaseException:
# if starting scanning failed, don't leak the callbacks
self._advertisement_callbacks.remove(callback_and_state)
self._advertisement_callbacks[adapter_path].remove(
advertisement_callback
)
self._device_removed_callbacks.remove(device_removed_callback_and_state)
raise

Expand Down Expand Up @@ -1041,13 +1034,9 @@ def _run_advertisement_callbacks(self, device_path: str, device: Device1) -> Non
Args:
device_path: The D-Bus object path of the remote device.
device: The current D-Bus properties of the device.
changed: A list of properties that have changed since the last call.
"""
for callback, adapter_path in self._advertisement_callbacks:
# filter messages from other adapters
if adapter_path != device["Adapter"]:
continue

adapter_path = device["Adapter"]
for callback in self._advertisement_callbacks[adapter_path]:
callback(device_path, device.copy())


Expand Down

0 comments on commit 7130c40

Please sign in to comment.