Você está na página 1de 5

8/31/2017 How to handle or avoid an optimistic locking exception - Flowable Engine - Flowable

How to handle or avoid an


optimistic locking exception
Flowable Engine

schmke Mar 17
I have an interesting scenario using 5.21 (moving to 5.22 and perhaps Flowable 6 soon) where I'm getting an
ActivitiOptimisticLockingException. I'd like to figure out the best way to handle it or avoid it entirely.

I have a process that has a user task that on completion has Java service task that calls an API. The process then continues on to an
end step. Pretty simple.

However, as a result of the API call, several other instances of the same process may be invalidated and I want to remove them. This
gets triggered by the server the API call is made to sending out a JMS message, and the processing of the message looking up the
related process instances by a specific variable/value. Presently, the process instance being completed shows up as a related process
too (it has the same variable/value as the others).
Mar 17
This message and processing is all asynchronous to the process instance completion and happens quickly enough that the message
1/5
processing tries to delete the process instance that the user completed the task for before the engine has fully finished the completion,
and I get an ActivitiOptimisticLockingException, usually on the thread completing the task. Specifically: Mar 18

org.activiti.engine.ActivitiOptimisticLockingException: HistoricVariableInstanceEntity[id=2647, name=euid2, revision=0, type=string,


textValue=1000002002] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:880)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:619)
at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:212)
at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:138)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)
at org.activiti.engine.impl.interceptor.JtaTransactionInterceptor.execute(JtaTransactionInterceptor.java:65)
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.FormServiceImpl.submitTaskFormData(FormServiceImpl.java:70)

Now, this occurs late enough in the task completion that the task itself is actually completed, so I suspect it is just historical variables
that may not be fully populated correctly, but I'd still like to avoid it happening.
Mar 22
So, is there any way to avoid a simultaneous process instance deletion from affecting the processing of the same process instance
being completed? Does 5.22 have anything to help with this? Will version 6 change this behavior at all? Or do I need to build
something into my process to detect and avoid this? Or are there any engine settings that would alter this behavior?

One thought I had was in the message processing doing the delete, to look for any tasks for the process instance and if there are
none, that would indicate the process instance is the one we want to let complete. The others we want to delete would still be at the
user task.

Or do I need to set a process variable before the API call in the process that identifies that process instance as the one to not delete
and use it when looking up the related process instances?

Thanks for any insight or suggestions.

created last reply 4 486 2 3


Mar 17 Mar 22 replies views users

https://forum.flowable.org/t/how-to-handle-or-avoid-an-optimistic-locking-exception/410 1/5
8/31/2017 How to handle or avoid an optimistic locking exception - Flowable Engine - Flowable

tijs Mar 20
Hi,

When there are multiple threads working with the same process instance, there is the chance of optimistic lock exceptions, but this
means that the first one "wins", and the second transaction is just rolled back. So in this case you get an exception, but there's
probably no need for retry.

If you would like to prevent the optimistic lock exception from happening you could have a look at making the async continuations
exclusive and see if that helps in your case.

A process diagram of the process definition you are getting these errors on would help to get more insight in what you are trying to do
and what possible solutions are available.

Best regards,

Tijs

schmke Mar 21
Thanks for the reply.

Can you elaborate on making the continuations exclusive? What is that or how would I do that?

https://forum.flowable.org/t/how-to-handle-or-avoid-an-optimistic-locking-exception/410 2/5
8/31/2017 How to handle or avoid an optimistic locking exception - Flowable Engine - Flowable
And yes, I see the behavior you are describing that one or the other "wins". If I could reliably have the delete be the one that fails, that
would actually be ok.

When doing the delete, if there was a way for the engine to tell me the instance was "in process" that would be useful too, but I don't
see a way to do that. I actually tried having the delete lookup the tasks for the instance first to see if there were any, hoping that there
being none would be an indication that the instance was "in process", but it appears at least in some cases the task being completed
has not been committed yet so that isn't a reliable indication.

I could also design into my process setting a variable on the instance being completed that could be used as a filter, but I'm afraid that
would suffer from the same issue, it may not be committed yet so the delete thread would not see it to use as a filter.

My process is pretty simple for the path taken. There is a user task where a decision is made to perform a merge or not. If the user
elects to do a merge, the path taken has a Java service task that calls an API to perform the merge and then goes to an end event. So
there isn't a lot of time between the API being called (which triggers the notification which when received results in the delete logic)
and the engine being finished completing everything required for the process instance, but sometimes there is enough.

Sometimes the failure is on the delete with an exception like this:

org.activiti.engine.ActivitiOptimisticLockingException: VariableInstanceEntity[id=22625, name=_summary, type=string, textValue=EUID


1000103004: Kevin, Schmidt, 09/2...] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession$CheckedDeleteOperation.execute(DbSqlSession.java:295)
at org.activiti.engine.impl.db.DbSqlSession.flushRegularDeletes(DbSqlSession.java:897)
at org.activiti.engine.impl.db.DbSqlSession.flushDeletes(DbSqlSession.java:890)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:617)
at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:212)
at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:138)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)
at org.activiti.engine.impl.interceptor.JtaTransactionInterceptor.execute(JtaTransactionInterceptor.java:65)
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.RuntimeServiceImpl.deleteProcessInstance(RuntimeServiceImpl.java:122)

Other times the failure is back to the client that completed the task, the exception in the log being this:

org.activiti.engine.ActivitiOptimisticLockingException: HistoricVariableInstanceEntity[id=2647, name=euid2, revision=0, type=string,


textValue=1000002002] was updated by another transaction concurrently
at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:880)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:619)
at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:212)
at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:138)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)
at org.activiti.engine.impl.interceptor.JtaTransactionInterceptor.execute(JtaTransactionInterceptor.java:65)
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.FormServiceImpl.submitTaskFormData(FormServiceImpl.java:70)

https://forum.flowable.org/t/how-to-handle-or-avoid-an-optimistic-locking-exception/410 3/5
8/31/2017 How to handle or avoid an optimistic locking exception - Flowable Engine - Flowable

tijs Mar 22
Hi,

I still don't understand the process definition completely. Could you share a process diagram of the process definition?

From what I understand from the description, making the async continuations exclusive won't make a difference, and they are
exclusive by default, so probably this is already the case.

For these kind of issues to think of a solution it's really important to understand the whole process definition flow, and a process
diagram helps a lot with that.

Best regards,

Tijs

https://forum.flowable.org/t/how-to-handle-or-avoid-an-optimistic-locking-exception/410 4/5
8/31/2017 How to handle or avoid an optimistic locking exception - Flowable Engine - Flowable

schmke Mar 22
Here is the relevant part of the diagram.

A user is making a decision in the user task, if they elect to merge two records together the path shown is taken. The API that is called
from the Java service task is what triggers the notification that in turn when processed is making the call to delete the process
instance.

You are right that making the Java service task exclusive is the default and so that doesn't change anything. What I did try that
seemed to change behavior was making the Java service task async. By doing this, the user task transaction is completed right away
and the API call from the Java service task is done in a separate transaction. This means when the code doing the delete is executed,
it can check to see if the process instances it finds have a user task (the one we don't want to delete and let finish won't) and use that
as the filter. I could also add setting a process variable to the process definition before the Java service task to use to filter on too.

So that is the approach I'm taking for now.

https://forum.flowable.org/t/how-to-handle-or-avoid-an-optimistic-locking-exception/410 5/5

Você também pode gostar