AnsweredAssumed Answered

Out of ideas for ActivitOptimisticLockingException in Workflow

Question asked by philippmatthiasschäfer on Sep 25, 2015
Latest reply on Sep 25, 2015 by trademak
We have a problem with a Workflow that we are running.

[h1]The Workflow[/h1]

[img]http://i.imgur.com/d6lEu4d.png?1[/img]

[h1]The Environment[/h1]

Activiti 5.15.1 against Oracle 11

[h1]The Exceptions[/h1]

For that Workflow we occasionally get ActivitOptimisticLockingExceptions, though we cannot reproduce them. We are fairly sure, that there is only one job executor running (there are only database sessions from one host, there is only one process on that host, and we corrected the problem that we started a second executor as with the explorer).


Fehler beim Starten des Workflowprozesses WiedervorlageNachrichtAnVIMorg.activiti.engine.ActivitiOptimisticLockingException: ByteArrayEntity[id=165438, name=var-ValueMap, size=5534] was updated by another transaction concurrently
Wrapped by: com.godyo.p5.resources.exceptions.P4ServerException: [org.activiti.engine.ActivitiOptimisticLockingException] ByteArrayEntity[id=165438, name=var-ValueMap, size=5534] was updated by another transaction concurrently

                at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:620) ~[na:na]
                at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:502) ~[na:na]
                at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:175) ~[na:na]
                at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:122) ~[na:na]
                at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66) ~[na:na]
                at org.activiti.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:47) ~[na:na]
                at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130) ~[na:na]
                at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:45) ~[na:na]
                at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:31) ~[na:na]
                at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:40) ~[na:na]
                at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:35) ~[na:na]
                at org.activiti.engine.impl.RuntimeServiceImpl.startProcessInstanceByKey(RuntimeServiceImpl.java:70) ~[na:na]
                at com.godyo.p5.server.workflow.ActivitiWorkflowService.createProcessInstance(ActivitiWorkflowService.java:60) ~[na:na]
                at com.godyo.p5.server.workflow.starter.WorkflowStartJob.run(WorkflowStartJob.java:47) ~[na:na]
                at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54) [org.eclipse.core.jobs_3.5.200.v20120521-2346.jar:na]

               
They number in the tens per day, while we have a couple hundred workflows of that type running, i.e.


select count(*) from act_ru_execution where proc_def_id_ like 'Wie%' and is_active_ = 1;

yields about 1800 currently.

The affected variables are ValueMap, which is given to the workflow at the start and from then on only ever read from. The other is resAngKopf which is filled by 'Einfaches Select', implemented like:


public class SimpleSelectService implements JavaDelegate {
                private Expression resultName;

                @Override
                public void execute(final DelegateExecution execution) throws Exception {
                        // do database stuff and fill Map<String, Object> map
                               execution.setVariable(resultName.getValue(execution).toString(), map);
                }
}

[h1] Question [/h1]

What did we miss? There are no joins, no multiple end nodes, no multi instance shenanigans, we have only one executor. We are out of ideas (except for optimizing the workflow, but that will not solve the underlying technical problem for others and might not even solve ours). If any more information is needed to answer the question, we'll be glad to provide it.

[h1] Further Notes on the Workflow[/h1]

The timer has a period of P1D.

"Neuster Prozess Tester" is a custom task implemented as listed below. We use it determine, whether a given workflow is the newest for a given set of parameters. Note, that we access the variables of other process instances ("ValueMap"), however we don't write to them.



import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.delegate.JavaDelegate;
import org.activiti.engine.runtime.ProcessInstance;

public class NewestProcessService implements JavaDelegate {

                private static class ProcessFilter {

                               private final DelegateExecution execution;
                               private final List<String> keys;

                               public ProcessFilter(final DelegateExecution execution, final List<String> keys) {
                                               this.execution = execution;
                                               this.keys = keys;
                               }

                               public boolean isNewestProcess() {
                                               return filterByStartTime(filterByPrimaryKey(getSiblingProcesses())).isEmpty();
                               }

                               private List<ProcessInstance> filterByStartTime(final List<ProcessInstance> processes) {
                                               final Date ownStartTime = (Date) execution.getVariable("StartTime");
                                               final List<ProcessInstance> filteredProcesses = new ArrayList<>();
                                               for (final ProcessInstance process : processes) {
                                                               final Date startTime = (Date) execution.getEngineServices().getRuntimeService()
                                                                                              .getVariables(process.getProcessInstanceId()).get("StartTime");
                                                               if (ownStartTime.getTime() < startTime.getTime()) {
                                                                              filteredProcesses.add(process);
                                                               }
                                               }
                                               return filteredProcesses;
                               }

                               @SuppressWarnings("unchecked")
                               private List<ProcessInstance> filterByPrimaryKey(final List<ProcessInstance> processes) {
                                               final Map<String, Object> ownValueMap = (Map<String, Object>) execution.getVariable("ValueMap");
                                               final List<ProcessInstance> filteredProcesses = new ArrayList<>();
                                               for (final ProcessInstance process : processes) {
                                                               final Map<String, Object> valueMap = (Map<String, Object>) execution.getEngineServices()
                                                                                              .getRuntimeService().getVariables(process.getProcessInstanceId()).get("ValueMap");
                                                               boolean same = true;
                                                               for (final String key : keys) {
                                                                              if (!valueMap.get(key).equals(ownValueMap.get(key))) {
                                                                                              same = false;
                                                                                              break;
                                                                              }
                                                               }
                                                               if (same) {
                                                                              filteredProcesses.add(process);
                                                               }
                                               }
                                               return filteredProcesses;
                               }

                               private List<ProcessInstance> getSiblingProcesses() {
                                               final String ownDefinitionId = execution.getProcessDefinitionId();
                                               return execution.getEngineServices().getRuntimeService().createProcessInstanceQuery()
                                                                              .processDefinitionId(ownDefinitionId).list();

                               }

                }

                private Expression keys;
                private Expression targetVariable;

                @Override
                public void execute(final DelegateExecution execution) throws Exception {

                               final List<String> keyList = Arrays.asList(keys.getValue(execution).toString().split(","));

                               final boolean isNewest = new ProcessFilter(execution, keyList).isNewestProcess();

                               final String variable = targetVariable.getValue(execution).toString();
                               execution.setVariable(variable, isNewest ? "yes" : "no");

                }
}

Outcomes