Re: [PATCH v1] gdbus: Skip BLE random-address devices and remove broad match rule
Xiuzhuo Shang <[email protected]> Wed, 15 Jul 2026 14:47:08 +0800
| Newsgroups | dev.linux.lists.ofono,org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <[email protected]> |
On 7/14/2026 10:01 PM, Luiz Augusto von Dentz wrote: > Hi Xiuzhuo, > > On Tue, Jul 14, 2026 at 7:04 AM Xiuzhuo Shang > <[email protected]> wrote: >> >> >> >> 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. > > Are we talking about an embedded device here? How come it takes > seconds to process hundreds of events, or is it simply running out of > memory and lacking swap space? If this were a general problem we would > have seen this before. Even with hundreds of devices in the vicinity, > it never took this much time to process. > Yes, this is QCS6490/QCS8300 — ARM64 automotive embedded platforms (Snapdragon Ride). The following memory snapshots were captured from the actual hung logs at the time bluetoothd stalled: btd_stuck_20260703 (ofono running, dbus-daemon at 457 MB): Mem: 7378 MB total, 5270 MB free, 5975 MB available Swap: 5404 MB total, 0 MB used PSI memory: avg10=0.00 avg60=0.00 avg300=0.00 btd_stuck_20260707 (WirePlumber running, dbus-daemon at 357 MB): Mem: 7378 MB total, 3636 MB free, 4385 MB available Swap: 5404 MB total, 0 MB used PSI memory: avg10=0.00 avg60=0.00 avg300=0.00 In both cases PSI memory stall is zero and swap is unused. The system had 3.5–5.9 GB of free RAM even while the hang was active. The problem is a D-Bus socket backpressure cascade, not memory shortage >> 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. > > That sounds like a process that is not dropping its messages; even if > we generate fewer messages, it will eventually stop working because > too many messages are cached or something similar. > The full backpressure chain is: 1. Transport=le-only filter triggers filtered_discovery=true and delta_threshold=0 in bluetoothd, producing an unthrottled PropertiesChanged(RSSI) stream (bursts up to 392/s). 2. ofono's broad path_namespace='/' match rule causes dbus-daemon to route every signal to ofono. But ofono's signal_func is NULL (g_dbus_client_set_signal_watch() is never called), so message_filter() silently discards them — yet dbus-daemon still queues each message for delivery. 3. Undelivered messages accumulate in dbus-daemon. After ~3 hours of scanning, dbus-daemon VmRSS reaches 457 MB. 4. dbus-daemon, busy draining its write queue toward ofono, can no longer read from bluetoothd's socket promptly. bluetoothd's kernel send buffer fills up. 5. bluetoothd's sendmsg() starts returning EAGAIN. strace on the hung bluetoothd shows this repeating pattern: sendmsg(7, {org.bluez.Device1 PropertiesChanged}) = -1 EAGAIN (Resource temporarily unavailable) ppoll([{fd=7, events=POLLOUT}], 1, {tv_sec=0, tv_nsec=0}) = 0 (Timeout) fd=7 never becomes writable. With POLLOUT registered on fd=7, bluetoothd's GMainLoop stalls and can no longer dispatch any D-Bus replies or signals — the daemon appears hung and unresponsive to commands including bluetoothctl. >> How do you think the logic of bluez about delta_threshold = 0 >> when just Transport=le? > > It sounds like another problem is at hand—not just too many signals > but them not being consumed. Over time, this issue will recur if not > fixed properly. How do you think of these two fix? Fix 1 — subscriber side (this patch, Change 2): Remove the broad path_namespace='/' match rule from g_dbus_client_new_full(). This stops dbus-daemon from routing bluetoothd signals to ofono through that rule. Since signal_func is always NULL in ofono's usage, no functionality is lost. InterfacesAdded/InterfacesRemoved remain covered by the precise watches already registered. This directly breaks the backpressure chain regardless of signal volume. Fix 2 — signal generation side (BlueZ): Only apply delta_threshold=0 when the discovery filter contains an actual proximity condition (RSSI or pathloss). A transport-only filter (Transport=le, no RSSI/pathloss) should use the default delta (RSSI_THRESHOLD=8) rather than 0. We have an internal patch for this and can submit it upstream if appropriate. I will send a v2 of the ofono patch containing only Change 2. For Fix 2, should I submit the BlueZ delta_threshold fix as a separate patch to linux-bluetooth?