Skip to content

Trying Fix Memory leaks

Emmanuel Fleury requested to merge emmanuel.fleury/glib:memory_leaks into master

Don't panic, this is NOT an MR. It is more intended to draw attention to a branch on which I am working to know if it worth keep going or not.

So, I have noticed that Gtk is leaking quite a lot of memory which tends to make the development quite tedious as you do not know if your code is leaking or the library does.

I know that some people would say that memory is cheap nowadays, but optimizing memory consumption and being careful about that is GOOD and I like to do things properly.

So, I went down to Glib to see if there were memory leaks... and there are as well.

Here is my sample program:

#include <stdlib.h>
#include <stdio.h>

#include <glib.h>

int main (void)
{
  gchar *my_string = "Hello World!\n";
  g_print(my_string);

  return EXIT_SUCCESS;
}

And, here is the leaks I found on it:

#> valgrind --leak-check=full  --show-leak-kinds=all --track-origins=yes ./hello-glib
==6048== Memcheck, a memory error detector
==6048== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6048== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==6048== Command: ./hello-glib
==6048== 
Hello World!
==6048== 
==6048== HEAP SUMMARY:
==6048==     in use at exit: 18,630 bytes in 10 blocks
==6048==   total heap usage: 19 allocs, 9 frees, 53,332 bytes allocated
==6048== 
==6048== 4 bytes in 1 blocks are still reachable in loss record 1 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48F9C29: g_private_impl_new (gthread-posix.c:1007)
==6048==    by 0x48F9CF4: g_private_get_impl (gthread-posix.c:1035)
==6048==    by 0x48F9D49: g_private_get (gthread-posix.c:1062)
==6048==    by 0x48BF9F4: thread_memory_from_self (gslice.c:507)
==6048==    by 0x48C0521: g_slice_alloc (gslice.c:1002)
==6048==    by 0x4888A6E: g_hash_table_new_full (ghash.c:1018)
==6048==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==6048==    by 0x48B1251: g_quark_init (gquark.c:61)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048== 
==6048== 4 bytes in 1 blocks are still reachable in loss record 2 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48F9C29: g_private_impl_new (gthread-posix.c:1007)
==6048==    by 0x48F9CF4: g_private_get_impl (gthread-posix.c:1035)
==6048==    by 0x48F9D49: g_private_get (gthread-posix.c:1062)
==6048==    by 0x486E56E: g_get_charset (gcharset.c:187)
==6048==    by 0x48A960E: g_print (gmessages.c:3189)
==6048==    by 0x109158: main (hello-glib.c:12)
==6048== 
==6048== 15 bytes in 1 blocks are still reachable in loss record 3 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48A55FA: g_malloc (gmem.c:99)
==6048==    by 0x48A58A8: g_malloc_n (gmem.c:337)
==6048==    by 0x48C2FCE: g_strdup (gstrfuncs.c:363)
==6048==    by 0x486E603: g_get_charset (gcharset.c:203)
==6048==    by 0x48A960E: g_print (gmessages.c:3189)
==6048==    by 0x109158: main (hello-glib.c:12)
==6048== 
==6048== 15 bytes in 1 blocks are still reachable in loss record 4 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48A55FA: g_malloc (gmem.c:99)
==6048==    by 0x48A58A8: g_malloc_n (gmem.c:337)
==6048==    by 0x48C2FCE: g_strdup (gstrfuncs.c:363)
==6048==    by 0x486E635: g_get_charset (gcharset.c:205)
==6048==    by 0x48A960E: g_print (gmessages.c:3189)
==6048==    by 0x109158: main (hello-glib.c:12)
==6048== 
==6048== 24 bytes in 1 blocks are still reachable in loss record 5 of 10
==6048==    at 0x4837B65: calloc (vg_replace_malloc.c:752)
==6048==    by 0x48A5663: g_malloc0 (gmem.c:129)
==6048==    by 0x48CEC16: g_private_set_alloc0 (gthread.c:540)
==6048==    by 0x486E58A: g_get_charset (gcharset.c:191)
==6048==    by 0x48A960E: g_print (gmessages.c:3189)
==6048==    by 0x109158: main (hello-glib.c:12)
==6048== 
==6048== 32 bytes in 1 blocks are still reachable in loss record 6 of 10
==6048==    at 0x48356AF: malloc (vg_replace_malloc.c:298)
==6048==    by 0x4837DE7: realloc (vg_replace_malloc.c:826)
==6048==    by 0x48A56D2: g_realloc (gmem.c:164)
==6048==    by 0x48879B9: g_hash_table_realloc_key_or_value_array (ghash.c:371)
==6048==    by 0x4888B0B: g_hash_table_new_full (ghash.c:1030)
==6048==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==6048==    by 0x48B1251: g_quark_init (gquark.c:61)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048==    by 0x400F4B5: call_init (dl-init.c:118)
==6048==    by 0x400F4B5: _dl_init (dl-init.c:119)
==6048==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.28.so)
==6048== 
==6048== 32 bytes in 1 blocks are still reachable in loss record 7 of 10
==6048==    at 0x4837B65: calloc (vg_replace_malloc.c:752)
==6048==    by 0x48A5663: g_malloc0 (gmem.c:129)
==6048==    by 0x48A5922: g_malloc0_n (gmem.c:361)
==6048==    by 0x4888B3B: g_hash_table_new_full (ghash.c:1032)
==6048==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==6048==    by 0x48B1251: g_quark_init (gquark.c:61)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048==    by 0x400F4B5: call_init (dl-init.c:118)
==6048==    by 0x400F4B5: _dl_init (dl-init.c:119)
==6048==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.28.so)
==6048== 
==6048== 88 bytes in 1 blocks are still reachable in loss record 8 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48A55FA: g_malloc (gmem.c:99)
==6048==    by 0x48C05FD: g_slice_alloc (gslice.c:1024)
==6048==    by 0x4888A6E: g_hash_table_new_full (ghash.c:1018)
==6048==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==6048==    by 0x48B1251: g_quark_init (gquark.c:61)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048==    by 0x400F4B5: call_init (dl-init.c:118)
==6048==    by 0x400F4B5: _dl_init (dl-init.c:119)
==6048==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.28.so)
==6048== 
==6048== 2,032 bytes in 1 blocks are still reachable in loss record 9 of 10
==6048==    at 0x4837B65: calloc (vg_replace_malloc.c:752)
==6048==    by 0x48A5663: g_malloc0 (gmem.c:129)
==6048==    by 0x48CEC16: g_private_set_alloc0 (gthread.c:540)
==6048==    by 0x48BFA58: thread_memory_from_self (gslice.c:519)
==6048==    by 0x48C0521: g_slice_alloc (gslice.c:1002)
==6048==    by 0x4888A6E: g_hash_table_new_full (ghash.c:1018)
==6048==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==6048==    by 0x48B1251: g_quark_init (gquark.c:61)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048==    by 0x400F4B5: call_init (dl-init.c:118)
==6048==    by 0x400F4B5: _dl_init (dl-init.c:119)
==6048== 
==6048== 16,384 bytes in 1 blocks are still reachable in loss record 10 of 10
==6048==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==6048==    by 0x48A55FA: g_malloc (gmem.c:99)
==6048==    by 0x48A58A8: g_malloc_n (gmem.c:337)
==6048==    by 0x48B1267: g_quark_init (gquark.c:62)
==6048==    by 0x4898BE8: glib_init (glib-init.c:269)
==6048==    by 0x4898BF6: glib_init_ctor (glib-init.c:329)
==6048==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==6048==    by 0x400F4B5: call_init (dl-init.c:118)
==6048==    by 0x400F4B5: _dl_init (dl-init.c:119)
==6048==    by 0x40010C9: ??? (in /lib/x86_64-linux-gnu/ld-2.28.so)
==6048== 
==6048== LEAK SUMMARY:
==6048==    definitely lost: 0 bytes in 0 blocks
==6048==    indirectly lost: 0 bytes in 0 blocks
==6048==      possibly lost: 0 bytes in 0 blocks
==6048==    still reachable: 18,630 bytes in 10 blocks
==6048==         suppressed: 0 bytes in 0 blocks
==6048== 
==6048== For counts of detected and suppressed errors, rerun with: -v
==6048== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

