Skip to content

Never resolve the target platform from PDELabelProvider - #2417

Open
vogella wants to merge 1 commit into
eclipse-pde:masterfrom
vogella:label-provider-no-target-resolution
Open

Never resolve the target platform from PDELabelProvider#2417
vogella wants to merge 1 commit into
eclipse-pde:masterfrom
vogella:label-provider-no-target-resolution

Conversation

@vogella

@vogella vogella commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Label providers run on the UI thread, so they must not ask a question whose answer requires resolving the target platform.
PDELabelProvider did, through both PluginModelManager and FeatureModelManager, and either one can freeze the IDE for a long time when the target contains an m2e Maven location, because resolution runs a full MavenExecutionContext and bnd-wraps every transitive artifact while holding the manager's monitor.

Sampling the main thread every 500 ms during IDE startup, with a Category Definition editor restored from the previous session, put 10 consecutive samples, roughly 8 seconds, inside target resolution reached from getObjectText(ISiteFeature).
The originally reported manifest editor freeze is the same shape through the other manager, from getObjectImage(ImportObject).

Both are guarded now, building on the isInitialized() fast paths the two managers already had.
While the models are unknown the plain label is returned and initialization is scheduled in a background job; when it finishes the label provider fires a LabelProviderChangedEvent, so every viewer sharing it repaints with the resolved state.
Unresolved imports and missing features still get their error overlay once the models are available.

The two managers are two doors into the same room: whichever is opened first pays for the full resolution and caches the resolved target.
That is visible in the sampled run, which never entered initializeTable at all because the feature manager had already paid.
Guarding only one door would not have changed what the user sees, which is why both are in this change.

"main" #1 prio=6 elapsed=61.84s runnable
   java.lang.Thread.State: RUNNABLE
	at org.eclipse.m2e.core.internal.embedder.PlexusContainerManager.newPlexusContainer(PlexusContainerManager.java:207)
	at org.eclipse.m2e.core.internal.embedder.PlexusContainerManager.aquire(PlexusContainerManager.java:139)
	- locked <a java.util.HashMap>
	at org.eclipse.m2e.core.internal.embedder.MavenImpl.getSettings(MavenImpl.java:320)
	at org.eclipse.m2e.core.internal.embedder.MavenImpl.getArtifactRepositories(MavenImpl.java:851)
	at org.eclipse.m2e.pde.target.MavenTargetLocation.getAvailableArtifactRepositories(MavenTargetLocation.java:496)
	at org.eclipse.m2e.pde.target.MavenTargetLocation.resolveArtifacts(MavenTargetLocation.java:179)
	- locked <a org.eclipse.m2e.pde.target.MavenTargetLocation>
	at org.eclipse.m2e.pde.target.MavenTargetLocation.resolveBundles(MavenTargetLocation.java:169)
	at org.eclipse.pde.internal.core.target.AbstractBundleContainer.resolve(AbstractBundleContainer.java:96)
	at org.eclipse.pde.internal.core.target.TargetDefinition.resolve(TargetDefinition.java:379)
	at org.eclipse.pde.internal.core.TargetPlatformHelper.getWorkspaceTargetResolved(TargetPlatformHelper.java:558)
	at org.eclipse.pde.internal.core.ExternalFeatureModelManager.getExternalModels(ExternalFeatureModelManager.java:124)
	at org.eclipse.pde.internal.core.ExternalFeatureModelManager.initialize(ExternalFeatureModelManager.java:98)
	- locked <a org.eclipse.pde.internal.core.ExternalFeatureModelManager>
	at org.eclipse.pde.internal.core.FeatureModelManager.init(FeatureModelManager.java:133)
	- locked <a org.eclipse.pde.internal.core.FeatureModelManager>
	at org.eclipse.pde.internal.core.FeatureModelManager.findFeatureModel(FeatureModelManager.java:174)
	at org.eclipse.pde.internal.ui.PDELabelProvider.getObjectText(PDELabelProvider.java:388)
	at org.eclipse.pde.internal.ui.editor.category.CategoryLabelProvider.getText(CategoryLabelProvider.java:91)
	at org.eclipse.jface.viewers.WrappedViewerLabelProvider.update(WrappedViewerLabelProvider.java:150)
	at org.eclipse.jface.viewers.AbstractTreeViewer.doUpdateItem(AbstractTreeViewer.java:1005)
	at org.eclipse.jface.viewers.AbstractTreeViewer.createTreeItem(AbstractTreeViewer.java:897)
	at org.eclipse.jface.viewers.AbstractTreeViewer.createChildren(AbstractTreeViewer.java:875)
	at org.eclipse.jface.viewers.AbstractTreeViewer.internalInitializeTree(AbstractTreeViewer.java:1653)
	at org.eclipse.jface.viewers.AbstractTreeViewer.inputChanged(AbstractTreeViewer.java:1632)
	at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:279)
	at org.eclipse.jface.viewers.StructuredViewer.setInput(StructuredViewer.java:1640)
	at org.eclipse.pde.internal.ui.editor.category.CategorySection.createClient(CategorySection.java:219)
	at org.eclipse.pde.internal.ui.editor.category.IUsPage$SiteFeaturesBlock.createMasterSection(IUsPage.java:52)
	at org.eclipse.pde.internal.ui.editor.category.IUsPage.createFormContent(IUsPage.java:102)
	at org.eclipse.ui.forms.editor.FormPage.createPartControl(FormPage.java:168)
	at org.eclipse.pde.internal.ui.editor.PDEFormEditor.createPages(PDEFormEditor.java:275)
	at org.eclipse.ui.part.MultiPageEditorPart.createPartControl(MultiPageEditorPart.java:387)
	at org.eclipse.ui.internal.e4.compatibility.CompatibilityPart.createPartControl(CompatibilityPart.java:160)
	...
	at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
	at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:193)

