eclipse-platform/eclipse.platform

Regression: BundleContext.getBundles() no longer supports Eclipse extension point principles

Closed this issue · 13 comments

In 2024-06 invoking InternalPlatform.getDefault().getBundleContext().getBundles() returns an alphabeticised array of all bundles. This ensures that tools such as EcorePlugin.ExtensionProcessor that extract information from extension points to provide a coherent complete mapping from an extension access such as a file extension to its registered support without the caller being aware of the registration locality. (Almost the most fundamental USP of Eclipse).

However in recent I-builds the same call returns a disordered array of only those bundles on the classpath subverting the ability to use extension poinmt registrations.

Using the following called from a JUnit plugin setup():

static int debugRegistrations = 0;

` private void debugRegistrations() {
if (debugRegistrations++ == 0) {
System.out.println(debugRegistrations + " : " + debugSimpleName(this));
BundleContext bundleContext = InternalPlatform.getDefault().getBundleContext();
if (bundleContext != null) {
for (Bundle bundle : bundleContext.getBundles()) {
System.out.println(bundle.getSymbolicName() + " : " + bundle.getState());
}
}
for (Map.Entry entry : Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().entrySet()) {
System.out.println(entry.getKey() + " => " + debugSimpleName(entry.getValue()));
}

	    IExtensionRegistry registry = RegistryFactory.getRegistry();
	    if (registry != null) {
			String pluginID = EcorePlugin.INSTANCE.getSymbolicName();
			String extensionPointID = EcorePlugin.EXTENSION_PARSER_PPID;
			final IExtensionPoint point = registry.getExtensionPoint(pluginID, extensionPointID);
		    if (point != null)
		    {
				for (IConfigurationElement element : point.getConfigurationElements()) {
					System.out.println(element.getNamespaceIdentifier()
					+ " : " + element.getAttribute("type") + " => " + element.getAttribute("class"));
				}
		    }
	    }
	}
}

`

The old 2024-06 code returned 968 plugins, 48 extension points and so 48 extension registry entries. In contrast the new code at line 4646 of https://ci.eclipse.org/ocl/job/ocl-branch-tests/404/console lists 120 plugins and 4 extension points and registry entries.

@ewillink : could you please point to the actual code (not the snippet) which is calling InternalPlatform.getDefault().getBundleContext().getBundles()?

I wonder if there is something how this code is called (at which time or in which context) responsible for the difference you observe.

See javadoc of org.osgi.framework.BundleContext.getBundles() that says that

This method returns a list of all bundles installed in the OSGi
environment at the time of the call to this method. However, since the
Framework is a very dynamic environment, bundles can be installed or
uninstalled at anytime.

I'm also not aware about any "breaking" changes in equinox recently that would explain your problem.

@tjwatson : FYI.

I noticed the problem when an Xtext file failed to open in a longstanding test. I traced that to a deficient analysis by EcorePlugin.ExtensionProcessor and debugged that by just calling InternalPlatform.getDefault().getBundleContext().getBundles() so that I could re-implement the EcorePlugin.ExtensionProcessor processing and see the defect.

The change from ordered to disordered is a sure sign that someone has rewritten. The dramatically reduced returns is another sign that the rewriter is not familiar with Eclipse. The really distrurbing aspect of this is that no JUnit test detected that a fundamental Eclipse principle is no longer observed.

