Intermittent segfault when freeing MMManager object using g_object_unref()

Andrew Stoehr astoehr at hedcontrols.com
Tue Jun 8 20:32:45 UTC 2021


Hello again,

> https://aleksander.es/data/valgrind-memcheck.pdf :)

Wow, this is a great resource!  Thanks!

> How are you running the valgrind test? Are you running it with
> G_SLICE=always-malloc?
> What version of GLib are you using? I hope it's GLib >= 2.55.1.

I was not including G_SLICE=always-malloc before, but I am using GLib 2.64.4.

> But that would not circumvent any leak, you would be moving it to a
> different place. Or do you mean you would fork a process every time
> you need to run some libmm-glib operation?

Yes, that is what I was originally thinking -- forking a new process every time.  Definitely not ideal, I know :)

> What's happening here is that there is some g_object_unref() inside
> GIO that *creates* an idle source and attaches it to the main context
> in GLib. The purpose, I assume, is to run something once the object
> has been fully disposed, but in your case, the problem is that as
> there is no main loop, those idles keep being added for every unref
> you run.

This makes a lot of sense.  I really appreciate the explanation.  I was curious before how things got cleaned up, and it seems obvious now that something needs to be running in the background taking care of that.

> You should be able to solve this issue without further help or change
> in libmm-glib or glib by switching your logic to use a main loop and
> use the async APIs instead or in addition to the sync ones. This is
> relatively easy to do, even if your program doesn't use a GLib
> mainloop, because you could have all libmm-glib related stuff running
> in a separate thread with its own mainloop inside the thread, and then
> syncing whatever info you need via mutexes or something like that.

I was able to incorporate this by doing:

    GMainLoop *g_loop = NULL;
    GThread *g_loop_thread = NULL;

    g_loop = g_main_loop_new (NULL, FALSE);
    g_loop_thread = g_thread_new(NULL, (GThreadFunc)g_main_loop_run, g_loop);

And then cleaning up the main loop and thread after my application's loop.  The application loop is unchanged otherwise, still using the "sync" APIs.  Seems to work really well -- thanks for the guidance.

> If you cannot change the whole logic to use a main loop in a separate
> thread, you could still have a dummy main loop running some iterations
> for some time; e.g. in your tester, instead of the usleep() call you
> could put:
> 
>    {
>         GMainLoop *loop;
> 
>         loop = g_main_loop_new (NULL, FALSE);
>         g_idle_add ((GSourceFunc)stop_loop, loop);
>         g_main_loop_run (loop);
>         g_main_loop_unref (loop);
>     }
> 
> with a separate stop_loop method like this:
> 
> static gboolean
> stop_loop (GMainLoop *loop)
> {
>     g_main_loop_quit (loop);
>     return G_SOURCE_REMOVE;
> }
> 
> What we're doing is creating a main loop that will run the events in
> the main context (the GDBus setup would have scheduled events in that
> main context). And we also add our own idle source in the main
> context, and given that idles are all scheduled one after the other,
> our idle source would be scheduled LAST. And then, in the idle source
> we stop the main loop, as there were no more idles scheduled
> afterwards. It's a bit hacky, but should work when you need to
> synchronize the idles setup by GDBus. Note that the main loop will run
> exclusively for the time needed to cleanup the event list until your
> idle, so that should be extremely quick.

> After that change, I cannot see any major still reachable leak
> happening any more, see the attached source file with my changes.

This is a clever workaround!  I tried with this method also, just for fun, and I did not see any more leaking memory in my application.  I did notice the memory usage for dbus-daemon increased steadily over a period of time, though -- with my application running overnight, it grew to take up about 20% of my hardware's 128MB of RAM.  I did not see this increase with the main loop in a separate thread as I mentioned above, so it might have something to do with the constant creation and destruction of main loops, or perhaps I am just missing something.  I figured I would mention it to you anyways just in case it is something you want to investigate.

> I should definitely document this solution in modemmanager.org...

I agree -- it is a really convenient way to get the functionality while remaining synchronous.

Thank you so much for all of the help and information -- you are a lifesaver!

Best,

Andrew


More information about the ModemManager-devel mailing list