Bug 578564 - Optimization in StateImpl.getFragent fragments order (575125) leads to a regression (560822): full builds on Eclipse start up
Summary: Optimization in StateImpl.getFragent fragments order (575125) leads to a regr...
Status: RESOLVED FIXED
Alias: None
Product: Equinox
Classification: Eclipse Project
Component: Framework (show other bugs)
Version: 4.21   Edit
Hardware: All All
: P3 major (vote)
Target Milestone: 4.23 M3   Edit
Assignee: Hannes Wellmann CLA
QA Contact:
URL:
Whiteboard:
Keywords: regression
: 578968 (view as bug list)
Depends on:
Blocks:
 
Reported: 2022-02-03 09:22 EST by Nils Ruhr CLA
Modified: 2022-02-25 08:05 EST (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Nils Ruhr CLA 2022-02-03 09:22:42 EST
The fix in https://bugs.eclipse.org/bugs/show_bug.cgi?id=575125 resolves fragments using host.getDependents(). However now the resulting order of the fragments relies on the order of ResolverImpl.bundleMapping.values(), which is different every time Eclipse starts up.

This leads to reordering class path changes in JDT, which in turn can lead to full builds in Xtext projects --> https://bugs.eclipse.org/bugs/show_bug.cgi?id=560822.

Before the fix StateImpl.bundleDescriptions was used to resolve fragments. This is also a HashMap. However, it uses bundle ids as keys. ResolverImpl.bundleMapping has system hashcodes as keys, because BundleDescriptionImpl doesn't override hashCode().

Because of this issue we experience full builds in our projects every time Eclipse starts up.
Comment 1 Andrey Loskutov CLA 2022-02-03 10:21:07 EST
(In reply to Nils Ruhr from comment #0)
> However now the resulting order of the
> fragments relies on the order of ResolverImpl.bundleMapping.values(), which
> is different every time Eclipse starts up.

Do you mean the iteration in 
org.eclipse.osgi.internal.module.ResolverImpl.rewireBundles() ?

@Hannes: could you please check that?

Ideally we could use LinkedHashMap (or sort, whatever appropriate) to preserve order of fragments, even if the order is probably not guaranteed to be stable in the contract. But permanent rebuilds is surely a major regression.
Comment 2 Thomas Watson CLA 2022-02-03 10:44:39 EST
This seems like a PDE issue vs some issue in the old ResolverImpl.  I presume we are observing a change in behavior (order) of org.eclipse.osgi.service.resolver.BundleDescription.getFragments()

If PDE needs a consistent order here (which is not prescribed in the javadoc of BundleDescription.getFragments()) then PDE should sort the result however they see fit to get a consistent order from one launch to the next.
Comment 3 Hannes Wellmann CLA 2022-02-03 19:13:06 EST

(In reply to Andrey Loskutov from comment #1)
> (In reply to Nils Ruhr from comment #0)
> > However now the resulting order of the
> > fragments relies on the order of ResolverImpl.bundleMapping.values(), which
> > is different every time Eclipse starts up.
> 
> Do you mean the iteration in 
> org.eclipse.osgi.internal.module.ResolverImpl.rewireBundles() ?
> 
> @Hannes: could you please check that?

I tried to check it but I could not yet identify the path between BundleDescriptionImpl.addDependent() and ResolverImpl.bundleMapping (or it has to be quite long). However if it is ResolverImpl.bundleMapping, the location Andrey suggested seems to be the only possible one.

A minimal reproducer would help here. @Nils could you please provide one?

> 
> Ideally we could use LinkedHashMap (or sort, whatever appropriate) to
> preserve order of fragments, even if the order is probably not guaranteed to
> be stable in the contract. But permanent rebuilds is surely a major
> regression.

Using a LinkedHashMap would be one solution.
Another solution, that only affects getFragments(), would be to use in StateImpl.getFragments() either a TreeSet for fragments or create a stream and sort the elements before creating the array of BundleDescriptions. In both cases the BundleDescriptions could be sorted according to their Bundle-Id.

(In reply to Thomas Watson from comment #2)
> This seems like a PDE issue vs some issue in the old ResolverImpl.  I
> presume we are observing a change in behavior (order) of
> org.eclipse.osgi.service.resolver.BundleDescription.getFragments()

That's right, no guarantee is made about any stable ordering of fragments in the javadoc. But if it has been stable before I could understand that somebody expect a stable ordering now.

> 
> If PDE needs a consistent order here (which is not prescribed in the javadoc
> of BundleDescription.getFragments()) then PDE should sort the result however
> they see fit to get a consistent order from one launch to the next.

This would be possible as well. In PDE I think this has to be handled in RequiredPluginsClasspathContainer. All three calls to BundleDescription.getFragments() would have to sort the descriptions first before further processing. A small static function getSortedFragments(BundleDescription) could do the job here.


I would prefer solution 2 or 3, because its effect is only local.
Comment 4 Nils Ruhr CLA 2022-02-04 02:35:59 EST
Hi. Here's the relevant part of the call stack (I'm using org.eclipse.osgi.compatibility.state_1.2.500.v20210730-0750):

BundleDescriptionImpl.addDependent(BundleDescription) line: 616	
BundleDescriptionImpl.addDependency(BaseDescriptionImpl, boolean) line: 587	
BundleDescriptionImpl.addDependencies(BaseDescription[], boolean) line: 574	
UserState(StateImpl).resolveConstraints(BundleDescriptionImpl, BundleDescription[], ExportPackageDescription[], ExportPackageDescription[], GenericDescription[], BundleDescription[], ExportPackageDescription[], GenericDescription[], Map<String,List<StateWire>>) line: 496	
UserState(StateImpl).resolveBundle(BundleDescription, boolean, BundleDescription[], ExportPackageDescription[], ExportPackageDescription[], GenericDescription[], BundleDescription[], ExportPackageDescription[], GenericDescription[], Map<String,List<StateWire>>) line: 463	
ResolverImpl.stateResolveBundle(ResolverBundle) line: 2015	
ResolverImpl.stateResolveBundles(ResolverBundle[]) line: 1839	
ResolverImpl.resolve(BundleDescription[], Dictionary<Object,Object>[]) line: 530	
UserState(StateImpl).resolve(boolean, BundleDescription[], BundleDescription[]) line: 601	
UserState(StateImpl).resolve(boolean) line: 661	
PDEState(MinimalState).internalResolveState(boolean) line: 245	
PDEState(MinimalState).resolveState(boolean) line: 222	
PluginModelManager.initializeTable(IProgressMonitor) line: 631	
PluginModelManager.findModel(IProject) line: 1053	
RequiredPluginsInitializer.initialize(IPath, IJavaProject) line: 50	
JavaModelManager.initializeContainer(IJavaProject, IPath) line: 3144	
JavaModelManager.getClasspathContainer(IPath, IJavaProject) line: 2092	
JavaCore.getClasspathContainer(IPath, IJavaProject) line: 3783	
(...)

Current bundle is "org.elipse.osgi" and the dependent is "com.diffplug.osgi.extension.sun.misc".
Comment 5 Nils Ruhr CLA 2022-02-04 02:38:37 EST
This is where bundleMapping.values() is called:
ResolverImpl.resolve(BundleDescription[], Dictionary<Object,Object>[]) line: 530
Comment 6 Thomas Watson CLA 2022-02-04 08:56:24 EST
(In reply to Hannes Wellmann from comment #3)
> 
> I would prefer solution 2 or 3, because its effect is only local.

I'm not clear what the proposed solutions 1, 2 or 3 are.  If others want to get change the Equinox State impl to have a consistent ordering of BundleDescription.getFragments() I'm not going to block that and I can review the changes.

But I do think if PDE needs some consistent ordering then it likely is not only BundleDescription.getFragments() that needs to be consistent but the other getResolved methods need to be consistent also:

BundleDescription.getResolvedImports()
BundleDescription.getResolvedRequires()

IIRC these should go in the order they are specified in the Import-Package and Require-Bundle header but that is not clear by the javadoc.  It would be good to confirm that stays consistent also.  If such guarantees are important then I suggest some testcases be added to org.eclipse.osgi.tests.services.resolver.AllTests to ensure this consistency remains.
Comment 7 Hannes Wellmann CLA 2022-02-04 15:56:55 EST
(In reply to Nils Ruhr from comment #4)
> Hi. Here's the relevant part of the call stack (I'm using
> org.eclipse.osgi.compatibility.state_1.2.500.v20210730-0750):
> 
 Thank you, with the provided information and the linked Bug 560822 I was able to reproduce/confirm the issue and that a stable fragments order resolves it.

(In reply to Thomas Watson from comment #6)
> (In reply to Hannes Wellmann from comment #3)
> > 
> > I would prefer solution 2 or 3, because its effect is only local.
> 
> I'm not clear what the proposed solutions 1, 2 or 3 are.  If others want to
> get change the Equinox State impl to have a consistent ordering of
> BundleDescription.getFragments() I'm not going to block that and I can
> review the changes.

Sorry for being not clear.

Option 1: Assign StateImpl.bundleDescriptions a LinkedHashMap
Option 2: Sort the array returned by StateImpl.getFragments() (e.g. by their bundle-id)
Option 3: sort the array returned by BundleDescription.getFragments() in PDE's RequiredPluginsClasspathContainer and other locations

> But I do think if PDE needs some consistent ordering then it likely is not
> only BundleDescription.getFragments() that needs to be consistent but the
> other getResolved methods need to be consistent also:
> 
> BundleDescription.getResolvedImports()
> BundleDescription.getResolvedRequires()
> 
> IIRC these should go in the order they are specified in the Import-Package
> and Require-Bundle header but that is not clear by the javadoc.  It would be
> good to confirm that stays consistent also.  If such guarantees are
> important then I suggest some testcases be added to
> org.eclipse.osgi.tests.services.resolver.AllTests to ensure this consistency
> remains.

I agree. And probably you are right since the issue did not appear before getFragment() was change.

But after going through PDE's calls to getFragments() I suggest to resolve this with option-2.
There are multiple other locations in PDE besides RequiredPluginsClasspathContainer that might be affected by a changing fragment-order (for example the API-tools in BundleComponent.createApiDescription()) and I cannot tell if they are sensible or not. If I wouldn't know it now I couldn't tell that a changing order in getFragments() causes full rebuilds on start up, because cause and effect are very far apparat here, even in different Eclipse projects.
Therefore I think for now the fastest way to fix this is option-2 to restore the behaviour as it was before (even if it was not specified).

But when PDE moves off the resolver as suggested in Bug 570198 this has to be considered again.
Comment 8 Eclipse Genie CLA 2022-02-05 03:19:23 EST
New Gerrit change created: https://git.eclipse.org/r/c/equinox/rt.equinox.framework/+/190483
Comment 10 Andrey Loskutov CLA 2022-02-07 09:35:52 EST
@Hannes: are you done with the bug, or is something missing? If we are done, please update the bug status.
Comment 11 Hannes Wellmann CLA 2022-02-08 04:07:54 EST
(In reply to Andrey Loskutov from comment #10)
> @Hannes: are you done with the bug, or is something missing? If we are done,
> please update the bug status.

Yes I'm done here.
The behaviour should now be as it before. A user that switches from an older version to the up-comming one will then experience at most one rebuild in case the order based on the bundle-ids is different as it was before. But there should not follow more due to fragment order change, since the bundle-id is stable.
Comment 12 Andrey Loskutov CLA 2022-02-10 03:49:30 EST
(In reply to Hannes Wellmann from comment #11)
> Yes I'm done here.

Thanks Hannes.
Comment 13 Hannes Wellmann CLA 2022-02-25 08:05:32 EST
*** Bug 578968 has been marked as a duplicate of this bug. ***