-
Notifications
You must be signed in to change notification settings - Fork 556
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
9759: Reject duplicate parallel gateway activate command r=remcowesterhoud a=remcowesterhoud ## Description <!-- Please explain the changes you made here. --> Parallel gateways get activated by taking a sequence flow and checking if the number of taken flows is greater or equal to the number of incoming sequence flows. If this is the case an activate command is sent. The number of taken sequence flows get rest upon activation of the parallel gateway. This proves troublesome when a "bad" model causes one of the incoming sequence flows to be taken twice. This could result in the activation command being sent twice. Imagine there is a parallel gateway with 2 incoming flows. What would happen is: 1. First flow is taken 2. Second flow is taken. Incoming flows == taken flows so an activate command is sent. 3. Second flow is taken again. The first activate command has not been processed yet. The number of taken flows has not been reset. As a result incoming flows < taken flows. A second activate command is sent. This is solved by always sending an activate command when a sequence flow is taken. Once the `BpmnStreamProcessor` tries to process the record it will check if the state is valid. Here a check has been added to verify that when we receive an activate command for a parallel gateway we will first check if all the incoming flows have been taken. If this is not the case we will reject the command. ## Related issues <!-- Which issues are closed by this PR or are related --> closes #6778 Co-authored-by: Remco Westerhoud <remco@westerhoud.nl>
- Loading branch information
Showing
9 changed files
with
235 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
...in/java/io/camunda/zeebe/engine/state/appliers/ProcessInstanceCreationCreatedApplier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under | ||
* one or more contributor license agreements. See the NOTICE file distributed | ||
* with this work for additional information regarding copyright ownership. | ||
* Licensed under the Zeebe Community License 1.1. You may not use this file | ||
* except in compliance with the Zeebe Community License 1.1. | ||
*/ | ||
package io.camunda.zeebe.engine.state.appliers; | ||
|
||
import io.camunda.zeebe.engine.processing.deployment.model.element.ExecutableFlowNode; | ||
import io.camunda.zeebe.engine.state.TypedEventApplier; | ||
import io.camunda.zeebe.engine.state.immutable.ProcessState; | ||
import io.camunda.zeebe.engine.state.instance.ElementInstance; | ||
import io.camunda.zeebe.engine.state.mutable.MutableElementInstanceState; | ||
import io.camunda.zeebe.engine.state.mutable.MutableProcessState; | ||
import io.camunda.zeebe.protocol.impl.record.value.processinstance.ProcessInstanceCreationRecord; | ||
import io.camunda.zeebe.protocol.record.intent.ProcessInstanceCreationIntent; | ||
import io.camunda.zeebe.protocol.record.value.BpmnElementType; | ||
import java.util.List; | ||
import org.agrona.DirectBuffer; | ||
|
||
final class ProcessInstanceCreationCreatedApplier | ||
implements TypedEventApplier<ProcessInstanceCreationIntent, ProcessInstanceCreationRecord> { | ||
|
||
private final ProcessState processState; | ||
private final MutableElementInstanceState elementInstanceState; | ||
|
||
public ProcessInstanceCreationCreatedApplier( | ||
final MutableProcessState processState, | ||
final MutableElementInstanceState elementInstanceState) { | ||
this.processState = processState; | ||
this.elementInstanceState = elementInstanceState; | ||
} | ||
|
||
@Override | ||
public void applyState(final long key, final ProcessInstanceCreationRecord value) { | ||
if (value.hasStartInstructions()) { | ||
final var process = | ||
processState.getProcessByKey(value.getProcessDefinitionKey()).getProcess(); | ||
final ElementInstance processInstance = | ||
elementInstanceState.getInstance(value.getProcessInstanceKey()); | ||
|
||
value.getStartInstructions().stream() | ||
.map(instruction -> process.getElementById(instruction.getElementId())) | ||
.filter(element -> element.getElementType().equals(BpmnElementType.PARALLEL_GATEWAY)) | ||
.map(ExecutableFlowNode.class::cast) | ||
.forEach( | ||
element -> { | ||
final var parentElementId = element.getFlowScope().getId(); | ||
final ElementInstance flowScope = | ||
findParentFlowScope(processInstance, parentElementId); | ||
incrementNumberOfTakenSequenceFlows(element, flowScope); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Traverses the element instances to find one that matches the parent element id. If this is the | ||
* process instance, it is returned immediately. This will work because we will always activate | ||
* the flow scope of a start instruction once. | ||
* | ||
* @param processInstance the highest element instance of a process | ||
* @param targetElementId the id we are looking for | ||
* @return the element instance which matches the targetElementId | ||
*/ | ||
private ElementInstance findParentFlowScope( | ||
final ElementInstance processInstance, final DirectBuffer targetElementId) { | ||
if (processInstance.getValue().getElementIdBuffer().equals(targetElementId)) { | ||
return processInstance; | ||
} | ||
return findFlowScopeInChildren(processInstance, targetElementId); | ||
} | ||
|
||
private ElementInstance findFlowScopeInChildren( | ||
final ElementInstance processInstance, final DirectBuffer targetElementId) { | ||
ElementInstance found = null; | ||
final List<ElementInstance> children = | ||
elementInstanceState.getChildren(processInstance.getKey()); | ||
|
||
for (final ElementInstance childInstance : children) { | ||
if (childInstance.getValue().getElementIdBuffer().equals(targetElementId)) { | ||
found = childInstance; | ||
break; | ||
} else { | ||
found = findFlowScopeInChildren(childInstance, targetElementId); | ||
if (found != null) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return found; | ||
} | ||
|
||
private void incrementNumberOfTakenSequenceFlows( | ||
final ExecutableFlowNode element, final ElementInstance flowScope) { | ||
element | ||
.getIncoming() | ||
.forEach( | ||
incoming -> | ||
elementInstanceState.incrementNumberOfTakenSequenceFlows( | ||
flowScope.getKey(), element.getId(), incoming.getId())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters