Exception when compensating a cancelled transaction with user task

Hi :slight_smile:

I am facing a problem when I want to stop a transaction using a cancel end event and compensate one of the transaction tasks by a user task. This gives me a database exception (see below).
If I change the type of the compensation task from user task to service task, it works.

Is this the wrong way to model such a process or is this unexpected behaviour?
What can I do to be able to compensate the task correctly?

This is my process diagram (I am aware of the fact that it currently won’t finish on the “happy path”):
57

Code causing the exception:

taskId = taskService.createTaskQuery().taskDefinitionKey("sendInvitation").singleResult().getId();
taskService.complete(taskId);


taskId = taskService.createTaskQuery().taskDefinitionKey("receivePayment").singleResult().getId();
taskService.complete(taskId);

taskId = taskService.createTaskQuery().taskDefinitionKey("handleCancellation").singleResult().getId();
taskService.complete(taskId);  // here the exception is thrown

The exception:

org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "ACT_FK_EXE_PARENT: PUBLIC.ACT_RU_EXECUTION FOREIGN KEY(PARENT_ID_) REFERENCES PUBLIC.ACT_RU_EXECUTION(ID_) ('24')"; SQL statement:
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_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_, ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_, START_ACT_ID_, START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_, TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_, DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_)
    values (
      ?,
      1,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?
    ) [23506-196]
### The error may involve org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl.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_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_, ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_, START_ACT_ID_, START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_, TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_, DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_)     values (       ?,       1,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?,       ?     )
### Cause: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "ACT_FK_EXE_PARENT: PUBLIC.ACT_RU_EXECUTION FOREIGN KEY(PARENT_ID_) REFERENCES PUBLIC.ACT_RU_EXECUTION(ID_) ('24')"; SQL statement:
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_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_, ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_, START_ACT_ID_, START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_, TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_, DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_)
    values (
      ?,
      1,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?
    ) [23506-196]

	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:200)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)
	at org.flowable.engine.common.impl.db.DbSqlSession.flushRegularInsert(DbSqlSession.java:437)
	at org.flowable.engine.common.impl.db.DbSqlSession.flushInsertEntities(DbSqlSession.java:418)
	at org.flowable.engine.common.impl.db.DbSqlSession.flushInserts(DbSqlSession.java:401)
	at org.flowable.engine.common.impl.db.DbSqlSession.flush(DbSqlSession.java:290)
	at org.flowable.engine.common.impl.interceptor.CommandContext.flushSessions(CommandContext.java:191)
	at org.flowable.engine.common.impl.interceptor.CommandContext.close(CommandContext.java:61)
	at org.flowable.engine.common.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:77)
	at org.flowable.engine.common.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:29)
	at org.flowable.engine.common.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:56)
	at org.flowable.engine.common.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:51)
	at org.flowable.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:187)
	at CompensationTest2.testCompensation(CompensationTest2.java:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "ACT_FK_EXE_PARENT: PUBLIC.ACT_RU_EXECUTION FOREIGN KEY(PARENT_ID_) REFERENCES PUBLIC.ACT_RU_EXECUTION(ID_) ('24')"; SQL statement:
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_, IS_MI_ROOT_, PARENT_ID_, SUPER_EXEC_, ROOT_PROC_INST_ID_, SUSPENSION_STATE_, TENANT_ID_, NAME_, START_ACT_ID_, START_TIME_, START_USER_ID_, IS_COUNT_ENABLED_, EVT_SUBSCR_COUNT_, TASK_COUNT_, JOB_COUNT_, TIMER_JOB_COUNT_, SUSP_JOB_COUNT_, DEADLETTER_JOB_COUNT_, VAR_COUNT_, ID_LINK_COUNT_)
    values (
      ?,
      1,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?,
      ?
    ) [23506-196]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
	at org.h2.message.DbException.get(DbException.java:179)
	at org.h2.message.DbException.get(DbException.java:155)
	at org.h2.constraint.ConstraintReferential.checkRowOwnTable(ConstraintReferential.java:371)
	at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:313)
	at org.h2.table.Table.fireConstraints(Table.java:980)
	at org.h2.table.Table.fireAfterRow(Table.java:998)
	at org.h2.command.dml.Insert.insertRows(Insert.java:161)
	at org.h2.command.dml.Insert.update(Insert.java:114)
	at org.h2.command.CommandContainer.update(CommandContainer.java:101)
	at org.h2.command.Command.executeUpdate(Command.java:260)
	at org.h2.jdbc.JdbcPreparedStatement.execute(JdbcPreparedStatement.java:207)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:46)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
	... 35 more

Thanks in advance :slight_smile:

Hi,

I have create a test case on GitHub:

Could you please have a look at it? :slight_smile:

Best
Marian

I don’t believe this pattern is supported: the problem here is that the boundary event deletes the subprocess, but you’re asking the user task to be created within that subprocess context.

If you want to model this use case, you can simply add the user task (and other tasks) after the boundary cancel event.

hi joram,

thanks for the suggestion!
this should work for me.

still, i’d like to understand why it doesn’t work as i expected.
could you explain what is meant by “The cancel boundary event then cancels the transaction and triggers compensation.” in Cancel End Event documentation?
after your explanation for me it is unclear what can be compensated, as cancel end event is only for transactions, as far as i understood.
https://www.flowable.org/docs/userguide/index.html#bpmnCancelEndEvent

best

edit:
i know that flowable is not camunda :slight_smile:

… still i was looking up their documenation

A transaction is canceled if an execution reaches the cancel end event. In that case all executions are terminated and removed. A single remaining execution is then set to the cancel boundary event, which triggers compensation. After compensation is completed the transaction subprocess is left, using the outgoing sequence flow(s) of the cancel boundary event."
https://docs.camunda.org/manual/7.6/reference/bpmn20/subprocesses/transaction-subprocess/

i guess this is what i expected?

can you explain why it is different in flowable? :slight_smile: