GDBusConnection cleans up memory in idle
A user using libmm-glib is reporting (see https://lists.freedesktop.org/archives/modemmanager-devel/2020-March/007726.html) that if multiple MMManager objects (GDBusObjectManagerClient subclass) are created and unref-ed right away, without a GMainLoop in use, the used memory keeps growing and growing all the time. See this simple tester:
// build with gcc `pkg-config --cflags --libs mm-glib` -o test test.c
#include <libmm-glib.h>
static gboolean
stop_loop (GMainLoop *loop)
{
g_main_loop_quit (loop);
}
int main(int argc, char* argv[])
{
MMManager *manager;
GError *error = NULL;
GDBusConnection *connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
gint i = 0;
while(++i < 100){
manager = mm_manager_new_sync(connection,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
NULL, &error);
g_object_unref(manager);
g_usleep(100000);
}
g_object_unref (connection);
if (0) {
GMainLoop *loop;
loop = g_main_loop_new (NULL, FALSE);
g_timeout_add_seconds (1, (GSourceFunc)stop_loop, loop);
g_main_loop_run (loop);
g_main_loop_unref (loop);
}
}
I would have expected that if the GDBusConnection is unref-ed, all the memory would be correctly cleaned up, but it looks like there are some signal handling related cleanups that are scheduled in idles, and would only be cleaned up if e.g. we setup a GMainLoop afterwards (just a hack to prove the point, see above the if (0) loop).
The report by valgrind looks like this:
==43435== 31,680 bytes in 990 blocks are still reachable in loss
record 2,179 of 2,182
==43435== at 0x483977F: malloc (vg_replace_malloc.c:309)
==43435== by 0x4C02FA8: g_malloc (gmem.c:102)
==43435== by 0x4BFAE3F: g_source_set_callback (gmain.c:1758)
==43435== by 0x4A7A311: call_destroy_notify.part.0 (gdbusconnection.c:266)
==43435== by 0x4A7A5E2: call_destroy_notify (gdbusconnection.c:257)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3286)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3278)
==43435== by 0x4BCC705: ptr_array_remove_index (garray.c:1545)
==43435== by 0x4A7DF9E: unsubscribe_id_internal (gdbusconnection.c:3645)
==43435== by 0x4A8225A: g_dbus_connection_signal_unsubscribe
(gdbusconnection.c:3717)
==43435== by 0x4A8CDCC: g_dbus_proxy_finalize (gdbusproxy.c:189)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3499)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3391)
==43435== by 0x4BE9D61: g_hash_table_remove_all_nodes.part.0 (ghash.c:706)
==43435== by 0x4BEB10E: g_hash_table_remove_all_nodes (ghash.c:628)
==43435== by 0x4BEB10E: g_hash_table_unref (ghash.c:1461)
==43435==
==43435== 38,610 bytes in 990 blocks are still reachable in loss
record 2,180 of 2,182
==43435== at 0x483977F: malloc (vg_replace_malloc.c:309)
==43435== by 0x4C02FA8: g_malloc (gmem.c:102)
==43435== by 0x4C1CD5F: g_strdup (gstrfuncs.c:363)
==43435== by 0x4BFB490: g_source_set_name (gmain.c:2082)
==43435== by 0x4A7A320: call_destroy_notify.part.0 (gdbusconnection.c:270)
==43435== by 0x4A7A5E2: call_destroy_notify (gdbusconnection.c:257)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3286)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3278)
==43435== by 0x4BCC705: ptr_array_remove_index (garray.c:1545)
==43435== by 0x4A7DF9E: unsubscribe_id_internal (gdbusconnection.c:3645)
==43435== by 0x4A8225A: g_dbus_connection_signal_unsubscribe
(gdbusconnection.c:3717)
==43435== by 0x4A8CDCC: g_dbus_proxy_finalize (gdbusproxy.c:189)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3499)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3391)
==43435== by 0x4BE9D61: g_hash_table_remove_all_nodes.part.0 (ghash.c:706)
==43435==
==43435== 43,560 bytes in 1,089 blocks are still reachable in loss
record 2,181 of 2,182
==43435== at 0x483977F: malloc (vg_replace_malloc.c:309)
==43435== by 0x4C02FA8: g_malloc (gmem.c:102)
==43435== by 0x4C1AF21: g_slice_alloc (gslice.c:1025)
==43435== by 0x4C1B549: g_slice_alloc0 (gslice.c:1051)
==43435== by 0x4BFA73B: g_source_new (gmain.c:945)
==43435== by 0x4BFE322: g_idle_source_new (gmain.c:5780)
==43435== by 0x4A7A2EB: call_destroy_notify.part.0 (gdbusconnection.c:264)
==43435== by 0x4A7A5E2: call_destroy_notify (gdbusconnection.c:257)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3286)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3278)
==43435== by 0x4BCC705: ptr_array_remove_index (garray.c:1545)
==43435== by 0x4A7DF9E: unsubscribe_id_internal (gdbusconnection.c:3645)
==43435== by 0x4A8225A: g_dbus_connection_signal_unsubscribe
(gdbusconnection.c:3717)
==43435== by 0x4A8CDCC: g_dbus_proxy_finalize (gdbusproxy.c:189)
==43435==
==43435== 95,040 bytes in 990 blocks are still reachable in loss
record 2,182 of 2,182
==43435== at 0x483BB65: calloc (vg_replace_malloc.c:762)
==43435== by 0x4C03000: g_malloc0 (gmem.c:132)
==43435== by 0x4BFA72E: g_source_new (gmain.c:944)
==43435== by 0x4BFE322: g_idle_source_new (gmain.c:5780)
==43435== by 0x4A7A2EB: call_destroy_notify.part.0 (gdbusconnection.c:264)
==43435== by 0x4A7A5E2: call_destroy_notify (gdbusconnection.c:257)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3286)
==43435== by 0x4A7A5E2: signal_subscriber_unref (gdbusconnection.c:3278)
==43435== by 0x4BCC705: ptr_array_remove_index (garray.c:1545)
==43435== by 0x4A7DF9E: unsubscribe_id_internal (gdbusconnection.c:3645)
==43435== by 0x4A8225A: g_dbus_connection_signal_unsubscribe
(gdbusconnection.c:3717)
==43435== by 0x4A8CDCC: g_dbus_proxy_finalize (gdbusproxy.c:189)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3499)
==43435== by 0x4B6727D: g_object_unref (gobject.c:3391)
==43435== by 0x4BE9D61: g_hash_table_remove_all_nodes.part.0 (ghash.c:706)
==43435==
==43435== LEAK SUMMARY:
==43435== definitely lost: 0 bytes in 0 blocks
==43435== indirectly lost: 0 bytes in 0 blocks
==43435== possibly lost: 2,664 bytes in 27 blocks
==43435== still reachable: 515,070 bytes in 9,835 blocks
==43435== of which reachable via heuristic:
==43435== length64 : 4,944 bytes in 57 blocks
==43435== newarray : 1,936 bytes in 41 blocks
==43435== suppressed: 0 bytes in 0 blocks
Question being, why are all these cleanups scheduled in idles? What would be a cleaner way to avoid the problem if the user doesn't want to use an explicit GMainLoop and keep using sync() API methods provided by libmm-glib and GLib?