Bug 552373 - [GTK] Hover can cause UI Freeze on huge input string
Summary: [GTK] Hover can cause UI Freeze on huge input string
Status: NEW
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 4.14   Edit
Hardware: PC All
: P3 normal with 1 vote (vote)
Target Milestone: ---   Edit
Assignee: Platform-SWT-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: performance
: 553581 (view as bug list)
Depends on:
Blocks:
 
Reported: 2019-10-24 06:00 EDT by Lars Vogel CLA
Modified: 2022-03-09 11:11 EST (History)
12 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Lars Vogel CLA 2019-10-24 06:00:30 EDT
I'm very much impressed with the work Mickael is doing to make code completion not running in the main thread. See Bug 547683 and Bug 538656 for example.

Another thing with AFAIK Eclipse runs in the main thread is the calculation of the Javadoc hover, most like hovers in general.

From discussion with Google in the past, I remember that this was a frequently cause of UI freezes for very large workspace. Maybe this can also moved to be asynchronously?
Comment 1 Lars Vogel CLA 2019-10-24 06:01:09 EDT
Mickael, WDYT?
Comment 2 Mickael Istria CLA 2019-10-24 07:43:02 EDT
I support fully the idea of hover being non-blocking, but IIRC, computing the hover content is already happening in non-UI Thread; see TextViewerHoverManager line 159.
But in all those operations some may later request to run in UI Thread (Display.syncExec() or UIJob.join()) and cause a freeze.
It IMO needs to be investigated first when does a freeze happen and why. Do you have a simple example scenario that can help?
Comment 3 Lars Vogel CLA 2020-02-05 06:12:54 EST
*** Bug 553581 has been marked as a duplicate of this bug. ***
Comment 4 Dani Megert CLA 2020-02-19 08:26:56 EST
(In reply to Mickael Istria from comment #2)
> I support fully the idea of hover being non-blocking, but IIRC, computing
> the hover content is already happening in non-UI Thread
Correct.
Comment 5 Mauro Molinari CLA 2020-04-29 04:54:55 EDT
Just wanted to add that, as described in bug #553581, I also hit frequent and very annoying UI freezes in facelet editing (.xhtml). This is a problem Eclipse has been having for years and if you don't have a very powerful CPU, in a medium to large project hurts productivity significantly (and hurts user experience a lot).

I really hope things could be improved here.
Comment 6 Mickael Istria CLA 2020-04-29 12:53:51 EDT
FWIW, we also notice such freezes in ShellWax, the Bash editor based on language server, generic editor and LSP4E.
We did some investigation, and although the hover request and computation is processed asynchronously without blocking anything (standard Platform behavior here), the freeze is happening later, when trying to compute the size of the popup content (which in some cases is super big, like try `man export`) to decide of the size of the popup to render. But in case of such big input, computing the size of the content is a long operation, and that does cause the freeze.
Comment 7 Lars Vogel CLA 2020-04-30 02:37:51 EDT
(In reply to Mickael Istria from comment #6)
> FWIW, we also notice such freezes in ShellWax, the Bash editor based on
> language server, generic editor and LSP4E.
> We did some investigation, and although the hover request and computation is
> processed asynchronously without blocking anything (standard Platform
> behavior here), the freeze is happening later, when trying to compute the
> size of the popup content (which in some cases is super big, like try `man
> export`) to decide of the size of the popup to render.

Can you post the code which does this? Maybe we can set a default size if the input is large?
Comment 8 Dani Megert CLA 2020-04-30 03:03:03 EDT
(In reply to Lars Vogel from comment #7)
> (In reply to Mickael Istria from comment #6)
> > FWIW, we also notice such freezes in ShellWax, the Bash editor based on
> > language server, generic editor and LSP4E.
> > We did some investigation, and although the hover request and computation is
> > processed asynchronously without blocking anything (standard Platform
> > behavior here), the freeze is happening later, when trying to compute the
> > size of the popup content (which in some cases is super big, like try `man
> > export`) to decide of the size of the popup to render.
> 
> Can you post the code which does this? Maybe we can set a default size if
> the input is large?
Best is the whole stack trace.
Comment 9 Mickael Istria CLA 2020-04-30 03:35:31 EDT
From https://github.com/eclipse/shellwax/issues/18

```
main" #1 prio=6 os_prio=0 cpu=215657.60ms elapsed=1835.72s tid=0x00007f0238012800 nid=0x1459 runnable  [0x00007f023d20c000]
   java.lang.Thread.State: RUNNABLE
	at org.eclipse.swt.internal.gtk.OS._pango_layout_get_line_count(Native Method)
	at org.eclipse.swt.internal.gtk.OS.pango_layout_get_line_count(OS.java:3762)
	at org.eclipse.swt.graphics.TextLayout.getLineBoundsInPixels(TextLayout.java:981)
	at org.eclipse.swt.graphics.TextLayout.getLineBounds(TextLayout.java:976)
	at org.eclipse.jface.internal.text.html.BrowserInformationControl.computeSizeHint(BrowserInformationControl.java:441)
	at org.eclipse.jface.text.AbstractInformationControlManager.internalShowInformationControl(AbstractInformationControlManager.java:1174)
	at org.eclipse.jface.text.AbstractInformationControlManager.presentInformation(AbstractInformationControlManager.java:1120)
	at org.eclipse.jface.text.AbstractHoverInformationControlManager.presentInformation(AbstractHoverInformationControlManager.java:884)
	at org.eclipse.jface.text.TextViewerHoverManager.doPresentInformation(TextViewerHoverManager.java:251)
	at org.eclipse.jface.text.TextViewerHoverManager.lambda$0(TextViewerHoverManager.java:241)
	at org.eclipse.jface.text.TextViewerHoverManager$$Lambda$776/0x0000000840cf4440.run(Unknown Source)
```
Comment 10 Andrey Loskutov CLA 2020-04-30 04:21:26 EDT
(In reply to Mickael Istria from comment #9)
> From https://github.com/eclipse/shellwax/issues/18
> 
> ```
> main" #1 prio=6 os_prio=0 cpu=215657.60ms elapsed=1835.72s
> tid=0x00007f0238012800 nid=0x1459 runnable  [0x00007f023d20c000]
>    java.lang.Thread.State: RUNNABLE
> 	at org.eclipse.swt.internal.gtk.OS._pango_layout_get_line_count(Native
> Method)
> 	at org.eclipse.swt.internal.gtk.OS.pango_layout_get_line_count(OS.java:3762)
> 	at
> org.eclipse.swt.graphics.TextLayout.getLineBoundsInPixels(TextLayout.java:

Same ticket title is: "Some UI Freeze on hover when man documentation is huge", and it gives an explanation: "my script contains the dbus-daemon command and when cursor stays on it for too long, I get a UI Freeze"

"man dbus-daemon" is huge, so an attempt to show hover with that content freezes UI. We saw exact same problems with DLTK shell editor, and the workaround was to limit the input for html hovers on the DLTK side.

So it is not Platform UI, it is SWT GTK issue, and async or sync hover will make no difference here because the freeze is in GTK/SWT itself.
Comment 11 Mauro Molinari CLA 2020-04-30 05:41:49 EDT
(In reply to Andrey Loskutov from comment #10)
> So it is not Platform UI, it is SWT GTK issue, and async or sync hover will
> make no difference here because the freeze is in GTK/SWT itself.

Just wanted to add that freezes in the facelet editor I'm experiencing occur on Windows too. They are probably shorter than those I experience in my own Linux system, but I can't say whether if it is due to a better SWT implementation or just because the Windows system I use has a better CPU (i7 Haswell desktop CPU vs i5 Haswell mobile CPU).
Comment 12 Andrey Loskutov CLA 2020-04-30 05:54:11 EDT
(In reply to Mauro Molinari from comment #11)
> Just wanted to add that freezes in the facelet editor I'm experiencing occur
> on Windows too. They are probably shorter than those I experience in my own
> Linux system, 

5 seconds or 5 minutes? On Linux it freezes almost forever with big enough input.

> but I can't say whether if it is due to a better SWT
> implementation or just because the Windows system I use has a better CPU (i7
> Haswell desktop CPU vs i5 Haswell mobile CPU).

It's more visible on GTK, we have here 16 core Xeon's that freeze like nothing.
Comment 13 Mauro Molinari CLA 2020-04-30 08:17:21 EDT
> 5 seconds or 5 minutes? On Linux it freezes almost forever with big enough
> input.

I'd say 10-15 seconds on Windows, but I've not measured it.

I may try to give you better information the next time I use it, but in these days I'm working from home and I use Linux here.
Comment 14 Thomas Wolf CLA 2020-05-01 02:51:58 EDT
TextLayout.getLineBounds() uses a Pango iterator to iterate from the top to the wanted line. BrowserInformationControl.computeSizeHint() calls TextLayout.getLineBounds() for each line.

That's a quadratic (in terms of number of lines) Pango iteration.

Isn't that textWidth computed in BrowserInformationControl.computeSizeHint() essentially the same as what OS.pango_layout_get_size() would return as the width? (OK, computeSizeHint adds an image width on the first line.) If so, is it maybe time to add a TextLayout.getUnwrappedBounds() public method? On platforms where this line iteration is fast, it could be implemented that way, and on GTK it could be implemented either with pango_layout_set_size() or alternatively with a single iteration over the lines.

BroswerInformationControl could then compute the width of the first line with that extra image width and then call getUnwrappedWidth, and take the maximum of these.

Another thing to consider in BrowserInformationControl: it calculates the width of all lines, but typically clamps that at the end to sizeConstraints.x anyway. So one could exit that line loop as soon as the textWidth becomes larger than  that sizeConstraint.
Comment 15 Lars Vogel CLA 2020-05-01 08:21:23 EDT
(In reply to Thomas Wolf from comment #14)
> TextLayout.getLineBounds() uses a Pango iterator to iterate from the top to
> the wanted line. BrowserInformationControl.computeSizeHint() calls
> TextLayout.getLineBounds() for each line.
> 
> That's a quadratic (in terms of number of lines) Pango iteration.
> 
> Isn't that textWidth computed in BrowserInformationControl.computeSizeHint()
> essentially the same as what OS.pango_layout_get_size() would return as the
> width? (OK, computeSizeHint adds an image width on the first line.) If so,
> is it maybe time to add a TextLayout.getUnwrappedBounds() public method? On
> platforms where this line iteration is fast, it could be implemented that
> way, and on GTK it could be implemented either with pango_layout_set_size()
> or alternatively with a single iteration over the lines.
> 
> BroswerInformationControl could then compute the width of the first line
> with that extra image width and then call getUnwrappedWidth, and take the
> maximum of these.
> 
> Another thing to consider in BrowserInformationControl: it calculates the
> width of all lines, but typically clamps that at the end to
> sizeConstraints.x anyway. So one could exit that line loop as soon as the
> textWidth becomes larger than  that sizeConstraint.

Tbh. I did not try to validate your suggestion in the code but it sounds good. Can you provide Gerrits for the implementation?
Comment 16 Thomas Wolf CLA 2020-05-01 08:49:45 EDT
(In reply to Lars Vogel from comment #15)
> Tbh. I did not try to validate your suggestion in the code but it sounds
> good. Can you provide Gerrits for the implementation?

No; I don't have an SWT development environment on Linux. I'm on OS X, and only use a CentOS VM from time to time to do superficial checks. Can't really hack the internals of SWT that way.
Comment 17 Lars Vogel CLA 2020-09-07 10:33:51 EDT
Soraphol, can you have a look?
Comment 18 Soraphol (Paul) Damrongpiriyapong CLA 2020-09-08 12:14:28 EDT
I'm trying to reproduce the problem on my end and haven't really seen the issue of the UI hanging forever (~5 seconds or less).

The way I am testing it is by making a class and a Javadoc that is 2k lines long. When I hover over the class to get the Javadoc, the cursor of the editor freezes for a bit, then the hover appears. 

I will try to look into ways to reduce this small freeze, but I am wondering if there is a way to get a large freeze like Andrey is talking about?
Comment 19 Mickael Istria CLA 2020-09-08 13:19:48 EDT
(In reply to Soraphol (Paul) Damrongpiriyapong from comment #18)
> I'm trying to reproduce the problem on my end and haven't really seen the
> issue of the UI hanging forever (~5 seconds or less).

Even a 5 seconds freeze isn't really acceptable.

> The way I am testing it is by making a class and a Javadoc that is 2k lines
> long. When I hover over the class to get the Javadoc, the cursor of the
> editor freezes for a bit, then the hover appears.
> 
> I will try to look into ways to reduce this small freeze, but I am wondering
> if there is a way to get a large freeze like Andrey is talking about?


Did you try the use-case of ShellWax described in https://github.com/eclipse/shellwax/issues/18 ? I imagine there can be some smart caching in Pango to optimize the computation of size for some lines which have same content or other similarities.
Comment 20 Soraphol (Paul) Damrongpiriyapong CLA 2020-09-08 14:57:38 EDT
(In reply to Mickael Istria from comment #19)
> Even a 5 seconds freeze isn't really acceptable.

Yeah I agree, I will try to get a patch going.

> Did you try the use-case of ShellWax described in
> https://github.com/eclipse/shellwax/issues/18 ? I imagine there can be some
> smart caching in Pango to optimize the computation of size for some lines
> which have same content or other similarities.

Yep, just checked it and the freeze is much longer here. What I find odd is that in the Javadoc test, I copy pasted the dbus-daemon man page to simulate this case. I'm guessing the speed difference is also due to the hover process having to fetch the man page? I'm not sure, but at least I can reproduce it. :)
Comment 21 Mickael Istria CLA 2020-09-08 15:43:35 EDT
(In reply to Soraphol (Paul) Damrongpiriyapong from comment #20)
> Yep, just checked it and the freeze is much longer here. What I find odd is
> that in the Javadoc test, I copy pasted the dbus-daemon man page to simulate
> this case. I'm guessing the speed difference is also due to the hover
> process having to fetch the man page? I'm not sure, but at least I can
> reproduce it. :)


The javadoc hover probably has some strategy that avoids the issue that LSP4E doesn't have with plain BrowserInformationControl, maybe forcing a default size. It'd be great if you identify the reason by the way.
But I agree that the best would be if we can improve the general computeSize to avoid O(n^2) pango calls.
Comment 22 Soraphol (Paul) Damrongpiriyapong CLA 2020-09-10 11:56:26 EDT
Okay, so after some debugging, I found that the reason for the majority of the slow down is due to the computeRuns call in getLineBoundsInPixels(int). This call to computeRuns actually seems to do nothing but waste memory and time due to its call to convert the input text into a byte buffer and then store it in the pango layout. I say it does nothing because in reality the call to set the text to the layout only needs to be done once, and probably only when we draw the text. In terms of the dbus-daemon case, what is happening is that for each of the 2k+ lines we copy the entire text and place/overwrite the same layout with the text over and over again. 

Now I just want some verification of my thought process. Wondering if I someone who is more familiar with the pango stuff can agree with what I have found. The fix to stop the freeze would be to not call computeRuns in getLineBoundsInPixel (and possibly in all the other functions other than draw).
Comment 23 Mickael Istria CLA 2021-12-06 10:47:57 EST
It might be 1 specific case of the TextLayout improvements targeted in bug 577649
Comment 24 Jörg Kubitz CLA 2022-03-09 10:50:05 EST
i just stumpled on a freeze where UI thread TextViewerHoverManager is waiting for the jdt indexer. This must not happen in the UI thread. I could wait hours.


"main" #1 prio=6 os_prio=0 cpu=83812.50ms elapsed=2996.09s tid=0x0000020ae2c7b800 nid=0x5124 in Object.wait()  [0x000000ee96ffc000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(java.base@11.0.9.1/Native Method)
        - waiting on <no object reference available>
        at org.eclipse.jdt.internal.core.search.processing.JobManager.performConcurrentJob(JobManager.java:284)
        - waiting to re-lock in wait() <0x0000000744d78898> (a org.eclipse.jdt.internal.core.search.indexing.IndexManager)
        at org.eclipse.jdt.internal.core.search.BasicSearchEngine.findMatches(BasicSearchEngine.java:235)
        at org.eclipse.jdt.internal.core.search.BasicSearchEngine.search(BasicSearchEngine.java:602)
        at org.eclipse.jdt.core.search.SearchEngine.search(SearchEngine.java:670)
        at org.eclipse.pde.internal.ui.correction.java.FindClassResolutionsOperation.findValidPackagesContainingSimpleType(FindClassResolutionsOperation.java:302)
        at org.eclipse.pde.internal.ui.correction.java.FindClassResolutionsOperation.getValidPackages(FindClassResolutionsOperation.java:235)
        at org.eclipse.pde.internal.ui.correction.java.FindClassResolutionsOperation.run(FindClassResolutionsOperation.java:170)
        at org.eclipse.pde.internal.ui.correction.java.QuickFixProcessor.handleImportNotFound(QuickFixProcessor.java:223)
        at org.eclipse.pde.internal.ui.correction.java.QuickFixProcessor.getCorrections(QuickFixProcessor.java:52)
        at org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor$SafeCorrectionCollector.safeRun(JavaCorrectionProcessor.java:381)
        at org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor$SafeCorrectionProcessorAccess.run(JavaCorrectionProcessor.java:341)
        at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
        at org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor$SafeCorrectionProcessorAccess.process(JavaCorrectionProcessor.java:336)
        at org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor.collectCorrections(JavaCorrectionProcessor.java:465)
        at org.eclipse.jdt.internal.ui.text.java.hover.ProblemHover$ProblemInfo.getJavaAnnotationFixes(ProblemHover.java:105)
        at org.eclipse.jdt.internal.ui.text.java.hover.ProblemHover$ProblemInfo.getCompletionProposals(ProblemHover.java:79)
        at org.eclipse.jdt.internal.ui.text.java.hover.AbstractAnnotationHover$AnnotationInformationControl.deferredCreateContent(AbstractAnnotationHover.java:304)
        at org.eclipse.jdt.internal.ui.text.java.hover.AbstractAnnotationHover$AnnotationInformationControl.setInput(AbstractAnnotationHover.java:190)
        at org.eclipse.jface.text.AbstractInformationControlManager.internalShowInformationControl(AbstractInformationControlManager.java:1151)
        at org.eclipse.jface.text.AbstractInformationControlManager.presentInformation(AbstractInformationControlManager.java:1120)
        at org.eclipse.jface.text.AbstractHoverInformationControlManager.presentInformation(AbstractHoverInformationControlManager.java:884)
        at org.eclipse.jface.text.TextViewerHoverManager.doPresentInformation(TextViewerHoverManager.java:237)
        at org.eclipse.jface.text.TextViewerHoverManager.lambda$3(TextViewerHoverManager.java:227)
        at org.eclipse.jface.text.TextViewerHoverManager$$Lambda$1538/0x000000080145b040.run(Unknown Source)
        at org.eclipse.swt.widgets.RunnableLock.run(RunnableLock.java:40)
        at org.eclipse.swt.widgets.Synchronizer.runAsyncMessages(Synchronizer.java:185)
        - locked <0x00000007610012c8> (a org.eclipse.swt.widgets.RunnableLock)
        at org.eclipse.swt.widgets.Display.runAsyncMessages(Display.java:4035)
        at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3635)
        at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1154)
        at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:338)
        at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1045)
        at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:155)
        at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:644)
        at org.eclipse.ui.internal.Workbench$$Lambda$217/0x00000008003d1040.run(Unknown Source)
        at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:338)
        at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:551)
        at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:156)
        at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:152)
        at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:203)
        at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:136)
        at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:401)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:255)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@11.0.9.1/Native Method)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@11.0.9.1/NativeMethodAccessorImpl.java:62)
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@11.0.9.1/DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(java.base@11.0.9.1/Method.java:566)
        at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:659)
        at org.eclipse.equinox.launcher.Main.basicRun(Main.java:596)
        at org.eclipse.equinox.launcher.Main.run(Main.java:1467)
        at org.eclipse.equinox.launcher.Main.main(Main.java:1440)
Comment 25 Mickael Istria CLA 2022-03-09 11:06:28 EST
(In reply to Jörg Kubitz from comment #24)
> i just stumpled on a freeze where UI thread TextViewerHoverManager is
> waiting for the jdt indexer.

That's IMO a different issue/cause, which lead to same visible symptom. Please open a separate issue, and please open it against JDT. Generic Editor for instance does hover computation in non-UI Thread IIRC.