And the originally reported manifest editor freeze, through the other manager:

"main" #1 prio=6 elapsed=75.24s runnable
   java.lang.Thread.State: RUNNABLE
	at aQute.bnd.osgi.Jar.buildFromZip(Jar.java:304)
	at org.eclipse.m2e.pde.target.shared.MavenBundleWrapper.getCachedJar(MavenBundleWrapper.java:303)
	at org.eclipse.m2e.pde.target.shared.MavenBundleWrapper.getWrappedArtifact(MavenBundleWrapper.java:157)
	at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.execute(MavenExecutionContext.java:281)
	at org.eclipse.m2e.pde.target.MavenTargetBundle.generateOrOverrideManifest(MavenTargetBundle.java:133)
	at org.eclipse.m2e.pde.target.MavenTargetLocation.addBundleForArtifact(MavenTargetLocation.java:394)
	at org.eclipse.m2e.pde.target.MavenTargetLocation.resolveArtifacts(MavenTargetLocation.java:182)
	- locked <a org.eclipse.m2e.pde.target.MavenTargetLocation>
	at org.eclipse.pde.internal.core.target.AbstractBundleContainer.resolve(AbstractBundleContainer.java:96)
	at org.eclipse.pde.internal.core.TargetPlatformHelper.getWorkspaceTargetResolved(TargetPlatformHelper.java:558)
	at org.eclipse.pde.internal.core.PluginModelManager.getExternalBundles(PluginModelManager.java:563)
	at org.eclipse.pde.internal.core.PluginModelManager.initializeTable(PluginModelManager.java:486)
	at org.eclipse.pde.internal.core.PluginModelManager.getEntryTable(PluginModelManager.java:436)
	at org.eclipse.pde.internal.core.PluginModelManager.findEntry(PluginModelManager.java:911)
	- locked <fEntriesSynchronizer>
	at org.eclipse.pde.core.plugin.PluginRegistry.findModel(PluginRegistry.java:387)
	at org.eclipse.pde.internal.core.plugin.ImportObject.findModel(ImportObject.java:92)
	at org.eclipse.pde.internal.core.plugin.PluginReference.isResolved(PluginReference.java:64)
	at org.eclipse.pde.internal.ui.PDELabelProvider.getObjectImage(PDELabelProvider.java:647)
	at org.eclipse.pde.internal.ui.PDELabelProvider.getImage(PDELabelProvider.java:454)
	at org.eclipse.jface.viewers.AbstractTableViewer.doUpdateItem(AbstractTableViewer.java:393)
	at org.eclipse.jface.viewers.AbstractTableViewer.inputChanged(AbstractTableViewer.java:567)
	at org.eclipse.jface.viewers.StructuredViewer.setInput(StructuredViewer.java:1640)
	at org.eclipse.pde.internal.ui.editor.plugin.RequiresSection.initialize(RequiresSection.java:550)
	at org.eclipse.pde.internal.ui.editor.plugin.RequiresSection.createClient(RequiresSection.java:161)

Both isInitialized() implementations had to become honest and non-blocking, otherwise the guards would have replaced one freeze with another.
FeatureModelManager reported success as soon as fActiveModels was assigned, which happens long before the external features are read, so a caller could be told the models are there and then block on the init() monitor.
It now reports the completion of init().
PluginModelManager answered while holding fEntriesSynchronizer, which the background job holds for the whole resolution, so asking whether initialization is needed would have waited for exactly that initialization; fEntries is volatile now and read without the lock, and it is only ever assigned a fully populated table.

