Task completion not deleting identity links causing FK violation


#1

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:

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?


#2

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…


#4

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)


#5

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


#6

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


#7

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…


#8

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

Best regards,

Tijs


#9

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>

#10

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


#11

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.


#12

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?


#13

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


#14

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…