cancel
Showing results for 
Search instead for 
Did you mean: 

How can I prevent a process execution is run multiple times?

mamue
Champ in-the-making
Champ in-the-making
Hi,

we provide a list of open tasks to our users.
The same user can display this list in different web sessions.
He or she may proceed the same task from different sessions at nearly the same time.
This results in multiple calls to taskService.complete(taskId) with the same taskId.
I expect that the second and all further calls of taskService.complete(taskd) fail, but they do not - at least if the first call is still be executed.

How can I prevent multiple executions of the same task?

Kind regards,
Markus Mueller
24 REPLIES 24

frederikherema1
Star Contributor
Star Contributor
An org.activiti.engine.ActivitiOptimisticLockingException is thorn when the threads actually call the same method simultaneously. Otherwise (if first transaction is committed before second one does the call), an ActivitiException will be thrown (task not found).

The logic using the engine should be prepared for this, and potentially retry or check if operation that is performed is still valid (eg. check task exists).

mamue
Champ in-the-making
Champ in-the-making
Hi Frederik,

thank you very much for your reply.

I've now built a small test demo:
A process consisting of a user task followed by a service task which contains a delay of one second.
If I run this test slowly in debugger, there is the exception "task not found" - exactly as you have predicted.

But if I run the test without any breakpoints set, then there is no exception - both calls of taskService.complete() succeed: The single service task is executed twice and in parallel!

You find the source code in attached zip file.
You can copy the files directly in the Activiti 5.9 demo workspace "activiti-engine-examples".

Kind regards,
MaMü

frederikherema1
Star Contributor
Star Contributor
Doesn't seem to fail here when running… Are you sure you get no failture when just running it?

mamue
Champ in-the-making
Champ in-the-making
Hi Frederik,

please check the console log for stack dumps to find the cause of the test failure - or set a breakpoint on ae.printStackTrace(); in method CompleterThread.run().

I have run the test on my box just now in debugger (without any breaks set) and outside debugger (ie. with run command of eclipse):
Always I see the green bar of junit and no stack dumps in console view.

Kind regards,
MaMue

mamue
Champ in-the-making
Champ in-the-making
Hi Frederik,

currently the test is green if activiti behaves not as expected.
This is a little bit twisted.

Therefore I have changed the second check of the succeeded flag:


    //assertTrue("Second complete() failed.", thread2.isSucceeded());
    assertFalse("Second complete() didn't fail as expected. ", thread2.isSucceeded());

Now the test fails as long as activiti does not guarantee that a task may be completed only once.
I think this would be more clear to a casual reader of the test.

Kind regards,
MaMü

frederikherema1
Star Contributor
Star Contributor
Hi,

Discovered what is going on. The optimistic-locking mechanism that is in place, is not triggered in the process you attached. After completing the task, the execution is not persisted anymore, hence the "revision" of the execution isn't altered. This causes the 2 task.completes() to actually be successful simultaneously. When adding an additional user-task (or other wait-state like receive-taks) after the userTaks in your process, the second thread actually fails:


org.activiti.engine.ActivitiOptimisticLockingException: ExecutionEntity[5] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:476)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:372)

Good catch, I'll make sure this gets implemented in 5.10 release, you can track this here: https://jira.codehaus.org/browse/ACT-1259. So this only occurs if the last task of a process is completed concurrently. As current workaround, you should add some kind of synchronized mutex/lock based on process/task-id in the logic that actually calls the Activiti API to be absolutely sure. This wouldn't cause a big overhead I guess unless you have high concurrency

mamue
Champ in-the-making
Champ in-the-making
Hi Frederik,

thank you very much for analyzing the problem and creating issue ACT-1259.

BTW, I think I have found the problem shown in http://forums.activiti.org/en/download/file.php?id=634 above:
The second thread may start accidentally before the first thread.
To avoid this race condition I have added a small sleep between the starting of thread1 and thread2.

There is still another problem - but an unresolved one:

The service task in my example represents (or is accompanied by) a physical production process of a new ID card.
a) This production process may last a few tens of seconds.
b) The production process is not "transactional", i.e. if the ID card is produced, it exists and can't be "rolled back".

Both aspects are the cause of problems:
a) The user who comes in second has to wait the few tens seconds before he gets the failure.
b) The additional ID card (that can't be used because it is a duplicate and because the corresponding database record can't be saved successfully) causes waste and charges.

The bottom line is: We need a chance to check the situation quickly and before executing the pending service task and this check must be done transactionally.

Is there a chance that Activiti will provide this feature?

Attached you find a jUnit test which demonstrates the problem.
The TaskFailureDelay test consists of a user task, followed by the service task which needs ten seconds, followed by the second user task.
The test fails because the second complete call needs too much time before it fails.

Kind regards,
MaMü

frederikherema1
Star Contributor
Star Contributor
As I suggested, add some kind of mutex-mechanism in the layer above the Activiti-layer. Example:


public  class YourCompanyService {


  private Set<String> processLocks = new HashSet<…>;


public boolean printTheCard(String cardIdentifier) {
   // Correlate between card-id and task/process to use
   String processId = runtimeService.createtaskQuery()……singleResult();

   // JVM-wide lock for this specific use-case.
   boolean isSafe = false;
   synchronized(this) {
        isSafe = processLocks.contains(processId);
        if(isSafe) {
           processLocks.add(processId);
        }
   }

   if(!isSafe) {
        throw new MyCompanyException("looks like you're too late…");
   }

   // important, release the lock in "finally"
   try {
        // Finish task
        taskService……
        ….
    }
    finally
    {
       synchronized(this) {
             processLocks.remove(processId);
       }

    }
}

}

This is just off the top of my head, doesn't actually compile Smiley Wink To make it more "generic" (if you have more of these CRITICAL stuff that needs exception right away before actually performing the operation and getting optimisticLockEx) you can build some kind of callback mechanism (like springs doInTransactions, but then doWhilLockingProcess(processId).

mamue
Champ in-the-making
Champ in-the-making
Hi Frederik,

> … add some kind of mutex-mechanism …

ok, we can implement this.
It must be a database based mutex, not a Java based mutex, because there may be several parallel machines/JVMs (because of load balancing etc).

> … you can build some kind of callback mechanism … spring …

Cool idea, yes this would be nice!


This database based mutex is needed always if there are time-consuming tasks or tasks with non-transactional workload.
I think this is a general requirement which may be needed by many Activiti users, isn't it?
Is there a chance that Activiti will provide this feature some day?

In Activiti this feature may be perhaps simply achieved by an additional flush before the delegate is called, isn't it?

Kind regards,
MaMü