OptimisticLockingException in multithreaded process execution

Hello everyone!

I have a problem with multithreaded process execution.
When one process is running then all works correct. But when I start multiple processes (ex. 1 per each 200 milliseconds or 1 per each 1 second - interval isn’t matter) then in logs I see an exception like this:

org.camunda.bpm.engine.OptimisticLockingException: ENGINE-03005 Execution of ‘UPDATE ExecutionEntity[553]’ failed. Entity was updated by another transaction concurrently.
at org.camunda.bpm.engine.impl.db.EnginePersistenceLogger.concurrentUpdateDbEntityException(EnginePersistenceLogger.java:125)
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.handleOptimisticLockingException(DbEntityManager.java:328)
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushDbOperationManager(DbEntityManager.java:300)
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flush(DbEntityManager.java:283)
at org.camunda.bpm.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:321)
at org.camunda.bpm.engine.impl.interceptor.CommandContext.close(CommandContext.java:249)
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:113)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40)
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:66)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:30)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.executeJob(ExecuteJobsRunnable.java:79)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.run(ExecuteJobsRunnable.java:57)
at org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceRunnable.run(SpanContinuingTraceRunnable.java:52)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

My environment

  1. Database Postgresql 9.6
  2. Spring-Boot: (v1.5.6.RELEASE)
  3. Camunda BPM: (v7.5.0)
  4. Camunda BPM Spring Boot Starter: (v1.3.0)

There are simplified description of a bpmn processes that i used
DataSourceModule.bpmn (2.6 KB) TestProcess.bpmn (4.8 KB).

  1. Process starts from call in java code

processEngine.getRuntimeService().startProcessInstanceByKey(“TestProcess”, parameters)
Process starts with some variables. that not matter here.

  1. When “TestProcess” process has fired, execution checks some information via call diagram “DataSourceModule”.

  2. Diagram “DataSourceModule” calls java class DataSourceModule that implements JavaDelegate class. After that execeution stopping until message “moduleResponse” will be recevied.

  3. Message “moduleResponse” sends later (usually after 1-5 seconds later) with saving process variables via next code:

 RuntimeService runtimeService = processEngine.getRuntimeService();
 processEngine.getRuntimeService().setVariables(processInstanceId, variables);
 ProcessInstance pi = runtimeService
           .createProcessInstanceQuery()
           .processInstanceId(processInstanceId)
           .singleResult();

 EventSubscription subscription = runtimeService
           .createEventSubscriptionQuery()
           .processInstanceId(pi.getId())
           .eventType(MESSAGE_EVENT_TYPE)
           .singleResult();
if (subscription != null) { 
    runtimeService.messageEventReceived(subscription.getEventName(), subscription.getExecutionId());
}

OptimisticLockingException always occurs in line where I save process variables or where I send event.

There is class that configure camunda for spring

@Slf4j
@Configuration
@RequiredArgsConstructor
public class CamundaConfiguration implements ProcessEnginePlugin {

private final ApplicationContext applicationContext;
private final CamundaDatasourceConfiguration camundaDatasourceConfiguration;

`@Override`
public void preInit(ProcessEngineConfigurationImpl config) {
    config.setJobExecutorActivate(true);
    config.setDefaultSerializationFormat("application/json");

    config.getJobExecutor().setWaitTimeInMillis(500);
    config.getJobExecutor().setMaxWait(1000);

    initArtifactFactory(config);
    config.setDataSource(camundaDatasourceConfiguration.secondaryDataSource());
    if (StringUtils.isNotEmpty(camundaDatasourceConfiguration.getSchema())) {
        config.setDatabaseSchema(camundaDatasourceConfiguration.getSchema());
        config.setDatabaseTablePrefix(String.format("%s.", camundaDatasourceConfiguration.getSchema()));
    }
    config.setDbHistoryUsed(false);
    if (config.getHistoryLevels() == null) {
        config.initHistoryLevel();
    }
    config.setHistoryLevel(HistoryLevel.HISTORY_LEVEL_NONE);
    log.debug("Camunda configuration: [{}]", config);
}

private void initArtifactFactory(ProcessEngineConfigurationImpl config) {
    config.setArtifactFactory(new ArtifactFactory() {
        final ArtifactFactory defaultArtifactFactory = new DefaultArtifactFactory();

        `@Override`
        public <T> T getArtifact(Class<T> clazz) {
            T instance;
            try {
                instance = applicationContext.getBean(clazz);
            } catch (NoSuchBeanDefinitionException ex) {
                // fall back to using newInstance()
                instance = defaultArtifactFactory.getArtifact(clazz);
            }
            return instance;
        }
    });
}

`@Bean`
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(7);
    executor.setMaxPoolSize(42);
    executor.setQueueCapacity(11);
    executor.setThreadNamePrefix("cmd-executor-");
    executor.initialize();
    return executor;

}

`@Override`
public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
    log.trace("Call postInit CamundaConfiguration");
}

`@Override`
public void postProcessEngineBuild(ProcessEngine processEngine) {
    log.trace("Call postProcessEngineBuild CamundaConfiguration");
}

}

Can someone help me with this problem?

Missed diagram BaseDataSourceProcess.bpmn (5.6 KB) that uses in process.