The debug call is just:
@Override public void setUp() throws Exception { debugRegistrations();
in a TestCase.

I can't reproduce a problem in an up-to-date environment:

image

I see lots of bundles and in order.

There appear to be plenty of tests:

image

https://ci.eclipse.org/ocl/job/ocl-branch-tests/404/console used
[INFO] Fetching org.eclipse.osgi_3.21.0.v20240717-2103.jar from https://download.eclipse.org/eclipse/updates/4.33-I-builds/I20240805-1800/plugins/ (1.48MB)
and printf's plugins in perhaps chronological order, certainly not alphabetical,
org.eclipse.osgi : 32
bcpg : 4
bcprov : 4
bcutil : 4
com.ibm.icu : 4
com.sun.jna : 32
com.sun.jna.platform : 4
jakarta.annotation-api : 4
jakarta.inject.jakarta.inject-api : 4
javax.annotation : 4
org.apache.ant : 4
org.apache.batik.constants : 4
org.apache.batik.css : 4
org.eclipse.orbit.xml-apis-ext : 4
org.apache.xmlgraphics : 4
`

plugins in perhaps chronological order, certainly not alphabetical,

No specific order (and actually no order at all) is guaranteed per API of org.osgi.framework.BundleContext.getBundles().
In fact it was ordered, but wasn't ever alphabetically ordered. The bundles are sorted by their numerical id, see org.eclipse.osgi.container.ModuleDatabase.sortModules(List<Module>, Sort...)

I'm not complaining about the order; just observing that it has changed significantly since 2024-06. (A deterministic order might be helpful in reproducing obscure bugs but that would be a different issue.)

Indeed, some of the tests I run suggested to me that one should not generally expect a sort order based on names. E.g., I think org.osgi.framework.BundleContext.installBundle(String) will add the bundle to the end of the array.

In the end, no order is promised by the API so if you want a certain order you should sort it yourself.

I really think it's impossible that the method returns an incomplete list of installed bundles because I would expect all hell would break loose in such a case. Most likely something has change in your test to alter the bundles being launched by the test...

I really think it's impossible that the method returns an incomplete list of installed bundles because I would expect all hell would break loose in such a case. Most likely something has change in your test to alter the bundles being launched by the test...

Nothing has changed WRT to how the ModuleContainer returns the order of Module objects from the org.eclipse.osgi.container.ModuleContainer.getModules() method. This is what backs the BundleContext.getBundles() method.

As others have said, that is using Bundle ID ordering (or install order). If you observed alphabetical ordering in the past that is likely because p2 typically installs bundles in that order. But as you update Eclipse p2 uninstalls and re-installs bundles so the overall order deviates from alphabetical order.

The only way I can see the method changing what is returned is if there is a org.osgi.framework.hooks.bundle.FindHook that is removing (hiding) bundles from the collection of bundles in org.osgi.framework.hooks.bundle.FindHook.find(BundleContext, Collection<Bundle>) before the framework returns the array of bundles to the calling BundleContext.

Please, order is not this issue.
For me there is a simple workaround. I just add an additional JUnit test that references the 'missing' plugin and so ensures that there is an explicit dependency and so the plugin is on the classpath. Its extension point is now processed by EcorePlugin.ExtensionProcessor enabling an Xtext file to be opened by the correct tooling. I just raise this issue to highlight the regression that requires me to do this rather than relying on extension point principles.

I just add an additional JUnit test that references the 'missing' plugin and so ensures that there is an explicit dependency and so the plugin is on the classpath.

On the classpath of what? Is that on the classpath of the Eclipse Project (with PDE)? Or do you mean the Bundle-ClassPath header of bundle? Or something else?

I suspect you mean the classpath of the Eclipse project. If so then there is likely a change of behavior somewhere in the tools you are using in Eclipse (maybe PDE) that decides what bundles to provision into the OSGi Framework instance to run your test.

I just raise this issue to highlight the regression that requires me to do this rather than relying on extension point principles.

Thanks, but I suspect the regression is outside of the OSGi Framework implementation. Perhaps in some tools used to launch the tests?

I mean the classpath of a JUnit plugin test running under tycho surefire (locally or on Jenkins). I presume since I have built and am now testing an OCL distribution, that tycho surefire installs the OCL distribution into a workspace and some additional test code is built to run against it. The 'missing' plugin is part of the regular OCL distribution. All regular plugins/bundles should be visible even if the test code has transitove dependencies to only a few of them.

I was wondering whether this is perhaps a Tycho issue, but since the Tycho code version is frozen by pom.xml, Tycho cannot have changed; it might be wrong, but it's the same wrong. Sadly Tycho is not a regular Eclipse product supporting Import Plugins and Fragments with sources so debugging Tycho-level issues is too much of a nightmare for me unless I'm really really desparate. I have a workaround so I'm not desparate.

Given the provided information this is a configuration issue and not a platform issue:

  1. InternalPlatform.getDefault().getBundleContext().getBundles() is internal and should never be invoked by any client code, it can change anytime in any way, including ordering.
  2. This has nothing to do with Tycho (that uses P2 to collect dependencies) and outcomes can depend on various things as you noticed, since extension points usually not declare any requirements on their childs, there is simply no way for Tycho/P2 to know they are "required" (what obviously is not the case here as you can fix it by adding additional requirements)

Sadly Tycho is not a regular Eclipse product supporting Import Plugins and Fragments with sources so debugging Tycho-level

Tycho contains regular Eclipse Projects one can import with usual Eclipse EEPs made for developing Java, from that on one can start a remote debugger (mvnDebug <your arguments>) and connect to the maven build process. This also works from the UI with m2e.