diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionContext.java b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionContext.java index 359abc8..670dd82 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionContext.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionContext.java @@ -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 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 target, Map 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()