Re: [PATCH v2] gdbus: Remove broad match rule and add proxy filter support
Xiuzhuo Shang <[email protected]> Mon, 27 Jul 2026 15:39:34 +0800
| Newsgroups | dev.linux.lists.ofono,org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <[email protected]> |
On 7/21/2026 2:42 AM, Luiz Augusto von Dentz wrote: > Hi Xiuzhuo, > > On Wed, Jul 15, 2026 at 4:59 AM Xiuzhuo Shang > <[email protected]> wrote: >> >> Problem >> ------- >> On embedded platforms running continuous BLE scanning, bluetoothd >> eventually stalls with its D-Bus socket to dbus-daemon full. strace >> on a hung bluetoothd shows a repeating pattern: >> >> sendmsg(7, {org.bluez.Device1 PropertiesChanged}, MSG_NOSIGNAL) >> = -1 EAGAIN (Resource temporarily unavailable) >> ppoll([{fd=7, events=POLLOUT}], 1, {tv_sec=0, tv_nsec=0}) = 0 (Timeout) >> >> fd=7 never becomes writable; bluetoothd's GMainLoop remains stuck >> waiting for POLLOUT and cannot dispatch any further D-Bus events, >> making the daemon appear hung and unresponsive to commands. >> >> The backpressure chain that causes this: >> 1. BLE scanning generates high-rate PropertiesChanged(RSSI) signals >> (~400/s with typical BLE traffic). >> 2. ofono's broad path_namespace='/' match rule causes dbus-daemon to >> route all these signals to ofono even though ofono has no use for >> BLE RSSI data. >> 3. ofono's single-threaded GLib loop cannot consume them fast enough; >> undelivered messages accumulate inside dbus-daemon (457 MB >> observed after ~3 hours of scanning). >> 4. dbus-daemon, busy draining its write queue toward ofono, stops >> reading from bluetoothd's socket in time; bluetoothd's kernel >> send buffer fills up and sendmsg() returns EAGAIN. >> 5. With POLLOUT registered on fd=7, bluetoothd's GMainLoop stalls >> and can no longer send D-Bus replies or signals. >> >> Fix >> --- >> Three related changes: >> >> 1. Remove the broad type='signal',sender=<svc>,path_namespace=<path> >> match rule from g_dbus_client_new_full(). 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 to ofono. >> >> 2. Remove the now-empty match_rules GPtrArray infrastructure >> (field declaration, init, AddMatch loop, RemoveMatch loop, free). >> No match rules are added to this array any more. >> >> 3. Add a generic GDBusProxyFilterFunction callback and >> g_dbus_client_set_proxy_filter() API to GDBusClient. The filter >> is called from parse_properties() before proxy_new(), so a FALSE >> return prevents both proxy creation and per-device >> PropertiesChanged watch registration. This keeps all BlueZ- >> specific logic out of the gdbus layer. >> >> Use this in hfp_hf_bluez5.c to skip Device1 proxies for BLE >> random-address devices: ofono only needs BR/EDR (AddressType= >> 'public') devices for HFP/HSP. Skipping BLE proxies prevents >> dbus-daemon from registering per-device PropertiesChanged match >> rules for advertising peripherals and eliminates the remaining >> RSSI signal delivery to ofono. > >> Together these changes prevent dbus-daemon from routing BLE >> advertising signals to ofono, breaking the backpressure chain: >> dbus-daemon memory stops growing, its write queue drains, and >> bluetoothd's send buffer clears so that sendmsg() no longer returns >> EAGAIN and the GMainLoop stall is resolved. >> >> Signed-off-by: Xiuzhuo Shang <[email protected]> >> --- >> Changes in v2: >> - Drop Change 1 (BLE address-type filter in parse_properties()) per >> review feedback; BlueZ-specific logic does not belong in the gdbus >> layer. >> - Add generic GDBusProxyFilterFunction callback and >> g_dbus_client_set_proxy_filter() API to GDBusClient. The filter is >> invoked before proxy_new() so a FALSE return prevents both proxy >> creation and per-device PropertiesChanged watch registration. >> - Use the new filter in hfp_hf_bluez5.c to skip Device1 proxies for >> BLE random-address devices, keeping all BlueZ-specific logic in the >> plugin as suggested. >> - Remove now-empty match_rules GPtrArray infrastructure (field, >> init, AddMatch loop, RemoveMatch loop, free) and unused variables. >> - Link to v1: >> https://lore.kernel.org/ofono/[email protected]/ >> >> gdbus/client.c | 45 +++++++++++++++++++++-------------------- >> gdbus/gdbus.h | 8 ++++++++ >> plugins/hfp_hf_bluez5.c | 38 ++++++++++++++++++++++++++++++++++ >> 3 files changed, 69 insertions(+), 22 deletions(-) >> >> diff --git a/gdbus/client.c b/gdbus/client.c >> index 48711ae8..fa2e75c0 100644 >> --- a/gdbus/client.c >> +++ b/gdbus/client.c >> @@ -46,7 +46,6 @@ struct GDBusClient { >> guint watch; >> guint added_watch; >> guint removed_watch; >> - GPtrArray *match_rules; >> DBusPendingCall *pending_call; >> DBusPendingCall *get_objects_call; >> GDBusWatchFunction connect_func; >> @@ -61,6 +60,8 @@ struct GDBusClient { >> GDBusClientFunction ready; >> void *ready_data; >> GDBusPropertyFunction property_changed; >> + GDBusProxyFilterFunction proxy_filter; >> + void *filter_user_data; >> void *user_data; >> GList *proxy_list; >> }; >> @@ -943,6 +944,14 @@ static void parse_properties(GDBusClient *client, const char *path, >> return; >> } >> >> + if (client->proxy_filter) { >> + DBusMessageIter copy = *iter; >> + >> + if (!client->proxy_filter(client, path, interface, >> + ©, client->filter_user_data)) >> + return; >> + } >> + >> proxy = proxy_new(client, path, interface); >> if (proxy == NULL) >> return; >> @@ -1211,7 +1220,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection, >> const char *root_path) >> { >> GDBusClient *client; >> - unsigned int i; >> >> if (!connection || !service) >> return NULL; >> @@ -1232,9 +1240,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection, >> client->root_path = g_strdup(root_path); >> client->connected = FALSE; >> >> - client->match_rules = g_ptr_array_sized_new(1); >> - g_ptr_array_set_free_func(client->match_rules, g_free); >> - >> client->watch = g_dbus_add_service_watch(connection, service, >> service_connect, >> service_disconnect, >> @@ -1255,14 +1260,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)); >> - } >> >> return g_dbus_client_ref(client); >> } >> @@ -1279,8 +1276,6 @@ GDBusClient *g_dbus_client_ref(GDBusClient *client) >> >> void g_dbus_client_unref(GDBusClient *client) >> { >> - unsigned int i; >> - >> if (client == NULL) >> return; >> >> @@ -1297,13 +1292,6 @@ void g_dbus_client_unref(GDBusClient *client) >> dbus_pending_call_unref(client->get_objects_call); >> } >> >> - for (i = 0; i < client->match_rules->len; i++) { >> - modify_match(client->dbus_conn, "RemoveMatch", >> - g_ptr_array_index(client->match_rules, i)); >> - } >> - >> - g_ptr_array_free(client->match_rules, TRUE); >> - >> dbus_connection_remove_filter(client->dbus_conn, >> message_filter, client); >> >> @@ -1396,3 +1384,16 @@ gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, >> >> return TRUE; >> } >> + >> +gboolean g_dbus_client_set_proxy_filter(GDBusClient *client, >> + GDBusProxyFilterFunction proxy_filter, >> + void *user_data) >> +{ >> + if (client == NULL) >> + return FALSE; >> + >> + client->proxy_filter = proxy_filter; >> + client->filter_user_data = user_data; >> + >> + return TRUE; >> +} >> diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h >> index d99c2549..cc3c4e16 100644 >> --- a/gdbus/gdbus.h >> +++ b/gdbus/gdbus.h >> @@ -347,6 +347,11 @@ typedef void (* GDBusClientFunction) (GDBusClient *client, void *user_data); >> typedef void (* GDBusProxyFunction) (GDBusProxy *proxy, void *user_data); >> typedef void (* GDBusPropertyFunction) (GDBusProxy *proxy, const char *name, >> DBusMessageIter *iter, void *user_data); >> +typedef gboolean (* GDBusProxyFilterFunction) (GDBusClient *client, >> + const char *path, >> + const char *interface, >> + DBusMessageIter *iter, >> + void *user_data); >> >> gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, >> GDBusPropertyFunction function, void *user_data); >> @@ -377,6 +382,9 @@ gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, >> GDBusProxyFunction proxy_removed, >> GDBusPropertyFunction property_changed, >> void *user_data); >> +gboolean g_dbus_client_set_proxy_filter(GDBusClient *client, >> + GDBusProxyFilterFunction proxy_filter, >> + void *user_data); >> >> #ifdef __cplusplus >> } >> diff --git a/plugins/hfp_hf_bluez5.c b/plugins/hfp_hf_bluez5.c >> index 5ad1674f..141dc5c4 100644 >> --- a/plugins/hfp_hf_bluez5.c >> +++ b/plugins/hfp_hf_bluez5.c >> @@ -791,6 +791,43 @@ static void proxy_added(GDBusProxy *proxy, void *user_data) >> device_changed(proxy, path); >> } >> >> +static gboolean proxy_filter(GDBusClient *client, const char *path, >> + const char *interface, DBusMessageIter *iter, >> + void *user_data) >> +{ >> + DBusMessageIter props, entry; >> + >> + if (g_str_equal(BLUEZ_DEVICE_INTERFACE, interface) == FALSE) >> + return TRUE; >> + >> + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) >> + return TRUE; >> + >> + dbus_message_iter_recurse(iter, &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); >> + >> + return !g_str_equal(addr_type, "random"); >> + } >> + >> + dbus_message_iter_next(&props); >> + } >> + >> + return TRUE; >> +} >> + >> static void property_changed(GDBusProxy *proxy, const char *name, >> DBusMessageIter *iter, void *user_data) >> { >> @@ -844,6 +881,7 @@ static int hfp_init(void) >> g_dbus_client_set_connect_watch(bluez, connect_handler, NULL); >> g_dbus_client_set_proxy_handlers(bluez, proxy_added, NULL, >> property_changed, NULL); >> + g_dbus_client_set_proxy_filter(bluez, proxy_filter, NULL); > > I don't really follow; would this register a proxy filter and > automatically remove it on the first match of an AddressType=random?? > Sounds not really useful to me, what is the difference if we don't use > set_proxy_filter above? Thank you for the review. Let me clarify how proxy_filter works. The filter is a persistent callback registered on the GDBusClient instance. It is called from parse_properties() for every device object as it appears (via InterfacesAdded or GetManagedObjects). Returning FALSE means "do not create a proxy for this specific device" -- it does NOT remove or deregister the filter itself. So the behaviour is: Device A (AddressType=random) -> proxy_filter called -> FALSE -> proxy_new() skipped Device B (AddressType=random) -> proxy_filter called again -> FALSE -> proxy_new() skipped Device C (AddressType=public) -> proxy_filter called -> TRUE -> proxy created as normal The filter stays registered for the lifetime of the GDBusClient and is invoked once per device per interface, not just once globally. Without set_proxy_filter: Every Device1 object, including BLE random-address devices, goes through proxy_new(). Each proxy registers a per-device PropertiesChanged watch via g_dbus_add_properties_watch(). In a dense BLE environment with 200-300 advertising peripherals, this results in 200-300 per-device match rules registered with dbus-daemon, which routes every RSSI PropertiesChanged signal for each of those devices to ofono. None of these signals are useful to ofono (it only needs BR/EDR devices for HFP/HSP), but the routing overhead causes dbus-daemon memory growth and eventually backpressures the bluetoothd socket. With set_proxy_filter: BLE random-address devices are rejected before proxy_new() is called, so no per-device PropertiesChanged watch is registered for them. Only BR/EDR devices (AddressType='public') get proxies and watches, which is all ofono needs. To summarise, the proxy_filter is not a one-shot mechanism; it persistently gates every proxy creation for the lifetime of the client. Without it, per-device watches accumulate for all BLE peripherals in the vicinity, not just the first one encountered. > >> >> ofono_handsfree_audio_ref(); >> >> -- >> 2.43.0 >> > >