Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 311212 - [package explorer] Performance problem with refreshing external class folders
Summary: [package explorer] Performance problem with refreshing external class folders
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 3.6   Edit
Hardware: PC Windows XP
: P2 major (vote)
Target Milestone: 3.7.2   Edit
Assignee: Markus Keller CLA
QA Contact:
URL:
Whiteboard:
Keywords: performance
: 359569 (view as bug list)
Depends on:
Blocks:
 
Reported: 2010-04-30 12:45 EDT by Markus Keller CLA
Modified: 2012-01-17 08:17 EST (History)
5 users (show)

See Also:
deepakazad: review+


Attachments
Breakpoints (3.50 KB, text/plain)
2010-05-04 15:16 EDT, Markus Keller CLA
no flags Details
fix candidate (3.38 KB, patch)
2010-05-04 16:23 EDT, Markus Keller CLA
no flags Details | Diff
fix candidate 2 (3.21 KB, patch)
2010-05-05 12:04 EDT, Markus Keller CLA
no flags Details | Diff
Updated fix 2 (3.13 KB, patch)
2011-04-19 13:37 EDT, Markus Keller CLA
no flags Details | Diff
Reverse patch with test fixes (5.09 KB, patch)
2011-04-20 12:03 EDT, Markus Keller CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Markus Keller CLA 2010-04-30 12:45:35 EDT
I20100429-1549 and before

Every once in a while, my target workbench (with a some projects like jdt.ui checked out from CVS) spends a lot of cycles (dozens of seconds) to refresh the Package Explorer.

I've now set a conditional breakpoint at
PackageExplorerContentProvider#elementChanged(ElementChangedEvent) line 128	
and looked at a case where this method collected about 1800 runnables, which translates into 1800 refreshes in the TreeViewer, which is simply too much.

It looks like the problem is that the optimization we implemented for large refreshes like this only works for archives, but not for external class folders. I have a lot of external folders, since PDE puts them on the classpath in the runtime workbench (they reference the bin folders from my host workbench). AFAIK, this scenario is not common for normal plug-in development, but it is used by EMF (see bug 182537).

Analysis:
=========
PackageExplorerContentProvider#processDelta(IJavaElementDelta, Collection)
line 713 checks IJavaElementDelta#getFlags() against F_ARCHIVE_CONTENT_CHANGED, but that flag is not set when an external folder is refreshed.

The next test is:
  if ((flags & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) == IJavaElementDelta.F_CONTENT)
  // content change, without children info (for example resource added/removed to class folder package)

=> I don't think this will ever be true for class folders, since F_CONTENT is only valid for files.

Next hope is that the tests at the beginning of
PackageExplorerContentProvider#handleAffectedChildren(..) cause an early return, but they don't, since they only check for ADDED and REMOVED, but not CHANGED. In the end, every single class file is refreshed!

My current thinking is that we should:
- remove IJavaElementDelta.F_CONTENT from the cited test (and the second place where we have the same bug), and probably add a check for IPFR#isExternal()
- check whether we should change the loops at the beginning of PackageExplorerContentProvider#handleAffectedChildren() to also count changes and refresh the parent if there were more than a few children changed
-
Comment 1 Markus Keller CLA 2010-05-04 15:16:21 EDT
Created attachment 167029 [details]
Breakpoints

