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,64 @@ +package org.apache.karaf.main; + +import java.util.Properties; +import java.util.logging.Logger; + +/* + * 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,100 @@ +/* + * 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.swissbox.tinybundles.core.TinyBundles.withBnd; + +import java.io.File; + +import junit.framework.Assert; + +import org.junit.Test; +import org.ops4j.pax.swissbox.tinybundles.core.TinyBundles; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.launch.Framework; +import org.osgi.service.startlevel.StartLevel; + +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"); + + // enable locking + 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(); + Thread.sleep(1000); + + Framework framework = main.getFramework(); + String activatorName = TimeoutShutdownActivator.class.getName().replace('.', '/') + ".class"; + Bundle bundle = framework.getBundleContext().installBundle("foo", + TinyBundles.newBundle() + .set( Constants.BUNDLE_ACTIVATOR, TimeoutShutdownActivator.class.getName() ) + .add( activatorName, getClass().getClassLoader().getResourceAsStream( activatorName ) ) + .build( withBnd() ) + ); + bundle.start(); + + BundleContext ctx = framework.getBundleContext(); + ServiceReference[] refs = ctx.getServiceReferences(StartLevel.class.getName(), null); + StartLevel sl = (StartLevel) ctx.getService(refs[0]); + + MockLock lock = (MockLock) main.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 1401301) +++ src/main/java/org/apache/karaf/main/Main.java (working copy) @@ -51,6 +51,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.ServiceReference; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; @@ -210,8 +211,9 @@ private boolean exiting = false; private ShutdownCallback shutdownCallback; private List karafActivators = new ArrayList(); + private Object startLevelLock = new Object(); + private StartLevelListener startLevelListener; - public Main(String[] args) { this.args = args; } @@ -290,6 +292,10 @@ // Process properties loadStartupProperties(configProps); processAutoProperties(framework.getBundleContext()); + + startLevelListener = new StartLevelListener(startLevelLock); + framework.getBundleContext().addFrameworkListener(startLevelListener); + framework.start(); // Start custom activators startKarafActivators(classLoader); @@ -1370,8 +1376,16 @@ Thread.sleep(lockDelay); } if (framework.getState() == Bundle.ACTIVE && !exiting) { - LOG.info("Lost the lock, stopping this instance ..."); - setStartLevel(lockStartLevel); + LOG.info("Lost the lock, reducing start level to " + lockStartLevel); + synchronized (startLevelLock) { + setStartLevel(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..."); + startLevelLock.wait(shutdownTimeout); + } } } else if (!lockLogged) { LOG.info("Waiting for the lock ..."); @@ -1444,6 +1458,10 @@ } } + public Lock getLock() { + return lock; + } + private class ShutdownSocketThread extends Thread { private final String shutdown; Index: src/main/java/org/apache/karaf/main/StartLevelListener.java =================================================================== --- src/main/java/org/apache/karaf/main/StartLevelListener.java (revision 0) +++ src/main/java/org/apache/karaf/main/StartLevelListener.java (revision 0) @@ -0,0 +1,43 @@ +/* + * 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 java.util.logging.Logger; + +import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.FrameworkListener; + +public class StartLevelListener implements FrameworkListener { + + Logger LOG = Logger.getLogger(this.getClass().getName()); + private Object startLevelLock; + + public StartLevelListener(Object startLevelLock) { + this.startLevelLock = startLevelLock; + } + + public void frameworkEvent(FrameworkEvent event) { + if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED) { + synchronized (startLevelLock) { + LOG.fine("Start level change complete."); + startLevelLock.notifyAll(); + } + } + } +}