cancel
Showing results for 
Search instead for 
Did you mean: 

Multi-instance Task and ActivitiOptimisticLockingException

razu
Champ in-the-making
Champ in-the-making
Hi,

I experience ActivitiOptimisticLockingException when 2 threads want to complete different instances of tasks from multi-instance User Task in the same time. Is looks like a bug or bad design for me. Please tell me which object is locked when completing instance of multi-instance User Task?


org.activiti.engine.ActivitiOptimisticLockingException: ByteArrayEntity[244] was updated by another transaction concurrently
   at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:435) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:348) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:147) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:103) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:49) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42) ~[activiti-spring-5.8.jar:na]
   at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130) ~[org.springframework.transaction-3.0.5.RELEASE.jar:3.0.5.RELEASE]
   at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40) ~[activiti-spring-5.8.jar:na]
   at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33) ~[activiti-engine-5.8.jar:5.8]
   at org.activiti.engine.impl.RuntimeServiceImpl.setVariableLocal(RuntimeServiceImpl.java:119) ~[activiti-engine-5.8.jar:5.8]
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_30]
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_30]
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_30]
   at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_30]
   at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:229) ~[groovy-all-1.7.5.jar:1.7.5]
   at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:52) ~[groovy-all-1.7.5.jar:1.7.5]
   at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:132) ~[groovy-all-1.7.5.jar:1.7.5]
   at com.example.MyService.completeTask(MyService.groovy:262) ~[MyService.class:na]

8 REPLIES 8

plehal
Champ in-the-making
Champ in-the-making
When is this issue going to be fixed? I am facing the same issue too.

jbarrez
Star Contributor
Star Contributor
I would not call this 'bad design'. If you get an optimistic locking exception, it means that probably the same execution entity was changed during both transactions.

However, if you can demonstrate with a simple unit test example, I can look into it. Maybe we were overzealous about performance and are we trying to reuse to much the same execution to avoid extra database inserts.

plehal
Champ in-the-making
Champ in-the-making
In my case it occurs occasionally at the time of creation of multi-instance user task with multiple listeners (on create).  It may not be possible to reproduce it 100% of times with a unit test or otherwise.

xman-berlin
Champ in-the-making
Champ in-the-making
I've got the same Exception but when I try to complete a task which is the last one of a multi instance subprocess. I read in the Activiti documentation that there are some variables stored in the process instance scope, e.g. nrOfInstances, nrOfActiveInstances and nrOfCompletedInstances which are probably causing the issue (but I'm not sure). Here's a stack trace:
<code>
Caused by: org.activiti.engine.ActivitiOptimisticLockingException: ScopeExecution[65] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:872)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:611)
at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:211)
at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:137)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:31)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:40)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:35)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:183)
</code>
Somtimes there is another cause in the stacktrace:
<code>
Caused by: org.activiti.engine.ActivitiOptimisticLockingException: VariableInstanceEntity[id=58, name=nrOfActiveInstances, type=integer, longValue=2, textValue=2] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:872)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:611)
at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:211)
at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:137)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:31)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:40)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:35)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:183)
</code>

The issues start when there are more than 5 instances running in parallel. Bit this might depend on your hardware.

I wrote a unit test which runs into the same issue.
<java>
        …
        List<Task> tasks = taskService.createTaskQuery().list();
        class Testclass implements Runnable {
            private Task task;
            private Waiter waiter;
            public Testclass(Task task, Waiter waiter) {
                this.task = task;
                this.waiter = waiter;
            }
            @Override
            public void run() {
                LOG.debug("+++ Start thread task " + task);
                HibernateUtil.beginTransaction();
                try {
                    Map<String, Object> variables = new HashMap<String, Object>();
                    variables.put("action", "approve");
                    Thread.sleep(ThreadLocalRandom.current().nextLong(200));
                    task = taskService.createTaskQuery().taskId(this.task.getId()).singleResult();
                    taskService.complete(this.task.getId(), variables, true);
                    HibernateUtil.commitTransaction();
                    waiter.assertTrue(true);
                } catch (Exception e) {
                    HibernateUtil.rollbackTransaction();
                    waiter.fail(e);
                } finally {
                    waiter.resume();
                    LOG.debug("— Finish thread task " + task);
                }
            }
        };
        Waiter waiter = new Waiter();
        for (Task task : tasks) {
            Thread t = new Thread(new Testclass(task, waiter));
            t.start();
        }
        waiter.await(0, tasks.size());
</java>

Any idea how to solve this issue?

Thanks,
Torsten

jbarrez
Star Contributor
Star Contributor
@xman-berlin : can you create that unit test together with a process definition so we can test it?

xman-berlin
Champ in-the-making
Champ in-the-making
Hi Joram,
you can download a sample Maven project here: http://expirebox.com/download/8642c38439dfaaeee4cab0c6d9ea912e.html
Just download, unzip and perform "mvn clean test" which will fail.
I hope this helps,
Best regards,
Torsten

jbarrez
Star Contributor
Star Contributor
ok, ran your code. Took me a couple of minutes to figure out what the Waiter does.
So you are concurrently completing tasks with multiple threads … if so an OptimisticLockingException is expected. Activiti doesn't lock anything do solve this (as this would impact horizontal scaling).

You could try to add the RetryInterceptor to your config: when such an exception happens, it will automatically retry again.

xman-berlin
Champ in-the-making
Champ in-the-making
Hi Joram,
that's exactly what we did (retry completion) to work around this issue. Nice to hear that activiti already has a solution (RetryInterceptor). But I couldn't find any documentation, sample, whatsoever, where the configuration is explained. Could you give me a hint?
Thanks,
Torsten