Bug 573727 - [GTK3] TextLayout leaks native memory
Summary: [GTK3] TextLayout leaks native memory
Status: NEW
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 4.20   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Platform-SWT-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-05-24 10:15 EDT by Simeon Andreev CLA
Modified: 2021-06-02 03:16 EDT (History)
2 users (show)

See Also:


Attachments
jemalloc leak reports seen with the snippet from the description. (149.14 KB, application/zip)
2021-05-24 10:15 EDT, Simeon Andreev CLA
no flags Details
jemalloc leaks reported in Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout during debug and switch perspectives test (14.19 KB, application/zip)
2021-06-02 03:16 EDT, Simeon Andreev CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Simeon Andreev CLA 2021-05-24 10:15:40 EDT
Created attachment 286444 [details]
jemalloc leak reports seen with the snippet from the description.

Snippet to reproduce the leak (based on http://www.java2s.com/Tutorial/Java/0280__SWT/TextLayoutFontStyleandColor.htm)

  public static void main(String[] args) {
    Display display = new Display();
    Shell shell = new Shell(display);

    final int fontsCount = 30;
    String[] fontNames = {
    		"Tahoma", 
    		"MS Mincho",
    		"Arabic Transparent",
    		"Arial",
    };
    int[] fontStyles = {
    		SWT.BOLD,
    		SWT.ITALIC,
    		SWT.NORMAL,
    };
    Color[] fontColors = {
    	    display.getSystemColor(SWT.COLOR_BLUE),
    	    display.getSystemColor(SWT.COLOR_GREEN),
    	    display.getSystemColor(SWT.COLOR_YELLOW),
    	    display.getSystemColor(SWT.COLOR_GRAY),
    	    null,
    };
    StringBuilder text = new StringBuilder();
    List<String> fontTexts = new ArrayList<>();
    for (int i = 0; i < fontsCount; ++i) {
    	String fontText = "font " + i + " ";
		fontTexts.add(fontText);
    	text.append(fontText);
    }

    final TextLayout layout = new TextLayout(display);
    layout.setText(text.toString());

    List<Font> fonts = new ArrayList<>();
    int index = 0;
    for (int i = 0; i < fontsCount; ++i) {
    	Font font = new Font(display, fontNames[i % fontNames.length], 14 + (i % 5), fontStyles[i % fontStyles.length]);
    	fonts.add(font);
    	TextStyle style = new TextStyle(font, fontColors[i % fontColors.length], fontColors[(i*i) % fontColors.length]);
    	String fontText = fontTexts.get(i);
    	layout.setStyle(style, index, index + fontText.length());
    	index += fontText.length();
    }

    shell.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
    shell.addListener(SWT.Paint, new Listener() {
      public void handleEvent(Event event) {
        layout.draw(event.gc, 10, 10);
      }
    });
    shell.open();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
    for (Font font : fonts) {
    	font.dispose();
    }
    layout.dispose();
    display.dispose();
  }

Reports by jemalloc when using 5 fonts resp. using 30 fonts (there are multiple, see attached comparison "textlayout_leak_5_vs_30_fonts.zip"):
leak with 5 fonts in snippet

[52224 bytes leaked]
je_prof_backtrace (/home/sandreev/git/misc/jemalloc/src/prof.c:636 (discriminator 2))
je_malloc_default (/home/sandreev/git/misc/jemalloc/src/jemalloc.c:2289)
ft_mem_qalloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:76)
ft_mem_alloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:55)
ft_mem_qrealloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:145)
ft_mem_realloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:101)
tt_face_load_hmtx (/usr/src/debug/freetype-2.4.11/src/sfnt/ttmtx.c:178)
sfnt_load_face (/usr/src/debug/freetype-2.4.11/src/sfnt/sfobjs.c:658)
tt_face_init (/usr/src/debug/freetype-2.4.11/src/truetype/ttobjs.c:557)
open_face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:1154)
FT_Open_Face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:2115)
FT_New_Face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:1216)
_cairo_ft_unscaled_font_lock_face (/usr/src/debug/cairo-1.14.8/src/cairo-ft-font.c:689)
_cairo_ft_font_face_scaled_font_create (/usr/src/debug/cairo-1.14.8/src/cairo-ft-font.c:1927)
INT_cairo_scaled_font_create (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:1147)
_pango_cairo_font_private_get_scaled_font.part.0 (/usr/src/debug/pango-1.40.4/pango/pangocairo-font.c:82)
pango_cairo_fc_font_lock_face (/usr/src/debug/pango-1.40.4/pango/pangocairo-fcfont.c:118)
_pango_fc_shape (/usr/src/debug/pango-1.40.4/pango/pangofc-shape.c:296)
pango_shape_full (/usr/src/debug/pango-1.40.4/pango/shape.c:116)
shape_run (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:3300)
process_item (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:3413)
pango_layout_check_lines (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:4040)
pango_layout_get_iter (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:5767)
pango_renderer_draw_layout (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:182)
_pango_cairo_do_layout (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:1020)
Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout (??:?)
?? (??:0)