So, I started to look a bit at the constructors/destructors of the glib to try to understand its memory model, but I only found constructors and absolutely no code for the destructors...

Then, I decided to give it try (as a proof of concept) and this is actually my patch in this MR (tests pass with no problem).

If I rerun the exact same program as previously, I going from 18Kb down to 62b of leaks:

valgrind --leak-check=full  --show-leak-kinds=all --track-origins=yes ./hello-glib
==12099== Memcheck, a memory error detector
==12099== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12099== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==12099== Command: ./hello-glib
==12099== 
Hello World!
==12099== 
==12099== HEAP SUMMARY:
==12099==     in use at exit: 62 bytes in 5 blocks
==12099==   total heap usage: 19 allocs, 14 frees, 53,332 bytes allocated
==12099== 
==12099== 4 bytes in 1 blocks are still reachable in loss record 1 of 5
==12099==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==12099==    by 0x48F9C99: g_private_impl_new (gthread-posix.c:1007)
==12099==    by 0x48F9D64: g_private_get_impl (gthread-posix.c:1035)
==12099==    by 0x48F9DB9: g_private_get (gthread-posix.c:1062)
==12099==    by 0x48BFA36: thread_memory_from_self (gslice.c:507)
==12099==    by 0x48C0563: g_slice_alloc (gslice.c:1002)
==12099==    by 0x4888A6E: g_hash_table_new_full (ghash.c:1018)
==12099==    by 0x4888A4A: g_hash_table_new (ghash.c:981)
==12099==    by 0x48B126E: g_quark_init (gquark.c:61)
==12099==    by 0x4898BE8: glib_init (glib-init.c:269)
==12099==    by 0x4898C07: glib_init_ctor (glib-init.c:336)
==12099==    by 0x400F3B9: call_init.part.0 (dl-init.c:72)
==12099== 
==12099== 4 bytes in 1 blocks are still reachable in loss record 2 of 5
==12099==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==12099==    by 0x48F9C99: g_private_impl_new (gthread-posix.c:1007)
==12099==    by 0x48F9D64: g_private_get_impl (gthread-posix.c:1035)
==12099==    by 0x48F9DB9: g_private_get (gthread-posix.c:1062)
==12099==    by 0x486E56E: g_get_charset (gcharset.c:187)
==12099==    by 0x48A962B: g_print (gmessages.c:3189)
==12099==    by 0x109158: main (hello-glib.c:12)
==12099== 
==12099== 15 bytes in 1 blocks are still reachable in loss record 3 of 5
==12099==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==12099==    by 0x48A5617: g_malloc (gmem.c:99)
==12099==    by 0x48A58C5: g_malloc_n (gmem.c:337)
==12099==    by 0x48C303E: g_strdup (gstrfuncs.c:363)
==12099==    by 0x486E603: g_get_charset (gcharset.c:203)
==12099==    by 0x48A962B: g_print (gmessages.c:3189)
==12099==    by 0x109158: main (hello-glib.c:12)
==12099== 
==12099== 15 bytes in 1 blocks are still reachable in loss record 4 of 5
==12099==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==12099==    by 0x48A5617: g_malloc (gmem.c:99)
==12099==    by 0x48A58C5: g_malloc_n (gmem.c:337)
==12099==    by 0x48C303E: g_strdup (gstrfuncs.c:363)
==12099==    by 0x486E635: g_get_charset (gcharset.c:205)
==12099==    by 0x48A962B: g_print (gmessages.c:3189)
==12099==    by 0x109158: main (hello-glib.c:12)
==12099== 
==12099== 24 bytes in 1 blocks are still reachable in loss record 5 of 5
==12099==    at 0x4837B65: calloc (vg_replace_malloc.c:752)
==12099==    by 0x48A5680: g_malloc0 (gmem.c:129)
==12099==    by 0x48CEC86: g_private_set_alloc0 (gthread.c:540)
==12099==    by 0x486E58A: g_get_charset (gcharset.c:191)
==12099==    by 0x48A962B: g_print (gmessages.c:3189)
==12099==    by 0x109158: main (hello-glib.c:12)
==12099== 
==12099== LEAK SUMMARY:
==12099==    definitely lost: 0 bytes in 0 blocks
==12099==    indirectly lost: 0 bytes in 0 blocks
==12099==      possibly lost: 0 bytes in 0 blocks
==12099==    still reachable: 62 bytes in 5 blocks
==12099==         suppressed: 0 bytes in 0 blocks
==12099== 
==12099== For counts of detected and suppressed errors, rerun with: -v
==12099== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

As said at the beginning, this patch is not intended to go to master. But, I really want to know if there is a chance to get through one day if I manage to build a proper memory model that will keep track of released memory... In fact, the gthreads seems to be the place to start with after having this glib-init done.

So, what do you think ?

Merge request reports