picoe / Eto

Cross platform GUI framework for desktop and mobile applications in .NET
Other
3.57k stars 325 forks source link

GTK3 Pango.Context leak in Eto.GtkSharp.Forms.Controls.DrawableHandler #921

Open derbyw opened 6 years ago

derbyw commented 6 years ago

I have a GTK3 Eto.Application form with a bunch of custom (i.e. Drawable) based controls (text and bar gauges) and for the last several weeks have been chasing down a severe memory leak which is coming from DrawableHandler.HandleDrawn. The leaked object is a Pango.Context which gets allocated in unmanaged memory via CreatePangoContext helper in Widget.cs (GTK#). On my app I have about 12 controls all updating at about 100ms and so this leak grows very large over time and kills the app eventually. i.e. If I cut back on the number of controls that are updating, the leak rate slows but still leaks all the same. I have spent a bunch of time making sure that this is not a threading or managed memory issue.

Running Valgrind on it shows the following smoking gun after running the app for about 5 minutes (with all the controls):

==3212== 1,389,427 (387,200 direct, 1,002,227 indirect) bytes in 4,400 blocks are definitely lost in loss record 51,757 of 51,757 ==3212== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==3212== by 0xDB8F578: g_malloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.5400.1) ==3212== by 0xDBA70F5: g_slice_alloc (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.5400.1) ==3212== by 0xDBA7588: g_slice_alloc0 (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.5400.1) ==3212== by 0xD91F6D4: g_type_create_instance (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.5400.1) ==3212== by 0xD9005E7: ??? (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.5400.1) ==3212== by 0xD901D84: g_object_new_with_properties (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.5400.1) ==3212== by 0xD902800: g_object_new (in /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0.5400.1) ==3212== by 0xD0DA062: pango_font_map_create_context (in /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0.4000.12) ==3212== by 0xB1FA33B: gdk_pango_context_get_for_screen (in /usr/lib/x86_64-linux-gnu/libgdk-3.so.0.2200.24) ==3212== by 0xABE585C: gtk_widget_create_pango_context (in /usr/lib/libgtk-3.so.0) ==3212== by 0x22ABE574: ???

This call gets made in HandleDrawn here:

    using (var graphics = new Graphics(new GraphicsHandler(args.Cr, h.Control.CreatePangoContext (), false)))
        {
            if (h.SelectedBackgroundColor != null)
                graphics.Clear(h.SelectedBackgroundColor.Value);

            h.Callback.OnPaint(h.Widget, new PaintEventArgs (graphics, rect.ToEto()));
        }

So the CreatePangoContext (in the GTK# library) looks like so:

    public Pango.Context CreatePangoContext() {
        IntPtr raw_ret = gtk_widget_create_pango_context(Handle);
        Pango.Context ret = GLib.Object.GetObject(raw_ret) as Pango.Context;
        return ret;
    }

and the GraphicsHandler class saves the passed pango context but it looks like the Dispose on the Graphics object at the end of the using block is not correctly g_free'ing the pango context. It does not appear that the GraphicsHandler class disposes the pango context -it inherits from WidgetHandler but doesn't override the Dispose and release the context and nothing else seems to dispose it.

I realize this may in fact be a GTK# issue and I'm still trying to make sense of GTK#'s Pango.Context and GLib.Object code so I understand how/why the dispose is supposed to work - but this issue affects Eto in a big way. I will try to make a good test app which which isolates this behavior and put it up on github.

In the meantime has anyone else seen this?

derbyw commented 6 years ago

I have created an simple project that demonstrates the slow leak I'm seeing in my Eto apps.

(https://github.com/derbyw/PangoSanityCheck.git)

The solution builds both GTK2 and GTK3 versions of the application built against the 2.3 Eto packages from nuget

To test: hit the "Start/Stop" button which will start the display updates and watch the Process memory line.

As the updates run, the app will display both the GC and private (Process) memory of the app. The private memory includes non-managed memory allocations. The app shows the change since start as well as totals for each. The app *should* allocate memory when the updates first start and then stablize to a steady allocation and stay there. This is what occurs on the GTK2 app but the GTK3 version leaks and allocates more process memory every few seconds. 

I have tested this on Ubuntu 16.04 and Ubunto 17.10 as well as an embedded system and have seen the same results but I want to verify that this is a universal issue. Can someone try the app and report if they see the same thing?
derbyw commented 6 years ago

I have isolated and fixed the problem which is two-fold and affects the Drawable and ImageView handlers.

A. In HandleDrawn for both types, the Pango.Context is created but not disposed correctly - there needs to be a using statement on it to correctly dispose the managed side

B. the GTK3 CreatePangoContext call is leaking badly in un-managed space NOT in managed space. the current code is calling gtk_widget_create_pango_context which creates a new pango context each time the drawn handler is called. The Dispose of the managed Pango.Context does not un-reference the underlying unmanaged object but kills the reference to it and the memory is lost.

The solution I propose is to change the call to gtk_widget_get_pango_context which basically only allocates the Pango context once (for each thread) and then reuses the context for all subsequent calls. This is obviously more efficient in any case.

I will submit a PR shortly with the fixes for the above.

cwensley commented 6 years ago

@derbyw that's great, thanks for tracking that down. I agree, using gtk_widget_get_pango_context sounds like the way to go!