package org.marketcetera.module; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.misc.NamedThreadFactory; import org.marketcetera.util.log.I18NMessage0P; import org.junit.Test; import org.junit.After; import org.junit.Before; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.*; import java.util.Arrays; import java.util.Set; import java.util.HashSet; import java.util.List; /* $License$ */ /** * Tests operation of various locks used within the ModuleManager. * * @author anshul@marketcetera.com * @version $Id: ModuleConcurrencyTest.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.1.0 */ @ClassVersion("$Id: ModuleConcurrencyTest.java 16154 2012-07-14 16:34:05Z colin $") public class ModuleConcurrencyTest extends ModuleTestBase { /** * Tests locks acquisition when creating module instances * from singleton factories. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void singletonFactoryLocking() throws Exception { //set up the factory ConcurrentTestFactory.setSingleton(true); ReentrantLock lock = new ReentrantLock(); ConcurrentTestFactory.setNextCreateLock(lock); //initialize the module manager initManager(); //acquire the lock to cause the module instance creation to block lock.lock(); //Verify provider state ProviderInfo info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertProviderInfo(info, ConcurrentTestFactory.PROVIDER_URN, new String[]{ModuleURN.class.getName()}, new Class[]{ModuleURN.class}, new I18NMessage0P( Messages.LOGGER, "provider").getText(), false, false); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); //Spawn a thread to create the instance. final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); Future<ModuleURN> result1 = sService.submit(new Callable<ModuleURN>() { public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //Wait until it gets stuck acquiring the lock while(lock.getQueueLength() == 0) { Thread.sleep(500); } //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertTrue(info.isLocked()); assertEquals(0, info.getLockQueueLength()); //Clear the factory lock ConcurrentTestFactory.setNextCreateLock(null); //Spawn another thread to create the instance final Future<ModuleURN> result2 = sService.submit(new Callable<ModuleURN>() { public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //Wait for this thread to get stuck acquiring the factory lock while(info.getLockQueueLength() < 1) { Thread.sleep(500); info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); } //Verify that modules from other providers can be instantiated //while this one is locked canPerformModuleOperationsDifferentProvider(); //release the lock so that the first task gets through. lock.unlock(); //wait for the first task to complete assertEquals(urn,result1.get()); //verify that the second task fails ExecutionException failure = new ExpectedFailure<ExecutionException>() { protected void run() throws Exception { result2.get(); } }.getException(); assertTrue(failure.getCause() instanceof ModuleCreationException); assertEquals(Messages.CANNOT_CREATE_SINGLETON, ((ModuleCreationException)failure.getCause()). getI18NBoundMessage().getMessage()); //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); } /** * Verifies that factory locks are acquired when a factory is * not singleton and that attempts to create modules with duplicate * URNs fails. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void multipleFactoryLocking() throws Exception { //set up the factory ConcurrentTestFactory.setSingleton(false); ReentrantLock lock = new ReentrantLock(); ConcurrentTestFactory.setNextCreateLock(lock); //initialize the module manager initManager(); //acquire the lock to cause the module instance creation to block lock.lock(); //Verify provider state ProviderInfo info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertProviderInfo(info, ConcurrentTestFactory.PROVIDER_URN, new String[]{ModuleURN.class.getName()}, new Class[]{ModuleURN.class}, new I18NMessage0P( Messages.LOGGER, "provider").getText(), false, true); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); //Spawn a thread to create the instance. final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); Future<ModuleURN> result1 = sService.submit(new Callable<ModuleURN>() { public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //Wait until it gets stuck acquiring the lock while(lock.getQueueLength() == 0) { Thread.sleep(500); } //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertTrue(info.isLocked()); assertEquals(0, info.getLockQueueLength()); //Clear the factory lock ConcurrentTestFactory.setNextCreateLock(null); //Spawn another thread to create the instance final Future<ModuleURN> result2 = sService.submit(new Callable<ModuleURN>() { @Override public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //This thread will get blocked on the factory lock. Wait until it does while(info.getLockQueueLength() < 1) { Thread.sleep(500); info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); } //Verify that modules from other providers can be instantiated //while this one is locked canPerformModuleOperationsDifferentProvider(); //Now unlock the first thread and verify that is succeeds lock.unlock(); assertEquals(urn, result1.get()); //Verify that the second thread fails with duplicate URN error. ExecutionException failure = new ExpectedFailure<ExecutionException>() { @Override protected void run() throws Exception { result2.get(); } }.getException(); assertTrue(failure.getCause() instanceof ModuleCreationException); assertEquals(failure.toString(), Messages.DUPLICATE_MODULE_URN, ((ModuleCreationException)failure.getCause()). getI18NBoundMessage().getMessage()); //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); } /** * Verifies that factory lock is acquired when module instance attribute * setters are being invoked. This is only verifying the current behavior. * * @throws Exception if there were errors. */ @Test(timeout = 10000) public void moduleSetAttributeLocking() throws Exception { //set up the factory ConcurrentTestFactory.setSingleton(false); //initialize the module manager initManager(); //Add a configuration provider to cause module attribute setters //to get invoked getManager().setConfigurationProvider(new ModuleConfigurationProvider() { @Override public String getDefaultFor(ModuleURN inURN, String inAttribute) throws ModuleException { return "myvalue"; } @Override public void refresh() throws ModuleException { //do nothing. } }); //acquire a lock to cause the module attribute setting to block ReentrantLock lock = new ReentrantLock(); lock.lock(); //Verify provider state ProviderInfo info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertProviderInfo(info, ConcurrentTestFactory.PROVIDER_URN, new String[]{ModuleURN.class.getName()}, new Class[]{ModuleURN.class}, new I18NMessage0P( Messages.LOGGER, "provider").getText(), false, true); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); //Setup the module to block when it's attribute value is set. ConcurrentTestModule.helper(urn).setSetValueLock(lock); //Spawn a thread to create the instance. Future<ModuleURN> result1 = sService.submit(new Callable<ModuleURN>() { public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //Wait until it gets stuck acquiring the lock while(lock.getQueueLength() == 0) { Thread.sleep(500); } //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertTrue(info.isLocked()); assertEquals(0, info.getLockQueueLength()); //Clear the module instance lock ConcurrentTestModule.helper(urn).setSetValueLock(null); //Spawn another thread to create the instance final Future<ModuleURN> result2 = sService.submit(new Callable<ModuleURN>() { @Override public ModuleURN call() throws Exception { return getManager().createModule(ConcurrentTestFactory.PROVIDER_URN, urn); } }); //This thread will get blocked on the factory lock. Wait until it does while(info.getLockQueueLength() < 1) { Thread.sleep(500); info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); } //Verify that modules from other providers can be instantiated //while this one is locked canPerformModuleOperationsDifferentProvider(); //Now unlock the first thread and verify that is succeeds lock.unlock(); assertEquals(urn, result1.get()); //Verify that the second thread fails with duplicate URN error. ExecutionException failure = new ExpectedFailure<ExecutionException>() { @Override protected void run() throws Exception { result2.get(); } }.getException(); assertTrue(failure.getCause() instanceof ModuleCreationException); assertEquals(failure.toString(), Messages.DUPLICATE_MODULE_URN, ((ModuleCreationException)failure.getCause()). getI18NBoundMessage().getMessage()); //Verify provider state info = getManager().getProviderInfo(ConcurrentTestFactory.PROVIDER_URN); assertFalse(info.isLocked()); assertEquals(0, info.getLockQueueLength()); } /** * Tests locking & module operations when module start takes a long time * to complete and succeeds. * * @throws Exception if there were errors. */ @Test(timeout = 10000) public void moduleBlockedPreStartPass() throws Exception { //Setup the module to block on start final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); ReentrantLock lock = new ReentrantLock(); ConcurrentTestModule.helper(urn).setPreStartLock(lock); lock.lock(); //Run the start tests. Future<Object> result1 = runStartTests(lock, urn); //wait for start to complete result1.get(); //verify module state ModuleInfo info = getManager().getModuleInfo(urn); //the only data flows running are the ones initiated by the module DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); assertModuleInfo(info, urn, ModuleState.STARTED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //stop and delete the module getManager().stop(urn); getManager().deleteModule(urn); } /** * Tests locking & module operations when module start takes a long time * to complete and fails. * * @throws Exception if there were errors. */ @Test(timeout = 10000) public void moduleBlockedPreStartFail() throws Exception { //Setup the module to block on start final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); ReentrantLock lock = new ReentrantLock(); ConcurrentTestModule.helper(urn).setPreStartLock(lock).setPreStartFail(true); lock.lock(); //Run the start tests. final Future<Object> result1 = runStartTests(lock, urn); //wait for start to complete ExecutionException failure = new ExpectedFailure<ExecutionException>() { protected void run() throws Exception { result1.get(); } }.getException(); ExpectedFailure.assertI18NException(failure.getCause(), TestMessages.FAILURE); //verify module state ModuleInfo info = getManager().getModuleInfo(urn); assertModuleInfo(info, urn, ModuleState.START_FAILED, null, null, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //delete the module getManager().deleteModule(urn); } /** * Tests locking & module operations when module's setFlowSupport * API takes a lot of time. Do note that setFlowSupport is meant * to be doing anything complicated. This unit test is there to * verify that the system is robust enough to deal with a rogue * implementation of setFlowSupport. * * @throws Exception if there were errors. */ @Test(timeout = 10000) public void moduleBlockedSetFlowSupport() throws Exception { //Setup the module to block on start final ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "service"); ReentrantLock lock = new ReentrantLock(); ConcurrentTestModule.helper(urn).setSetFlowSupportLock(lock); lock.lock(); //Run the start tests. final Future<Object> result1 = runStartTests(lock, urn); //wait for start to complete result1.get(); //verify module state ModuleInfo info = getManager().getModuleInfo(urn); //the only data flows running are the ones initiated by the module DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); assertModuleInfo(info, urn, ModuleState.STARTED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //stop and delete the module getManager().stop(urn); getManager().deleteModule(urn); } /** * Verifies locking & module operations when stopping module instances. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleBlockedPreStopPass() throws Exception { //Setup the module to block on stop final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); ReentrantLock lock = new ReentrantLock(); ConcurrentTestModule.helper(urn).setPreStopLock(lock); lock.lock(); Future<Object> result1 = runStopTests(urn, lock); ModuleInfo info; result1.get(); //verify module state info = getManager().getModuleInfo(urn); assertModuleInfo(info, urn, ModuleState.STOPPED, null, null, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //delete the module getManager().deleteModule(urn); } /** * Verifies locking & module operations when stopping module instance fails. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleBlockedStopFail() throws Exception { //Setup the module to block and then fail on stop final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); ReentrantLock lock = new ReentrantLock(); ConcurrentTestModule.helper(urn).setPreStopLock(lock).setPreStopFail(true); lock.lock(); final Future<Object> future = runStopTests(urn, lock); //wait for stop to complete ExecutionException failure = new ExpectedFailure<ExecutionException>() { protected void run() throws Exception { future.get(); } }.getException(); ExpectedFailure.assertI18NException(failure.getCause(), TestMessages.FAILURE); //verify module state ModuleInfo info = getManager().getModuleInfo(urn); DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); assertModuleInfo(info, urn, ModuleState.STOP_FAILED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //stop the module ConcurrentTestModule.helper(urn).setPreStopLock(null).setPreStopFail(false); getManager().stop(urn); //delete the module getManager().deleteModule(urn); } /** * Verifies locking vis-a-vis module start when a module's requestData() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedRequestDataAndStart() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module start as a concurrent operation. It should block //initially when request data is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public ModuleStateException call() throws Exception { createStartFailure(urn, ModuleState.STARTED); return null; } }; runBlockedRequestDataTests(urn, concurrentTestOperation); } /** * Verifies locking vis-a-vis module stop when a module's requestData() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedRequestDataAndStop() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module stop as a concurrent operation. It should block //initially when request data is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { new ExpectedFailure<DataFlowException>( Messages.CANNOT_STOP_MODULE_DATAFLOWS, urn.toString(), ExpectedFailure.IGNORE) { protected void run() throws Exception { getManager().stop(urn); } }; return null; } }; runBlockedRequestDataTests(urn, concurrentTestOperation); } @Test(timeout = 10000) public void requestingModuleBlockedRequestDataAndStart() throws Exception { final ModuleURN reqUrn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "requester"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createStartFailure(reqUrn, ModuleState.STARTING); return null; } }; runRequestingModuleBlockedTests(reqUrn, concurrentTestOperation); } @Test(timeout = 10000) public void requestingModuleBlockedRequestDataAndStop() throws Exception { final ModuleURN reqUrn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "requester"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createStopFailure(reqUrn, ModuleState.STARTING); return null; } }; runRequestingModuleBlockedTests(reqUrn, concurrentTestOperation); } @Test(timeout = 10000) public void requestingModuleBlockedRequestDataAndDelete() throws Exception { final ModuleURN reqUrn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "requester"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createDeleteFailure(reqUrn, ModuleState.STARTING); return null; } }; runRequestingModuleBlockedTests(reqUrn, concurrentTestOperation); } /** * Verifies locking vis-a-vis module delete when a module's requestData() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedRequestDataAndDelete() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module stop as a concurrent operation. It should block //initially when request data is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createDeleteFailure(urn, ModuleState.STARTED); return null; } }; runBlockedRequestDataTests(urn, concurrentTestOperation); } /** * Verifies locking vis-a-vis module start when a module's cancel() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedCancelAndStart() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module start as a concurrent operation. It should block //initially when cancel request is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public ModuleStateException call() throws Exception { createStartFailure(urn, ModuleState.STARTED); return null; } }; runBlockedCancelRequestTests(urn, concurrentTestOperation); } /** * Verifies locking vis-a-vis module stop when a module's cancel() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedCancelAndStop() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module stop as a concurrent operation. It should block //initially when cancel is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { new ExpectedFailure<DataFlowException>( Messages.CANNOT_STOP_MODULE_DATAFLOWS, urn.toString(), ExpectedFailure.IGNORE) { protected void run() throws Exception { getManager().stop(urn); } }; return null; } }; runBlockedCancelRequestTests(urn, concurrentTestOperation); } /** * Verifies locking vis-a-vis module delete when a module's cancel() * API is slow. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void participatingModuleBlockedCancelAndDelete() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); //test module stop as a concurrent operation. It should block //initially when cancel request is in progress and should eventually //succeed. Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createDeleteFailure(urn, ModuleState.STARTED); return null; } }; runBlockedCancelRequestTests(urn, concurrentTestOperation); } /** * Verifies write locking done when deleting auto-created instances. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleAutoDeleteLockAndStart() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { new ExpectedFailure<ModuleNotFoundException>( Messages.MODULE_NOT_FOUND, urn.toString()) { protected void run() throws Exception { getManager().start(urn); } }; return null; } }; runModuleAutoDeleteTest(urn, concurrentTestOperation); } /** * Verifies write locking done when deleting auto-created instances. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleAutoDeleteLockAndStop() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createStopFailure(urn, ModuleState.STOPPED); return null; } }; runModuleAutoDeleteTest(urn, concurrentTestOperation); } /** * Verifies write locking done when deleting auto-created instances. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleAutoDeleteLockAndDelete() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { new ExpectedFailure<ModuleNotFoundException>( Messages.MODULE_NOT_FOUND, urn.toString()) { protected void run() throws Exception { getManager().deleteModule(urn); } }; return null; } }; runModuleAutoDeleteTest(urn, concurrentTestOperation); } /** * Verifies write locking done when deleting auto-created instances. * * @throws Exception if there were errors */ @Test(timeout = 10000) public void moduleAutoDeleteLockAndDataFlow() throws Exception { final ModuleURN urn = new ModuleURN( ConcurrentTestFactory.PROVIDER_URN, "service"); Callable<Object> concurrentTestOperation = new Callable<Object>() { public Object call() throws Exception { createFlowFailure(urn, ModuleState.STOPPED); return null; } }; runModuleAutoDeleteTest(urn, concurrentTestOperation); } /** * Runs a series of tests where call to requestData() blocks. While that * call is blocked, the supplied operation is executed in parallel. This * supplied operation is expected to block until requestData() unblocks, as * it will attempt to acquire the module write lock. * * @param inUrn the module URN of the module to create. * @param inConcurrentTestOperation the test operation that needs to be * run concurrently to verify that it acquires a write lock. * * @throws Exception if there is an error */ private void runBlockedRequestDataTests( final ModuleURN inUrn, Callable<Object> inConcurrentTestOperation) throws Exception { ReentrantLock lock = new ReentrantLock(); //set up the factory ConcurrentTestFactory.setSingleton(false); //initialize the module manager initManager(); //Create an instance assertEquals(inUrn, getManager().createModule( ConcurrentTestFactory.PROVIDER_URN, inUrn)); //Start the module getManager().start(inUrn); //Verify module state ModuleInfo info = getManager().getModuleInfo(inUrn); //the only data flows running are the ones initiated by the module DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); assertModuleInfo(info, inUrn, ModuleState.STARTED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //Setup the module to block on request data ConcurrentTestModule.helper(inUrn).setRequestDataLock(lock); lock.lock(); //spawn a thread to setup a data flow that will block Future<DataFlowID> future1 = sService.submit(new Callable<DataFlowID>() { public DataFlowID call() throws Exception { return getManager().createDataFlow(new DataRequest[]{ new DataRequest(inUrn) }); } }); //Wait until it gets blocked on the lock within requestData while(lock.getQueueLength() == 0) { Thread.sleep(500); } //verify module state info = getManager().getModuleInfo(inUrn); //refresh flows as the flow currently in progress gets reported DataFlowID[] pflows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertModuleInfo(info, inUrn, ModuleState.STARTED, flows, pflows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(1, info.getReadLockCount()); //Allow subsequent request data to not lock ConcurrentTestModule.helper(inUrn).setRequestDataLock(null); //verify we can create and cancel data flows DataFlowID flowID = getManager().createDataFlow(new DataRequest[]{ new DataRequest(inUrn) }); getManager().cancel(flowID); //verify we can perform operations on other modules canPerformModuleOperationsDifferentProvider(); canPerformModuleOperationsSameProvider(); //Run the concurrent test operation Future<Object> future2 = sService.submit(inConcurrentTestOperation); //wait until this operation blocks as it tries to acquire write lock do { Thread.sleep(500); info = getManager().getModuleInfo(inUrn); } while (info.getLockQueueLength() < 1); //now unlock the lock to let the data flow creation complete lock.unlock(); //Wait for the data flow to get created flowID = future1.get(); //Wait for the concurrent test task to complete successfully future2.get(); //Now cancel the second flow getManager().cancel(flowID); //And verify the module state info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.STARTED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //stop and delete the module getManager().stop(inUrn); getManager().deleteModule(inUrn); } private void runRequestingModuleBlockedTests( final ModuleURN inReqUrn, Callable<Object> inConcurrentTestOperation) throws Exception { ModuleURN pcptURN = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "participant"); //Setup the factory ConcurrentTestFactory.setSingleton(false); //init manager initManager(); //create the modules assertEquals(pcptURN, getManager().createModule(pcptURN.parent(), pcptURN)); assertEquals(inReqUrn, getManager().createModule(inReqUrn.parent(), inReqUrn)); //start the participant getManager().start(pcptURN); //the only data flows running are the ones initiated by the module DataFlowID[] pFlows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, pFlows.length); //configure the participant to block when it's requested data ReentrantLock pcptLock = new ReentrantLock(); ConcurrentTestModule.helper(pcptURN).setRequestDataLock(pcptLock); pcptLock.lock(); //configure the flow requester to create a data flow with the participant ConcurrentTestModule.helper(inReqUrn).setFlowRequests(new DataRequest[]{ new DataRequest(pcptURN) }); //start the requester. it will get blocked in prestart when creating //the data flow for the participant Future<Object> future1 = sService.submit(new Callable<Object>() { public Object call() throws Exception { getManager().start(inReqUrn); return null; } }); //wait until this task gets blocked on the thread while(pcptLock.getQueueLength() < 1) { Thread.sleep(500); } //Verify module state ModuleInfo info = getManager().getModuleInfo(inReqUrn); //the only data flows running are the ones initiated by modules DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(3, flows.length); //The requester initiated flows is the total set of flows minus //the participant flows DataFlowID[] rFlows = subtract(flows, pFlows); //The only flow the requester is participating in is the one it initiated. DataFlowID[] rpFlows = new DataFlowID[]{ ConcurrentTestModule.getModule(inReqUrn).getFlowID()}; assertModuleInfo(info, inReqUrn, ModuleState.STARTING, rFlows, rpFlows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(1, info.getReadLockCount()); assertEquals(0, info.getLockQueueLength()); //Verify that an attempt to create data flow over this module fails createFlowFailure(inReqUrn, ModuleState.STARTING); //Verify that the module can itself request data flows assertNotNull(ConcurrentTestModule.getModule(inReqUrn)); DataFlowID flowID = ConcurrentTestModule.getModule( inReqUrn).createFlow(new DataRequest[]{ new DataRequest(inReqUrn) }); //Run the concurrent operation and verify that it initially blocks //and eventually succeeds Future<Object> future2 = sService.submit(inConcurrentTestOperation); //Wait until this thread gets blocked on the module lock while(info.getLockQueueLength() < 1) { Thread.sleep(500); info = getManager().getModuleInfo(inReqUrn); } //Ensure that requesters preStart locks so that we can let the other //task acquire the lock without the possibility of start completing //and completely starting the module. ReentrantLock reqLock = new ReentrantLock(); ConcurrentTestModule.helper(inReqUrn).setPreStartLock(reqLock); reqLock.lock(); //Now unblock the requestData() call pcptLock.unlock(); //Wait until it gets blocked in the reqLock. while(reqLock.getQueueLength() < 1) { Thread.sleep(500); } //wait for the second task to complete future2.get(); //unblock the requesters preStart() reqLock.unlock(); //wait for the first task to complete future1.get(); //cancel the other flow that we had created ConcurrentTestModule.getModule(inReqUrn).cancelFlow(flowID); //verify module state info = getManager().getModuleInfo(inReqUrn); assertEquals(ModuleState.STARTED, info.getState()); assertEquals(2, info.getInitiatedDataFlows().length); assertEquals(1, info.getParticipatingDataFlows().length); HashSet<DataFlowID> flowSet = new HashSet<DataFlowID>( Arrays.asList(info.getInitiatedDataFlows())); flowSet.remove(info.getParticipatingDataFlows()[0]); flowSet.addAll(Arrays.asList(pFlows)); //verify the participating module state assertModuleInfo(getManager(), pcptURN, ModuleState.STARTED, pFlows, flowSet.toArray(new DataFlowID[flowSet.size()]), false, false, true, true, true); //wait for second task to complete, it should not fail. future2.get(); //stop and delete the modules getManager().stop(inReqUrn); getManager().stop(pcptURN); getManager().deleteModule(inReqUrn); getManager().deleteModule(pcptURN); } /** * Runs a series of tests where call to cancel() blocks. While that * call is blocked, the supplied operation is executed in parallel. This * supplied operation is expected to block until requestData() unblocks, as * it will attempt to acquire the write lock on the module. * * @param inUrn the module URN of the module to create. * @param inConcurrentTestOperation the test operation that needs to be * run concurrently. * @throws Exception if there is an error */ private void runBlockedCancelRequestTests( final ModuleURN inUrn, Callable<Object> inConcurrentTestOperation) throws Exception { ReentrantLock lock = new ReentrantLock(); //set up the factory ConcurrentTestFactory.setSingleton(false); //initialize the module manager initManager(); //Create an instance assertEquals(inUrn, getManager().createModule( ConcurrentTestFactory.PROVIDER_URN, inUrn)); //Start the module getManager().start(inUrn); //Verify module state ModuleInfo info = getManager().getModuleInfo(inUrn); //the only data flows running are the ones initiated by the module DataFlowID[] initFlows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, initFlows.length); assertModuleInfo(info, inUrn, ModuleState.STARTED, initFlows, initFlows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //Create a data flow. DataFlowID flowID = getManager().createDataFlow(new DataRequest[]{ new DataRequest(inUrn) }); //Refresh the set of flows for this module DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(2, flows.length); //verify module state info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.STARTED, initFlows, flows, false, false, true, true, true); //Setup the module to block on cancel ConcurrentTestModule.helper(inUrn).setCancelLock(lock); lock.lock(); //spawn a thread to cancel the dataflow, which blocks final DataFlowID flowID1 = flowID; Future<Object> future1 = sService.submit(new Callable<Object>() { public Object call() throws Exception { getManager().cancel(flowID1); return null; } }); //Wait until it gets blocked on the lock within cancel while (lock.getQueueLength() == 0) { Thread.sleep(500); } //verify module state info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.STARTED, initFlows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //Allow subsequent request data to not lock ConcurrentTestModule.helper(inUrn).setCancelLock(null); //verify that another attempt to cancel the flow doesn't block and fails new ExpectedFailure<DataFlowException>( Messages.DATA_FLOW_ALREADY_CANCELING, flowID1.toString()){ protected void run() throws Exception { getManager().cancel(flowID1); } }; //verify we can create and cancel data flows on the //same module without blocking flowID = getManager().createDataFlow(new DataRequest[]{ new DataRequest(inUrn) }); getManager().cancel(flowID); //verify we can perform operations on other modules canPerformModuleOperationsDifferentProvider(); canPerformModuleOperationsSameProvider(); //Initiate the concurrent testing operation Future<Object> future2 = sService.submit(inConcurrentTestOperation); //the concurrent operation doesn't block as cancellation doesn't //acquire any locks and so it should complete without failures. future2.get(); //now unlock the lock to let the data flow creation complete lock.unlock(); //Wait for the data flow to get canceled future1.get(); //Refresh the set of flows for this module flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); //And verify the module state info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.STARTED, initFlows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //stop and delete the module getManager().stop(inUrn); getManager().deleteModule(inUrn); } private void runModuleAutoDeleteTest( ModuleURN inUrn, Callable<Object> inConcurrentTestOperation) throws Exception { ReentrantLock lock = new ReentrantLock(); //set up the factory ConcurrentTestFactory.setSingleton(false); ConcurrentTestFactory.setAutoCreate(true); //initialize the module manager initManager(); //verify factory assertProviderInfo(getManager(), ConcurrentTestFactory.PROVIDER_URN, new String[]{ModuleURN.class.getName()}, new Class[]{ModuleURN.class}, new I18NMessage0P( Messages.LOGGER, "provider").getText(),true, true); //Create a data flow final DataFlowID flowID = getManager().createDataFlow(new DataRequest[]{ new DataRequest(inUrn) }); //verify module ModuleInfo info = getManager().getModuleInfo(inUrn); DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(2, flows.length); //init flows is the set of flows minus the flow we created above Set<DataFlowID> initFlowsList = new HashSet<DataFlowID>(Arrays.asList(flows)); initFlowsList.remove(flowID); DataFlowID[] initFlows = initFlowsList.toArray(new DataFlowID[initFlowsList.size()]); assertModuleInfo(info, inUrn, ModuleState.STARTED, initFlows, flows, true, true, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0,info.getReadLockCount()); //Setup the module to acquire a lock when it's deleted ConcurrentTestModule.helper(inUrn).setPreStopLock(lock); //Now cancel the flow, it should block as the attempt to stop the //module will block lock.lock(); Future<Object> future1 = sService.submit(new Callable<Object>() { public Object call() throws Exception { getManager().cancel(flowID); return null; } }); //Wait until it blocks while(!info.isWriteLocked()) { Thread.sleep(500); info = getManager().getModuleInfo(inUrn); } //verify module state assertModuleInfo(info, inUrn, ModuleState.STOPPING, initFlows, initFlows, true, true, true, true, true); //Setup the module to not acquire a lock when it's deleted anymore ConcurrentTestModule.helper(inUrn).setPreStopLock(null); //verify that we can perform other operations while this module is stuck canPerformModuleOperationsDifferentProvider(); canPerformModuleOperationsSameProvider(); // Now verify the supplied concurrent operation on this module block // and succeed as expected //Spawn a thread to start the module, it should get stuck and then //eventually fail Future<Object> future2 = sService.submit(inConcurrentTestOperation); //wait until it gets stuck in the lock while(info.getLockQueueLength() < 1) { Thread.sleep(500); info = getManager().getModuleInfo(inUrn); } //let the delete complete by unlocking the lock lock.unlock(); future1.get(); //verify that the module is deleted assertTrue(getManager().getModuleInstances( ConcurrentTestFactory.PROVIDER_URN).isEmpty()); //verify that the concurrent task completes successfully future2.get(); } private Future<Object> runStartTests(ReentrantLock inLock, final ModuleURN inUrn) throws Exception { //set up the factory ConcurrentTestFactory.setSingleton(false); //initialize the module manager initManager(); //Create an instance assertEquals(inUrn, getManager().createModule( ConcurrentTestFactory.PROVIDER_URN, inUrn)); //Verify module state ModuleInfo info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.CREATED, null, null, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //Spawn a thread to start the module Future<Object>result1 = sService.submit(new Callable<Object>(){ public Object call() throws Exception { getManager().start(inUrn); return null; } }); //Wait until it gets blocked on the lock within prestart while(inLock.getQueueLength() == 0) { Thread.sleep(500); } //verify module state info = getManager().getModuleInfo(inUrn); //the only flows running right now List<DataFlowID> flowList = getManager().getDataFlows(true); DataFlowID[] flows = flowList.isEmpty()? null: flowList.toArray(new DataFlowID[flowList.size()]); assertModuleInfo(info, inUrn, ModuleState.STARTING, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //verify that another attempt to start the module fails createStartFailure(inUrn, ModuleState.STARTING); //verify that stop module fails createStopFailure(inUrn, ModuleState.STARTING); //verify that delete module fails createDeleteFailure(inUrn, ModuleState.STARTING); //verify create data flow fails createFlowFailure(inUrn, ModuleState.STARTING); //verify that module operations from a different provider can be performed canPerformModuleOperationsDifferentProvider(); //verify that module operations for a different module from the same //provider can be carried out. canPerformModuleOperationsSameProvider(); //unlock to let start complete inLock.unlock(); return result1; } private Future<Object> runStopTests(final ModuleURN inUrn, ReentrantLock inLock) throws Exception { //set up the factory ConcurrentTestFactory.setSingleton(false); //initialize the module manager initManager(); //Create an instance assertEquals(inUrn, getManager().createModule( ConcurrentTestFactory.PROVIDER_URN, inUrn)); //Start the module getManager().start(inUrn); //Verify module state ModuleInfo info = getManager().getModuleInfo(inUrn); //the only data flows running are the ones initiated by the module DataFlowID[] flows = getManager().getDataFlows(true).toArray(new DataFlowID[0]); assertEquals(1, flows.length); assertModuleInfo(info, inUrn, ModuleState.STARTED, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //Spawn a thread to stop the module Future<Object> future = sService.submit(new Callable<Object>(){ public Object call() throws Exception { getManager().stop(inUrn); return null; } }); //Wait until it gets blocked on the lock within prestop while(inLock.getQueueLength() == 0) { Thread.sleep(500); } //verify module state info = getManager().getModuleInfo(inUrn); assertModuleInfo(info, inUrn, ModuleState.STOPPING, flows, flows, false, false, true, true, true); assertFalse(info.isWriteLocked()); assertEquals(0, info.getReadLockCount()); //verify that another attempt to start the module fails createStartFailure(inUrn, ModuleState.STOPPING); //verify that stop module fails createStopFailure(inUrn, ModuleState.STOPPING); //verify that delete module fails createDeleteFailure(inUrn, ModuleState.STOPPING); //verify create data flow fails createFlowFailure(inUrn, ModuleState.STOPPING); //verify that module operations from a different provider can be performed canPerformModuleOperationsDifferentProvider(); //verify that module operations for a different module from the same //provider can be carried out. canPerformModuleOperationsSameProvider(); //unlock to let stop complete inLock.unlock(); return future; } /** * Verifies that modules from a different provider can be created * concurrently with a different provider creating its modules. * * @throws Exception if there were errors. */ private void canPerformModuleOperationsDifferentProvider() throws Exception { ModuleURN urn = new ModuleURN(MultipleModuleFactory.PROVIDER_URN, "test"); assertEquals(urn, getManager().createModule( MultipleModuleFactory.PROVIDER_URN, urn)); //this module is autostarted getManager().stop(urn); getManager().deleteModule(urn); } /** * Verifies that modules from a different provider can be created * concurrently with a different provider creating its modules. * * @throws Exception if there were errors. */ private void canPerformModuleOperationsSameProvider() throws Exception { //Clear the module so that it does not acquire any locks. ModuleURN urn = new ModuleURN(ConcurrentTestFactory.PROVIDER_URN, "test"); assertEquals(urn, getManager().createModule( ConcurrentTestFactory.PROVIDER_URN, urn)); //Start the module if it wasn't autostarted if (!getManager().getModuleInfo(urn).getState().isStarted()) { getManager().start(urn); } DataFlowID flowID = getManager().createDataFlow(new DataRequest[]{ new DataRequest(urn, null) }); getManager().cancel(flowID); getManager().stop(urn); getManager().deleteModule(urn); } private DataFlowID[] subtract(DataFlowID[] inFrom, DataFlowID[]inValue) { HashSet<DataFlowID> set = new HashSet<DataFlowID>(Arrays.asList(inFrom)); set.removeAll(Arrays.asList(inValue)); return set.toArray(new DataFlowID[set.size()]); } private ExpectedFailure createFlowFailure(final ModuleURN inURN, ModuleState inExpectedState) throws Exception { return new ExpectedFailure<ModuleStateException>( Messages.DATAFLOW_FAILED_PCPT_MODULE_STATE_INCORRECT, inURN.toString(), inExpectedState, ModuleState.PARTICIPATE_FLOW_STATES.toString()){ protected void run() throws Exception { getManager().createDataFlow(new DataRequest[]{ new DataRequest(inURN) }); } }; } private ExpectedFailure createDeleteFailure(final ModuleURN inUrn, Object inExpectedState) throws Exception { return new ExpectedFailure<ModuleStateException>( Messages.DELETE_FAILED_MODULE_STATE_INCORRECT, inUrn.toString(), inExpectedState, ModuleState.DELETABLE_STATES.toString()){ protected void run() throws Exception { getManager().deleteModule(inUrn); } }; } private ExpectedFailure createStopFailure(final ModuleURN inUrn, Object inExpectedState) throws Exception { return new ExpectedFailure<ModuleStateException>( Messages.MODULE_NOT_STOPPED_STATE_INCORRECT, inUrn.toString(), inExpectedState, ModuleState.STOPPABLE_STATES.toString()){ protected void run() throws Exception { getManager().stop(inUrn); } }; } private ExpectedFailure createStartFailure(final ModuleURN inUrn, Object inExpectedState) throws Exception { return new ExpectedFailure<ModuleStateException>( Messages.MODULE_NOT_STARTED_STATE_INCORRECT, inUrn.toString(), inExpectedState, ModuleState.STARTABLE_STATES.toString()){ protected void run() throws Exception { getManager().start(inUrn); } }; } ModuleManager getManager() { return mManager; } void initManager() throws Exception { mManager = new ModuleManager(); mManager.init(); } @Before public void setUp() throws Exception { ConcurrentTestFactory.clear(); ConcurrentTestModule.clear(); } @After public void cleanUp() throws Exception { if(mManager != null) { mManager.stop(); mManager = null; } } private final static ExecutorService sService = Executors.newCachedThreadPool( new NamedThreadFactory("TestModuleConcurrency")); private ModuleManager mManager; }