/* * 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 static java.lang.System.err; import static java.lang.System.out; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.launch.Framework; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import junit.framework.Assert; import junit.framework.TestCase; /** * This test performs concurrent registration of service components that have dependencies between each other. * There are 10 components. The first one has an optional dependency on the second one, the second on the third , etc ... The last component * does not have any dependencies. * At the beginning of a concurrent test, the nth component creates a service tracker on the nth+1 component and registers in the registry * (and components and their associated Service Trackers are registered/opened concurrently). * At the end of an iteration test, we check that all nth component are properly injected (satisfied) with the nth+1 component (except the * last one which has no dependency). */ public class ConcurrencyTest extends TestCase { public static final int DELAY = 1000; final static int NPROCS = Runtime.getRuntime().availableProcessors(); final static int COMPONENTS = (NPROCS == 1) ? 4 : NPROCS; final static int ITERATIONS = 50000; /** * Threadpool which can be optionally used by parallel scenarios. */ private final static Executor TPOOL = Executors.newFixedThreadPool(COMPONENTS); /** * Latch used to ensure that the test has completed propertly. */ private final CountDownLatch m_testDone = new CountDownLatch(1); /** * Starts a concurrent test. */ public void testConcurrentComponents() throws Exception { Map<String, Object> params = new HashMap<String, Object>(); 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); Framework f = new Felix(params); f.init(); f.start(); try { out.println("Starting load test."); Loader loader = new Loader(f.getBundleContext()); loader.start(); Assert.assertTrue(m_testDone.await(60, TimeUnit.SECONDS)); loader.stop(); } finally { f.stop(); Thread.sleep(DELAY); deleteDir(cacheDir); } } private static void deleteDir(File root) throws IOException { if (root.isDirectory()) { for (File file : root.listFiles()) { deleteDir(file); } } assertTrue(root.delete()); } /** * A simple component, that creates a service tracker on another component and registers in the osgi service registry. */ public class Component implements ServiceTrackerCustomizer<Component, Component> { private final BundleContext m_ctx; private final int m_id; private volatile int m_dependsOnId = -1; private volatile ServiceTracker<Component, Component> m_tracker; private volatile boolean m_satisfied; private volatile ServiceRegistration<?> m_registration; public Component(BundleContext ctx, int id) { m_ctx = ctx; m_id = id; } public void dependsOn(int componentId) { m_dependsOnId = componentId; } public void start() { // if we depends on another component, add the dependency. if (m_dependsOnId != -1) { Filter filter; try { filter = m_ctx.createFilter( "(&(objectClass=" + Component.class.getName() + ")(id=" + m_dependsOnId + "))"); } catch (InvalidSyntaxException e) { e.printStackTrace(); return; } m_tracker = new ServiceTracker<Component, Component>(m_ctx, filter, this); m_tracker.open(); } else { // We don't depend on anything, mark our satisfied flag to true m_satisfied = true; } register(); } public boolean isSatisfied() { return m_satisfied; } public void stop() { if (m_tracker != null) { m_tracker.close(); } m_registration.unregister(); } public Component addingService(ServiceReference<Component> reference) { Component service = m_ctx.getService(reference); String id = (String) reference.getProperty("id"); if (String.valueOf(m_dependsOnId).equals(id)) { m_satisfied = true; } else { System.err.println("Component#" + m_id + " received wrong dependency #" + id); } return service; } public void modifiedService(ServiceReference<Component> reference, Component service) { } public void removedService(ServiceReference<Component> reference, Component service) { try { m_ctx.ungetService(reference); } catch (IllegalStateException e) { e.printStackTrace(); } } private void register() { Hashtable<String, Object> properties = new Hashtable<String, Object>(); properties.put("id", String.valueOf(m_id)); m_registration = m_ctx.registerService(Component.class.getName(), this, properties); } } public class Loader implements Runnable { final BundleContext m_ctx; private Thread m_thread; Loader(BundleContext ctx) { m_ctx = ctx; } public void start() { m_thread = new Thread(this); m_thread.start(); } public void stop() { m_thread.interrupt(); } public void run() { // Creates all components. Each nth components will depends on the // nth+1 component. The last component does not depend on another one. out.println("Starting Concurrency test ..."); for (int i = 0; i < ITERATIONS; i++) { try { if (i % 1000 == 0) { out.println("."); } createComponentsConcurrently(i); } catch (Throwable error) { err.println("Test failed"); error.printStackTrace(err); return; } } out.println("\nTest successful."); m_testDone.countDown(); } private void createComponentsConcurrently(int iteration) throws Exception { // Create components. final List<Component> components = new ArrayList<Component>(); for (int i = 0; i < COMPONENTS; i++) { components.add(createComponents(iteration, i)); } // Start all components asynchronously. final CountDownLatch latch = new CountDownLatch(components.size()); for (int i = 0; i < COMPONENTS; i++) { final Component component = components.get(i); TPOOL.execute(new Runnable() { public void run() { try { component.start(); } finally { latch.countDown(); } } }); } if (!latch.await(5, TimeUnit.SECONDS)) { System.err.println("ThreadPool did not complete timely."); return; } // Count the number of satisfied components. long satisfied = 0; for (int i = 0; i < COMPONENTS; i++) { satisfied += (components.get(i).isSatisfied()) ? 1 : 0; } // Report an error if we don't have expected satisfied components. if (satisfied != COMPONENTS) { for (int i = 0; i < COMPONENTS; i ++) { if (! components.get(i).isSatisfied()) { out.println("Component #" + i + " unsatisfied."); } } Assert.fail("Found unsatisfied components: " + String.valueOf(COMPONENTS - satisfied)); return; } // Stop all components for (int i = 0; i < COMPONENTS; i++) { components.get(i).stop(); } } private Component createComponents(int iteration, int i) { Component component = new Component(m_ctx, iteration + i); if (i < (COMPONENTS - 1)) { component.dependsOn(iteration + i + 1); } return component; } } }