Uploaded image for project: 'Felix'
  1. Felix
  2. FELIX-6586

Invalid startlevel ordering when startlevels are changed in Bundle Activator

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Open
    • Major
    • Resolution: Unresolved
    • framework-6.0.2
    • None
    • Framework
    • 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.

      1. startlevel(A) == 10, startlevel(B)=11.
      2. A.start sets startlevel(B)==30
      3. 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

       

      Attachments

        Activity

          People

            Unassigned Unassigned
            pkriens Peter Kriens
            Votes:
            1 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated: