cancel
Showing results for 
Search instead for 
Did you mean: 

Behavior of Multi Instance Tasks/Subprocesses on emtpy collections

jakobtonn
Champ in-the-making
Champ in-the-making
In the application we are currently developing, we have the scenario of a multi-instance subprocesses which has a collection that might be empty. Our expectation was that if the collection was empty, Activiti would just skip the sub-process altogether and continue the execution after the sub-process, same as e.g. Java would execute a for-each-loop on an empty collection. Instead, we found out that Actviti 5.17.0-SNAPSHOT will throw an exception like this:


org.activiti.engine.ActivitiIllegalArgumentException: Invalid number of instances: must be positive integer value, but was 0
   at org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior.createInstances(ParallelMultiInstanceBehavior.java:41)
   at org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior.execute(MultiInstanceActivityBehavior.java:90)
   at org.activiti.engine.impl.pvm.runtime.AtomicOperationActivityExecute.execute(AtomicOperationActivityExecute.java:60)
   at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:96)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:621)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:616)
   at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(AtomicOperationTransitionNotifyListenerStart.java:52)
   at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:56)
   at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:96)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:621)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:616)
   at org.activiti.engine.impl.pvm.runtime.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:49)
   at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:96)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:621)
   at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:616)
        …

Older versions will throw an NPE instead at this point, so I guess this exception was introduced in 5.17.0-SNAPSHOT.

My question is, is there any reason why this exception is thrown instead of just skipping the multi instance activity (which would still fulfill the specification of creating an instance for each element of the collection) and continue the process after it? If not, I could prepare a pull request to change this behavior to our expectations.
8 REPLIES 8

jbarrez
Star Contributor
Star Contributor
Are you using current master version?

Cause, the code looks like it incorporates the '0' case: https://github.com/Activiti/Activiti/blob/master/modules/activiti-engine/src/main/java/org/activiti/...

Also, the exception  you get seems changed : https://github.com/Activiti/Activiti/blob/master/modules/activiti-engine/src/main/java/org/activiti/...

jakobtonn
Champ in-the-making
Champ in-the-making
From the code you posted, it seems like the code does now work the way I had expected, I will re-check my test case against the current master state, if it indeed does work, my question is answered.

schaumtier
Champ in-the-making
Champ in-the-making
There seems to be a difference between multi-instance subprocesses and multi-instance tasks. When using multi-instance tasks there is no problem with the current master version (at last the problem was in 5.16). But using multi-instance subprocesses with an empty collection results in a NullPointerException in MultiInstanceActivityBehavior.java in line 223.

Edit: The problem exists only if you use parallel multi-instance subprocesses. If you use sequential ones then it works as expected.

jbarrez
Star Contributor
Star Contributor
Hmmm i see. Could you create a simple example process that demonstrates this problem? Then I can jump in straight away at fixing the bug.

saig0
Champ in-the-making
Champ in-the-making
Hi,

here is the simple example process with a parallel multi instance subprocess.

[img]https://www.dropbox.com/s/wrel8dbq33v83xj/ProcessWithMultiInstance.jpg?dl=1[/img]
Process definition: https://www.dropbox.com/s/oy8l3v8x5jbg04z/ProcessWithMultiInstance.bpmn?dl=0

If you start the process instance with an empty collection, activiti throw a null-pointer execption.
<java>
java.lang.NullPointerException
at org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior.setLoopVariable(MultiInstanceActivityBehavior.java:223)
at org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior.leave(ParallelMultiInstanceBehavior.java:116)
at org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior.execute(MultiInstanceActivityBehavior.java:96)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationActivityExecute.execute(AtomicOperationActivityExecute.java:60)
at org.activiti.engine.impl.interceptor.CommandContext.performOperation(CommandContext.java:96)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:631)
at org.activiti.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:626)
at org.activiti.engine.impl.pvm.runtime.AtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(AtomicOperationTransitionNotifyListenerStart.java:52)
</java>

Test case:
<java>
    @Deployment(resources = "ProcessWithMultiInstance.bpmn")
    @Test
    public void multiInstanceWithEmptyCollection()
    {
        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("messages", Collections.EMPTY_LIST);
        activitiRule.getRuntimeService().startProcessInstanceByKey("myProcess", vars);

        assertThat(activitiRule.getHistoryService().createHistoricProcessInstanceQuery().count(), is(1L));
    }
</java>
Complete test case: https://www.dropbox.com/s/n83f17b0441521w/MultiInstanceTest.java?dl=0

Greetings.

jbarrez
Star Contributor
Star Contributor
You are correct. Fixed it : https://github.com/Activiti/Activiti/commit/053d8e7965de8e42758f196bbc098d46e0be0221

Thanks for the easy to use process definition and test. This really helped me speed up to resolve the problem!

gi00vani_kun
Champ in-the-making
Champ in-the-making
The important fix is provided by ParallelMultiInstanceBehavior.java

After applying the fix in activiti-engine-5.16.4.jar  I got the below error when executing my project


<code>
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (BB.ACT_FK_EXE_PROCINST) violated - parent key not found

### The error may involve org.activiti.engine.impl.persistence.entity.ExecutionEntity.insertExecution-Inline
### The error occurred while setting parameters
### SQL: insert into ACT_RU_EXECUTION (ID_, REV_, PROC_INST_ID_, BUSINESS_KEY_, PROC_DEF_ID_, ACT_ID_, IS_ACTIVE_, IS_CONCURRENT_, IS_SCOPE_,IS_EVENT_SCOPE_, PARENT_ID_, SUPER_EXEC_, SUSPENSION_STATE_, CACHED_ENT_STATE_, TENANT_ID_, NAME_)     values (       ?,       1,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?     )
### Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (BB.ACT_FK_EXE_PROCINST) violated - parent key not found

at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:150)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:137)
at org.activiti.engine.impl.db.DbSqlSession.flushInserts(DbSqlSession.java:758)
at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:590)
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.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:47)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:45)
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.startProcessInstanceById(RuntimeServiceImpl.java:101)
at org.activiti.explorer.ui.process.listener.StartProcessInstanceClickListener.buttonClick(StartProcessInstanceClickListener.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510)
… 33 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (BB.ACT_FK_EXE_PROCINST) violated - parent key not found

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:879)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1044)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1329)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3593)
at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3694)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1378)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:41)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:66)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:45)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:100)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:75)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:148)
… 52 more
</code>

gi00vani_kun
Champ in-the-making
Champ in-the-making
I've fixed this with a simpler solution:

<code>
<subProcess id="subprocess1" name="Sub Process">
           <multiInstanceLoopCharacteristics isSequential="false" activiti:collection="list2process">
                   <!– completionCondition>${nrOfCompletedInstances/nrOfInstances > 0.5}</completionCondition –>
           </multiInstanceLoopCharacteristics>
</code>

And Java hack :

<code>
if (f.size() > 1) {
   f.remove(0);
   execution.setVariable("list2process", f);
  }
</code>