/* 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.db; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import org.activiti.engine.ActivitiOptimisticLockingException; import org.activiti.engine.impl.cmd.SetExecutionVariablesCmd; import org.activiti.engine.impl.cmd.SetTaskVariablesCmd; import org.activiti.engine.impl.interceptor.Command; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.test.PluggableActivitiTestCase; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; public class DuplicateVariableInsertTest extends PluggableActivitiTestCase { /** * Test for ACT-1887: Inserting the same new variable at the same time, from 2 different threads, using 2 modified commands that use * a barrier for starting and a barrier for completing the command, so they each insert a new variable guaranteed. */ public void testDuplicateVariableInsertOnExecution() throws Exception { String processDefinitionId = deployOneTaskTestProcess(); final ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId); final CyclicBarrier startBarrier = new CyclicBarrier(2); final CyclicBarrier endBarrier = new CyclicBarrier(2); final List<Exception> exceptions = new ArrayList<Exception>(); Thread firstInstertThread = new Thread(new Runnable() { @Override public void run() { try { managementService.executeCommand(new SetVariableWithBarriersCommand(startBarrier, endBarrier, processInstance.getId())); } catch(Exception e) { exceptions.add(e); } } }); Thread secondInsertThread = new Thread(new Runnable() { @Override public void run() { try { managementService.executeCommand(new SetVariableWithBarriersCommand(startBarrier, endBarrier, processInstance.getId())); } catch(Exception e) { exceptions.add(e); } } }); firstInstertThread.start(); secondInsertThread.start(); // Wait for threads to complete firstInstertThread.join(); secondInsertThread.join(); // One of the 2 threads should get an optimistic lock exception assertEquals(1, exceptions.size()); // One variable should be set Map<String, Object> variables = runtimeService.getVariables(processInstance.getId()); assertEquals(1, variables.size()); assertEquals("12345", variables.get("var")); runtimeService.deleteProcessInstance(processInstance.getId(), "ShouldNotFail"); } /** * Test for ACT-1887: Inserting the same new variable at the same time, from 2 different threads, using 2 modified commands that use * a barrier for starting and a barrier for completing the command, so they each insert a new variable guaranteed. */ public void testDuplicateVariableInsertOnTask() throws Exception { String processDefinitionId = deployOneTaskTestProcess(); final ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId); final Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); final CyclicBarrier startBarrier = new CyclicBarrier(2); final CyclicBarrier endBarrier = new CyclicBarrier(2); final List<Exception> exceptions = new ArrayList<Exception>(); Thread firstInstertThread = new Thread(new Runnable() { @Override public void run() { try { managementService.executeCommand(new SetTaskVariableWithBarriersCommand(startBarrier, endBarrier, task.getId())); } catch(Exception e) { exceptions.add(e); } } }); Thread secondInsertThread = new Thread(new Runnable() { @Override public void run() { try { managementService.executeCommand(new SetTaskVariableWithBarriersCommand(startBarrier, endBarrier, task.getId())); } catch(Exception e) { exceptions.add(e); } } }); firstInstertThread.start(); secondInsertThread.start(); // Wait for threads to complete firstInstertThread.join(); secondInsertThread.join(); // One of the 2 threads should get an optimistic lock exception assertEquals(1, exceptions.size()); assertTrue(exceptions.get(0) instanceof ActivitiOptimisticLockingException); // One variable should be set Map<String, Object> variables = runtimeService.getVariables(processInstance.getId()); assertEquals(1, variables.size()); assertEquals("12345", variables.get("var")); runtimeService.deleteProcessInstance(processInstance.getId(), "ShouldNotFail"); } /** * Command wrapping a SetExecutionVariablesCmd, waiting in to start and end on the barriers passed in. * * @author Frederik Heremans * */ private class SetVariableWithBarriersCommand implements Command<Void> { private CyclicBarrier startBarrier; private CyclicBarrier endBarrier; private String executionId; public SetVariableWithBarriersCommand(CyclicBarrier startBarrier, CyclicBarrier endBarrier, String executionId) { this.startBarrier = startBarrier; this.endBarrier = endBarrier; this.executionId = executionId; } @Override public Void execute(CommandContext commandContext) { try { startBarrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } new SetExecutionVariablesCmd(executionId, Collections.singletonMap("var", "12345"), false).execute(commandContext); try { endBarrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } return null; } } /** * Command wrapping a SetTaskVariablesCmd, waiting in to start and end on the barriers passed in. * * @author Frederik Heremans * */ private class SetTaskVariableWithBarriersCommand implements Command<Void> { private CyclicBarrier startBarrier; private CyclicBarrier endBarrier; private String taskId; public SetTaskVariableWithBarriersCommand(CyclicBarrier startBarrier, CyclicBarrier endBarrier, String taskId) { this.startBarrier = startBarrier; this.endBarrier = endBarrier; this.taskId = taskId; } @Override public Void execute(CommandContext commandContext) { try { startBarrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } new SetTaskVariablesCmd(taskId, Collections.singletonMap("var", "12345"), false).execute(commandContext); try { endBarrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } return null; } } }