/* 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.jobexecutor; import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import org.activiti.engine.ActivitiException; import org.activiti.engine.ProcessEngine; import org.activiti.engine.impl.asyncexecutor.AsyncExecutor; import org.activiti.engine.impl.asyncexecutor.DefaultAsyncJobExecutor; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration; import org.activiti.engine.impl.persistence.entity.JobEntity; import org.activiti.engine.impl.test.JobTestHelper; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tests specifically for the {@link AsyncExecutor}. * * Note that all tests with jobs already use this async executor, so this * is really all the 'edgy' cases here. * * @author jbarrez */ public class AsyncExecutorTest { @Test public void testRegularAsyncExecution() { ProcessEngine processEngine = null; try { // Deploy processEngine = createProcessEngine(true); setClockToCurrentTime(processEngine); deploy(processEngine, "AsyncExecutorTest.testRegularAsyncExecution.bpmn20.xml"); // Start process instance. Wait for all jobs to be done processEngine.getRuntimeService().startProcessInstanceByKey("asyncExecutor"); // Move clock 3 minutes. Nothing should happen addSecondsToCurrentTime(processEngine, 180L); try { waitForAllJobsBeingExecuted(processEngine, 500L); Assert.fail(); } catch (ActivitiException e) { // Expected } Assert.assertEquals(1, processEngine.getTaskService().createTaskQuery().taskName("The Task").count()); Assert.assertEquals(0, processEngine.getTaskService().createTaskQuery().taskName("Task after timer").count()); Assert.assertEquals(1, processEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(0, getAsyncExecutorJobCount(processEngine)); // Move clock 3 minutes and 1 second. Triggers the timer addSecondsToCurrentTime(processEngine, 181); waitForAllJobsBeingExecuted(processEngine); // Verify if all is as expected Assert.assertEquals(0, processEngine.getTaskService().createTaskQuery().taskName("The Task").count()); Assert.assertEquals(1, processEngine.getTaskService().createTaskQuery().taskName("Task after timer").count()); Assert.assertEquals(0, processEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(1, getAsyncExecutorJobCount(processEngine)); } finally { // Clean up if (processEngine != null) { cleanup(processEngine); } } } @Test public void testAsyncExecutorDisabledOnOneEngine() { ProcessEngine firstProcessEngine = null; ProcessEngine secondProcessEngine = null; try { // Deploy on one engine, where the async executor is disabled firstProcessEngine = createProcessEngine(false); Date now = setClockToCurrentTime(firstProcessEngine); deploy(firstProcessEngine, "AsyncExecutorTest.testRegularAsyncExecution.bpmn20.xml"); // Start process instance on first engine firstProcessEngine.getRuntimeService().startProcessInstanceByKey("asyncExecutor"); // Move clock 5 minutes and 1 second. Triggers the timer normally, but not now since async execution is disabled addSecondsToCurrentTime(firstProcessEngine, 301); // 301 = 5m01s Assert.assertEquals(1, firstProcessEngine.getTaskService().createTaskQuery().taskName("The Task").count()); Assert.assertEquals(0, firstProcessEngine.getTaskService().createTaskQuery().taskName("Task after timer").count()); Assert.assertEquals(1, firstProcessEngine.getManagementService().createJobQuery().count()); // Create second engine, with async executor enabled. Same time as the first engine to start, then add 301 seconds secondProcessEngine = createProcessEngine(true, now); addSecondsToCurrentTime(secondProcessEngine, 361); waitForAllJobsBeingExecuted(secondProcessEngine); // Verify if all is as expected Assert.assertEquals(0, firstProcessEngine.getTaskService().createTaskQuery().taskName("The Task").count()); Assert.assertEquals(1, firstProcessEngine.getTaskService().createTaskQuery().taskName("Task after timer").count()); Assert.assertEquals(0, firstProcessEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(0, getAsyncExecutorJobCount(firstProcessEngine)); Assert.assertEquals(1, getAsyncExecutorJobCount(secondProcessEngine)); } finally { // Clean up cleanup(firstProcessEngine); cleanup(secondProcessEngine); } } @Test public void testAsyncScriptExecution() { ProcessEngine processEngine = null; try { // Deploy processEngine = createProcessEngine(true); setClockToCurrentTime(processEngine); deploy(processEngine,"AsyncExecutorTest.testAsyncScriptExecution.bpmn20.xml"); // Start process instance. Wait for all jobs to be done processEngine.getRuntimeService().startProcessInstanceByKey("asyncScript"); waitForAllJobsBeingExecuted(processEngine); // Verify if all is as expected Assert.assertEquals(1, processEngine.getTaskService().createTaskQuery().taskName("Task after script").count()); Assert.assertEquals(0, processEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(1, getAsyncExecutorJobCount(processEngine)); } finally { // Clean up cleanup(processEngine); } } @Test public void testAsyncScriptExecutionOnTwoEngines() { ProcessEngine firstProcessEngine = null; ProcessEngine secondProcessEngine = null; try { // Deploy firstProcessEngine = createProcessEngine(false); Date now = setClockToCurrentTime(firstProcessEngine); deploy(firstProcessEngine,"AsyncExecutorTest.testAsyncScriptExecution.bpmn20.xml"); // Start process instance. Nothing should happen firstProcessEngine.getRuntimeService().startProcessInstanceByKey("asyncScript"); Assert.assertEquals(0, firstProcessEngine.getTaskService().createTaskQuery().taskName("Task after script").count()); Assert.assertEquals(1, firstProcessEngine.getManagementService().createJobQuery().count()); // Start second engine, with async executor enabled secondProcessEngine = createProcessEngine(true, now); // Same timestamp as first engine Assert.assertEquals(0, firstProcessEngine.getTaskService().createTaskQuery().taskName("Task after script").count()); Assert.assertEquals(1, firstProcessEngine.getManagementService().createJobQuery().count()); // Move the clock 1 second. Should be executed now by second engine addSecondsToCurrentTime(secondProcessEngine, 1); waitForAllJobsBeingExecuted(secondProcessEngine, 10000L); // Verify if all is as expected Assert.assertEquals(1, firstProcessEngine.getTaskService().createTaskQuery().taskName("Task after script").count()); Assert.assertEquals(0, firstProcessEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(0, getAsyncExecutorJobCount(firstProcessEngine)); Assert.assertEquals(1, getAsyncExecutorJobCount(secondProcessEngine)); } finally { // Clean up cleanup(firstProcessEngine); cleanup(secondProcessEngine); } } @Test public void testAsyncFailingScript() { ProcessEngine processEngine = null; try { // Deploy processEngine = createProcessEngine(true); processEngine.getProcessEngineConfiguration().getClock().reset(); deploy(processEngine,"AsyncExecutorTest.testAsyncFailingScript.bpmn20.xml"); // Start process instance. Wait for all jobs to be done. processEngine.getRuntimeService().startProcessInstanceByKey("asyncScript"); // There is a back off mechanism for the retry, so need a bit of time // But to be sure, we make the wait time small processEngine.getProcessEngineConfiguration().setAsyncFailedJobWaitTime(1); final ProcessEngine processEngineCopy = processEngine; JobTestHelper.waitForJobExecutorOnCondition(processEngine.getProcessEngineConfiguration(), 10000L, 2000L, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return processEngineCopy.getManagementService().createJobQuery().withRetriesLeft().count() == 0; } }); // Verify if all is as expected Assert.assertEquals(0, processEngine.getTaskService().createTaskQuery().taskName("Task after script").count()); Assert.assertEquals(1, processEngine.getManagementService().createJobQuery().count()); Assert.assertEquals(0, processEngine.getManagementService().createJobQuery().withRetriesLeft().count()); Assert.assertEquals(1, processEngine.getManagementService().createJobQuery().noRetriesLeft().count()); Assert.assertEquals(1, processEngine.getManagementService().createJobQuery().withException().count()); Assert.assertEquals(3, getAsyncExecutorJobCount(processEngine)); } finally { // Clean up cleanup(processEngine); } } // Helpers private ProcessEngine createProcessEngine(boolean enableAsyncExecutor) { return createProcessEngine(enableAsyncExecutor, null); } private ProcessEngine createProcessEngine(boolean enableAsyncExecutor, Date time) { ProcessEngineConfigurationImpl processEngineConfiguration = new StandaloneInMemProcessEngineConfiguration(); processEngineConfiguration.setJdbcUrl("jdbc:h2:mem:activiti-AsyncExecutorTest;DB_CLOSE_DELAY=1000"); processEngineConfiguration.setDatabaseSchemaUpdate("true"); processEngineConfiguration.setJobExecutorActivate(false); // No need for that old job executor if (enableAsyncExecutor) { processEngineConfiguration.setAsyncExecutorEnabled(true); processEngineConfiguration.setAsyncExecutorActivate(true); CountingAsyncExecutor countingAsyncExecutor = new CountingAsyncExecutor(); countingAsyncExecutor.setDefaultAsyncJobAcquireWaitTimeInMillis(50); // To avoid waiting too long when a retry happens countingAsyncExecutor.setDefaultTimerJobAcquireWaitTimeInMillis(50); processEngineConfiguration.setAsyncExecutor(countingAsyncExecutor); } ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine(); if (time != null) { processEngine.getProcessEngineConfiguration().getClock().setCurrentTime(time); } return processEngine; } private Date setClockToCurrentTime(ProcessEngine processEngine) { Date date = new Date(); processEngine.getProcessEngineConfiguration().getClock().setCurrentTime(date); return date; } private void addSecondsToCurrentTime(ProcessEngine processEngine, long nrOfSeconds) { Date currentTime = processEngine.getProcessEngineConfiguration().getClock().getCurrentTime(); processEngine.getProcessEngineConfiguration().getClock().setCurrentTime(new Date(currentTime.getTime() + (nrOfSeconds * 1000L))); } private void cleanup(ProcessEngine processEngine) { for (org.activiti.engine.repository.Deployment deployment : processEngine.getRepositoryService().createDeploymentQuery().list()) { processEngine.getRepositoryService().deleteDeployment(deployment.getId(), true); } processEngine.close(); } private String deploy(ProcessEngine processEngine, String resource) { return processEngine.getRepositoryService().createDeployment().addClasspathResource("org/activiti/engine/test/jobexecutor/" + resource).deploy().getId(); } private void waitForAllJobsBeingExecuted(ProcessEngine processEngine) { waitForAllJobsBeingExecuted(processEngine, 10000L); } private void waitForAllJobsBeingExecuted(ProcessEngine processEngine, long maxWaitTime) { JobTestHelper.waitForJobExecutorToProcessAllJobs(processEngine.getProcessEngineConfiguration(), processEngine.getManagementService(), maxWaitTime, 1000L, false); } private int getAsyncExecutorJobCount(ProcessEngine processEngine) { AsyncExecutor asyncExecutor = processEngine.getProcessEngineConfiguration().getAsyncExecutor(); if (asyncExecutor != null) { return ((CountingAsyncExecutor) asyncExecutor).getCounter().get(); } return 0; } static class CountingAsyncExecutor extends DefaultAsyncJobExecutor { private static final Logger logger = LoggerFactory.getLogger(CountingAsyncExecutor.class); private AtomicInteger counter = new AtomicInteger(0); @Override public boolean executeAsyncJob(JobEntity job) { logger.info("About to execute job " + job.getId()); counter.incrementAndGet(); boolean success = super.executeAsyncJob(job); logger.info("Handed off job " + job.getId() + " to async executor (retries=" + job.getRetries() + ")"); return success; } public AtomicInteger getCounter() { return counter; } public void setCounter(AtomicInteger counter) { this.counter = counter; } } }