PDELabelProviderTest covers both paths.
For plug-ins, an unresolvable import is still decorated once the models are available and gets the plain image while they are not.
For features, the same holds for a product feature, a site feature falls back to its URL while the models are unavailable, and resolves to the label of a workspace feature once they are there.

Manual check: fresh workspace, a target with an m2e Maven location using dependencyDepth="infinite" and a cold ~/.m2, with a category definition editor and a manifest editor open from the previous session.
Both should render immediately with plain labels and fill in once resolution finishes in the background.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Test Results

  129 files  +    4    129 suites  +4   35m 35s ⏱️ + 6m 27s
3 544 tests +   10  3 490 ✅ +   11   54 💤 ± 0  0 ❌  - 1 
9 435 runs  +2 169  9 305 ✅ +2 146  130 💤 +24  0 ❌  - 1 

Results for commit 87fcbe2. ± Comparison against base commit ae96885.

♻️ This comment has been updated with latest results.

@vogella
vogella force-pushed the label-provider-no-target-resolution branch from 3679685 to d2740ce Compare August 11, 2026 08:49
@vogella
vogella marked this pull request as ready for review August 11, 2026 09:20
@vogella
vogella requested a lite review from Copilot August 11, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR prevents PDELabelProvider (which runs on the UI thread) from triggering target platform resolution via plug-in and feature model lookups, by introducing “models available?” guards and background initialization paths that refresh viewer labels once initialization completes.

Changes:

  • Add guarded accessors in PDELabelProvider that avoid target resolution on the UI thread and trigger async label refresh once models are ready.
  • Add initializeInBackground(...) entry points to PluginModelManager and FeatureModelManager, backed by a shared BackgroundInitialization helper.
  • Add PDELabelProviderTest and wire it into AllPDETests to verify the plug-in import decoration behavior before/after models are available.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDELabelProvider.java Guards label/image computation to avoid model queries that can resolve the target platform on the UI thread; refreshes labels after background init.
ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/PDELabelProviderTest.java Adds regression tests for the plug-in import decoration behavior when models are/aren’t available.
ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java Includes the new label provider test in the main test suite.
ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java Adds background initialization API for plug-in model table population.
ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/FeatureModelManager.java Adds background initialization API for feature model loading.
ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BackgroundInitialization.java Introduces shared helper to schedule init jobs and invoke callbacks once initialization completes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@vogella
vogella force-pushed the label-provider-no-target-resolution branch from d2740ce to eda34bb Compare August 11, 2026 10:07
@HannesWell

Copy link
Copy Markdown
Member

