Details
-
Bug
-
Status: Open
-
Major
-
Resolution: Unresolved
-
framework-6.0.2
-
None
-
None
Description
When a bundle A's activator modifies the start level of a bundle B that has an adjacent startlevel to a higher level, then the FrameworkStartLevelImpl class will still try to start the bundle B and this will throw an error because B's startlevel is now higher than the current start level.
Example.
- startlevel(A) == 10, startlevel(B)=11.
- A.start sets startlevel(B)==30
- Framework Error thrown that B cannot be started
ERROR: Bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] Error starting {qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2={}}-0 (org.osgi.framework.BundleException: Cannot start bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] because its start level is 30, which is greater than the framework's start level of 11.) org.osgi.framework.BundleException: Cannot start bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] because its start level is 30, which is greater than the framework's start level of 11. at org.apache.felix.framework.Felix.startBundle(Felix.java:2175) at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1539) at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:308) at java.lang.Thread.run(Thread.java:750)
The framework will start bundle B in the end after some delay.
The error does not occur if the startlevel(B) is not adjacent. For example, if it is set to 12, then the error will no occur.
The following is the test code that I used to verify the bug:
package qivicon.felix; import static org.assertj.core.api.Assertions.assertThat; import java.util.concurrent.Semaphore; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.launch.Framework; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.startlevel.FrameworkStartLevel; import aQute.launchpad.Launchpad; import aQute.launchpad.LaunchpadBuilder; public class TestStartLevels { final static LaunchpadBuilder builder = new LaunchpadBuilder().runfw("org.apache.felix.framework") .set("felix.log.level ", "4"); private BundleContext context; public static class ChangeSL implements BundleActivator{ @Override public void start(BundleContext context) throws Exception { System.out.println("In BundleActivator.start: Changing start level of bundles with sl=11 to sl=30"); for ( Bundle b : context.getBundles()) { BundleStartLevel sl = b.adapt(BundleStartLevel.class); int startLevel = sl.getStartLevel(); if ( startLevel == 11) sl.setStartLevel(30); } } @Override public void stop(BundleContext context) throws Exception { // TODO Auto-generated method stub } } @Test public void testFailingToSetStartLevelBecauseChanged() throws Exception { try (Launchpad lp = builder.nostart().create()) { context = lp.getFramework().getBundleContext(); context.addFrameworkListener(fe -> event(fe, "for errors")); Bundle changer = lp.bundle().bundleActivator(ChangeSL.class).start(); Bundle victim = lp.bundle().start(); setStartLevel(changer, 10); setStartLevel(victim, 11); Framework framework = lp.getFramework(); FrameworkStartLevel fsl = framework.adapt(FrameworkStartLevel.class); fsl.setStartLevel(1, fe -> event(fe, "setting FW startLevel=1")); System.out.println("starting framework up to level 1"); lp.start(); Semaphore s = new Semaphore(0); System.out.println("reached 1, setting fw startlevel to 1000"); fsl.setStartLevel(1000, fe -> { s.release(); event(fe, "reached startlevel 1000"); }); s.acquire(); System.out.print("the bundle is actually not started immediately: " ); bundle(victim); assertThat(victim.getState()).isNotEqualTo(Bundle.ACTIVE); Thread.sleep(100); System.out.print("but a bit later it is " ); bundle(victim); assertThat(victim.getState()).isEqualTo(Bundle.ACTIVE); } } private void bundle(Bundle b) { BundleStartLevel sl = b.adapt(BundleStartLevel.class); System.out.format("%02d %-20s %s\n", sl.getStartLevel(), b, state(b)); } private void setStartLevel(Bundle b, int startlevel) throws BundleException { BundleStartLevel slb = b.adapt(BundleStartLevel.class); slb.setStartLevel(startlevel); b.start(); } String state(Bundle b) { switch (b.getState()) { case Bundle.UNINSTALLED: return "UNINSTALLED"; case Bundle.INSTALLED: return "INSTALLED"; case Bundle.RESOLVED: return "RESOLVED"; case Bundle.STARTING: return "STARTING"; case Bundle.ACTIVE: return "ACTIVE"; case Bundle.STOPPING: return "STOPPING"; default: return "?" + b.getState(); } } private void event(FrameworkEvent frameworkevent, String msg) { } String event(FrameworkEvent e) { switch (e.getType()) { case FrameworkEvent.ERROR: return "ERROR"; case FrameworkEvent.INFO: return "INFO"; case FrameworkEvent.PACKAGES_REFRESHED: return "PACKAGES_REFRESHED"; case FrameworkEvent.STARTED: return "STARTED"; case FrameworkEvent.STARTLEVEL_CHANGED: return "STARTLEVEL_CHANGED"; case FrameworkEvent.STOPPED: return "STOPPED"; case FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED: return "STOPPED_BOOTCLASSPATH_MODIFIED"; case FrameworkEvent.STOPPED_UPDATE: return "STOPPED_UPDATE"; case FrameworkEvent.WAIT_TIMEDOUT: return "WAIT_TIMEDOUT"; default: return "?" + e.getType(); } } }
And the output:
starting framework up to level 1 reached 1, setting fw startlevel to 1000 In BundleActivator.start: Changing start level of bundles with sl=11 to sl=30 ERROR: Bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] Error starting {qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2={}}-0 (org.osgi.framework.BundleException: Cannot start bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] because its start level is 30, which is greater than the framework's start level of 11.) org.osgi.framework.BundleException: Cannot start bundle qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] because its start level is 30, which is greater than the framework's start level of 11. at org.apache.felix.framework.Felix.startBundle(Felix.java:2175) at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1539) at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:308) at java.lang.Thread.run(Thread.java:750) the bundle is actually not started immediately30 qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] INSTALLED but a bit later it is 30 qivicon.felix.TestStartLevels.testFailingToSetStartLevelBecauseChanged-2 [2] ACTIVE
This was tested on 6.0.2 but it occurred in the one of the latest 7.x versions. This very likely affects all versions