/* Licensed 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.activiti.engine.test.concurrency; import java.util.Collections; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.activiti.engine.impl.history.HistoryLevel; import org.activiti.engine.impl.identity.Authentication; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.task.Task; import org.activiti.engine.test.Deployment; import org.apache.ibatis.exceptions.PersistenceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Test that uses a number of threads to start processes and complete tasks concurrently. * * @author Frederik Heremans */ public class ConcurrentEngineUsageTest extends PluggableActivitiTestCase { private static Logger log = LoggerFactory.getLogger(ConcurrentEngineUsageTest.class); private static final int MAX_RETRIES = 5; @Deployment public void testConcurrentUsage() throws Exception { if(!processEngineConfiguration.getDatabaseType().equals("h2") && !processEngineConfiguration.getDatabaseType().equals("db2")) { int numberOfThreads = 5; int numberOfProcessesPerThread = 5; int totalNumberOfTasks = 2 * numberOfThreads * numberOfProcessesPerThread; ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(numberOfThreads)); for(int i=0; i< numberOfThreads; i++) { executor.execute(new ConcurrentProcessRunnerRunnable(numberOfProcessesPerThread, "kermit" + i)); } // Wait for termination or timeout and check if all tasks are complete executor.shutdown(); boolean isEnded = executor.awaitTermination(20000, TimeUnit.MILLISECONDS); if(!isEnded) { log.error("Executor was not shut down after timeout, not al tasks have been executed"); executor.shutdownNow(); } assertEquals(0, executor.getActiveCount()); // Check there are no processes active anymore assertEquals(0, runtimeService.createProcessInstanceQuery().count()); if(processEngineConfiguration.getHistoryLevel().isAtLeast(HistoryLevel.ACTIVITY)) { // Check if all processes and tasks are complete assertEquals(numberOfProcessesPerThread * numberOfThreads, historyService.createHistoricProcessInstanceQuery() .finished().count()); assertEquals(totalNumberOfTasks, historyService.createHistoricTaskInstanceQuery() .finished().count()); } } } protected void retryStartProcess(String runningUser) { int retries = MAX_RETRIES; int timeout = 200; boolean success = false; while(retries > 0 && !success) { try { runtimeService.startProcessInstanceByKey("concurrentProcess", Collections.singletonMap("assignee", (Object)runningUser)); success = true; } catch(PersistenceException pe) { retries = retries - 1; log.debug("Retrying process start - " + (MAX_RETRIES - retries)); try { Thread.sleep(timeout); } catch (InterruptedException ignore) { } timeout = timeout + 200; } } if(success == false) { log.debug("Retrying process start FAILED " + MAX_RETRIES + " times"); } } protected void retryFinishTask(String taskId) { int retries = MAX_RETRIES; int timeout = 200; boolean success = false; while(retries > 0 && !success) { try { taskService.complete(taskId); success = true; } catch(PersistenceException pe) { retries = retries - 1; log.debug("Retrying task completion - " + (MAX_RETRIES - retries)); try { Thread.sleep(timeout); } catch (InterruptedException ignore) { } timeout = timeout + 200; } } if(success == false) { log.debug("Retrying task completion FAILED " + MAX_RETRIES + " times"); } } private class ConcurrentProcessRunnerRunnable implements Runnable { private String drivingUser; private int numberOfProcesses; public ConcurrentProcessRunnerRunnable(int numberOfProcesses, String drivingUser) { this.drivingUser = drivingUser; this.numberOfProcesses = numberOfProcesses; } @Override public void run() { Authentication.setAuthenticatedUserId(drivingUser); boolean finishTask = false; boolean tasksAvailable = false; while(numberOfProcesses > 0 || tasksAvailable) { if(numberOfProcesses > 0 && !finishTask) { // Start a new process retryStartProcess(drivingUser); finishTask = true; if(numberOfProcesses == 0) { // Make sure while-loop doesn't stop when processes are all started tasksAvailable = taskService.createTaskQuery().taskAssignee(drivingUser).count() > 0; } numberOfProcesses = numberOfProcesses - 1; } else { // Finish a task List<Task> taskToComplete = taskService.createTaskQuery().taskAssignee(drivingUser).listPage(0, 1); tasksAvailable = taskToComplete.size() > 0; if(tasksAvailable) { retryFinishTask(taskToComplete.get(0).getId()); } finishTask = false; } } } } }