leak with 30 fonts in snippet

[58368 bytes leaked]
je_prof_backtrace (/home/sandreev/git/misc/jemalloc/src/prof.c:636 (discriminator 2))
je_malloc_default (/home/sandreev/git/misc/jemalloc/src/jemalloc.c:2289)
ft_mem_qalloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:76)
ft_mem_alloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:55)
ft_mem_qrealloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:145)
ft_mem_realloc (/usr/src/debug/freetype-2.4.11/src/base/ftutil.c:101)
tt_face_load_hmtx (/usr/src/debug/freetype-2.4.11/src/sfnt/ttmtx.c:178)
sfnt_load_face (/usr/src/debug/freetype-2.4.11/src/sfnt/sfobjs.c:658)
tt_face_init (/usr/src/debug/freetype-2.4.11/src/truetype/ttobjs.c:557)
open_face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:1154)
FT_Open_Face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:2115)
FT_New_Face (/usr/src/debug/freetype-2.4.11/src/base/ftobjs.c:1216)
_cairo_ft_unscaled_font_lock_face (/usr/src/debug/cairo-1.14.8/src/cairo-ft-font.c:689)
_cairo_ft_font_face_scaled_font_create (/usr/src/debug/cairo-1.14.8/src/cairo-ft-font.c:1927)
INT_cairo_scaled_font_create (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:1147)
_pango_cairo_font_private_get_scaled_font.part.0 (/usr/src/debug/pango-1.40.4/pango/pangocairo-font.c:82)
pango_cairo_fc_font_lock_face (/usr/src/debug/pango-1.40.4/pango/pangocairo-fcfont.c:118)
_pango_fc_shape (/usr/src/debug/pango-1.40.4/pango/pangofc-shape.c:296)
pango_shape_full (/usr/src/debug/pango-1.40.4/pango/shape.c:116)
shape_run (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:3300)
process_item (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:3413)
pango_layout_check_lines (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:4040)
pango_layout_get_iter (/usr/src/debug/pango-1.40.4/pango/pango-layout.c:5767)
pango_renderer_draw_layout (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:182)
_pango_cairo_do_layout (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:1020)
Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout (??:?)
?? (??:0)

Using more fonts in the snippet seem to lead to a slightly bigger leak.
Comment 1 Simeon Andreev CLA 2021-05-24 11:05:02 EDT
I no longer see any leak reports, using the snippet from the description, if I apply this change:

diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/TextLayout.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/TextLayout.java
index 3635d86168..2dc323176e 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/TextLayout.java  
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/TextLayout.java  
@@ -536,7 +536,7 @@ void drawInPixels(GC gc, int x, int y, int selectionStart, int selectionEnd, Col
                        Cairo.cairo_translate(cairo, -2 * x - width(), 0);
                }
                Cairo.cairo_move_to(cairo, x, y);
-               OS.pango_cairo_show_layout(cairo, layout);
+               //OS.pango_cairo_show_layout(cairo, layout);
                drawBorder(gc, x, y, null);
                if ((data.style & SWT.MIRRORED) != 0) {
                        Cairo.cairo_restore(cairo);

Though I don't really see what SWT has to do with this.

Alexander, any chance you can take a look here? Doesn't need to be immediately, I still have other leaks to go over.

Maybe this is a false positive that needs to be suppressed (though I do see a growing leak for Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout when I use Eclipse...).
Comment 2 Alexandr Miloslavskiy CLA 2021-05-24 20:40:39 EDT
I suspect that font information is permanently cached in GTK. I understand that you see no growing leak when using the same fonts? Probably 'FT_New_Face' can be suppressed. 

As for the growing leak in Eclipse you're seeing, is it also related to 'FT_New_Face' ?
Comment 3 Simeon Andreev CLA 2021-05-25 03:48:20 EDT
(In reply to Alexandr Miloslavskiy from comment #2)
> I suspect that font information is permanently cached in GTK. I understand
> that you see no growing leak when using the same fonts? Probably
> 'FT_New_Face' can be suppressed. 
> 
> As for the growing leak in Eclipse you're seeing, is it also related to
> 'FT_New_Face' ?

I'm not sure, I don't think so (I do see leaks with "FT_New_Face" on the trace). Here is the leak from multiple operations in Eclipse:

[18432 bytes leaked]
je_prof_backtrace (/home/sandreev/git/misc/jemalloc/src/prof.c:636 (discriminator 2))
je_malloc_default (/home/sandreev/git/misc/jemalloc/src/jemalloc.c:2289)
_cairo_scaled_glyph_lookup (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:2984)
_cairo_scaled_font_glyph_device_extents (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:2249)
_cairo_composite_rectangles_init_for_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-composite-rectangles.c:447)
_cairo_compositor_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-compositor.c:238)
_cairo_xlib_surface_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-xlib-surface.c:1671)
_cairo_surface_show_text_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-surface.c:2600)
_cairo_gstate_show_text_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-gstate.c:2023)
cairo_show_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo.c:3319)
pango_cairo_renderer_show_text_glyphs.isra.3 (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:510)
pango_cairo_renderer_draw_glyphs (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:526)
pango_renderer_draw_glyphs (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:642)
pango_renderer_draw_layout_line (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:571)
pango_renderer_draw_layout (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:195)
_pango_cairo_do_layout (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:1020)
Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout (??:?)
?? (??:0)

