cancel
Showing results for 
Search instead for 
Did you mean: 

Reassigning & Task Complete race problem

shiva_arunachal
Champ in-the-making
Champ in-the-making
i was doing some study on how to make task reassignment and completion fail proof. i tested explorer in below scenario and it failed.
i opened two browers. opened same user task on both browsers. Now did task "reassign" from one browser. and after that task "complete" from another browser. since i reassigned task before clicking on "complete" i expected some kind of error. but no error came. Now as per task service documentation,
void complete(String taskId); ‍
throws only two exceptions…:
 /*@throws ActivitiObjectNotFoundException when no task exists with the given id.   * @throws ActivitiException when this task is {@link DelegationState#PENDING} delegation.   */  void complete(String taskId);‍‍‍‍‍‍

  Now task exists with the given id so no exception was thrown.
My question 1: how can i prevent same situation from appearing in my own application? I have a supervisor who can reassign tasks huge number of tasks.
But if a user has already opened a task, and is working on it, i dont want supervisor to do a reassign on it. (OR) if a supervisor reassigns a task, the origin user should not be able to complete  the task.
question 2: How can i achieve this?
10 REPLIES 10

shiva_arunachal
Champ in-the-making
Champ in-the-making
Is there a kind of locking mechanism wherein when a user is working on a task, it will be locked or something like that? As far as i know there is no concept of statuses maintained on tasks. its either completed/uncompleted.

jbarrez
Star Contributor
Star Contributor
> Is there a kind of locking mechanism wherein when a user is working on a task,

No. When the task is assigned, it is supposed to be done by that user.

In your application, you should add a check before doing the  complete call. This is not done in Explorer, but could be added easily to your own application.

Thanks. As you suggested, i can add a check like below
<blockcode>
if ( taskService.createTaskQuery().taskAssignee(assignee).taskId(taskId).singleResult() != null )
{
      taskService.complete(taskId, taskVariables);
}
</blockcode>
But i will have to put it inside a synchronized block to prevent another thread (supervisor working on another browser) from reassigning after the execution of the "if" statement.

so i will have to change it as
<blockcode>
synchronized(this){
    if ( taskService.createTaskQuery().taskAssignee(assignee).taskId(taskId).singleResult() != null )
    {
          taskService.complete(taskId, taskVariables);
    }
}
</blockcode>

Similarly i will have to put all the code below run by a supervisor in synchronized block.
<blockcode>
//delink all tasks linked with this queue id.
   List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(queueId)
     .includeTaskLocalVariables().includeProcessVariables()
     .orderByTaskId().desc().list();
   logger.info("total tasks in "+queueId+"is:"+tasks.size());
   for(Task task : tasks){
     // Update assignee
          String originAssignee = task.getAssignee();
          logger.info("originAssignee:"+originAssignee+":created time:"+task.getCreateTime());
          taskService.deleteCandidateGroup(task.getId(), queueId);
                        // reassign to another user…
          task.setAssignee("kermit");
   }
</blockcode>  

Using synchronized on these statements, Will this affect the performance ? ?

jbarrez
Star Contributor
Star Contributor
You should not have to use synchronized. This won't work on a multi node cluster anyway.

Activiti is designed to cope with this: the taskquery goes to the database and will return null if the task is already fetched by someone else. So you need nothing else and don't need to worry about performance.

shiva_arunachal
Champ in-the-making
Champ in-the-making
I understand the <code>"taskquery goes to the database and will return null if the task is already fetched by someone else."</code>
The scenario where i feel it will fail is below:
User thread:
=======
<code>
(1)if ( taskService.createTaskQuery().taskAssignee(assignee).taskId(taskId).singleResult() != null )
    {
(2)          taskService.complete(taskId, taskVariables);
    }
</code>

Supervisor thread:
===========
<code>
//reassign user
(3)   TaskService().setAssignee(task.getId(), selectedUser));
</code>
if after execution of (1), (3) executes (they are running indifferent threads,) then (2) executes, then there will be problem.
if sequence of execution is (1),(3),(2) then there will be problem. Hence i have to use synchronised.
Am i wrong in saying to?
Please suggest.
Thanks.

jbarrez
Star Contributor
Star Contributor
No, because the setAssignee won't work if the task is already completed, as they are two different database transactions.

Hi Joram,
The javadoc for setAssignee only states the below from which i cannot infer what you state. Seems i have to dive deep into code..
"Changes the assignee of the given task to the given userId. No check is done whether the user is known by the identity component.
Parameters:
taskId id of the task, cannot be null.
userId id of the user to use as assignee.
Throws:
ActivitiObjectNotFoundException - when the task or user doesn't exist".

From this , i inferred that exception will be thrown only when task/user doesn't exist.

jbarrez
Star Contributor
Star Contributor
Yes, but you are forgetting all the API methods of the services are 1 transaction. So setAssignee is done in its own transaction. As does the complete method. So one of the two will 'win'.

shiva_arunachal
Champ in-the-making
Champ in-the-making
I am talking about the  sequence of execution is (1),(3),(2) . In that case, after <code>setAssignee //(reassignment by supervisor to another user)</code>, <code>complete </code>will be called (by original user) and will execute successfully which should not be the case.. Hope you understand my point and scenario well.
That's why i suggested using <code>synchronized</code>