blob: abe7c58367bd6081691acafcef2f39166d6a3e7d [file] [log] [blame]
/*
* 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 test;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentState;
import org.apache.felix.dm.ComponentStateListener;
import org.apache.felix.dm.context.Event;
import org.apache.felix.dm.impl.ComponentImpl;
import org.apache.felix.dm.impl.ConfigurationDependencyImpl;
import org.junit.Assert;
import org.junit.Test;
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 {
final static int STEP_WAIT = 5000;
final static int DEPENDENCIES = 10;
final static int LOOPS = 10000;
// Executor used to bind/unbind service dependencies.
ExecutorService m_threadpool;
// Timestamp used to log the time consumed to execute 100 tests.
long m_timeStamp;
/**
* Creates many service dependencies, and activate/deactivate them concurrently.
*/
@Test
public void createParallelComponentRegistgrationUnregistration() {
info("Starting createParallelComponentRegistgrationUnregistration test");
int cores = Math.max(16, Runtime.getRuntime().availableProcessors());
info("using " + cores + " cores.");
m_threadpool = Executors.newFixedThreadPool(Math.max(cores, DEPENDENCIES + 3 /* start/stop/configure */));
try {
m_timeStamp = System.currentTimeMillis();
for (int loop = 0; loop < LOOPS; loop++) {
doTest(loop);
}
}
catch (Throwable t) {
warn("got unexpected exception", t);
}
finally {
shutdown(m_threadpool);
}
}
void shutdown(ExecutorService exec) {
exec.shutdown();
try {
exec.awaitTermination(5, 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 ComponentImpl client = new ComponentImpl();
final Client theClient = new Client(step);
client.setImplementation(theClient);
// Create client service dependencies
final SimpleServiceDependency[] dependencies = new SimpleServiceDependency[DEPENDENCIES];
for (int i = 0; i < DEPENDENCIES; i++) {
dependencies[i] = new SimpleServiceDependency();
dependencies[i].setRequired(true);
dependencies[i].setCallbacks("add", "remove");
client.add(dependencies[i]);
}
final ConfigurationDependencyImpl confDependency = new ConfigurationDependencyImpl();
confDependency.setPid("mypid");
client.add(confDependency);
// Create Configuration (concurrently).
// We have to simulate the configuration update, using a component state listener, which will
// trigger an update thread, but only once the component is started.
final ComponentStateListener listener = new ComponentStateListener() {
private volatile Dictionary m_conf;
public void changed(Component c, ComponentState state) {
if (state == ComponentState.WAITING_FOR_REQUIRED && m_conf == null) {
m_conf = new Hashtable();
m_conf.put("foo", "bar");
m_threadpool.execute(new Runnable() {
public void run() {
try {
confDependency.updated(m_conf);
}
catch (ConfigurationException e) {
warn("configuration failed", e);
}
}
});
}
}
};
client.add(listener);
// Start the client (concurrently)
m_threadpool.execute(new Runnable() {
public void run() {
client.start();
// Activate the client service dependencies concurrently.
// We *must* do this after having started the component (in a reality, the dependencies can be
// injected only one the tracker has been opened ...
for (int i = 0; i < DEPENDENCIES; i++) {
final SimpleServiceDependency dep = dependencies[i];
final Event added = new EventImpl(i);
m_threadpool.execute(new Runnable() {
public void run() {
dep.add(added);
}
});
}
}
});
// Ensure that client has been started.
int expectedStep = 1 /* conf */ + DEPENDENCIES + 1 /* start */;
step.waitForStep(expectedStep, STEP_WAIT);
Assert.assertEquals(DEPENDENCIES, theClient.getDependencies());
Assert.assertNotNull(theClient.getConfiguration());
client.remove(listener);
// Stop the client and all dependencies concurrently.
for (int i = 0; i < DEPENDENCIES; i++) {
final SimpleServiceDependency dep = dependencies[i];
final Event removed = new EventImpl(i);
m_threadpool.execute(new Runnable() {
public void run() {
dep.remove(removed);
}
});
}
m_threadpool.execute(new Runnable() {
public void run() {
client.stop();
}
});
m_threadpool.execute(new Runnable() {
public void run() {
try {
// simulate a configuration suppression.
confDependency.updated(null);
}
catch (ConfigurationException 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);
step.ensure();
Assert.assertEquals(0, theClient.getDependencies());
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();
}
}
public class Client {
final Ensure m_step;
int m_dependencies;
volatile Dictionary m_conf;
public Client(Ensure step) {
m_step = step;
}
public void updated(Dictionary conf) throws ConfigurationException {
if (conf != null) {
Assert.assertNotNull(conf);
Assert.assertEquals("bar", conf.get("foo"));
m_conf = conf;
m_step.step(1);
}
}
synchronized void add() {
m_step.step();
m_dependencies++;
}
synchronized void remove() {
m_step.step();
m_dependencies--;
}
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 */);
}
synchronized int getDependencies() {
return m_dependencies;
}
Dictionary getConfiguration() {
return m_conf;
}
}
}