/* * 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.dm.itest.api; import java.io.IOException; import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import org.apache.felix.dm.Component; import org.apache.felix.dm.ConfigurationDependency; import org.apache.felix.dm.ServiceDependency; import org.apache.felix.dm.itest.util.Ensure; import org.apache.felix.dm.itest.util.TestBase; import org.junit.Assert; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationException; /** * This test class simulates a client having many dependencies being registered/unregistered concurrently. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ServiceRaceTest extends TestBase { volatile ConfigurationAdmin m_cm; final static int STEP_WAIT = 15000; final static int DEPENDENCIES = 10; final static int LOOPS = 3000; final Ensure m_done = new Ensure(true); // Timestamp used to log the time consumed to execute 100 tests. long m_timeStamp; public interface Dep { } public class DepImpl implements Dep { } /** * Creates many service dependencies, and activate/deactivate them concurrently. */ public void testCreateParallelComponentRegistgrationUnregistration() { m_dm.add(m_dm.createComponent() .setImplementation(this) .setCallbacks(null, "start", null, null) .add(m_dm.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true))); m_done.waitForStep(1, 60000); m_dm.clear(); Assert.assertFalse(super.errorsLogged()); } void start() { new Thread(new Runnable() { public void run() { doStart(); }}).start(); } void doStart() { info("Starting createParallelComponentRegistgrationUnregistration test"); initThreadPool(); // only if setParallel() has not been called (only if a parallel DM is not used). try { m_timeStamp = System.currentTimeMillis(); for (int loop = 0; loop < LOOPS; loop++) { doTest(loop); } } catch (Throwable t) { error("got unexpected exception", t); } finally { shutdownThreadPool(); m_done.step(1); } } private void initThreadPool() { if (! m_parallel) { // We are not using a parallel DM, so we create a custom threadpool in order to add components concurrently. int cores = Math.max(16, Runtime.getRuntime().availableProcessors()); info("using " + cores + " cores."); m_threadPool = new ForkJoinPool(Math.max(cores, DEPENDENCIES + 3 /* start/stop/configure */)); } } void shutdownThreadPool() { if (! m_parallel && m_threadPool != null) { m_threadPool.shutdown(); try { m_threadPool.awaitTermination(60, TimeUnit.SECONDS); } catch (InterruptedException e) { } } } void doTest(int loop) throws Throwable { debug("loop#%d -------------------------", loop); final Ensure step = new Ensure(false); // Create one client component, which depends on many service dependencies final Component client = m_dm.createComponent(); final Client clientImpl = new Client(step); client.setImplementation(clientImpl); // Create client service dependencies final ServiceDependency[] dependencies = new ServiceDependency[DEPENDENCIES]; for (int i = 0; i < DEPENDENCIES; i++) { final String filter = "(id=loop" + loop + "." + i + ")"; dependencies[i] = m_dm.createServiceDependency().setService(Dep.class, filter) .setRequired(true) .setCallbacks("add", "remove"); client.add(dependencies[i]); } String pid = "pid." + loop; final ConfigurationDependency confDependency = m_dm.createConfigurationDependency().setPid(pid); client.add(confDependency); // Create Configuration (concurrently). final Configuration conf = m_cm.getConfiguration(pid, null); final Hashtable props = new Hashtable(); props.put("foo", "bar"); schedule(new Runnable() { public void run() { try { conf.update(props); } catch (IOException e) { error("update failed", e); } } }); // Activate the client service dependencies concurrently. List<Component> deps = new ArrayList(); for (int i = 0; i < DEPENDENCIES; i++) { Hashtable h = new Hashtable(); h.put("id", "loop" + loop + "." + i); final Component s = m_dm.createComponent() .setInterface(Dep.class.getName(), h) .setImplementation(new DepImpl()); deps.add(s); schedule(new Runnable() { public void run() { m_dm.add(s); } }); } // Start the client (concurrently) schedule(new Runnable() { public void run() { m_dm.add(client); } }); // Ensure that client has been started. int expectedStep = 1 /* conf */ + DEPENDENCIES + 1 /* start */; step.waitForStep(expectedStep, STEP_WAIT); Assert.assertEquals(DEPENDENCIES, clientImpl.getDependencies()); Assert.assertNotNull(clientImpl.getConfiguration()); // Stop all dependencies concurrently. for (Component dep : deps) { final Component dependency = dep; schedule(new Runnable() { public void run() { m_dm.remove(dependency); } }); } // Stop client concurrently. schedule(new Runnable() { public void run() { m_dm.remove(client); } }); // Remove configuration (asynchronously) final Ensure stepConfDeleted = new Ensure(false); schedule(new Runnable() { public void run() { try { conf.delete(); stepConfDeleted.step(1); } catch (IOException e) { warn("error while unconfiguring", e); } } }); // Ensure that client has been stopped, then destroyed, then unbound from all dependencies expectedStep += 2; // stop/destroy expectedStep += DEPENDENCIES; // removed all dependencies step.waitForStep(expectedStep, STEP_WAIT); // Make sure configuration is removed stepConfDeleted.waitForStep(1, STEP_WAIT); step.ensure(); Assert.assertEquals(0, clientImpl.getDependencies()); m_threadPool.awaitQuiescence(5000, TimeUnit.MILLISECONDS); if (super.errorsLogged()) { throw new IllegalStateException("Race test interrupted (some error occured, see previous logs)"); } debug("finished one test loop"); if ((loop + 1) % 100 == 0) { long duration = System.currentTimeMillis() - m_timeStamp; warn("Performed 100 tests (total=%d) in %d ms.", (loop + 1), duration); m_timeStamp = System.currentTimeMillis(); } } private void schedule(Runnable task) { if (! m_parallel) { // not using parallel DM, so use our custom threadpool. m_threadPool.execute(task); } else { task.run(); } } public class Client { final Ensure m_step; volatile int m_dependencies; volatile Dictionary m_conf; public Client(Ensure step) { m_step = step; } public void updated(Dictionary conf) throws ConfigurationException { if (conf != null) { try { Assert.assertEquals("bar", conf.get("foo")); m_conf = conf; m_step.step(1); } catch (Throwable t) { m_step.throwable(t); } } } void add(Dep d) { Assert.assertNotNull(d); m_dependencies ++; m_step.step(); } void remove(Dep d) { Assert.assertNotNull(d); m_dependencies --; m_step.step(); } void start() { m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */); } void stop() { m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */); } void destroy() { m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */ + 1 /* destroy */); } int getDependencies() { return m_dependencies; } Dictionary getConfiguration() { return m_conf; } } }