/* * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. Camunda licenses this file to you under the Apache License, * Version 2.0; 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.camunda.bpm.engine.test.jobexecutor; import java.util.List; import org.camunda.bpm.engine.ProcessEngineConfiguration; import org.camunda.bpm.engine.impl.ProcessEngineImpl; import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.camunda.bpm.engine.test.Deployment; import org.camunda.bpm.engine.test.ProcessEngineRule; import org.camunda.bpm.engine.test.concurrency.ConcurrencyTestCase.ThreadControl; import org.camunda.bpm.engine.test.jobexecutor.RecordingAcquireJobsRunnable.RecordedAcquisitionEvent; import org.camunda.bpm.engine.test.jobexecutor.RecordingAcquireJobsRunnable.RecordedWaitEvent; import org.camunda.bpm.engine.test.util.ProvidedProcessEngineRule; import org.camunda.bpm.engine.test.util.ProcessEngineBootstrapRule; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; /** * @author Thorben Lindhauer * */ public class JobAcquisitionBackoffTest { protected static final int BASE_BACKOFF_TIME = 1000; protected static final int MAX_BACKOFF_TIME = 5000; protected static final int BACKOFF_FACTOR = 2; protected static final int BACKOFF_DECREASE_THRESHOLD = 2; protected static final int DEFAULT_NUM_JOBS_TO_ACQUIRE = 3; protected ProcessEngineBootstrapRule bootstrapRule = new ProcessEngineBootstrapRule() { @Override public ProcessEngineConfiguration configureEngine(ProcessEngineConfigurationImpl configuration) { return configuration.setJobExecutor(new ControllableJobExecutor()); } }; protected ProcessEngineRule engineRule = new ProvidedProcessEngineRule(bootstrapRule); @Rule public RuleChain ruleChain = RuleChain.outerRule(bootstrapRule).around(engineRule); protected ControllableJobExecutor jobExecutor1; protected ControllableJobExecutor jobExecutor2; protected ThreadControl acquisitionThread1; protected ThreadControl acquisitionThread2; @Before public void setUp() throws Exception { jobExecutor1 = (ControllableJobExecutor) ((ProcessEngineConfigurationImpl) engineRule.getProcessEngine().getProcessEngineConfiguration()) .getJobExecutor(); jobExecutor1.setMaxJobsPerAcquisition(DEFAULT_NUM_JOBS_TO_ACQUIRE); jobExecutor1.setBackoffTimeInMillis(BASE_BACKOFF_TIME); jobExecutor1.setMaxBackoff(MAX_BACKOFF_TIME); jobExecutor1.setBackoffDecreaseThreshold(BACKOFF_DECREASE_THRESHOLD); acquisitionThread1 = jobExecutor1.getAcquisitionThreadControl(); jobExecutor2 = new ControllableJobExecutor((ProcessEngineImpl) engineRule.getProcessEngine()); jobExecutor2.setMaxJobsPerAcquisition(DEFAULT_NUM_JOBS_TO_ACQUIRE); jobExecutor2.setBackoffTimeInMillis(BASE_BACKOFF_TIME); jobExecutor2.setMaxBackoff(MAX_BACKOFF_TIME); jobExecutor2.setBackoffDecreaseThreshold(BACKOFF_DECREASE_THRESHOLD); acquisitionThread2 = jobExecutor2.getAcquisitionThreadControl(); } @After public void tearDown() throws Exception { jobExecutor1.shutdown(); jobExecutor2.shutdown(); } @Test @Deployment(resources = "org/camunda/bpm/engine/test/jobexecutor/simpleAsyncProcess.bpmn20.xml") public void testBackoffOnOptimisticLocking() { // when starting a number of process instances process instance for (int i = 0; i < 9; i++) { engineRule.getRuntimeService().startProcessInstanceByKey("simpleAsyncProcess").getId(); } // ensure that both acquisition threads acquire the same jobs thereby provoking an optimistic locking exception JobAcquisitionTestHelper.suspendInstances(engineRule.getProcessEngine(), 6); // when starting job execution, both acquisition threads wait before acquiring something jobExecutor1.start(); acquisitionThread1.waitForSync(); jobExecutor2.start(); acquisitionThread2.waitForSync(); // when having both threads acquire jobs // then both wait before committing the acquiring transaction (AcquireJobsCmd) acquisitionThread1.makeContinueAndWaitForSync(); acquisitionThread2.makeContinueAndWaitForSync(); // when continuing acquisition thread 1 acquisitionThread1.makeContinueAndWaitForSync(); // then it has not performed waiting since it was able to acquire and execute all jobs List<RecordedWaitEvent> jobExecutor1WaitEvents = jobExecutor1.getAcquireJobsRunnable().getWaitEvents(); Assert.assertEquals(1, jobExecutor1WaitEvents.size()); Assert.assertEquals(0, jobExecutor1WaitEvents.get(0).getTimeBetweenAcquisitions()); // when continuing acquisition thread 2, acquisition fails with an OLE acquisitionThread2.makeContinueAndWaitForSync(); // and has performed backoff List<RecordedWaitEvent> jobExecutor2WaitEvents = jobExecutor2.getAcquireJobsRunnable().getWaitEvents(); Assert.assertEquals(1, jobExecutor2WaitEvents.size()); RecordedWaitEvent waitEvent = jobExecutor2WaitEvents.get(0); // we don't know the exact wait time, // since there is random jitter applied JobAcquisitionTestHelper.assertInBetween(BASE_BACKOFF_TIME, BASE_BACKOFF_TIME + BASE_BACKOFF_TIME / 2, waitEvent.getTimeBetweenAcquisitions()); // when performing another cycle of acquisition JobAcquisitionTestHelper.activateInstances(engineRule.getProcessEngine(), 6); acquisitionThread1.makeContinueAndWaitForSync(); acquisitionThread2.makeContinueAndWaitForSync(); // and thread 1 again acquires all jobs successfully acquisitionThread1.makeContinueAndWaitForSync(); // while thread 2 again fails with OLE acquisitionThread2.makeContinueAndWaitForSync(); // then thread 1 has tried to acquired 3 jobs again List<RecordedAcquisitionEvent> jobExecutor1AcquisitionEvents = jobExecutor1.getAcquireJobsRunnable().getAcquisitionEvents(); RecordedAcquisitionEvent secondAcquisitionAttempt = jobExecutor1AcquisitionEvents.get(1); Assert.assertEquals(3, secondAcquisitionAttempt.getNumJobsToAcquire()); // and not waited jobExecutor1WaitEvents = jobExecutor1.getAcquireJobsRunnable().getWaitEvents(); Assert.assertEquals(2, jobExecutor1WaitEvents.size()); Assert.assertEquals(0, jobExecutor1WaitEvents.get(1).getTimeBetweenAcquisitions()); // then thread 2 has tried to acquire 6 jobs this time List<RecordedAcquisitionEvent> jobExecutor2AcquisitionEvents = jobExecutor2.getAcquireJobsRunnable().getAcquisitionEvents(); secondAcquisitionAttempt = jobExecutor2AcquisitionEvents.get(1); Assert.assertEquals(6, secondAcquisitionAttempt.getNumJobsToAcquire()); // and again increased its backoff jobExecutor2WaitEvents = jobExecutor2.getAcquireJobsRunnable().getWaitEvents(); Assert.assertEquals(2, jobExecutor2WaitEvents.size()); RecordedWaitEvent secondWaitEvent = jobExecutor2WaitEvents.get(1); long expectedBackoffTime = BASE_BACKOFF_TIME * BACKOFF_FACTOR; // 1000 * 2^1 JobAcquisitionTestHelper.assertInBetween(expectedBackoffTime, expectedBackoffTime + expectedBackoffTime / 2, secondWaitEvent.getTimeBetweenAcquisitions()); } @Test @Deployment(resources = "org/camunda/bpm/engine/test/jobexecutor/simpleAsyncProcess.bpmn20.xml") public void testBackoffDecrease() { // when starting a number of process instances process instance for (int i = 0; i < 15; i++) { engineRule.getRuntimeService().startProcessInstanceByKey("simpleAsyncProcess").getId(); } // ensure that both acquisition threads acquire the same jobs thereby provoking an optimistic locking exception JobAcquisitionTestHelper.suspendInstances(engineRule.getProcessEngine(), 12); // when starting job execution, both acquisition threads wait before acquiring something jobExecutor1.start(); acquisitionThread1.waitForSync(); jobExecutor2.start(); acquisitionThread2.waitForSync(); // when continuing acquisition thread 1 // then it is able to acquire and execute all jobs acquisitionThread1.makeContinueAndWaitForSync(); // when continuing acquisition thread 2 // acquisition fails with an OLE acquisitionThread2.makeContinueAndWaitForSync(); jobExecutor1.shutdown(); acquisitionThread1.waitUntilDone(); acquisitionThread2.makeContinueAndWaitForSync(); // such that acquisition thread 2 performs backoff List<RecordedWaitEvent> jobExecutor2WaitEvents = jobExecutor2.getAcquireJobsRunnable().getWaitEvents(); Assert.assertEquals(1, jobExecutor2WaitEvents.size()); // when in the next cycles acquisition thread2 successfully acquires jobs without OLE for n times JobAcquisitionTestHelper.activateInstances(engineRule.getProcessEngine(), 12); for (int i = 0; i < BACKOFF_DECREASE_THRESHOLD; i++) { // backoff has not decreased yet Assert.assertTrue(jobExecutor2WaitEvents.get(i).getTimeBetweenAcquisitions() > 0); acquisitionThread2.makeContinueAndWaitForSync(); // acquire acquisitionThread2.makeContinueAndWaitForSync(); // continue after acquisition with next cycle } // it decreases its backoff again long lastBackoff = jobExecutor2WaitEvents.get(BACKOFF_DECREASE_THRESHOLD).getTimeBetweenAcquisitions(); Assert.assertEquals(0, lastBackoff); } }