Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: multi-instance collection expression is invoked with inconsistent delegate execution state #4916

Closed
wants to merge 7 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,31 @@ else if (nrOfInstances < 0) {
}
}

protected void performInstance(ActivityExecution execution, PvmActivity activity, int loopCounter) {
protected void performInstance(ActivityExecution execution, PvmActivity activity, int loopCounter, Collection<?> collection) {
setLoopVariable(execution, LOOP_COUNTER, loopCounter);
evaluateCollectionVariable(execution, loopCounter);
evaluateCollectionVariable(execution, collection, loopCounter);
execution.setEnded(false);
execution.setActive(true);
execution.executeActivity(activity);
}

protected void evaluateCollectionVariable(ActivityExecution execution, int loopCounter) {
protected void evaluateCollectionVariable(ActivityExecution execution, Collection<?> collection, int loopCounter) {
if (usesCollection() && collectionElementVariable != null && collection != null) {
Object value = getElementAtIndex(loopCounter, collection);
setLoopVariable(execution, collectionElementVariable, value);
}
}

protected Collection<?> evaluateCollection(ActivityExecution execution) {
Collection<?> collection = null;
if (usesCollection() && collectionElementVariable != null) {
Collection<?> collection = null;
if (collectionExpression != null) {
collection = (Collection<?>) collectionExpression.getValue(execution);
} else if (collectionVariable != null) {
collection = (Collection<?>) execution.getVariable(collectionVariable);
}

Object value = getElementAtIndex(loopCounter, collection);
setLoopVariable(execution, collectionElementVariable, value);
}
return collection;
}

protected abstract void createInstances(ActivityExecution execution, int nrOfInstances) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
Expand All @@ -40,6 +41,9 @@ public class ParallelMultiInstanceActivityBehavior extends MultiInstanceActivity
protected void createInstances(ActivityExecution execution, int nrOfInstances) throws Exception {
PvmActivity innerActivity = getInnerActivity(execution.getActivity());

// evaluate the collection to ensure the input of collectionExpression same as before
// also reduces the loop count of collectionExpression
Collection<?> collection = evaluateCollection(execution);
// initialize the scope and create the desired number of child executions
prepareScopeExecution(execution, nrOfInstances);

Expand All @@ -53,7 +57,7 @@ protected void createInstances(ActivityExecution execution, int nrOfInstances) t
// actually be started in correct order :) )
for (int i = (nrOfInstances - 1); i >= 0; i--) {
ActivityExecution activityExecution = concurrentExecutions.get(i);
performInstance(activityExecution, innerActivity, i);
performInstance(activityExecution, innerActivity, i, collection);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.camunda.bpm.engine.impl.bpmn.behavior;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.camunda.bpm.engine.impl.ProcessEngineLogger;
Expand All @@ -35,27 +36,27 @@ public class SequentialMultiInstanceActivityBehavior extends MultiInstanceActivi

@Override
protected void createInstances(ActivityExecution execution, int nrOfInstances) throws Exception {

Collection<?> collection = evaluateCollection(execution);
prepareScope(execution, nrOfInstances);
setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, 1);

ActivityImpl innerActivity = getInnerActivity(execution.getActivity());
performInstance(execution, innerActivity, 0);
performInstance(execution, innerActivity, 0, collection);
}

public void complete(ActivityExecution scopeExecution) {
int loopCounter = getLoopVariable(scopeExecution, LOOP_COUNTER) + 1;
int nrOfInstances = getLoopVariable(scopeExecution, NUMBER_OF_INSTANCES);
int nrOfCompletedInstances = getLoopVariable(scopeExecution, NUMBER_OF_COMPLETED_INSTANCES) + 1;

setLoopVariable(scopeExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances);

if (loopCounter == nrOfInstances || completionConditionSatisfied(scopeExecution)) {
leave(scopeExecution);
}
else {
Collection<?> collection = evaluateCollection(scopeExecution);
PvmActivity innerActivity = getInnerActivity(scopeExecution.getActivity());
performInstance(scopeExecution, innerActivity, loopCounter);
performInstance(scopeExecution, innerActivity, loopCounter, collection);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
import org.camunda.bpm.engine.test.util.PluggableProcessEngineTest;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.engine.variable.Variables;
import org.junit.Ignore;
import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.junit.Test;


Expand Down Expand Up @@ -422,7 +423,6 @@ public void testEmptyCollectionInMI() {
}

@Deployment
@Ignore
@Test
public void testParallelUserTasksBasedOnCollectionExpression() {
DelegateEvent.clearEvents();
Expand All @@ -439,6 +439,37 @@ public void testParallelUserTasksBasedOnCollectionExpression() {
DelegateEvent.clearEvents();
}

@Test
public void testSequentialUserTasksBasedOnCollectionExpression() {
DelegateEvent.clearEvents();

BpmnModelInstance model = Bpmn.createExecutableProcess("process")
.startEvent()
.userTask("miTasks")
.multiInstance()
.sequential()
.camundaCollection("${myBean.resolveCollection(execution)}")
.camundaElementVariable("elementVar")
.multiInstanceDone()
.endEvent()
.done();

testRule.deploy(model);

runtimeService.startProcessInstanceByKey("process",
Variables.createVariables().putValue("myBean", new DelegateBean()));

Task singleResult = taskService.createTaskQuery().singleResult();
taskService.complete(singleResult.getId());
List<DelegateEvent> recordedEvents = DelegateEvent.getEvents();
assertEquals(2, recordedEvents.size());

assertEquals("miTasks#multiInstanceBody", recordedEvents.get(0).getCurrentActivityId());
assertEquals("miTasks#multiInstanceBody", recordedEvents.get(1).getCurrentActivityId()); // or miTasks

DelegateEvent.clearEvents();
}

@Deployment
@Test
public void testParallelUserTasksCustomExtensions() {
Expand Down