Skip to content

Commit 5f6620d

Browse files
committed
fix: race condition in workflow reconciler (#2549)
Signed-off-by: Chris Laprun <[email protected]>
1 parent d0f1be5 commit 5f6620d

File tree

4 files changed

+18
-14
lines changed

4 files changed

+18
-14
lines changed

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/AbstractWorkflowExecutor.java

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.javaoperatorsdk.operator.processing.dependent.workflow;
22

3+
import java.util.HashMap;
34
import java.util.Map;
45
import java.util.Optional;
56
import java.util.concurrent.ConcurrentHashMap;
@@ -36,7 +37,7 @@ protected AbstractWorkflowExecutor(DefaultWorkflow<P> workflow, P primary, Conte
3637
this.context = context;
3738
this.primaryID = ResourceID.fromResource(primary);
3839
executorService = context.getWorkflowExecutorService();
39-
results = new ConcurrentHashMap<>(workflow.getDependentResourcesByName().size());
40+
results = new HashMap<>(workflow.getDependentResourcesByName().size());
4041
}
4142

4243
protected abstract Logger logger();
@@ -84,13 +85,13 @@ protected boolean isMarkedForDelete(DependentResourceNode<?, P> drn) {
8485
return getResultFlagFor(drn, WorkflowResult.DetailBuilder::isMarkedForDelete);
8586
}
8687

87-
protected WorkflowResult.DetailBuilder createOrGetResultFor(
88+
protected synchronized WorkflowResult.DetailBuilder createOrGetResultFor(
8889
DependentResourceNode<?, P> dependentResourceNode) {
8990
return results.computeIfAbsent(dependentResourceNode,
9091
unused -> new WorkflowResult.DetailBuilder());
9192
}
9293

93-
protected Optional<WorkflowResult.DetailBuilder<?>> getResultFor(
94+
protected synchronized Optional<WorkflowResult.DetailBuilder<?>> getResultFor(
9495
DependentResourceNode<?, P> dependentResourceNode) {
9596
return Optional.ofNullable(results.get(dependentResourceNode));
9697
}
@@ -115,8 +116,8 @@ protected synchronized void handleExceptionInExecutor(
115116
createOrGetResultFor(dependentResourceNode).withError(e);
116117
}
117118

118-
protected boolean isNotReady(DependentResourceNode<?, P> dependentResourceNode) {
119-
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isNotReady);
119+
protected boolean isReady(DependentResourceNode<?, P> dependentResourceNode) {
120+
return getResultFlagFor(dependentResourceNode, WorkflowResult.DetailBuilder::isReady);
120121
}
121122

122123
protected boolean isInError(DependentResourceNode<?, P> dependentResourceNode) {
@@ -132,15 +133,17 @@ protected synchronized void handleNodeExecutionFinish(
132133
}
133134
}
134135

135-
@SuppressWarnings("unchecked")
136+
@SuppressWarnings({"unchecked", "OptionalUsedAsFieldOrParameterType"})
136137
protected <R> boolean isConditionMet(
137138
Optional<ConditionWithType<R, P, ?>> condition,
138139
DependentResourceNode<R, P> dependentResource) {
139140
final var dr = dependentResource.getDependentResource();
140141
return condition.map(c -> {
141142
final DetailedCondition.Result<?> r = c.detailedIsMet(dr, primary, context);
142-
results.computeIfAbsent(dependentResource, unused -> new WorkflowResult.DetailBuilder())
143-
.withResultForCondition(c, r);
143+
synchronized (this) {
144+
results.computeIfAbsent(dependentResource, unused -> new WorkflowResult.DetailBuilder())
145+
.withResultForCondition(c, r);
146+
}
144147
return r;
145148
}).orElse(DetailedCondition.Result.metWithoutResult).isSuccess();
146149
}
@@ -170,7 +173,7 @@ protected <R> void registerOrDeregisterEventSourceBasedOnActivation(
170173
}
171174
}
172175

173-
protected Map<DependentResource, WorkflowResult.Detail<?>> asDetails() {
176+
protected synchronized Map<DependentResource, WorkflowResult.Detail<?>> asDetails() {
174177
return results.entrySet().stream()
175178
.collect(
176179
Collectors.toMap(e -> e.getKey().getDependentResource(), e -> e.getValue().build()));

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private synchronized void handleDelete(DependentResourceNode dependentResourceNo
120120

121121
private boolean allDependentsDeletedAlready(DependentResourceNode<?, P> dependentResourceNode) {
122122
var dependents = dependentResourceNode.getParents();
123-
return dependents.stream().allMatch(d -> alreadyVisited(d) && !isNotReady(d)
123+
return dependents.stream().allMatch(d -> alreadyVisited(d) && isReady(d)
124124
&& !isInError(d) && !postDeleteConditionNotMet(d));
125125
}
126126

@@ -231,7 +231,7 @@ private void markDependentsForDelete(DependentResourceNode<?, P> dependentResour
231231
private boolean allParentsReconciledAndReady(DependentResourceNode<?, ?> dependentResourceNode) {
232232
return dependentResourceNode.getDependsOn().isEmpty()
233233
|| dependentResourceNode.getDependsOn().stream()
234-
.allMatch(d -> alreadyVisited(d) && !isNotReady(d));
234+
.allMatch(d -> alreadyVisited(d) && isReady(d));
235235
}
236236

237237
private boolean hasErroredParent(DependentResourceNode<?, ?> dependentResourceNode) {

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowResult.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ public boolean hasPostDeleteConditionNotMet() {
178178
return deletePostconditionResult != null && !deletePostconditionResult.isSuccess();
179179
}
180180

181-
public boolean isNotReady() {
182-
return readyPostconditionResult != null && !readyPostconditionResult.isSuccess();
181+
public boolean isReady() {
182+
return readyPostconditionResult == null || readyPostconditionResult.isSuccess();
183183
}
184184

185185
DetailBuilder<R> markAsVisited() {

Diff for: operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutorTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,8 @@ void readyConditionNotMetInOneParent() {
450450
void diamondShareWithReadyCondition() {
451451
var workflow = new WorkflowBuilder<TestCustomResource>()
452452
.addDependentResource(dr1)
453-
.addDependentResourceAndConfigure(dr2).toDependOn(dr1)
453+
.addDependentResourceAndConfigure(dr2)
454+
.toDependOn(dr1)
454455
.withReadyPostcondition(notMetCondition)
455456
.addDependentResourceAndConfigure(dr3).toDependOn(dr1)
456457
.addDependentResourceAndConfigure(dr4).toDependOn(dr2, dr3)

0 commit comments

Comments
 (0)