Re: [PATCH v1] gdbus: Skip BLE random-address devices and remove broad match rule
Xiuzhuo Shang <[email protected]> Tue, 14 Jul 2026 19:04:01 +0800
| Newsgroups | dev.linux.lists.ofono,org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <[email protected]> |
On 7/13/2026 10:00 PM, Luiz Augusto von Dentz wrote: > Hi Xiuzhuo, > > On Mon, Jul 13, 2026 at 3:03 AM Xiuzhuo Shang > <[email protected]> wrote: >> >> >> >> On 7/10/2026 10:12 PM, Luiz Augusto von Dentz wrote: >>> Hi Xiuzhuo, >>> >>> On Fri, Jul 10, 2026 at 3:55 AM Xiuzhuo Shang >>> <[email protected]> wrote: >>>> >>>> Two related changes to reduce dbus-daemon load caused by high-rate >>>> BLE advertising signals: >>>> >>>> 1. In parse_properties(), skip creating a GDBusProxy for Device1 >>>> objects whose AddressType is 'random' (BLE-only devices). Each >>>> proxy registers a per-device PropertiesChanged watch via >>>> g_dbus_add_properties_watch(); with hundreds of BLE peripherals >>>> advertising simultaneously, this results in hundreds of match rules >>>> and dbus-daemon routing thousands of RSSI PropertiesChanged signals >>>> per second to ofono, none of which ofono processes. The >>>> AddressType property is available in the InterfacesAdded dict at >>>> the time parse_properties() is called, so the check is reliable >>>> and race-free. >>>> >>>> BR/EDR devices (AddressType='public') are unaffected: their proxies >>>> are created as before, and the property_changed callback continues >>>> to receive Paired and ServicesResolved updates needed for HFP/HSP. >>>> >>>> 2. In g_dbus_client_new_full(), remove the broad >>>> type='signal',sender=<service>,path_namespace=<path> match rule. >>>> This rule was the sole feeder for the signal_func path in >>>> message_filter(). ofono never calls g_dbus_client_set_signal_watch() >>>> so signal_func is always NULL; the broad rule therefore served no >>>> purpose and caused dbus-daemon to route every bluetoothd signal >>>> (including all BLE PropertiesChanged) to ofono. >>>> >>>> InterfacesAdded and InterfacesRemoved are already covered by the >>>> precise watches registered via g_dbus_add_signal_watch() earlier in >>>> g_dbus_client_new_full(), so no functionality is lost. >>> >>> Looks like this is on ofono though, why are you chaning the BlueZ side? >> >> Regarding your suggestion to move the BLE filter to hfp_hf_bluez5.c: filtering in proxy_added() >> would only prevent ofono from processing BLE devices at the application level. The per-device >> PropertiesChanged watch is registered inside proxy_new(), which is called before proxy_added(). So >> dbus-daemon would still route all RSSI signals to the process — the D-Bus pressure problem would >> remain unchanged. >> >> I will drop the change from the gdbus patch per your feedback >> >>> >>>> Together these two changes prevent dbus-daemon from routing BLE >>>> advertising PropertiesChanged signals to ofono entirely, eliminating >>>> the dbus-daemon memory growth and bluetoothd socket backpressure seen >>>> after hours of BLE scanning. >>>> >>>> Signed-off-by: Xiuzhuo Shang <[email protected]> >>>> --- >>>> gdbus/client.c | 46 ++++++++++++++++++++++++++++++++++++++-------- >>>> 1 file changed, 38 insertions(+), 8 deletions(-) >>>> >>>> diff --git a/gdbus/client.c b/gdbus/client.c >>>> index 48711ae8..dd3c17f9 100644 >>>> --- a/gdbus/client.c >>>> +++ b/gdbus/client.c >>>> @@ -937,6 +937,44 @@ static void parse_properties(GDBusClient *client, const char *path, >>>> if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) >>>> return; >>>> >>>> + /* >>>> + * Skip BLE devices (AddressType='random') to avoid registering a >>>> + * per-device PropertiesChanged watch for every advertising BLE >>>> + * peripheral. ofono only needs BR/EDR devices for HFP/HSP; the >>>> + * high-rate RSSI PropertiesChanged signals from BLE scanners cause >>>> + * dbus-daemon memory bloat and socket backpressure in bluetoothd. >>>> + * >>>> + * The AddressType property is present in the InterfacesAdded dict >>>> + * (iter) at this point, so it can be checked before proxy_new(). >>>> + */ >>>> + if (g_str_equal(interface, "org.bluez.Device1") == TRUE) { >>>> + DBusMessageIter props, entry; >>>> + DBusMessageIter copy = *iter; >>>> + >>>> + if (dbus_message_iter_get_arg_type(©) == DBUS_TYPE_ARRAY) { >>>> + dbus_message_iter_recurse(©, &props); >>>> + while (dbus_message_iter_get_arg_type(&props) == >>>> + DBUS_TYPE_DICT_ENTRY) { >>>> + const char *key; >>>> + dbus_message_iter_recurse(&props, &entry); >>>> + dbus_message_iter_get_basic(&entry, &key); >>>> + if (g_str_equal(key, "AddressType") == TRUE) { >>>> + DBusMessageIter var; >>>> + const char *addr_type; >>>> + dbus_message_iter_next(&entry); >>>> + dbus_message_iter_recurse(&entry, &var); >>>> + dbus_message_iter_get_basic(&var, >>>> + &addr_type); >>>> + if (g_str_equal(addr_type, >>>> + "random") == TRUE) >>>> + return; >>>> + break; >>>> + } >>>> + dbus_message_iter_next(&props); >>>> + } >>>> + } >>>> + } >>> >>> Nack, not going to introduce BlueZ specific logic into gdbus like >>> this. If we need to rate limit the RSSI notification then it means >>> it's not suitable to be a property, but I believe we have a threshold >>> logic in the likes of device_set_rssi_with_delta so perhaps we shall >>> check why it is not working in your case. >> >> I investigated why device_set_rssi_with_delta is not working in our case. >> >> Root cause in BlueZ >> >> The customer scans with the following bluetoothctl sequence: >> >> menu scan → transport le → duplicate-data off → back → scan le > > Ok, that is 1 not the default behavior and 2 if they are setting > duplicate-data off that means they want to see all advertisements, so > this is working as intended. Hi Luiz, Thank you for the clarification. I need to correct some points regarding duplicate-data off and the actual signal rate. What DuplicateData does in BlueZ vs. the controller BlueZ's DuplicateData parameter in SetDiscoveryFilter and the controller's hardware duplicate filter are two independent mechanisms: - The controller hardware duplicate filter (filter_dup in hci_le_set_scan_enable) is controlled by the kernel (hci_sync.c), defaulting to LE_SCAN_FILTER_DUP_ENABLE. It is only disabled for specific cases (AdvMonitor, Mesh, or HCI_QUIRK_STRICT_DUPLICATE_FILTER). SetDiscoveryFilter.DuplicateData does NOT directly control this. - BlueZ's DuplicateData=true flag (parse_duplicate_data() in src/adapter.c:2680) controls only whether device EIR data (manufacturer data, service data) is updated on duplicate advertisements at the application layer (device_set_manufacturer_data(), device_set_service_data(), src/adapter.c:7392). duplicate-data off has no effect In bluetoothctl, duplicate-data off sets filter.duplicate = false (client/main.c:1449). In set_discovery_filter_setup() (client/main.c:1251): if (args->duplicate) /* false -> DuplicateData is NOT sent */ g_dbus_dict_append_entry(&dict, "DuplicateData", ...); Since false is the default, DuplicateData is never included in the SetDiscoveryFilter call. The customer's duplicate-data off step has no effect — the controller hardware duplicate filter remains enabled throughout. Why RSSI delta=0 occurs The root cause is Transport=le on a dual-mode adapter (src/adapter.c:85): #define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM)) /* = 6 */ #define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) /* = 7 */ get_scan_type(adapter) returns 7 (DUAL) since the adapter supports both BR/EDR and LE. When Transport=le is set, filter->type = 6. In merge_discovery_filters() (src/adapter.c:2254): item->type == adapter_scan_type /* 6 != 7 -> condition fails */ This triggers has_filtered_discovery = true -> filtered_discovery = true -> device_set_rssi_with_delta(dev, rssi, 0) (src/adapter.c:7362). With delta_threshold=0, every advertisement emits PropertiesChanged(RSSI) regardless of RSSI change. We have captured a bluetoothd debug log (QCS6490 board, BlueZ 5.72) that directly confirms this sequence. The following lines are from the log with the customer's exact scan sequence (menu scan -> transport le -> duplicate-data off -> back -> scan le): # SetDiscoveryFilter called with Transport=le only, no RSSI/pathloss adapter.c:parse_discovery_filter_dict() filtered discovery params: transport: 6 rssi: 32767 pathloss: 32767 duplicate data: false discoverable false pattern (null) # current_discovery_filter is non-NULL — confirms filtered_discovery # will be set to true in start_discovery_complete() adapter.c:start_discovery_timeout() adapter->current_discovery_filter == 1 # filtered_discovery=1 directly confirmed by our added debug print adapter.c:start_discovery_complete() filtered_discovery=1 (current_discovery_filter=set) # delta_threshold=0 directly confirmed — every advertisement emits # PropertiesChanged(RSSI) regardless of whether RSSI changed device.c:device_set_rssi_with_delta() rssi=-86 delta_threshold=0 device.c:device_set_rssi_with_delta() rssi=-79 delta_threshold=0 device.c:device_set_rssi_with_delta() rssi=-72 delta_threshold=0 ... The log shows delta_threshold=0 on every single call. With hundreds of BLE devices advertising, this produces an unthrottled stream of PropertiesChanged(RSSI) signals. > >> scan le calls SetDiscoveryFilter({'Transport': 'le'}) — a transport-only filter with no RSSI or >> pathloss condition. In btd_adapter_device_found() (src/adapter.c): >> >> if (adapter->filtered_discovery) >> device_set_rssi_with_delta(dev, rssi, 0); /* delta forced to 0 */ >> else >> device_set_rssi(dev, rssi); /* delta = RSSI_THRESHOLD = 8 */ >> >> filtered_discovery is set true whenever current_discovery_filter is non-NULL (adapter.c:1872), which >> happens for any SetDiscoveryFilter() call — including a transport-only filter. With >> delta_threshold=0, the condition delta < 0 is never true, so g_dbus_emit_property_changed("RSSI") >> fires on every BLE advertisement regardless of whether the RSSI value changed. With 280+ BLE devices >> advertising continuously, this produces ~95 PropertiesChanged(RSSI) signals per second that flood >> dbus-daemon. > > Yeah, that is what you get to set duplicate-data off, that said ~95 > PropertiesChanged per seconds in not a lot of data, perhaps you are > hitting a process that don't consume the events immediately as it > should then the D-Bus daemon have to queue more and more memory? Actual signal rate from the captured log The log (30_Jun_2026/bluetooth.log.gz) shows duplicate data: true with Transport=le. The device_found_callback distribution over 1200 active seconds is: 0-99 events/s : 69 seconds (6%) 100-199 events/s : 187 seconds (16%) 200-299 events/s : 75 seconds (6%) 300+ events/s : 869 seconds (72%) <- burst, peak 392/s The average is ~95/s but that conceals the burst pattern: 72% of the time the rate exceeds 300/s. With 280+ BLE devices advertising, delta=0 means every single device_found_callback unconditionally emits a PropertiesChanged(RSSI) signal — including during bursts at 392/s. The actual bottleneck As you pointed out, ~95/s average may not seem large. The real bottleneck is that WirePlumber and ofono both register a broad path_namespace='/' match rule, causing dbus-daemon to route all these signals to processes that immediately discard them (message_filter() drops PropertiesChanged silently). Over 8 hours, dbus-daemon's routing table grows to 357 MB, making routing progressively slower until bluetoothd's socket fills and its GMainLoop stalls. How do you think the logic of bluez about delta_threshold = 0 when just Transport=le? > >> The delta=0 path was presumably intended for clients that set RSSI/pathloss proximity filters, so >> that is_filter_match() gets precise RSSI tracking. A transport-only filter does not involve any >> proximity condition, so forcing delta=0 is unnecessary in that case. >> >> Proposed BlueZ fix direction >> >> Only apply delta=0 when at least one active client filter has a real proximity condition (RSSI or >> pathloss): >> >> static bool discovery_filter_has_proximity(struct btd_adapter *adapter) >> { >> GSList *l; >> for (l = adapter->discovery_list; l; l = g_slist_next(l)) { >> struct discovery_client *client = l->data; >> struct discovery_filter *item = client->discovery_filter; >> if (item && (item->rssi != DISTANCE_VAL_INVALID || >> item->pathloss != DISTANCE_VAL_INVALID)) >> return true; >> } >> return false; >> } > > Duplicate data does consider RSSI as well, so the real problem seems > to be why disabling duplicate filtering is required since that is what > causes the RSSI threshold logic not to be used. Or perhaps you don't > trust your controller to do duplicate filtering. If so, we've gone > full circle because if something causes too much spamming, it's also > bad at the HCI driver, into the mgmt interface, etc, until it reaches > D-Bus, which is why BlueZ defaults to duplicate filtering. > >> /* in btd_adapter_device_found() */ >> if (adapter->filtered_discovery && >> discovery_filter_has_proximity(adapter)) >> device_set_rssi_with_delta(dev, rssi, 0); >> else >> device_set_rssi(dev, rssi); >> >> Could you advise whether this is the right approach, or if there is a better place to fix this? >> >> then I will Submit a separate BlueZ patch for the delta=0 fix once we agree on the approach. >> >>> >>>> proxy = proxy_lookup(client, path, interface); >>>> if (proxy) { >>>> update_properties(proxy, iter, FALSE); >>>> @@ -1255,14 +1293,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection, >>>> "InterfacesRemoved", >>>> interfaces_removed, >>>> client, NULL); >>>> - g_ptr_array_add(client->match_rules, g_strdup_printf("type='signal'," >>>> - "sender='%s',path_namespace='%s'", >>>> - client->service_name, client->base_path)); >>>> - >>>> - for (i = 0; i < client->match_rules->len; i++) { >>>> - modify_match(client->dbus_conn, "AddMatch", >>>> - g_ptr_array_index(client->match_rules, i)); >>>> - } >>> >>> This perhaps has a merit, but I can't recall what this watch was for. >>> Is it matching every signal from the sender? It seems it was introduce >>> before the watch infra existed, so if we can safely remove it we can >>> perhaps remove the whole client-->match_rules. >>> >>>> return g_dbus_client_ref(client); >>>> } >>>> -- >>>> 2.43.0 >>>> >>> >>> >> > >