Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GTK Platform/GtkSharp has a slow memory leak with Drawable controls #1796

Closed
derbyw opened this issue Oct 6, 2020 · 6 comments
Closed

GTK Platform/GtkSharp has a slow memory leak with Drawable controls #1796

derbyw opened this issue Oct 6, 2020 · 6 comments
Labels
Milestone

Comments

@derbyw
Copy link
Contributor

derbyw commented Oct 6, 2020

I have an application that uses many Drawable controls to put up custom gauges for my application.
Updating these controls will slowly consume memory/resources on the Gtk platform running on GTK+3
on both Unix and Windows. The memory issue does not occur on the WPF build and I have no mac to test there.

To isolate the problem, I have created the following test app that demonstrates the issue and put up on GitHub:

https://github.com/derbyw/EtoGtkReseourceLeakTest.git

see the readme for info on what buttons do. there are options to stop/start the updates

The app puts up a bunch of controls (16 text 4 bar gauge) and feeds them at about 100ms - the app will display a running total of memory which will slowly climb as the app runs. If you let it run in VS 2019 you can also watch it rise over time - give it a good 10 minutes to see the distinct ramp.

The app is currently running against the current release (2.5.6) built in VS 2019. running against the msys2 Gtk+3 system on Windows and also shows against the Gtk+-3.22.26 on Linux (embedded) and also Ubuntu LTS 18.04. (can't recall the GTS version there).

I had run into this previously on my original build for the embedded system and ended up fixing some of the auto generated code in GtkSharp after a bunch of time spent with Valgrind, etc. That system also supported the Gestures changes I proposed a while back and were not tied up cleanly to get back to Harry for GtkSharp. So I've been stuck at Eto.Forms from about 2 year ago till I had enough time to fix things again and get sync'd with the project. I finally got the time, ported everything to .Net Core 3.1, crossing my fingers that the issue was fixed in the meantime -- alas the issue remains.

IIRC - the leak was be related to the various resources allocated and release when the drawable control get drawn i.e. each point event. The reference counting seemed to get messed up and "freed" Pango, etc. resources don't quite all get returned. I will try to find my changes from when I fixed this last time to see what I modified. I was able to finally get the process net memory neutral and have been running that way for the last 2 years. Unfortunately, I have not been able to get the new GtkSharp build to build on my embedded machine so I will continue work there.

@cwensley cwensley added this to the 2.5.x milestone Oct 14, 2020
@derbyw
Copy link
Contributor Author

derbyw commented Oct 14, 2020

for context, here are my 2017 notes from the fixes to the original:

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 to be 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 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 githib. In
the meantime has anyone seen this?

@derbyw
Copy link
Contributor Author

derbyw commented Oct 14, 2020

I have modified the example app by adding the full Eto and GtkSharp libraries so it can be debugged/presented.
I have also made 2 proposed changes, (1) in the Gtk platform DrawHandler, change how the pango context is generated, the current code creates a new on on each draw sequence and then something fails to delete some piece of it when the draw sequence ends. Without changes, the created Pango context needs to be disposed - putting in a using block for the pango context around the graphics object significantly slowed but did not stop leaking - there is still something not working in that. However, the real problem is that we should not be creating a new context each time at at all, so change (2) adds the GetPangoContext (which is in the API but not currently in GtkSharp) to the GtkSharp widget code. Using this command we can get the context once on the first pass, and then re-use it on each pass. Obviously this will require coordinated changes to both libraries.

See here (and search for "get-pango-context") for details on the 2 variants:
https://developer.gnome.org/gtk3/unstable/GtkWidget.html#gtk-widget-get-pango-context

I am testing this now on Windows 10, Ubuntu 20.04, and my embedded system an all show significant reduction in the leaking rate which is now almost flat -I will continue testing the code and may run it with valgrind again so see what remaining items are still leaking.

https://github.com/derbyw/EtoGtkReseourceLeakTest.git

@derbyw
Copy link
Contributor Author

derbyw commented Oct 14, 2020

Also - for future testing purposes I have a proposed update to the DrawLoop Eto test that exercises the pango aspect of Drawable more strenuously - I can make a pull request for that once we are done getting the leak issue nailed down.

@derbyw
Copy link
Contributor Author

derbyw commented Oct 14, 2020

test results:
On my embedded system - after 2 hours the memory footprint growth is now dead flat
My test on Ubuntu was running in a debugger (Rider 2020.2) and was slow but still increasing - suspect this is due to debugger and environment so will repeat tests outside debugger but looking good overall.

@derbyw
Copy link
Contributor Author

derbyw commented Oct 15, 2020

even better news, discovered that get get-pango-context api call is being mapped to a property i.e. h.Control.PangoContext not a method - and already exists in GtkSharp so the fix is only on the Eto side. I will re-run testing and then submit a PR for this shortly..

@cwensley
Copy link
Member

Thanks again for looking into this issue! I think there was a reason why I didn't use the PangoContext property at one point, but I can't remember exactly why. If it works then 🤷🏻‍♂️

@cwensley cwensley added the bug label Nov 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants