cancel
Showing results for 
Search instead for 
Did you mean: 

5.11 breaks creating task & adding group -NeedsActiveTaskCmd

sangv
Champ in-the-making
Champ in-the-making
I recently upgraded to 5.11 and find that my working code is now broken.

For a specific usecase, we create a task on the fly from a service task and add a candidate group to it.


public void execute(DelegateExecution delegateExecution) throws Exception {
TaskEntity task = (TaskEntity) taskService.newTask();
          task.setName(name);
          task.setPriority(priority);
          task.setProcessInstanceId(processInstanceId);
        task.setProcessDefinitionId(processDefinitionId);
       …
       
        taskService.saveTask(task);

        for(String group: groups){
             addCandidateGroup(task.getId(), group);
        }
}

But since the upgrade to 5.11, it seems that the NeedsActiveTaskCmd gets invoked which checks against the task entity manager with the task id before allowing the candidate group to be added. However since the saveTask call has not been committed, the task does not exist in the database leading to this exception stack trace.

org.activiti.engine.ActivitiException: Cannot find task with id 196
     at org.activiti.engine.impl.cmd.NeedsActiveTaskCmd.execute(NeedsActiveTaskCmd.java:51)
     at org.activiti.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
     at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:60)
     at org.activiti.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42)
     at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
     at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40)
     at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:38)
     at org.activiti.engine.impl.TaskServiceImpl.addCandidateGroup(TaskServiceImpl.java:120)

Since, I am saving the task and adding the candidate group in the same service task (running under the context of activiti's transaction),  the task never gets persisted (even without the candidate group link). So, basically the interceptor now leads to my tasks not being persisted at all.

Basically, my questions is
i) Was this the expected behavior? It seems not to me because this will force me to potentially use 2 async service tasks or start managing multiple transactions within the service task. There was a similar issue reported with creating attachments which seems to have been caused by the same command being invoked. - http://forums.activiti.org/en/viewtopic.php?f=6&t=5718

Comments/Recommendations?

Thanks,
Sang
7 REPLIES 7

frederikherema1
Star Contributor
Star Contributor
If you use delegateExecution.getEngineServices() to obtain the taskService, it will participate in the ongoing transaction, the service-task is being executed in. So even if you first call a taskService.save() and afterwards the addCandidate(), both those changes will be committed together in the same transaction.

The NeedsActiveTask is actually the way it's intended to work. If there is no EXISTING task yet, this operation is illegal, as it would allow you to fill in any arbitrary ID and get a less obvious DB-exception later on when committing.

sangv
Champ in-the-making
Champ in-the-making
So even if you first call a taskService.save() and afterwards the addCandidate(), both those changes will be committed together in the same transaction.

I will go ahead and try this but I don't think this will work because the check for an active task happens between the saveTask and the addCandidateGroup. The only way it could work IMO is if we run the database (mysql) with an isolation level of READ UNCOMMITTED as opposed to REPEATABLE READ or READ COMMITTED.

Also, my service task is already a spring bean with the taskService injected in it. So why should there be a difference between the injected taskService and getting the task service from the delegateExecution.getEngineServices()?

– Sang

frederikherema1
Star Contributor
Star Contributor
When using spring and an transaction-aware datasource, this won't make any difference, indeed. But for standalone usage of activiti it makes a difference: every API-call uses it's own command-contentx internally, fething a SQLConnection from the datasource. When you're using the API from within a process, chances are high, this is the cause of another API-call. The getEngineServices() will make sure the same command-context is used.

The saveTask() will actually write the entities to the transaction. Subsequent commands will use the same transaction and will 'see' the task-entity, using READ_UNCOMITTED off course… If you're not allowing read-uncomitted AND you're transaction-boundaries are wider than a sign API-call (which is the case, since you're reusing the transaction the first API-call runs in and running 2 API-calls in it (saveTask and setCandidates).

sangv
Champ in-the-making
Champ in-the-making
Hi frederikheremans -

I created a scaled down example of what I am trying to do and this fails with 5.11. Can you please take a look at this (Don't worry about why we create tasks dynamically - we are trying to meet a special requirement and multi-instance tasks do not work for what we need.)

In the test process, we are trying to create user tasks in a service task. So basically the java code makes some calculations and creates N user tasks and associates each of those task to a candidate group. There are potentially multiple iterations of this.

Here is what the service task looks like:

public class DummyUserExposureServiceTask implements JavaDelegate {

@Override
public void execute(DelegateExecution delegateExecution) throws Exception {
 
  delegateExecution.setVariable("moreUsers", false);
 
  String[] userIdsToExposeInCurrentCycle = new String[]{"group1"};

  assignToUsers(Arrays.asList(userIdsToExposeInCurrentCycle), delegateExecution.getProcessInstanceId(),delegateExecution.getProcessDefinitionId(), delegateExecution.getEngineServices().getTaskService());

}


private void assignToUsers(List<String> userIds, String processInstanceId, String processDefinitionId, TaskService taskService){
   for (String each : userIds){
    TaskEntity task = (TaskEntity) taskService.newTask();
          task.setName("DynamicUserTask - " + each);
         task.setPriority(100);
         task.setProcessInstanceId(processInstanceId);
         task.setProcessDefinitionId(processDefinitionId);        
         
         System.out.println("About to save task " + task);
       taskService.saveTask(task);
       System.out.println("Done saving task " + task);
       try {
     taskService.addCandidateGroup(task.getId(), each);
     System.out.println("Done adding candidate groups for task " + task);
    } catch (Exception e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
      
    }
}

}

The test case is super simple and I am not able to run it without getting an exception about the task Id not being found.

@Test
public void testCreateTaskInServiceTask() throws Exception{
 
  runtimeService.startProcessInstanceByKey("CreateTasksProcess");
  Task task = taskService.createTaskQuery().processDefinitionKey("CreateTasksProcess").singleResult();
  taskService.addCandidateGroup(task.getId(), "user1");
  List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("group1").list();

  assertNotNull(tasks);

  assertEquals(1,tasks.size());
}

This is the log (strangely the exception happens after the system out about the candidate group being added):

About to save task Task[null]
Done saving task Task[111]
Done adding candidate groups for task Task[111]
Feb 08, 2013 4:31:43 PM org.activiti.engine.impl.interceptor.CommandContext close
SEVERE: Error while closing command context
org.activiti.engine.ActivitiException: Cannot find task with id 111
at org.activiti.engine.impl.cmd.NeedsActiveTaskCmd.execute(NeedsActiveTaskCmd.java:51)
at org.activiti.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:24)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:60)
at org.activiti.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.activiti.engine.impl.TaskServiceImpl.addCandidateGroup(TaskServiceImpl.java:120)
at simple.activiti.tasks.DummyUserExposureServiceTask.assignToUsers(DummyUserExposureServiceTask.java:37)
at simple.activiti.tasks.DummyUserExposureServiceTask.execute(DummyUserExposureServiceTask.java:20)
at org.activiti.engine.impl.delegate.JavaDelegateInvocation.invoke(JavaDelegateInvocation.java:34)
at org.activiti.engine.impl.delegate.DelegateInvocation.proceed(DelegateInvocation.java:37)
at org.activiti.engine.impl.delegate.DefaultDelegateInterceptor.handleInvocation(DefaultDelegateInterceptor.java:25)
at org.activiti.engine.impl.bpmn.behavior.ServiceTaskDelegateExpressionActivityBehavior.execute(ServiceTaskDelegateExpressionActivityBehavior.java:74)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationActivityExecute.execute(AtomicOperationActivityExecute.java:44)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(AtomicOperationTransitionNotifyListenerStart.java:52)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:56)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:49)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionCreateScope.execute(AtomicOperationTransitionCreateScope.java:49)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerTake.execute(AtomicOperationTransitionNotifyListenerTake.java:65)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionDestroyScope.execute(AtomicOperationTransitionDestroyScope.java:115)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerEnd.eventNotificationsCompleted(AtomicOperationTransitionNotifyListenerEnd.java:36)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:56)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:49)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.take(ExecutionEntity.java:370)
at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:74)
at org.activiti.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.execute(FlowNodeActivityBehavior.java:36)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationActivityExecute.execute(AtomicOperationActivityExecute.java:44)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(AtomicOperationTransitionNotifyListenerStart.java:52)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:56)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:49)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionCreateScope.execute(AtomicOperationTransitionCreateScope.java:49)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerTake.execute(AtomicOperationTransitionNotifyListenerTake.java:65)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionDestroyScope.execute(AtomicOperationTransitionDestroyScope.java:115)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:85)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:535)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:530)

My test process (its mainly a service task that creates user tasks programatically):

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlnsSmiley Surprisedmgdc="http://www.omg.org/spec/DD/20100524/DC" xmlnsSmiley Surprisedmgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="CreateTasksProcess" name="CreateTasksProcess">
    <endEvent id="endevent1" name="End"></endEvent>
    <serviceTask id="servicetask2" name="Send offer to users (create tasks)" default="flow46" activiti:delegateExpression="${userExposureServiceTask}"></serviceTask>
    <startEvent id="startevent1" name="Start"></startEvent>
    <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
    <exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
    <sequenceFlow id="flow21" name="${moreUsers}" sourceRef="exclusivegateway1" targetRef="exclusivegateway2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${moreUsers}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow22" name="" sourceRef="exclusivegateway2" targetRef="servicetask2"></sequenceFlow>
    <sequenceFlow id="flow46" name="" sourceRef="servicetask2" targetRef="exclusivegateway1"></sequenceFlow>
    <sequenceFlow id="flow47" name="" sourceRef="exclusivegateway1" targetRef="usertask1"></sequenceFlow>
    <sequenceFlow id="flow48" name="" sourceRef="startevent1" targetRef="exclusivegateway2"></sequenceFlow>
    <sequenceFlow id="flow49" name="" sourceRef="exclusivegateway2" targetRef="endevent1">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[false]]></conditionExpression>
    </sequenceFlow>
    <userTask id="usertask1" name="Dummy User Task to keep process running"></userTask>
    <sequenceFlow id="flow50" name="" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_CreateTasksProcess">
    <bpmndi:BPMNPlane bpmnElement="CreateTasksProcess" id="BPMNPlane_CreateTasksProcess">
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35" width="35" x="980" y="163"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="servicetask2" id="BPMNShape_servicetask2">
        <omgdc:Bounds height="55" width="105" x="520" y="152"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35" width="35" x="277" y="162"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
        <omgdc:Bounds height="40" width="40" x="763" y="160"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
        <omgdc:Bounds height="40" width="40" x="420" y="160"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="68" width="114" x="830" y="147"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow21" id="BPMNEdge_flow21">
        <omgdi:waypoint x="783" y="160"></omgdi:waypoint>
        <omgdi:waypoint x="782" y="65"></omgdi:waypoint>
        <omgdi:waypoint x="439" y="65"></omgdi:waypoint>
        <omgdi:waypoint x="440" y="160"></omgdi:waypoint>
        <bpmndi:BPMNLabel>
          <omgdc:Bounds height="13" width="100" x="10" y="0"></omgdc:Bounds>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow22" id="BPMNEdge_flow22">
        <omgdi:waypoint x="460" y="180"></omgdi:waypoint>
        <omgdi:waypoint x="520" y="179"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow46" id="BPMNEdge_flow46">
        <omgdi:waypoint x="625" y="179"></omgdi:waypoint>
        <omgdi:waypoint x="763" y="180"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow47" id="BPMNEdge_flow47">
        <omgdi:waypoint x="803" y="180"></omgdi:waypoint>
        <omgdi:waypoint x="830" y="181"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow48" id="BPMNEdge_flow48">
        <omgdi:waypoint x="312" y="179"></omgdi:waypoint>
        <omgdi:waypoint x="420" y="180"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow49" id="BPMNEdge_flow49">
        <omgdi:waypoint x="440" y="200"></omgdi:waypoint>
        <omgdi:waypoint x="440" y="325"></omgdi:waypoint>
        <omgdi:waypoint x="725" y="325"></omgdi:waypoint>
        <omgdi:waypoint x="997" y="325"></omgdi:waypoint>
        <omgdi:waypoint x="997" y="198"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow50" id="BPMNEdge_flow50">
        <omgdi:waypoint x="944" y="181"></omgdi:waypoint>
        <omgdi:waypoint x="980" y="180"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

jbarrez
Star Contributor
Star Contributor
I believe Frederik is right: the new task isn't yet known to the database, while the NeedActiveTaskCmd checks for that task ….

One option might be to make that logic aware of not yet persisted tasks which would solve the issue….

In the meantime you can always use your own command implementation, and copy the relevant bits from the command that now fails.

sangv
Champ in-the-making
Champ in-the-making
I spent some time looking at this but was unable to find a way to "wire" in my customized NeedsActiveTaskCmd. Can you point me to a forum post or documentation that can guide me?

My problem is that everything seems to be done through code. The TaskServiceImpl instantiates AddIdentityLinkCmd which delegates to NeedsActiveTaskCmd's execute method.


public class TaskServiceImpl extends ServiceImpl implements TaskService {

public void addCandidateGroup(String taskId, String groupId) {
    commandExecutor.execute(new AddIdentityLinkCmd(taskId, null, groupId, IdentityLinkType.CANDIDATE));
  }

}

public class AddIdentityLinkCmd extends NeedsActiveTaskCmd<Void>

Are you suggesting that I extend TaskServiceImpl and change the AddIdentityLinkCmd  (and NeedsActiveTaskCmd) that it uses in the addCandidateGroup method?

jbarrez
Star Contributor
Star Contributor
No, the easiest would be to cast your process engine to ProcessEngineImpl, and get the ProcessEngineConfigurationImpl from it.
From that, you can get the commandExecutorTxRequired (or something similar). That accepts a custom command implementation.