OK, there are quite a few design problems here, which we can't fix all so late in the game:
- we have no flag like F_ARCHIVE_CONTENT_CHANGED that shows when the whole class folder has been refreshed
- the content provider traverses deltas deeply and refreshes every single class file, even if all children of an element contain changes
- the content provider traverses deltas and posts refresh requests even if an element has not been created in the Tree (it doesn't talk to the viewer)

Here are steps to reproduce the problem:
- new workspace
- import org.eclipse.jdt.ui from CVS
- launch target workbench
- in target:
  - import org.eclipse.jdt.junit from CVS
  - create a Java working set WS and put the project into WS
  - Package Explorer > Top Level Elements > Working Set
  - exit target
- clean all projects
- set attached breakpoints
- debug target
  - expand WS and select the org.eclipse.jdt.junit project
  - File > Refresh (F5)

=> when you reach the first breakpoint, you can see how the PackageExplorerContentProvider processes all IClassFiles in the bin folder

- Resume (F8)
=> when you reach the second breakpoint, you can see how many pendingUpdates are going to be processed.

- Step Over (F6) once
=> On my machine, 7.5s are burned in calls to fViewer.refresh(..), most of that in getSelection(..) (4.3s) and setSelectiontoWidget(..) (2.6s)
Comment 2 Markus Keller CLA 2010-05-04 16:23:01 EDT
Created attachment 167038 [details]
fix candidate

It looks scary to fix the architectural issues in RC1. The safest fix I could come up with is to leave the processing as is, but just avoid the expensive preservingSelection(..) calls by testing first whether there really exists a TreeItem for the element. That check is cheap (a hash table lookup), and with this patch, the time spent goes down to 0.35s.

In my runtime workbench, the UI lockup on full refresh goes down to less than 4s (for 36000 pending updates).
Comment 3 Markus Keller CLA 2010-05-05 12:04:17 EDT
Created attachment 167167 [details]
fix candidate 2

First patch was not good (e.g. did not show new project).
This patch does not change postAdd(..).
Comment 4 Markus Keller CLA 2010-05-12 12:16:06 EDT
Too late to fix this now, especially since we didn't get bugs about this and external class folders are available since 3.4.

Punting to 3.7 (or 3.6.1).
Comment 5 Markus Keller CLA 2011-04-19 13:37:29 EDT
Created attachment 193605 [details]
Updated fix 2

The fundamental problems are:
- A full build of a project creates lots of individual class file changes rather than a clear indicator at the root (class folder) that everything should be refreshed (like for a JAR).
- JFace viewers have no mechanism to collect and batch updates, so each client has to implement this again.

Since we cannot fix these flaws now, I'll go with the fix candidate 2.
Comment 6 Markus Keller CLA 2011-04-19 13:38:19 EDT
Fixed in HEAD.
Comment 7 Markus Keller CLA 2011-04-20 07:01:04 EDT
The fix caused 7 test failures in PackageExplorerTests because the MockPluginView doesn't really show any tree elements, and thus the query for realized elements failed.

Fixed in HEAD of org.eclipse.jdt.ui.tests.packageview.MockPluginView.
Comment 8 Markus Keller CLA 2011-04-20 11:58:09 EDT
We're very late in the 3.7 cycle for such a general change in an important view like the Package Explorer / Project Explorer. I'll revert this for 3.7, but release it early in 3.8 with the option to backport it to 3.7.1.
Comment 9 Markus Keller CLA 2011-04-20 12:03:20 EDT
Created attachment 193718 [details]
Reverse patch with test fixes

postRefresh(..) also needs a closer look:
- Object[] elements should be moved into the Runnable
- behavior for null element could be wrong (if that ever happens). Fix would be to use "if (element == null || fViewer.testFindItems(element).length > 0)"
Comment 10 Markus Keller CLA 2011-11-07 14:00:22 EST
Fixed in master with commit 2b6ee74f15b0c815f9efa8b1b6d919f92bfba01e.
Comment 11 Dani Megert CLA 2011-11-08 06:03:51 EST
Reopening for 3.7.2 consideration.

Would also be nice to have a performance test for that.
Comment 12 Dani Megert CLA 2012-01-03 10:27:57 EST
*** Bug 359569 has been marked as a duplicate of this bug. ***
Comment 13 Markus Keller CLA 2012-01-05 14:10:18 EST
Fixed in R3_7_maintenance with commit de2543cd01b0d83870f6ea00adcf1e797e970ad1 and commit bb8e1514d4d96d51cfba8839a4ab4a21aacf9bb1.

Performance test in master: commit e840eccd2cc9454521d8928c4b7835ab671c2ee8
Performance test in perf_37x: commit 848d30296606e490ced5d96b56391912e69dedf5
Comment 14 Dani Megert CLA 2012-01-06 02:03:08 EST
We should review the fix since it goes into 3.7.2.

Deepak, please review.
Comment 15 Deepak Azad CLA 2012-01-09 09:36:24 EST
Fix looks good to me.
Comment 16 Markus Keller CLA 2012-01-17 08:17:57 EST
Verified in M20120111-0800:
- imported *.class files from rt.jar into a class folder (important: don't have any resources in the class folder)
- expanded the class folder and a few packages in the Package Explorer
- touched all *.class files with cygwin: find . -type f | xargs touch
- refreshed the project

Refresh locked the UI for about 30 seconds in 3.7.1.
M20120111-0800 has no lag any more.