Index: src/test/java/org/apache/karaf/main/MockLock.java =================================================================== --- src/test/java/org/apache/karaf/main/MockLock.java (revision 0) +++ src/test/java/org/apache/karaf/main/MockLock.java (revision 0) @@ -0,0 +1,66 @@ +package org.apache.karaf.main; + +import java.util.Properties; +import java.util.logging.Logger; + +import org.apache.karaf.main.lock.Lock; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +public class MockLock implements Lock { + + private boolean lock = true; + private boolean isAlive = true; + private static final Logger LOG = Logger.getLogger(MockLock.class.getName()); + private Object lockLock = new Object(); + + public MockLock(Properties props) { + } + + public boolean lock() throws Exception { + synchronized (lockLock) { + LOG.fine("lock = " + lock); + lockLock.notifyAll(); + } + return lock; + } + + public void release() throws Exception { + LOG.fine("release"); + } + + public boolean isAlive() throws Exception { + LOG.fine("isAlive = " + isAlive); + return isAlive; + } + + public void setLock(boolean lock) { + this.lock = lock; + } + + public void setIsAlive(boolean isAlive) { + this.isAlive = isAlive; + } + + public void waitForLock() throws InterruptedException { + synchronized (lockLock) { + lockLock.wait(1000 * 60 * 5); + } + } +} Index: src/test/java/org/apache/karaf/main/MainLockingTest.java =================================================================== --- src/test/java/org/apache/karaf/main/MainLockingTest.java (revision 0) +++ src/test/java/org/apache/karaf/main/MainLockingTest.java (revision 0) @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.karaf.main; + +import static org.ops4j.pax.tinybundles.core.TinyBundles.withBnd; + +import java.io.File; + +import junit.framework.Assert; + +import org.apache.karaf.main.util.Utils; +import org.junit.Test; +import org.ops4j.pax.tinybundles.core.TinyBundles; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.framework.launch.Framework; +import org.osgi.framework.startlevel.FrameworkStartLevel; + +public class MainLockingTest { + + @Test + public void testLostMasterLock() throws Exception { + File basedir = new File(getClass().getClassLoader().getResource("foo").getPath()).getParentFile(); + File home = new File(basedir, "test-karaf-home"); + File data = new File(home, "data"); + + Utils.deleteDirectory(data); + + String[] args = new String[0]; + System.setProperty("karaf.home", home.toString()); + System.setProperty("karaf.data", data.toString()); + System.setProperty("karaf.framework.factory", "org.apache.felix.framework.FrameworkFactory"); + + System.setProperty("karaf.lock","true"); + System.setProperty("karaf.lock.delay","1000"); + System.setProperty("karaf.lock.class","org.apache.karaf.main.MockLock"); + + Main main = new Main(args); + main.launch(); + Framework framework = main.getFramework(); + String activatorName = TimeoutShutdownActivator.class.getName().replace('.', '/') + ".class"; + Bundle bundle = framework.getBundleContext().installBundle("foo", + TinyBundles.bundle() + .set( Constants.BUNDLE_ACTIVATOR, TimeoutShutdownActivator.class.getName() ) + .add( activatorName, getClass().getClassLoader().getResourceAsStream( activatorName ) ) + .build( withBnd() ) + ); + + bundle.start(); + + Thread.sleep(2000); + + FrameworkStartLevel sl = framework.adapt(FrameworkStartLevel.class); + + MockLock lock = (MockLock) main.getLockManager().getLock(); + + Assert.assertEquals(100, sl.getStartLevel()); + + // simulate losing a lock + lock.setIsAlive(false); + lock.setLock(false); + + // lets wait until the start level change is complete + lock.waitForLock(); + Assert.assertEquals(1, sl.getStartLevel()); + + Thread.sleep(1000); + + // get lock back + lock.setIsAlive(true); + lock.setLock(true); + + // wait for loop to recognize lock + lock.waitForLock(); + + Thread.sleep(1000); + + // exit framework + lock loop + main.destroy(); + } +} Index: src/main/java/org/apache/karaf/main/Main.java =================================================================== --- src/main/java/org/apache/karaf/main/Main.java (revision 1401306) +++ src/main/java/org/apache/karaf/main/Main.java (working copy) @@ -50,6 +50,7 @@ import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.FrameworkListener; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.osgi.framework.startlevel.BundleStartLevel; @@ -215,7 +216,8 @@ } BootstrapLogManager.setProperties(config.props); Lock lock = createLock(); - lockManager = new LockManager(lock, new KarafLockCallback(), config.lockDelay); + KarafLockCallback lockCallback = new KarafLockCallback(); + lockManager = new LockManager(lock, lockCallback, config.lockDelay); InstanceHelper.updateInstancePid(config.karafHome, config.karafBase); LOG.addHandler(BootstrapLogManager.getDefaultHandler()); @@ -231,6 +233,7 @@ FrameworkFactory factory = loadFrameworkFactory(classLoader); framework = factory.newFramework(new StringMap(config.props, false)); framework.init(); + framework.getBundleContext().addFrameworkListener(lockCallback); framework.start(); FrameworkStartLevel sl = framework.adapt(FrameworkStartLevel.class); @@ -472,12 +475,30 @@ } } - private final class KarafLockCallback implements LockCallBack { + public LockManager getLockManager() { + return lockManager; + } + + private final class KarafLockCallback implements LockCallBack, FrameworkListener { + private Object startLevelLock = new Object(); + @Override public void lockLost() { if (framework.getState() == Bundle.ACTIVE) { LOG.warning("Lock lost. Setting startlevel to " + config.lockStartLevel); - setStartLevel(config.lockStartLevel); + synchronized (startLevelLock) { + setStartLevel(config.lockStartLevel); + + // we have to wait for the start level to be reduced here because + // if the lock is regained before the start level is fully changed + // things may not come up as expected + LOG.fine("Waiting for start level change to complete..."); + try { + startLevelLock.wait(config.shutdownTimeout); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } } } @@ -492,6 +513,16 @@ public void waitingForLock() { LOG.fine("Waiting for the lock ..."); } + + @Override + public void frameworkEvent(FrameworkEvent event) { + if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED) { + synchronized (startLevelLock) { + LOG.fine("Start level change complete."); + startLevelLock.notifyAll(); + } + } + } } } Index: src/main/java/org/apache/karaf/main/lock/LockManager.java =================================================================== --- src/main/java/org/apache/karaf/main/lock/LockManager.java (revision 1401306) +++ src/main/java/org/apache/karaf/main/lock/LockManager.java (working copy) @@ -85,4 +85,7 @@ lock.release(); } + public Lock getLock() { + return lock; + } }