diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java index 18c883ed4d0..ebf0b5e4230 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ErrorPropagation.java @@ -47,6 +47,7 @@ import org.flowable.engine.delegate.BpmnError; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.event.impl.FlowableEventBuilder; +import org.flowable.engine.history.DeleteReason; import org.flowable.common.engine.impl.callback.CallbackData; import org.flowable.common.engine.impl.callback.RuntimeInstanceStateChangeCallback; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; @@ -298,14 +299,16 @@ protected static void executeEventHandler(Event event, ExecutionEntity parentExe LOGGER.debug( "Ending and deleting child executions for parent execution '{}'. Reason: Parent Execution is processIntanceType. Current Execution '{}'", parentExecution, currentExecution); - executionEntityManager.deleteChildExecutions(parentExecution, null, true); + executionEntityManager.deleteChildExecutions(parentExecution, + DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", true); } else if (!currentExecution.getParentId().equals(parentExecution.getId())) { LOGGER.debug("Planing destroyScopeOperation for execution {}. Reason: {}. Parent execution: {}", currentExecution, "Current execution parentId odes not match parentExecution id", parentExecution); CommandContextUtil.getAgenda().planDestroyScopeOperation(currentExecution); } else { LOGGER.debug("Deleting execution and related data for execution {}.", currentExecution); - executionEntityManager.deleteExecutionAndRelatedData(currentExecution, null, false); + executionEntityManager.deleteExecutionAndRelatedData(currentExecution, + DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(" + event.getId() + ")", false); } ExecutionEntity eventSubProcessExecution = executionEntityManager.createChildExecution(parentExecution); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.java new file mode 100644 index 00000000000..074496c2e38 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.java @@ -0,0 +1,96 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.engine.test.api.deletereason; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.flowable.engine.history.DeleteReason; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.impl.test.PluggableFlowableTestCase; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.task.api.Task; +import org.junit.jupiter.api.Test; + +/** + * Companion to {@link DeleteReasonTest#testInterruptingBoundaryEvent()}, for the + * interrupting error event sub-process. + * + * An activity terminated by an interrupting start event is recorded with + * DeleteReason.EVENT_SUBPROCESS_INTERRUPTING, so history can tell it apart from + * one that completed normally. + */ +public class ErrorEventSubProcessDeleteReasonTest extends PluggableFlowableTestCase { + + /** + * A forked process: one branch parks on a user task inside a call activity, + * the other throws a BpmnError from a start execution listener. The + * interrupting error event sub-process catches it and terminates the whole + * scope, including the parked branch. + */ + @Test + @Deployment(resources = "org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.bpmn20.xml") + public void testInterruptingErrorEventSubProcess() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("errorEventSubProcessDeleteReason"); + + waitForHistoryJobExecutorToProcessAllJobs(7000, 100); + + // The parked branch's user task was destroyed by the interruption. + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isZero(); + assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isZero(); + + HistoricActivityInstance parkedBranch = historicActivity(processInstance.getId(), "waitingCall"); + + // An end time and a called process instance are also what a call + // activity that ran to completion records, so the delete reason is the + // only thing separating the two. + assertThat(parkedBranch.getEndTime()).isNotNull(); + assertThat(parkedBranch.getCalledProcessInstanceId()).isNotNull(); + + assertThat(parkedBranch.getDeleteReason()) + .as("delete reason on an activity terminated by the interrupting error event sub-process") + .isEqualTo(DeleteReason.EVENT_SUBPROCESS_INTERRUPTING + "(errorHandlerStart)"); + } + + /** + * The same BpmnError caught by an interrupting boundary event instead, which + * records DeleteReason.BOUNDARY_EVENT_INTERRUPTING. The two paths differ only + * in which vehicle catches the error. + */ + @Test + @Deployment(resources = "org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.boundary.bpmn20.xml") + public void testInterruptingBoundaryEvent() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("boundaryDeleteReason"); + + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult(); + assertThat(task).isNotNull(); + taskService.complete(task.getId()); + + waitForHistoryJobExecutorToProcessAllJobs(7000, 100); + + HistoricActivityInstance subProcess = historicActivity(processInstance.getId(), "theSubProcess"); + assertThat(subProcess.getDeleteReason()).contains(DeleteReason.BOUNDARY_EVENT_INTERRUPTING); + } + + private HistoricActivityInstance historicActivity(String processInstanceId, String activityId) { + List instances = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processInstanceId) + .activityId(activityId) + .list(); + assertThat(instances).hasSize(1); + return instances.get(0); + } + +} diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ThrowBpmnErrorListener.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ThrowBpmnErrorListener.java new file mode 100644 index 00000000000..a45bb8ff7c8 --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/deletereason/ThrowBpmnErrorListener.java @@ -0,0 +1,27 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.engine.test.api.deletereason; + +import org.flowable.engine.delegate.BpmnError; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; + +/** Raises a BpmnError from a start execution listener. */ +public class ThrowBpmnErrorListener implements ExecutionListener { + + @Override + public void notify(DelegateExecution execution) { + throw new BpmnError("someError", "raised from a start execution listener"); + } + +} diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.boundary.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.boundary.bpmn20.xml new file mode 100644 index 00000000000..59629cd6d1a --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.boundary.bpmn20.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.bpmn20.xml new file mode 100644 index 00000000000..5a377c1b3af --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/deletereason/ErrorEventSubProcessDeleteReasonTest.bpmn20.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +