Update execution variables in place instead of emptying them each time
Every step's completion emptied the execution's variable maps and filled them again, while steps running on other threads were reading them. Two branches running side by side could fail with a ConcurrentModificationException copying the map, or read it in the moment it was empty. Only what differs is now changed. Variables are declared up front, so usually nothing is written at all. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f6a968ab34
commit
6cb62c22ea
|
|
@ -899,8 +899,7 @@ public class ExecutionContext implements ExecutionListener {
|
|||
}
|
||||
|
||||
private void refreshRuntimeExecutionVariables() {
|
||||
this.runtimeExecutionVariables.clear();
|
||||
this.runtimeExecutionVariables.putAll(ExecutionRuntimeContextSupport.runtimeValues(
|
||||
replaceContents(this.runtimeExecutionVariables, ExecutionRuntimeContextSupport.runtimeValues(
|
||||
this.executionVariables,
|
||||
this.globalInputs,
|
||||
this.projectContext,
|
||||
|
|
@ -908,13 +907,29 @@ public class ExecutionContext implements ExecutionListener {
|
|||
}
|
||||
|
||||
private void synchronizeExecutionVariablesFromDescriptors() {
|
||||
this.executionVariables.clear();
|
||||
Map<String, Object> declared = new HashMap<>();
|
||||
this.executionVariableDescriptors.values().stream()
|
||||
.map(ExecutionVariableRegistry::normalize)
|
||||
.forEach(descriptor -> this.executionVariables.put(descriptor.getName(), descriptor.getValue()));
|
||||
.forEach(descriptor -> declared.put(descriptor.getName(), descriptor.getValue()));
|
||||
replaceContents(this.executionVariables, declared);
|
||||
refreshRuntimeExecutionVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Brings {@code target} to {@code desired} touching only what differs. Steps running on other
|
||||
* threads read these maps while this runs on every step's completion: emptied and refilled,
|
||||
* they would read a map with nothing in it, or fail copying one that changed under them. With
|
||||
* the same keys - the usual case, variables being declared up front - nothing is written at all.
|
||||
*/
|
||||
private static void replaceContents(Map<String, Object> target, Map<String, Object> desired) {
|
||||
target.keySet().removeIf(key -> !desired.containsKey(key));
|
||||
desired.forEach((key, value) -> {
|
||||
if (!target.containsKey(key) || !java.util.Objects.equals(target.get(key), value)) {
|
||||
target.put(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void abortAfterFailure(String failedStepId) {
|
||||
this.waitingSteps.clear();
|
||||
this.steps.values().stream()
|
||||
|
|
|
|||
Loading…
Reference in New Issue