/* * 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.ArrayList; import java.util.Dictionary; import junit.framework.TestCase; import org.apache.felix.cm.integration.helper.ConfigureThread; import org.apache.felix.cm.integration.helper.ManagedServiceFactoryThread; import org.apache.felix.cm.integration.helper.ManagedServiceThread; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.junit.JUnit4TestRunner; /** * The <code>ConfigUpdateStressTest</code> class tests the issues related to * concurrency between configuration update (Configuration.update(Dictionary)) * and ManagedService[Factory] registration. * <p> * @see <a href="https://issues.apache.org/jira/browse/FELIX-1545">FELIX-1545</a> */ @RunWith(JUnit4TestRunner.class) public class ConfigUpdateStressTest extends ConfigurationTestBase { @Test public void test_ManagedService_race_condition_test() { int counterMax = 30; int failures = 0; for ( int counter = 0; counter < counterMax; counter++ ) { try { single_test_ManagedService_race_condition_test( counter ); } catch ( Throwable ae ) { System.out.println( "single_test_ManagedService_race_condition_test#" + counter + " failed: " + ae ); ae.printStackTrace( System.out ); failures++; } } // fail the test if there is at least one failure if ( failures != 0 ) { TestCase.fail( failures + "/" + counterMax + " iterations failed" ); } } @Test public void test_ManagedServiceFactory_race_condition_test() { int counterMax = 30; int failures = 0; for ( int counter = 0; counter < counterMax; counter++ ) { try { single_test_ManagedServiceFactory_race_condition_test( counter ); } catch ( Throwable ae ) { System.out.println( "single_test_ManagedServiceFactory_race_condition_test#" + counter + " failed: " + ae ); ae.printStackTrace( System.out ); failures++; } } // fail the test if there is at least one failure if ( failures != 0 ) { TestCase.fail( failures + "/" + counterMax + " iterations failed" ); } } // runs a single test to encounter the race condition between ManagedService // registration and Configuration.update(Dictionary) // This test creates/updates configuration and registers a ManagedService // almost at the same time. The ManagedService must receive the // configuration // properties exactly once. private void single_test_ManagedService_race_condition_test( final int counter ) throws IOException, InterruptedException { final String pid = "single_test_ManagedService_race_condition_test." + counter; final ConfigureThread ct = new ConfigureThread( getConfigurationAdmin(), pid, false ); final ManagedServiceThread mt = new ManagedServiceThread( bundleContext, pid ); try { // start threads -- both are waiting to be triggered ct.start(); mt.start(); // trigger for action ct.trigger(); mt.trigger(); // wait for threads to terminate ct.join(); mt.join(); // wait for all tasks to terminate delay(); final boolean isConfigured = mt.isConfigured(); final ArrayList<Dictionary> configs = mt.getConfigs(); // terminate mt to ensure no further config updates mt.cleanup(); TestCase.assertTrue( "Last update call must have been with configuration", isConfigured); if ( configs.size() == 0 ) { TestCase.fail( "No configuration provided to ManagedService at all" ); } else if ( configs.size() == 2 ) { final Dictionary props0 = configs.get( 0 ); final Dictionary props1 = configs.get( 1 ); TestCase.assertNull( "Expected first (of two) updates without configuration", props0 ); TestCase.assertNotNull( "Expected second (of two) updates with configuration", props1 ); } else if ( configs.size() == 1 ) { final Dictionary props = configs.get( 0 ); TestCase.assertNotNull( "Expected non-null configuration: " + props, props ); } else { TestCase.fail( "Unexpectedly got " + configs.size() + " updated" ); } } finally { mt.cleanup(); ct.cleanup(); } } // runs a single test to encounter the race condition between // ManagedServiceFactory registration and Configuration.update(Dictionary) // This test creates/updates configuration and registers a // ManagedServiceFactory almost at the same time. The ManagedServiceFactory // must receive the configuration properties exactly once. private void single_test_ManagedServiceFactory_race_condition_test( final int counter ) throws IOException, InterruptedException { final String factoryPid = "single_test_ManagedServiceFactory_race_condition_test." + counter; final ConfigureThread ct = new ConfigureThread( getConfigurationAdmin(), factoryPid, true ); final ManagedServiceFactoryThread mt = new ManagedServiceFactoryThread( bundleContext, factoryPid ); try { // start threads -- both are waiting to be triggered ct.start(); mt.start(); // trigger for action ct.trigger(); mt.trigger(); // wait for threads to terminate ct.join(); mt.join(); // wait for all tasks to terminate delay(); final boolean isConfigured = mt.isConfigured(); final ArrayList<Dictionary> configs = mt.getConfigs(); // terminate mt to ensure no further config updates mt.cleanup(); TestCase.assertTrue( "Last update call must have been with configuration", isConfigured); if ( configs.size() == 0 ) { TestCase.fail( "No configuration provided to ManagedServiceFactory at all" ); } else if ( configs.size() == 1 ) { final Dictionary props = configs.get( 0 ); TestCase.assertNotNull( "Expected non-null configuration: " + props, props ); } else { TestCase.fail( "Unexpectedly got " + configs.size() + " updated" ); } } finally { mt.cleanup(); ct.cleanup(); } } }