And from fewer operations in Eclipse:

[6144 bytes leaked]
je_prof_backtrace (/home/sandreev/git/misc/jemalloc/src/prof.c:636 (discriminator 2))
je_malloc_default (/home/sandreev/git/misc/jemalloc/src/jemalloc.c:2289)
_cairo_scaled_glyph_lookup (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:2984)
_cairo_scaled_font_glyph_device_extents (/usr/src/debug/cairo-1.14.8/src/cairo-scaled-font.c:2231)
_cairo_composite_rectangles_init_for_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-composite-rectangles.c:447)
_cairo_compositor_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-compositor.c:238)
_cairo_xlib_surface_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-xlib-surface.c:1671)
_cairo_surface_show_text_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-surface.c:2600)
_cairo_gstate_show_text_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo-gstate.c:2023)
cairo_show_glyphs (/usr/src/debug/cairo-1.14.8/src/cairo.c:3319)
pango_cairo_renderer_show_text_glyphs.isra.3 (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:510)
pango_cairo_renderer_draw_glyphs (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:526)
pango_renderer_draw_glyphs (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:642)
pango_renderer_draw_layout_line (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:571)
pango_renderer_draw_layout (/usr/src/debug/pango-1.40.4/pango/pango-renderer.c:195)
_pango_cairo_do_layout (/usr/src/debug/pango-1.40.4/pango/pangocairo-render.c:1020)
Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout (??:?)
?? (??:0)
Comment 4 Simeon Andreev CLA 2021-05-25 04:39:01 EDT
(In reply to Alexandr Miloslavskiy from comment #2)
> I suspect that font information is permanently cached in GTK. I understand
> that you see no growing leak when using the same fonts?

I think so, at least when it comes to the same font SWT objects. As long as new font objects are created though, I think the leak grows.

In Eclipse, I see new Font objects created when I debug a snippet, the process console seems to create them (3 on each debug):

	at org.eclipse.swt.graphics.Font.init(Font.java:265)
	at org.eclipse.swt.graphics.Font.<init>(Font.java:116)
	at org.eclipse.swt.custom.StyledTextRenderer.getFont(StyledTextRenderer.java:563)
	at org.eclipse.swt.custom.StyledTextRenderer.setFont(StyledTextRenderer.java:1387)
	at org.eclipse.swt.custom.StyledText.setFont(StyledText.java:8971)
	at org.eclipse.ui.console.TextConsoleViewer.setFont(TextConsoleViewer.java:361)
	at org.eclipse.ui.console.TextConsolePage.propertyChange(TextConsolePage.java:213)
	at org.eclipse.ui.console.AbstractConsole$PropertyNotifier.run(AbstractConsole.java:97)
	at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
	at org.eclipse.ui.console.AbstractConsole$PropertyNotifier.notify(AbstractConsole.java:112)
	at org.eclipse.ui.console.AbstractConsole.firePropertyChange(AbstractConsole.java:230)
	at org.eclipse.ui.console.TextConsole.setFont(TextConsole.java:289)
	at org.eclipse.debug.internal.ui.views.console.ProcessConsole.lambda$0(ProcessConsole.java:575)

If GTK+ is caching anything, will it stop caching stuff at some point? I'm confused by the increase of the leak, while essentially doing the same operations. I would expect caching to be done for the first set of operations and to then not have to cache anything else?
Comment 5 Alexandr Miloslavskiy CLA 2021-05-25 05:07:58 EDT
I think that it only caches different fonts. I expect that creating multiple instances of the same font (same font size etc) will not grow the leak.
Comment 6 Simeon Andreev CLA 2021-06-01 07:24:21 EDT
I believe the snippet in the description actually shows expected behaviour, I've actually managed to create many different fonts with that snippet.

I'll have to find another snippet, one that reflects the leak we see with Eclipse interactions (so far I believe the leak is coming from the runtime console).
Comment 7 Simeon Andreev CLA 2021-06-02 03:16:17 EDT
Created attachment 286501 [details]
jemalloc leaks reported in Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout during debug and switch perspectives test

I've ran our test (on Eclipse 4.15 platform) 10 and 700 times (700 runs take about 12 hours) and I've compared the leak reports in Java_org_eclipse_swt_internal_gtk_OS_pango_1cairo_1show_1layout. I see only a total of 2 KB lost over 12 hours, so I'll leave this ticket for later on.

As far as I can tell, font caching is done at different calls (I don't know what causes the difference). This makes analysing the leak not trivial, since the leaked bytes are "shuffled" here and there. Sometimes the jemalloc leak reports would not differ at all, other times it would differ in the same stack trace (see e.g. description), and yet other times bytes lost would differ in different stack traces.

There does seem to be a leak, but its not large enough to bother (possibly, to bother at all).