You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pa...@apache.org on 2017/02/10 12:23:49 UTC
svn commit: r1782457 - in /felix/trunk/framework/src:
main/java/org/apache/felix/framework/Felix.java
test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java
Author: pauls
Date: Fri Feb 10 12:23:49 2017
New Revision: 1782457
URL: http://svn.apache.org/viewvc?rev=1782457&view=rev
Log:
Improve exception message when bundle update fails and improve the handling of concurrent bundle updates by waiting for STARTING and STOPPING bundles if possible (FELIX-5528,FELIX-5138).
Added:
felix/trunk/framework/src/test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java
Modified:
felix/trunk/framework/src/main/java/org/apache/felix/framework/Felix.java
Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/Felix.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/Felix.java?rev=1782457&r1=1782456&r2=1782457&view=diff
==============================================================================
--- felix/trunk/framework/src/main/java/org/apache/felix/framework/Felix.java (original)
+++ felix/trunk/framework/src/main/java/org/apache/felix/framework/Felix.java Fri Feb 10 12:23:49 2017
@@ -2211,15 +2211,27 @@ public class Felix extends BundleImpl im
{
return;
}
-
- // Fire STARTING event to signify call to bundle activator.
- fireBundleEvent(BundleEvent.STARTING, bundle);
-
+
+ Throwable rethrow = null;
try
{
- // Set the bundle's activator.
- bundle.setActivator(createBundleActivator(bundle));
+ // Set the bundle's activator.
+ bundle.setActivator(createBundleActivator(bundle));
+ }
+ catch (Throwable th)
+ {
+ rethrow = th;
+ }
+ try
+ {
+ // Fire STARTING event to signify call to bundle activator.
+ fireBundleEvent(BundleEvent.STARTING, bundle);
+
+ if (rethrow != null)
+ {
+ throw rethrow;
+ }
// Activate the bundle if it has an activator.
if (bundle.getActivator() != null)
{
@@ -2301,7 +2313,7 @@ public class Felix extends BundleImpl im
// Acquire bundle lock.
try
{
- acquireBundleLock(bundle, Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE);
+ acquireBundleLock(bundle, Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING);
}
catch (IllegalStateException ex)
{
@@ -2313,7 +2325,7 @@ public class Felix extends BundleImpl im
{
throw new BundleException(
"Bundle " + bundle
- + " cannot be update, since it is either starting or stopping.");
+ + " cannot be update, since we could not get a lock for it without deadlock.");
}
}
@@ -2321,6 +2333,15 @@ public class Felix extends BundleImpl im
// in a finally block.
try
{
+ // Check if the bundle is not currently STARTING or STOPPING because if it is
+ // we are in a loop where the bundle being started or stopped triggered an update
+ // of itself (either directly or indirectly) which we can not handle.
+ if ((bundle.getState() & (Bundle.STARTING | Bundle.STOPPING)) != 0)
+ {
+ throw new BundleException("Bundle " + bundle
+ + " cannot be update, since it is either STARTING or STOPPING.");
+ }
+
// Variable to indicate whether bundle is active or not.
Throwable rethrow = null;
Added: felix/trunk/framework/src/test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java?rev=1782457&view=auto
==============================================================================
--- felix/trunk/framework/src/test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java (added)
+++ felix/trunk/framework/src/test/java/org/apache/felix/framework/ConcurrentBundleUpdateTest.java Fri Feb 10 12:23:49 2017
@@ -0,0 +1,412 @@
+/*
+ * 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.felix.framework;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.Proxy.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.SynchronousBundleListener;
+
+import junit.framework.TestCase;
+
+public class ConcurrentBundleUpdateTest extends TestCase
+{
+ public void testConcurrentBundleUpdate() throws Exception
+ {
+ Map params = new HashMap();
+ params.put(Constants.FRAMEWORK_SYSTEMPACKAGES,
+ "org.osgi.framework; version=1.4.0,"
+ + "org.osgi.service.packageadmin; version=1.2.0,"
+ + "org.osgi.service.startlevel; version=1.1.0,"
+ + "org.osgi.util.tracker; version=1.3.3,"
+ + "org.osgi.service.url; version=1.0.0");
+ File cacheDir = File.createTempFile("felix-cache", ".dir");
+ cacheDir.delete();
+ cacheDir.mkdirs();
+ String cache = cacheDir.getPath();
+ params.put("felix.cache.profiledir", cache);
+ params.put("felix.cache.dir", cache);
+ params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+
+
+ try
+ {
+ Felix felix = new Felix(params);
+ try
+ {
+ felix.init();
+ felix.start();
+
+ String mf = "Bundle-SymbolicName: test.concurrent.bundleupdate\n"
+ + "Bundle-Version: 1.0.0\n"
+ + "Bundle-ManifestVersion: 2\n"
+ + "Import-Package: org.osgi.framework\n"
+ + "Manifest-Version: 1.0\n"
+ + "Bundle-Activator: " + ConcurrentBundleUpdaterActivator.class.getName() + "\n\n";
+
+ final BundleImpl updater = (BundleImpl) felix.getBundleContext().installBundle(createBundle(mf, ConcurrentBundleUpdaterActivator.class).toURI().toURL().toString());
+
+ final Semaphore step = new Semaphore(0);
+ SynchronousBundleListener listenerStarting = new SynchronousBundleListener()
+ {
+
+ @Override
+ public void bundleChanged(BundleEvent event)
+ {
+ if (event.getBundle().equals(updater) && event.getType() == BundleEvent.STARTING)
+ {
+ step.release();
+ }
+ }
+ };
+ felix.getBundleContext().addBundleListener(listenerStarting);
+ new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ updater.start();
+ }
+ catch (Exception ex)
+ {
+
+ }
+ }
+ }.start();
+
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+
+ felix.getBundleContext().removeBundleListener(listenerStarting);
+
+ assertEquals(Bundle.STARTING, updater.getState());
+ assertEquals(0, step.availablePermits());
+
+ new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ step.release();
+ updater.update();
+ step.release();
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }.start();
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+ SynchronousBundleListener listenerStarted = new SynchronousBundleListener()
+ {
+ @Override
+ public void bundleChanged(BundleEvent event)
+ {
+ if (event.getBundle().equals(updater) && event.getType() == BundleEvent.STARTED)
+ {
+ step.release();
+ }
+ if (event.getBundle().equals(updater) && event.getType() == BundleEvent.STOPPING)
+ {
+ step.release();
+ }
+ }
+ };
+ felix.getBundleContext().addBundleListener(listenerStarted);
+
+ ((Runnable) updater.getActivator()).run();
+
+ assertTrue(step.tryAcquire(2, 1, TimeUnit.SECONDS));
+
+ felix.getBundleContext().removeBundleListener(listenerStarted);
+
+ assertEquals(0, step.availablePermits());
+
+ assertEquals(Bundle.STOPPING, updater.getState());
+
+ felix.getBundleContext().addBundleListener(listenerStarting);
+
+ ((Runnable) updater.getActivator()).run();
+
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+
+ felix.getBundleContext().removeBundleListener(listenerStarting);
+
+ assertEquals(Bundle.STARTING, updater.getState());
+
+ ((Runnable) updater.getActivator()).run();
+
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+
+ assertEquals(Bundle.ACTIVE, updater.getState());
+
+ ((Runnable) updater.getActivator()).run();
+
+ updater.uninstall();
+
+ assertEquals(Bundle.UNINSTALLED, updater.getState());
+
+ try
+ {
+ updater.update();
+ fail("Expected exception on update of uninstalled bundle");
+ }
+ catch (IllegalStateException expected) {
+
+ }
+ }
+ finally
+ {
+ felix.stop();
+ felix.waitForStop(1000);
+ }
+ }
+ finally
+ {
+ delete(cacheDir);
+ }
+ }
+
+ public void testConcurrentBundleCycleUpdate() throws Exception
+ {
+ Map params = new HashMap();
+ params.put(Constants.FRAMEWORK_SYSTEMPACKAGES,
+ "org.osgi.framework; version=1.4.0,"
+ + "org.osgi.service.packageadmin; version=1.2.0,"
+ + "org.osgi.service.startlevel; version=1.1.0,"
+ + "org.osgi.util.tracker; version=1.3.3,"
+ + "org.osgi.service.url; version=1.0.0");
+ File cacheDir = File.createTempFile("felix-cache", ".dir");
+ cacheDir.delete();
+ cacheDir.mkdirs();
+ String cache = cacheDir.getPath();
+ params.put("felix.cache.profiledir", cache);
+ params.put("felix.cache.dir", cache);
+ params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+
+
+ try
+ {
+ Felix felix = new Felix(params);
+ try
+ {
+ felix.init();
+ felix.start();
+
+ String mf = "Bundle-SymbolicName: test.concurrent.bundleupdate\n"
+ + "Bundle-Version: 1.0.0\n"
+ + "Bundle-ManifestVersion: 2\n"
+ + "Import-Package: org.osgi.framework\n"
+ + "Manifest-Version: 1.0\n"
+ + "Bundle-Activator: " + ConcurrentBundleUpdaterCycleActivator.class.getName() + "\n\n";
+
+ final BundleImpl updater = (BundleImpl) felix.getBundleContext().installBundle(createBundle(mf, ConcurrentBundleUpdaterCycleActivator.class).toURI().toURL().toString());
+
+ final Semaphore step = new Semaphore(0);
+ SynchronousBundleListener listenerStarting = new SynchronousBundleListener()
+ {
+ @Override
+ public void bundleChanged(BundleEvent event)
+ {
+ if (event.getBundle().equals(updater) && event.getType() == BundleEvent.STARTING)
+ {
+ step.release();
+ }
+ }
+ };
+ felix.getBundleContext().addBundleListener(listenerStarting);
+ new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ updater.start();
+ }
+ catch (Exception ex)
+ {
+ step.release();
+ }
+ }
+ }.start();
+
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+
+ felix.getBundleContext().removeBundleListener(listenerStarting);
+
+ assertEquals(Bundle.STARTING, updater.getState());
+ assertEquals(0, step.availablePermits());
+
+ ((Runnable) updater.getActivator()).run();
+
+ assertTrue(step.tryAcquire(1, TimeUnit.SECONDS));
+ assertEquals(Bundle.RESOLVED, updater.getState());
+
+ updater.uninstall();
+
+ assertEquals(Bundle.UNINSTALLED, updater.getState());
+
+ try
+ {
+ updater.update();
+ fail("Expected exception on update of uninstalled bundle");
+ }
+ catch (IllegalStateException expected) {
+
+ }
+ }
+ finally
+ {
+ felix.stop();
+ felix.waitForStop(1000);
+ }
+ }
+ finally
+ {
+ delete(cacheDir);
+ }
+ }
+
+ public static final class ConcurrentBundleUpdaterActivator implements BundleActivator, Runnable
+ {
+ Semaphore semaphore = new Semaphore(0);
+
+ private BundleContext context;
+
+ @Override
+ public void start(BundleContext context) throws Exception
+ {
+ this.context = context;
+ if (!semaphore.tryAcquire(1, TimeUnit.SECONDS))
+ {
+ throw new BundleException("Timeout");
+ }
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception
+ {
+ this.context = context;
+ if (!semaphore.tryAcquire(1, TimeUnit.SECONDS))
+ {
+ throw new BundleException("Timeout");
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ semaphore.release();
+ }
+
+ }
+
+ public static final class ConcurrentBundleUpdaterCycleActivator implements BundleActivator, Runnable
+ {
+ Semaphore semaphore = new Semaphore(0);
+
+ private BundleContext context;
+
+ @Override
+ public void start(BundleContext context) throws Exception
+ {
+ this.context = context;
+ if (!semaphore.tryAcquire(1, TimeUnit.SECONDS))
+ {
+ throw new BundleException("Timeout");
+ }
+ context.getBundle().update();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ this.context = context;
+ if (!semaphore.tryAcquire(1, TimeUnit.SECONDS))
+ {
+ throw new BundleException("Timeout");
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ semaphore.release();
+ }
+
+ }
+
+ private static File createBundle(String manifest, Class... classes) throws IOException
+ {
+ File f = File.createTempFile("felix-bundle", ".jar");
+ f.deleteOnExit();
+
+ Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8")));
+ JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf);
+
+ for (Class clazz : classes)
+ {
+ String path = clazz.getName().replace('.', '/') + ".class";
+ os.putNextEntry(new ZipEntry(path));
+
+ InputStream is = clazz.getClassLoader().getResourceAsStream(path);
+ byte[] buffer = new byte[8 * 1024];
+ for (int i = is.read(buffer); i != -1; i = is.read(buffer))
+ {
+ os.write(buffer, 0, i);
+ }
+ is.close();
+ os.closeEntry();
+ }
+ os.close();
+ return f;
+ }
+
+ private static void delete(File file) throws IOException
+ {
+ if (file.isDirectory())
+ {
+ for (File child : file.listFiles())
+ {
+ delete(child);
+ }
+ }
+ file.delete();
+ }
+}