/* * 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.aries.subsystem.itests; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.service.subsystem.Subsystem; public class BundleEventHookTest extends SubsystemTest { /* * Bundle-SymbolicName: bundle.a.jar */ private static final String BUNDLE_A = "bundle.a.jar"; /* * Bundle-SymbolicName: bundle.b.jar */ private static final String BUNDLE_B = "bundle.b.jar"; @Override public void createApplications() throws Exception { createBundleA(); createBundleB(); } private void createBundleA() throws IOException { createBundle(name(BUNDLE_A)); } private void createBundleB() throws IOException { createBundle(name(BUNDLE_B)); } /* * See https://issues.apache.org/jira/browse/ARIES-982. * * When activating, the subsystems bundle must initialize the root subsystem * along with any persisted subsystems. Part of the root subsystem * initialization consists of adding all pre-existing bundles as * constituents. In order to ensure that no bundles are missed, a bundle * event hook is registered first. The bundle event hook cannot process * events until the initialization is complete. Another part of * initialization consists of registering the root subsystem service. * Therefore, a potential deadlock exists if something reacts to the * service registration by installing an unmanaged bundle. */ @Test public void testNoDeadlockWhenSubsystemsInitializing() throws Exception { final Bundle bundle = getSubsystemCoreBundle(); bundle.stop(); final AtomicBoolean completed = new AtomicBoolean(false); final ExecutorService executor = Executors.newFixedThreadPool(2); try { bundleContext.addServiceListener(new ServiceListener() { @Override public void serviceChanged(ServiceEvent event) { Future<?> future = executor.submit(new Runnable() { public void run() { try { Bundle a = bundle.getBundleContext().installBundle(BUNDLE_A, new FileInputStream(BUNDLE_A)); completed.set(true); a.uninstall(); } catch (Exception e) { e.printStackTrace(); } } }); try { future.get(); completed.set(true); } catch (Exception e) { e.printStackTrace(); } } }, "(&(objectClass=org.osgi.service.subsystem.Subsystem)(subsystem.id=0))"); Future<?> future = executor.submit(new Runnable() { public void run() { try { bundle.start(); } catch (Exception e) { e.printStackTrace(); } } }); try { future.get(3, TimeUnit.SECONDS); assertTrue("Deadlock detected", completed.get()); } catch (TimeoutException e) { fail("Deadlock detected"); } } finally { executor.shutdownNow(); } } /* * Because bundle events are queued for later asynchronous processing while * the root subsystem is initializing, it is possible to see an installed * event for a bundle that has been uninstalled (i.e. the bundle revision * will be null). These events should be ignored. */ @Test public void testIgnoreUninstalledBundleInAsyncInstalledEvent() throws Exception { final Bundle core = getSubsystemCoreBundle(); core.stop(); final AtomicReference<Bundle> a = new AtomicReference<Bundle>(); bundleContext.addServiceListener( new ServiceListener() { @SuppressWarnings("unchecked") @Override public void serviceChanged(ServiceEvent event) { if ((event.getType() & (ServiceEvent.REGISTERED | ServiceEvent.MODIFIED)) == 0) return; if (a.get() != null) // We've been here before and already done what needs doing. return; ServiceReference sr = (ServiceReference)event.getServiceReference(); bundleContext.getService(sr); try { // Queue up the installed event. a.set(core.getBundleContext().installBundle(BUNDLE_A, new FileInputStream(BUNDLE_A))); // Ensure the bundle will be uninstalled before the event is processed. a.get().uninstall(); } catch (Exception e) { e.printStackTrace(); } } }, "(&(objectClass=org.osgi.service.subsystem.Subsystem)(subsystem.id=0)(subsystem.state=RESOLVED))"); try { // Before the fix, this would fail due to an NPE resulting from a // null bundle revision. core.start(); } catch (BundleException e) { e.printStackTrace(); fail("Subsystems failed to handle an asynchronous bundle installed event after the bundle was uninstalled"); } assertBundleState(a.get(), Bundle.UNINSTALLED); Subsystem root = getRootSubsystem(); assertState(Subsystem.State.ACTIVE, root); assertNotConstituent(root, a.get().getSymbolicName()); } /* * Because bundle events are queued for later asynchronous processing while * the root subsystem is initializing, it is possible to see an installed * event whose origin bundle has been uninstalled (i.e. the origin bundle's * revision will be null). These events should result in the installed * bundle being associated with the root subsystem. */ @Test public void testIgnoreUninstalledOriginBundleInAsyncInstalledEvent() throws Exception { final Bundle core = getSubsystemCoreBundle(); core.stop(); final Bundle b = bundleContext.installBundle(BUNDLE_B, new FileInputStream(BUNDLE_B)); // Ensure bundle B has a context. b.start(); final AtomicReference<Bundle> a = new AtomicReference<Bundle>(); bundleContext.addServiceListener( new ServiceListener() { @SuppressWarnings("unchecked") @Override public void serviceChanged(ServiceEvent event) { if ((event.getType() & (ServiceEvent.REGISTERED | ServiceEvent.MODIFIED)) == 0) return; if (a.get() != null) // We've been here before and already done what needs doing. return; ServiceReference sr = (ServiceReference)event.getServiceReference(); bundleContext.getService(sr); try { // Queue up the installed event for bundle A using B's context. a.set(b.getBundleContext().installBundle(BUNDLE_A, new FileInputStream(BUNDLE_A))); // Ensure the origin bundle will be uninstalled before the event is processed. b.uninstall(); } catch (Exception e) { e.printStackTrace(); } } }, "(&(objectClass=org.osgi.service.subsystem.Subsystem)(subsystem.id=0)(subsystem.state=RESOLVED))"); try { // Before the fix, this would fail due to an NPE resulting from a // null bundle revision. core.start(); } catch (BundleException e) { e.printStackTrace(); fail("Subsystems failed to handle an asynchronous bundle installed event after the origin bundle was uninstalled"); } assertBundleState(a.get(), Bundle.INSTALLED); assertBundleState(b, Bundle.UNINSTALLED); Subsystem root = getRootSubsystem(); assertState(Subsystem.State.ACTIVE, root); assertConstituent(root, a.get().getSymbolicName()); } }