blob: 659ce88cd219aa91114212ed7ec892b725907ec3 [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 org.apache.felix.cm.integration;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.felix.cm.integration.helper.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
/**
* The <code>FELIX4385_StressTest</code> class tests the issue related to concurrency between configuration
* creation/update/removal and ManagedService registration/unregistration.
* The test performs some loops, each one is then executing the following scenario:
* Some ManagedServices are concurrently registered in the OSGi registry using an Executor, and for each
* managed service, we create a Configuration.
* We then wait until every managed services have been updated with a non null configuration. Care is taken when a
* ManagedService is called with an initial update(null) callback, because when a configuration is created the very first
* time, an empty configuration is delivered to the corresponding managed service until the configuration is really updated.
* Once all managed services have been updated, we then concurrently unregister the managed services, and we also
* delete every created configurations. We don't use an executor when deleting configuration because the configuration
* removal is already asynchronous.
*
* <p>
* @see <a href="https://issues.apache.org/jira/browse/FELIX-4385">FELIX-4385</a>
*/
@RunWith(JUnit4TestRunner.class)
public class FELIX4385_StressTest extends ConfigurationTestBase
{
final static int MAXWAIT = 10000;
final static int MANAGED_SERVICES = 3;
volatile ExecutorService executor;
@Test
public void test_ConcurrentManagedServicesWithConcurrentConfigurations()
{
final Log log = new Log(bundleContext);
log.info("starting test_ConcurrentManagedServicesWithConcurrentConfigurations");
// Use at least 10 parallel threads, or take all available processors if the running host contains more than 10 processors.
int parallelism = Math.max(10, Runtime.getRuntime().availableProcessors());
final ConfigurationAdmin ca = getConfigurationAdmin();
final ExecutorService executor = Executors.newFixedThreadPool(parallelism);
try
{
int pidCounter = 0;
long timeStamp = System.currentTimeMillis();
for (int loop = 0; loop < 1000; loop++)
{
log.debug("loop#%d -------------------------", (loop + 1));
final CountDownLatch managedServiceUpdated = new CountDownLatch(MANAGED_SERVICES);
final CountDownLatch managedServiceUnregistered = new CountDownLatch(MANAGED_SERVICES);
// Create some ManagedServices concurrently
log.info("registering aspects concurrently");
final CopyOnWriteArrayList<ServiceRegistration> managedServices = new CopyOnWriteArrayList<ServiceRegistration>();
final CopyOnWriteArrayList<Configuration> confs = new CopyOnWriteArrayList<Configuration>();
for (int i = 0; i < MANAGED_SERVICES; i++)
{
final String pid = "pid." + i + "-" + (pidCounter++);
executor.execute(new Runnable()
{
public void run()
{
Hashtable props = new Hashtable();
props.put(Constants.SERVICE_PID, pid);
ServiceRegistration sr = bundleContext.registerService(
ManagedService.class.getName(),
new TestManagedService(managedServiceUpdated), props);
managedServices.add(sr);
try
{
Configuration c = ca.getConfiguration(pid, null);
c.update(new Hashtable()
{
{
put("foo", "bar");
}
});
confs.add(c);
}
catch (IOException e)
{
log.error("could not create pid %s", e, pid);
return;
}
}
});
}
if (!managedServiceUpdated.await(MAXWAIT, TimeUnit.MILLISECONDS))
{
TestCase.fail("Detected errors logged during concurrent test");
break;
}
log.info("all managed services updated");
// Unregister managed services concurrently
log.info("unregistering services concurrently");
for (final ServiceRegistration sr : managedServices)
{
executor.execute(new Runnable()
{
public void run()
{
sr.unregister();
managedServiceUnregistered.countDown();
}
});
}
// Unregister configuration concurrently
log.info("unregistering configuration concurrently");
for (final Configuration c : confs)
{
c.delete();
}
// Wait until managed services have been unregistered
if (!managedServiceUnregistered.await(MAXWAIT, TimeUnit.MILLISECONDS))
{
TestCase.fail("Managed Servives could not be unregistered timely");
break;
}
if (log.errorsLogged())
{
TestCase.fail("Detected errors logged during concurrent test");
break;
}
log.info("finished one test loop");
if ((loop + 1) % 100 == 0)
{
long duration = System.currentTimeMillis() - timeStamp;
System.out.println(String.format("Performed %d tests in %d ms.", (loop + 1), duration));
timeStamp = System.currentTimeMillis();
}
}
}
catch (Throwable t)
{
Assert.fail("Test failed: " + t.getMessage());
}
finally
{
shutdown(executor);
log.close();
}
}
void shutdown(ExecutorService exec)
{
exec.shutdown();
try
{
exec.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
}
}
/**
* One ManagedService concurrently registered in the OSGI registry.
* We count down a latch once we have been updated with our configuration.
*/
public class TestManagedService implements ManagedService
{
private final CountDownLatch latch;
private Dictionary<String, ?> props;
TestManagedService(CountDownLatch latch)
{
this.latch = latch;
}
public synchronized void updated(Dictionary<String, ?> properties) throws ConfigurationException
{
if (this.props == null && properties == null)
{
// GetConfiguration has been called, but configuration have not yet been delivered.
return;
}
this.props = properties;
latch.countDown();
}
}
}