Skip to content

Defer expensive project reference computation during selection changes#3871

Merged
vogella merged 2 commits intoeclipse-platform:masterfrom
vogella:fix/close-unrelated-projects-performance-2636
Apr 8, 2026
Merged

Defer expensive project reference computation during selection changes#3871
vogella merged 2 commits intoeclipse-platform:masterfrom
vogella:fix/close-unrelated-projects-performance-2636

Conversation

@vogella
Copy link
Copy Markdown
Contributor

@vogella vogella commented Apr 7, 2026

CloseUnrelatedProjectsAction.getSelectedResources() calls computeRelated() which triggers buildConnectedComponents() and getReferencedProjects() for every project in the workspace. This is expensive because it can trigger classpath container resolution.

Previously, the inherited updateSelection() called getSelectedResources() on every selection change, causing this expensive computation to run on the UI thread during startup and navigation. This blocked the IDE.

Override updateSelection() to perform only a cheap enablement check: verify the selection contains at least one open IProject. The expensive computeRelated() call is deferred to getSelectedResources(), which is only invoked when the action is actually executed via run(). This is correct because getSelectedResources() already handles the selectionDirty flag and recomputes projectsToClose lazily when needed.

Fixes #2636

CloseUnrelatedProjectsAction.getSelectedResources() calls
computeRelated() which triggers buildConnectedComponents() and
getReferencedProjects() for every project in the workspace. This is
expensive because it can trigger classpath container resolution.

Previously, the inherited updateSelection() called getSelectedResources()
on every selection change, causing this expensive computation to run on
the UI thread during startup and navigation. This blocked the IDE.

Override updateSelection() to perform only a cheap enablement check:
verify the selection contains at least one open IProject. The expensive
computeRelated() call is deferred to getSelectedResources(), which is
only invoked when the action is actually executed via run(). This is
correct because getSelectedResources() already handles the selectionDirty
flag and recomputes projectsToClose lazily when needed.

Fixes eclipse-platform#2636
@vogella
Copy link
Copy Markdown
Contributor Author

vogella commented Apr 7, 2026

I also looked for other actions but I only found BuildAction which does work only on selection projects and this is empty during startup. The CloseUnrelatedProjectsAction was different because getSelectedResources() was overridden to compute the full workspace project graph regardless of what was selected

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 7, 2026

Test Results

   852 files  ±0     852 suites  ±0   52m 39s ⏱️ - 11m 7s
 7 894 tests ±0   7 651 ✅ ±0  243 💤 ±0  0 ❌ ±0 
20 184 runs  ±0  19 529 ✅ ±0  655 💤 ±0  0 ❌ ±0 

Results for commit 8c4d499. ± Comparison against base commit a7e91a0.

♻️ This comment has been updated with latest results.

Replace {@link #computeRelated(List)} with {@code computeRelated(List)}
since computeRelated is private and not visible at the protected
visibility level of updateSelection().
@vogella vogella merged commit 3b23ac5 into eclipse-platform:master Apr 8, 2026
18 checks passed
@vogella vogella deleted the fix/close-unrelated-projects-performance-2636 branch April 8, 2026 07:16
@basilevs
Copy link
Copy Markdown
Contributor

basilevs commented Apr 28, 2026

@vogella this causes the menu action to stay enabled after the first run.
Test steps:

  • create two linked projects A, B
  • create an unrelated project C
  • close unrelated for A
  • close unrelated for B

Old behavior:
menu item is disabled for the last step

New behavior:
menu item is always enabled.

Is this intentional? What does the phrase

This is correct because getSelectedResources() already handles the selectionDirty flag and recomputes projectsToClose lazily when needed.

mean?

Why menu item stays in an obsolete state?

First RCPTT test failure is observed on platform staging on April 24th.

@vogella
Copy link
Copy Markdown
Contributor Author

vogella commented Apr 28, 2026

I have a look....

@vogella
Copy link
Copy Markdown
Contributor Author

vogella commented Apr 28, 2026

Thanks for reporting @basilevs

I could reproduce with new unit tests. Please test #3941 with your scenario and let me know if the issue is fixed for you with it.

The new tests are failing before the change, and run fine with the change in the PR.

vogella added a commit to vogella/eclipse.platform.ui that referenced this pull request Apr 28, 2026
…ed projects

After PR eclipse-platform#3871, updateSelection() only checked whether the user selection
contained an open project, so the action stayed enabled even when no
unrelated projects were left to close. PR eclipse-platform#3871 was needed because the
prior check was triggering buildConnectedComponents() on every selection
change, which calls IProject#getReferencedProjects() across the workspace
and can resolve classpath containers, blocking the IDE.

Cache the workspace project graph and invalidate it from resourceChanged()
when project open state or description changes. The expensive graph build
now runs at most once per project-state change rather than once per
selection event, while updateSelection() can again ask whether any open
unrelated project actually exists. Selection-time work is reduced to a
non-mutating scan of the cached graph.

Add CloseUnrelatedProjectsActionEnablementTest covering the regression
scenario reported on PR eclipse-platform#3871: A and B linked, C unrelated; after closing
C, selecting B must disable the action.
vogella added a commit to vogella/eclipse.platform.ui that referenced this pull request Apr 29, 2026
…ed projects

After PR eclipse-platform#3871, updateSelection() only checked whether the selection
contained an open project, so the action stayed enabled even when no
unrelated projects were left to close. PR eclipse-platform#3871 avoided calling
buildConnectedComponents() on every selection change, since
IProject#getReferencedProjects() can resolve classpath containers and
block the IDE.

Cache the workspace project graph and invalidate it from resourceChanged()
on project open/description changes. The graph is now built at most once
per project-state change rather than once per selection event, while
updateSelection() can again check whether any open unrelated project
exists.

Add CloseUnrelatedProjectsActionEnablementTest covering the regression.
vogella added a commit to vogella/eclipse.platform.ui that referenced this pull request Apr 29, 2026
…ed projects

After PR eclipse-platform#3871, updateSelection() only checked whether the selection
contained an open project, so the action stayed enabled even when no
unrelated projects were left to close. PR eclipse-platform#3871 avoided calling
buildConnectedComponents() on every selection change, since
IProject#getReferencedProjects() can resolve classpath containers and
block the IDE.

Cache the workspace project graph and invalidate it from resourceChanged()
on project open/description changes. The graph is now built at most once
per project-state change rather than once per selection event, while
updateSelection() can again check whether any open unrelated project
exists.

Add CloseUnrelatedProjectsActionEnablementTest covering the regression.
vogella added a commit that referenced this pull request Apr 29, 2026
…n changes"

This reverts commit 3aaea37e05f6d0e2ff90db5b2030dca8b1a98c83 and the
follow-up Javadoc fix in 3b23ac5.

The override of updateSelection() introduced in #3871 left
CloseUnrelatedProjectsAction enabled after the last unrelated project
was closed, because the cheap enablement check no longer consulted the
project graph. Restoring the previous behavior is the simplest fix
while a follow-up addresses the original UI-thread cost separately.

Refs #2636
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[performance] CloseUnrelatedProjectsAction.buildConnectedComponents(IProject[]) blocks UI startup

2 participants