glibgnomegio

How to release resources used by GSettings?


I'm trying to write application in C which manipulates GSettings. Unfortunately I have encountered some kind of memory leaks so I tried to track them down. I'm not sure whether this is library bug or am I missing something. This is the smallest example I come up with that allocates memory till it crashes.

#include <glib.h>
#include <gio/gio.h>

int main() {
    while (1) {
        GSettings *settings = g_settings_new("com.linuxmint.updates");

        g_object_unref(settings);
        //g_clear_object(&settings); // This leaks as well but seems to leak "slower"
    }

    return 0;
}

Can anyone explain me why memory leaks in this example and how to fix it?

PS I'm using libglib-2.0 (version 2.56.3 which come with Ubuntu 18.04 LTS / Mint).


EDIT 1

As per request in comments I'm posting valgrind output. I'm using command: valgrind --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=50 --show-leak-kinds=definite ./main. I have changed program a little to be finite (it looping while 100.000 times). Here is output for that changed parameter.

==16375== Memcheck, a memory error detector
==16375== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==16375== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==16375== Command: ./main
==16375== 
==16375== 
==16375== HEAP SUMMARY:
==16375==     in use at exit: 297,081,397 bytes in 5,056,358 blocks
==16375==   total heap usage: 26,147,615 allocs, 21,091,257 frees, 1,064,178,170 bytes allocated
==16375== 
==16375== LEAK SUMMARY:
==16375==    definitely lost: 0 bytes in 0 blocks
==16375==    indirectly lost: 0 bytes in 0 blocks
==16375==      possibly lost: 2,840 bytes in 27 blocks
==16375==    still reachable: 297,066,261 bytes in 5,056,238 blocks
==16375==                       of which reachable via heuristic:
==16375==                         length64           : 1,384 bytes in 28 blocks
==16375==                         newarray           : 1,808 bytes in 33 blocks
==16375==         suppressed: 0 bytes in 0 blocks
==16375== Reachable blocks (those to which a pointer was found) are not shown.
==16375== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==16375== 
==16375== For counts of detected and suppressed errors, rerun with: -v
==16375== ERROR SUMMARY: 27 errors from 27 contexts (suppressed: 0 from 0)

I'm not an expert but parameter still reachable grows with number of loop. How those objects (or rather structures) can be reached if I'm consistently using one variable? Am I missing something? I'm trying do what is advised here: https://developer.gnome.org/gobject/stable/gobject-memory.html


EDIT 2

I was digging deeper into this problem. Because I was unsure that my code actually is correct I decided to change it to another GObject like this:

#include <glib.h>
#include <gio/gio.h>

int main() {
    while (1) {
        GFile *file = g_file_new_for_path ("/path/to/some/file");

        g_object_unref(file);
        //g_clear_object(&settings);
    }

    return 0;
}

I'm aware this do not open any file and only creates handle to resource but this code have constant memory usage over time. If I remove unref then it obviously leaks and crashes.

This is how valgrind output looks for this snippet for 100.000 and 1.000.000 iterations.

iterations = 100.000

==13257== Memcheck, a memory error detector
==13257== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13257== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13257== Command: ./main
==13257== 
==13257== 
==13257== HEAP SUMMARY:
==13257==     in use at exit: 159,435 bytes in 1,975 blocks
==13257==   total heap usage: 205,209 allocs, 203,234 frees, 6,758,893 bytes allocated
==13257== 
==13257== LEAK SUMMARY:
==13257==    definitely lost: 0 bytes in 0 blocks
==13257==    indirectly lost: 0 bytes in 0 blocks
==13257==      possibly lost: 2,528 bytes in 26 blocks
==13257==    still reachable: 144,699 bytes in 1,852 blocks
==13257==                       of which reachable via heuristic:
==13257==                         length64           : 1,688 bytes in 32 blocks
==13257==                         newarray           : 1,840 bytes in 35 blocks
==13257==         suppressed: 0 bytes in 0 blocks
==13257== Rerun with --leak-check=full to see details of leaked memory
==13257== 
==13257== For counts of detected and suppressed errors, rerun with: -v
==13257== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

iterations = 1.000.000

==12440== Memcheck, a memory error detector
==12440== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12440== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==12440== Command: ./main
==12440== 
==12440== 
==12440== HEAP SUMMARY:
==12440==     in use at exit: 157,241 bytes in 1,936 blocks
==12440==   total heap usage: 2,005,339 allocs, 2,003,403 frees, 64,363,746 bytes allocated
==12440== 
==12440== LEAK SUMMARY:
==12440==    definitely lost: 0 bytes in 0 blocks
==12440==    indirectly lost: 0 bytes in 0 blocks
==12440==      possibly lost: 2,528 bytes in 26 blocks
==12440==    still reachable: 142,505 bytes in 1,813 blocks
==12440==                       of which reachable via heuristic:
==12440==                         length64           : 1,688 bytes in 32 blocks
==12440==                         newarray           : 1,840 bytes in 35 blocks
==12440==         suppressed: 0 bytes in 0 blocks
==12440== Rerun with --leak-check=full to see details of leaked memory
==12440== 
==12440== For counts of detected and suppressed errors, rerun with: -v
==12440== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

This gives me some idea that second code have almost same number allocations vs frees (diffrence is in both cases < 2000 which are probably some static allocations for lifetime of library).

This is not the case in first snippet where I use GSettings object. Number of allocations vs frees is nowhere constant and it grows over time.

I will try running this program against latest glib when I'll have access to some rolling release distro (arch probably) because I think compiling latest glib and plug it into Ubuntu is too complicated to me.


Solution

  • This ‘leak’ of reachable memory is an artifact of your test program. Each time you construct a GSettings object, it needs to add some match rules to the D-Bus session bus so that it can receive signals from the dconf daemon. Adding a match rule means sending a D-Bus method call to the message bus daemon and then waiting for a reply.

    By creating 100000 GSettings objects in a row, you are queueing up 100000 AddMatch calls to the message bus daemon, including 100000 allocations containing information about the pending method call replies. However, your program exits before the message bus daemon replies to the majority of the AddMatch calls; so a lot of those allocations detailing the pending replies are still allocated on exit.

    If your program were to sleep for, say, a minute until the message bus daemon had replied to all the AddMatch calls, I would expect that the ‘still reachable’ allocations would be consistent with the GFile example you ran.

    (Note that it’s OK to do a usleep() call in your main() function to demonstrate this, as the D-Bus method calls and replies are handled in a separate worker thread.)