StackOverflowError when using a FlowableEventListener and an Inclusive Gateway

Hi,

I’m having some issues with Flowable and event listeners and although I think this is a bug in Flowable, it may be that I am doing something wrong. So I’m posting here instead of simply creating a github issue.

My usecase is that I want to send events that occur in the Flowable process to an external system for audit logging purposes.I do this using a FlowableEventListener to hook into specific events, and then sends a message to a Camel route (and onwards using JMS).
In particular I want to extract the business key from the process, as this is the ID used in the external system, but other process variables can also be sent.

The process that I have makes use of an inclusive gateway and when processing the event Flowable seems to get stuck in an infinite recursive loop, that eventually ends with a java.lang.StackOverflowError.I have a unit test that displays the problem, and I have described it below.

I have a simple workaround to avoid this problem by filtering out “inclusiveGateway” events from further processing, but I am concerned that other process contructions can have the same issue.

To reproduce the problem I use a simple process with an inclusive gateway:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
    <process id="inclusive-gateway-stackoverflow" name="inclusive-gateway-stackoverflow" isExecutable="true">
        <startEvent id="sid-AFF21E0A-B9AB-48DF-9250-90DF32B2C58A"></startEvent>
        <userTask id="sid-9D8F0713-5230-4B5D-B1DC-61B4AFE883F9" name="Review"></userTask>
        <endEvent id="sid-80808B51-F8EA-45BA-82BB-AFFD5C86C736"></endEvent>
        <inclusiveGateway id="sid-4ECA0909-3A04-4540-BFF9-C46EBF93FC8B" default="sid-727F54C5-AA5B-4578-BEBF-3E1BC273C24D"></inclusiveGateway>
        <sequenceFlow id="sid-0CE7A7B3-1F07-4B5E-9A28-21CCCEAC733B" sourceRef="sid-AFF21E0A-B9AB-48DF-9250-90DF32B2C58A" targetRef="sid-4ECA0909-3A04-4540-BFF9-C46EBF93FC8B"></sequenceFlow>
        <inclusiveGateway id="sid-54D9D201-B11D-48B1-B348-1329977E46AE"></inclusiveGateway>
        <sequenceFlow id="sid-A2414E84-854D-4573-B490-507BEA482D84" sourceRef="sid-9D8F0713-5230-4B5D-B1DC-61B4AFE883F9" targetRef="sid-54D9D201-B11D-48B1-B348-1329977E46AE"></sequenceFlow>
        <sequenceFlow id="sid-712AD927-16D4-4EC4-BEA9-538EDEFC96A7" sourceRef="sid-54D9D201-B11D-48B1-B348-1329977E46AE" targetRef="sid-80808B51-F8EA-45BA-82BB-AFFD5C86C736"></sequenceFlow>
        <sequenceFlow id="sid-727F54C5-AA5B-4578-BEBF-3E1BC273C24D" sourceRef="sid-4ECA0909-3A04-4540-BFF9-C46EBF93FC8B" targetRef="sid-54D9D201-B11D-48B1-B348-1329977E46AE"></sequenceFlow>
        <sequenceFlow id="sid-D804FF17-C59D-4C1C-88F1-1DAF3348159E" sourceRef="sid-4ECA0909-3A04-4540-BFF9-C46EBF93FC8B" targetRef="sid-9D8F0713-5230-4B5D-B1DC-61B4AFE883F9">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${review == true}]]></conditionExpression>
        </sequenceFlow>
    </process>
</definitions>

And have the following testcase:

package test;

import org.flowable.engine.RuntimeService;
import org.flowable.engine.common.api.delegate.event.FlowableEvent;
import org.flowable.engine.common.api.delegate.event.FlowableEventListener;
import org.flowable.engine.delegate.event.FlowableActivityEvent;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.test.Deployment;
import org.flowable.engine.test.FlowableRule;
import org.junit.Rule;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;

public class GetBusinessKeyInEventTest {

    @Rule
    public FlowableRule flowableRule = new FlowableRule();

    class GetBusinessKeyEventListener implements FlowableEventListener {

        @Override
        public void onEvent(FlowableEvent event) {
            if(FlowableActivityEvent.class.isAssignableFrom(event.getClass())) {
                String pid = ((FlowableActivityEvent) event).getProcessInstanceId();
                System.out.println("Processing Activity ID: " + ((FlowableActivityEvent) event).getActivityId());
                if(pid != null) {
                    ProcessInstance processInstance = flowableRule.getRuntimeService().createProcessInstanceQuery().processInstanceId(pid).singleResult();
                    if(processInstance != null) {
                        System.out.println("Business Key: " + processInstance.getBusinessKey());
                    }
                }
            }
        }

        @Override
        public boolean isFailOnException() {
            return true;
        }
    }


    @Test
    @Deployment
    public void inclusiveGatewayStackoverflow() {
        RuntimeService runtimeService = flowableRule.getRuntimeService();

        runtimeService.addEventListener(new GetBusinessKeyEventListener());

        Map<String, Object> variables = new HashMap<>();
        variables.put("review", true);

        ProcessInstance pi1 = runtimeService.startProcessInstanceByKey("inclusive-gateway-stackoverflow", "business-key-123", variables);
        assertEquals("business-key-123", runtimeService.createProcessInstanceQuery().processInstanceId(pi1.getProcessInstanceId()).singleResult().getBusinessKey());

    }

}

Running this testcase gives a stacktrace like below (I have cut out a lot of the repeated stacktrace).

12:08:19.814 [main] ERROR org.flowable.engine.common.impl.interceptor.AbstractCommandContext - Error while closing command context
org.flowable.engine.common.api.FlowableException: Exception while executing event-listener
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:107)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:89)
    at org.flowable.engine.delegate.event.impl.FlowableEventDispatcherImpl.dispatchEvent(FlowableEventDispatcherImpl.java:68)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleActivityEnd(TakeOutgoingSequenceFlowsOperation.java:111)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleFlowNode(TakeOutgoingSequenceFlowsOperation.java:90)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.run(TakeOutgoingSequenceFlowsOperation.java:83)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:75)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:59)
    at org.flowable.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:50)
    at org.flowable.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:51)
    at org.flowable.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:64)
    at org.flowable.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:36)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
    at org.flowable.engine.impl.AbstractQuery.singleResult(AbstractQuery.java:123)
    at test.GetBusinessKeyInEventTest$GetBusinessKeyEventListener.onEvent(GetBusinessKeyInEventTest.java:31)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:104)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:89)
    at org.flowable.engine.delegate.event.impl.FlowableEventDispatcherImpl.dispatchEvent(FlowableEventDispatcherImpl.java:68)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleActivityEnd(TakeOutgoingSequenceFlowsOperation.java:111)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleFlowNode(TakeOutgoingSequenceFlowsOperation.java:90)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.run(TakeOutgoingSequenceFlowsOperation.java:83)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:75)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:59)
    at org.flowable.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:50)
    at org.flowable.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:51)
    at org.flowable.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:64)
    at org.flowable.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:36)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
    at org.flowable.engine.impl.AbstractQuery.singleResult(AbstractQuery.java:123)
    at test.GetBusinessKeyInEventTest$GetBusinessKeyEventListener.onEvent(GetBusinessKeyInEventTest.java:31)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:104)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:89)
    at org.flowable.engine.delegate.event.impl.FlowableEventDispatcherImpl.dispatchEvent(FlowableEventDispatcherImpl.java:68)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleActivityEnd(TakeOutgoingSequenceFlowsOperation.java:111)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleFlowNode(TakeOutgoingSequenceFlowsOperation.java:90)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.run(TakeOutgoingSequenceFlowsOperation.java:83)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:75)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:59)
    at org.flowable.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:50)
    at org.flowable.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:51)
    at org.flowable.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:64)
    at org.flowable.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:36)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
    at org.flowable.engine.impl.AbstractQuery.singleResult(AbstractQuery.java:123)
    at test.GetBusinessKeyInEventTest$GetBusinessKeyEventListener.onEvent(GetBusinessKeyInEventTest.java:31)
...
...
Caused by: java.lang.StackOverflowError: null
    at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateIterable(ExpressionEvaluator.java:43)
    at org.apache.ibatis.scripting.xmltags.ForEachSqlNode.apply(ForEachSqlNode.java:55)
    at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:33)
    at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:41)
    at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:292)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:81)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at org.flowable.engine.impl.db.DbSqlSession.selectListWithRawParameter(DbSqlSession.java:313)
    at org.flowable.engine.impl.db.DbSqlSession.selectList(DbSqlSession.java:298)
    at org.flowable.engine.impl.persistence.entity.data.impl.MybatisExecutionDataManager.findProcessInstanceByQueryCriteria(MybatisExecutionDataManager.java:243)
    at org.flowable.engine.impl.persistence.entity.ExecutionEntityManagerImpl.findProcessInstanceByQueryCriteria(ExecutionEntityManagerImpl.java:117)
    at org.flowable.engine.impl.ProcessInstanceQueryImpl.executeList(ProcessInstanceQueryImpl.java:642)
    at org.flowable.engine.impl.AbstractQuery.executeSingleResult(AbstractQuery.java:179)
    at org.flowable.engine.impl.AbstractQuery.execute(AbstractQuery.java:160)
    at org.flowable.engine.impl.interceptor.CommandInvoker$1.run(CommandInvoker.java:39)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:80)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:59)
    at org.flowable.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:44)
    at org.flowable.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:51)
    at org.flowable.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:64)
    at org.flowable.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:36)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
    at org.flowable.engine.impl.AbstractQuery.singleResult(AbstractQuery.java:123)
    at test.GetBusinessKeyInEventTest$GetBusinessKeyEventListener.onEvent(GetBusinessKeyInEventTest.java:31)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:104)
    at org.flowable.engine.delegate.event.impl.FlowableEventSupport.dispatchEvent(FlowableEventSupport.java:89)
    at org.flowable.engine.delegate.event.impl.FlowableEventDispatcherImpl.dispatchEvent(FlowableEventDispatcherImpl.java:68)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleActivityEnd(TakeOutgoingSequenceFlowsOperation.java:111)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleFlowNode(TakeOutgoingSequenceFlowsOperation.java:90)
    at org.flowable.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.run(TakeOutgoingSequenceFlowsOperation.java:83)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:75)
    at org.flowable.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:59)
    at org.flowable.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:50)
    at org.flowable.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:51)
    at org.flowable.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:64)
    at org.flowable.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:36)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
    at org.flowable.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
    at org.flowable.engine.impl.AbstractQuery.singleResult(AbstractQuery.java:123)
    at test.GetBusinessKeyInEventTest$GetBusinessKeyEventListener.onEvent(GetBusinessKeyInEventTest.java:31)
...
...

Does this look like a bug? I can create a GitHub issue if it is.
Is my workaround of excluding inclusive gateways good enough? Or are there other BPMN components that can have a similar issue?

Thanks for any help!

Edit: Forgot to say that this is using Flowable 6.0.0.

Hi,

Thanks for the detailed explanation.
We’ve done a fix in the CommandInvoker that should solve the stackoverflow issue.

Note that this won’t return the process instance:

ProcessInstance processInstance = flowableRule.getRuntimeService().createProcessInstanceQuery().processInstanceId(pid).singleResult();

The process instance is not persisted at this point yet and the process instance query will try to fetch it from the database. You could go through the entity manager and the cache instead:

ProcessEngineConfigurationImpl processEngineConfiguration =
(ProcessEngineConfigurationImpl) flowableRule.getProcessEngine().getProcessEngineConfiguration();
ExecutionEntity processInstance = processEngineConfiguration.getExecutionEntityManager().findById(pid);

Best regards,

Tijs

Hi Tijs,

Thanks for fixing this so quickly and for the help.

I was wondering when the next version of Flowable would be available?
There are a few bug fixes that I am waiting for and I’d prefer not to work against a SNAPSHOT version.

Regards,
Paul

Hi Paul,

We want to release a new 6.0.1 shortly, probably next week or the week after.

Best regards,

Tijs