Such a large change that touches a lot of code in central parts of the model manager and introduces more concurrency should be reviewed carefully.
Unless this is very urgent (what I first assume it isn't) I don't think we should submit this short before M3.

@vogella
vogella force-pushed the label-provider-no-target-resolution branch from eda34bb to 2fe82f1 Compare August 12, 2026 15:35
@vogella

vogella commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Such a large change that touches a lot of code in central parts of the model manager and introduces more concurrency should be reviewed carefully. Unless this is very urgent (what I first assume it isn't) I don't think we should submit this short before M3.

Why I think it is worth doing: with an m2e Maven location in the target, sampling the main thread every 500 ms during startup put roughly 8 seconds inside target resolution reached from getObjectText(ISiteFeature), with a Category Definition editor restored from the previous session. On Windows with Defender active it gets considerably worse.

On risk: if I got a guard wrong, the visible effect is a label that stays plain until the refresh event arrives, not a wrong model state. PDELabelProviderTest covers both paths, plug-ins and features.

I am running it in my own patched IDE build now and will report back after more production use.

@vogella
vogella force-pushed the label-provider-no-target-resolution branch 2 times, most recently from e1277aa to dcc8e23 Compare August 13, 2026 08:15
Label providers run on the UI thread, so they must not ask a question whose
answer requires resolving the target platform. PDELabelProvider did, through
both model managers, and either one can freeze the IDE for minutes when the
target contains an m2e Maven location.

Sampling the main thread every 500 ms while an IDE with a Category Definition
editor started up put 10 consecutive samples, roughly 8 seconds, in this chain:

  CategorySection.createClient()
    TreeViewer.setInput()
      CategoryLabelProvider.getText()
        PDELabelProvider.getObjectText(ISiteFeature)
          FeatureModelManager.findFeatureModel()
            FeatureModelManager.init()                     locked
              ExternalFeatureModelManager.initialize()     locked
                TargetPlatformHelper.getWorkspaceTargetResolved()
                  MavenTargetLocation.resolveArtifacts()   locked

The reported manifest editor freeze is the same shape through the other
manager:

  RequiresSection.initialize()
    TableViewer.setInput()
      PDELabelProvider.getObjectImage(ImportObject)
        ImportObject.isResolved()
          PluginRegistry.findModel()
            PluginModelManager.findEntry()                 locked
              getEntryTable() -> initializeTable()
                TargetPlatformHelper.getWorkspaceTargetResolved()

PluginModelManager and FeatureModelManager are two doors into the same room.
Whichever is opened first pays for the full resolution and caches the resolved
target, which is why the sampled run never entered initializeTable at all: the
feature manager had already paid, and the plug-in manager got in free. Fixing
one door alone would not change what the user sees.

Both are guarded now. arePluginModelsAvailable() and areFeatureModelsAvailable()
build on the isInitialized() fast paths both managers already had. While the
models are unknown the plain label is returned and initialization is scheduled
through the new initializeInBackground(Runnable) on each manager, sharing the
race handling in BackgroundInitialization. When it completes the label provider
fires a LabelProviderChangedEvent, so every viewer sharing it repaints with the
resolved state. Unresolved imports and missing features still get their error
overlay once the models are there.

Guarded call sites, none of which had a non-resolving path before:

  plug-in models   getObjectImage(ImportObject), getObjectImage(PackageObject),
                   getObjectImage(IProductPlugin), getObjectImage(IFeatureImport),
                   getObjectImage(IFeaturePlugin), getObjectText(IPluginBase),
                   getObjectText(ImportObject), getObjectText(IPluginImport),
                   getObjectText(BundleDescription), getObjectText(FeaturePlugin),
                   getObjectText(ISiteBundle)
  feature models   getObjectText(ISiteFeature), getObjectText(FeatureImport),
                   getObjectImage(IProductFeature), getObjectImage(IFeatureChild),
                   getObjectImage(IFeatureImport)

getSystemBundleInfo() also stopped assuming that system.bundle resolves; it
dereferenced a possibly null model.

initializeInBackground() is new on both managers, but both live in
org.eclipse.pde.internal.core, so this is not published API. Firing a
PluginModelDelta after initialization was rejected on purpose: it would make
PDERegistryStrategy create the extension registry, FeatureRebuilder touch all
feature projects and PluginsView add every entry one by one.

Both isInitialized() implementations had to become honest and non-blocking for
the guards to work at all. FeatureModelManager reported success as soon as
fActiveModels was assigned, which happens long before the external features are
read, so a caller could be told the models are there and then block on the
init() monitor. It now reports the completion of init(). PluginModelManager
answered while holding fEntriesSynchronizer, which the background job holds for
the whole resolution, so asking whether initialization is needed would wait for
exactly that initialization. fEntries is volatile now and is read without the
lock; it is only ever assigned a fully populated table.

Scheduling that initialization must not take the resolution lock either.
initializeInBackground() created its job while holding the very monitor that
init() and initializeTable() hold across the resolution, so a label provider
painting during a target reload would block on it and the guard would have
bought nothing. Each manager now has a private fInitializationJobLock that is
only ever held for the lazy Job.create.

ModelManagerBackgroundInitializationTest holds the resolution lock on another
thread and asserts that initializeInBackground() returns before that thread
releases it. Both cases fail without the dedicated lock.

getObjectImage(IFeaturePlugin) needed the guard in two places. isFragment()
reaches the plug-in registry through FeaturePlugin.getPluginBase() exactly like
the error overlay does, so one hoisted check now covers both and the plain
plug-in image is returned while the models are unknown.

A failed external read no longer wedges the feature manager. init() fast-returns
once fActiveModels is set, so leaving fModelsAvailable false on the way out
would have kept isInitialized() false for good. It is set in a finally now,
which is the state the code reported before this change.

The lock assertion moved into ConcurrencyUtil so that PDELabelProviderTest can
hold fEntriesSynchronizer while asking for a feature plug-in image.

Two schema paths resolve underneath as well. getObjectImage(IPluginElement)
looks the extension point up in the schema registry, which builds the extension
registry from all workspace and external models, and reaches getState() even
when that registry is cached. The ISchemaInclude branch of getImage() resolves a
schema:// include through PluginRegistry.findEntry. Both are guarded now and
fall back to the undecorated image, so the Extensions page of the manifest
editor and the schema editor stop resolving too.
@vogella
vogella force-pushed the label-provider-no-target-resolution branch from dcc8e23 to 87fcbe2 Compare August 13, 2026 11:41
@vogella

vogella commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Found a few more places which would trigger an eager target platform load. As I'm only working for the client who had this issue in 2 weeks again, I post-phone this fix to 4.42 as I cannot verified the impact on startup time shortly before our release.

Thanks @HannesWell for the feedback.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants