Task completion not deleting identity links causing FK violation

Hi

After I upgraded from 6.3.0 to 6.4.0 I get an exception when calling TaskService.completeTaskWithForm.

Caused by: org.postgresql.util.PSQLException: ERROR: update or delete on table “act_ru_execution” violates foreign key constraint “act_fk_idl_procinst” on table “act_ru_identitylink”
Detail: Key (id_)=(cb33cd3e-1682-11e9-bd99-c67a55d1f07b) is still referenced from table “act_ru_identitylink”.

The database has the following:

flowable=> select id_, proc_inst_id_, start_user_id_, tenant_id_ from act_ru_execution where proc_inst_id_ = 'cb33cd3e-1682-11e9-bd99-c67a55d1f07b';
                 id_                  |            proc_inst_id_             | start_user_id_ | tenant_id_ 
--------------------------------------+--------------------------------------+----------------+------------
 cb33cd41-1682-11e9-bd99-c67a55d1f07b | cb33cd3e-1682-11e9-bd99-c67a55d1f07b |                | demo
 cb33cd3e-1682-11e9-bd99-c67a55d1f07b | cb33cd3e-1682-11e9-bd99-c67a55d1f07b | admin          | demo
(2 rows)

Looking at ExecutionEntityManagerImpl.deleteRelatedDataForExecution which then calls deleteIdentityLinks I see that for the second record above to which the identities are linked indeed goes through the process of adding a bulk delete.

In MybatisIdentityLinkDataManager.bulkDelete adds a delete bulk statement, and then it ends up calling deleteCachedEntities where there are 3 identity link entities with the same username. Two with type as ‘participant’ and a third with the type as ‘starter’, two of them are in the database and one ‘participant’ is not.

They go through this code:

image

With all three going through entityMatches and the one that is not in the database also passes the isInserted check.

In the logs the statement execution looks like this:

DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTask  - ==>  Preparing: delete from ACT_RU_TASK where ID_ = ? and REV_ = ? 
DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTask  - ==> Parameters: cb33f454-1682-11e9-bd99-c67a55d1f07b(String), 1(Integer)
DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTask  - <==    Updates: 1
DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTasksByExecutionId  - ==>  Preparing: delete from ACT_RU_TASK where EXECUTION_ID_ = ? 
DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTasksByExecutionId  - ==> Parameters: cb33cd3e-1682-11e9-bd99-c67a55d1f07b(String)
DEBUG org.flowable.task.service.impl.persistence.entity.TaskEntityImpl.deleteTasksByExecutionId  - <==    Updates: 0
DEBUG org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.deleteExecution  - ==>  Preparing: delete from ACT_RU_EXECUTION where ID_ = ? and REV_ = ? 
DEBUG org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.deleteExecution  - ==> Parameters: cb33cd41-1682-11e9-bd99-c67a55d1f07b(String), 2(Integer)
DEBUG org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.deleteExecution  - <==    Updates: 1
DEBUG org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.deleteExecution  - ==>  Preparing: delete from ACT_RU_EXECUTION where ID_ = ? and REV_ = ? 
DEBUG org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.deleteExecution  - ==> Parameters: cb33cd3e-1682-11e9-bd99-c67a55d1f07b(String), 2(Integer)

And then the failure is on the last one, due to the identity links not being deleted.

Any ideas where to look next?

Asking the REST API (running with docker) to complete the task works. So there is probably something related to my configuration, but I am not sure where to start looking…

This commit seems to remove IdentityLinkEntityImpl from the deletionOrder…

if (getEntityDeletionOrder() != null) {
    // remove identity link and variable entity classes due to foreign key handling
    dbSqlSessionFactory.getDeletionOrder().remove(IdentityLinkEntityImpl.class);
    dbSqlSessionFactory.getDeletionOrder().remove(VariableInstanceEntityImpl.class);
    dbSqlSessionFactory.getDeletionOrder().remove(VariableByteArrayEntityImpl.class);
    for (Class<? extends Entity> clazz : getEntityDeletionOrder()) {
        dbSqlSessionFactory.getDeletionOrder().add(clazz);
    }
}

Subclassing SpringProcessEngineConfigurator and overriding initDbSqlSessionFactory to remove the above, makes my app work again (but can’t tell if there are any side effects just yet)

Hi,

I’ve tried to reproduce the issue (see the new ProcessTest unit test in commit https://github.com/flowable/flowable-engine/commit/b47126bf7ab9f864d4b14679f6bf1b0628ee5226), but it worked fine in this test. Could you try to reproduce the error you are seeing by adding a similar test?

Best regards,

Tijs

Thanks Tijs, I’ll see what I can do…

Tough going here. I took your test and slowly adapted to my config. The test works fine.

Just to make sure I used the exact same xml config for the test and my webapp, and still the problem persists. I even execute the test code within the webapp and the problem is still there.

Will just have to keep delving…

Ok that’s strange. And you are not using the SpringProcessEngineConfiguration in your webapp?

Best regards,

Tijs

This is my config for test and app:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">
  
    <bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="org.postgresql.Driver" />
      <property name="url" value="jdbc:postgresql://localhost:5432/flowable" />
      <property name="username" value="flowable" />
      <property name="password" value="flowable" />
    </bean>
    
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="appEngineConfiguration" class="org.flowable.app.spring.SpringAppEngineConfiguration">
        <property name="dataSource" ref="dataSource" />
        <property name="databaseSchemaUpdate" value="false" />
        <property name="transactionManager" ref="transactionManager"/>
        <property name="disableIdmEngine" value="true"/>

        <property name="configurators">
            <list>
                <ref bean="processEngineConfigurator"/>
                <bean class="org.flowable.form.spring.configurator.SpringFormEngineConfigurator" />
                <bean class="org.flowable.content.spring.configurator.SpringContentEngineConfigurator" />
            </list>
        </property>
    </bean>

    <bean id="processEngineConfiguration" class="org.flowable.spring.SpringProcessEngineConfiguration">
        <property name="asyncExecutorActivate" value="false"/>
        
    </bean>

    <bean id="processEngineConfigurator" class="org.flowable.engine.spring.configurator.SpringProcessEngineConfigurator">
        <property name="processEngineConfiguration" ref="processEngineConfiguration"/>
    </bean>    
</beans>

Ok thanks for sharing, then we need an additional test to test the Spring equivalent, because the other test is using the non spring configurators.

Best regards,

Tijs

Yes, I modified the test to use this, and it works. Also tried with my own process and form, and that works too in junit. The same config in my webapp doesn’t.

Hi Tijs

I found the problem. By accident I found I could repeat the issue by adding to processEngineConfigurators. This led me to check whether there are two process engine instances created and indeed that was the case.

I traced the stack of the constructors of the two instances and found the following code which merely attempted to make the AppEngine injectable resulted in a second process engine instance:

    @Bean
    public AppEngineFactoryBean appEngineFactoryBean(AppEngineConfiguration appEngineConfiguration) {
        AppEngineFactoryBean factoryBean = new AppEngineFactoryBean();
        factoryBean.setAppEngineConfiguration(appEngineConfiguration);
        return factoryBean;
    }

    @Bean
    public AppEngine appEngine(AppEngineFactoryBean appEngineFactoryBean) {
        try {
            return appEngineFactoryBean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

I am not sure with all the refactorings how best to get an instance to the AppEngine for injection?

Hi,

Ok good that you could find the issue.
I think the way we use it in the flowable-spring-boot-autoconfigure module is a good example:

Are you not running Flowable in a Spring boot application? Because with Spring boot it would be really easy, just add the flowable-spring-boot-autoconfigure module dependency and that will make it work automatically. If you are using a Spring application (non Spring boot), then I think the code you share can be changed to this:

@Bean
public AppEngineFactoryBean appEngineFactoryBean() {
    AppEngineFactoryBean factoryBean = new AppEngineFactoryBean();
    factoryBean.setAppEngineConfiguration(appEngineConfiguration());
    return factoryBean;
}

public AppEngine appEngine() {
    try {
        return appEngineFactoryBean().getObject();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

@Bean
public AppRepositoryService appRepositoryService() {
	return appEngine().getAppRepositoryService();
}

So just remove the @Bean annotation from the appEngine method and change the method signatures slightly. Now you can call the appEngine method and it will not start another engine instance.

Best regards,

Tijs

No, I am not using spring boot, maybe in future. But I am in a bit of phase of not feeling the joy of spring right now (Memory consumption is one of the themes). On a related topic I like the approach micronaut (http://micronaut.io/) is taking, in terms of a compile time focus, but haven’t used it for anything yet.

Actually leaving out the appEngine method, I could still inject AppEngine instance.

Thanks for the assistance, that unit test certainly helped a lot…