Skip to content

Commit 79cf4fd

Browse files
authored
Merge pull request #944 from jglick/croak
Avoid holding locks from `croak` when calling `notifyListeners`
2 parents 74a482b + e0d2406 commit 79cf4fd

File tree

3 files changed

+34
-29
lines changed

3 files changed

+34
-29
lines changed

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ void croak(Throwable t) {
933933
LOGGER.log(Level.WARNING, "Failed to create placeholder nodes in " + owner, x);
934934
}
935935
} else {
936-
onProgramEnd(new Outcome(null, t));
936+
onProgramEnd(new Outcome(null, t), true);
937937
}
938938
cleanUpHeap();
939939
try {
@@ -1346,7 +1346,7 @@ public boolean isComplete() {
13461346
* Record the end of the build. Note: we should always follow this with a call to {@link #saveOwner()} to persist the result.
13471347
* @param outcome success; or a normal failure (uncaught exception); or a fatal error in VM machinery
13481348
*/
1349-
synchronized void onProgramEnd(Outcome outcome) {
1349+
synchronized void onProgramEnd(Outcome outcome, boolean asynchNotifications) {
13501350
FlowNode head = new FlowEndNode(this, iotaStr(), (FlowStartNode)startNodes.pop(), result, getCurrentHeads().toArray(new FlowNode[0]));
13511351
if (outcome.isFailure()) {
13521352
head.addAction(new ErrorAction(outcome.getAbnormal()));
@@ -1356,7 +1356,7 @@ synchronized void onProgramEnd(Outcome outcome) {
13561356
try {
13571357
FlowHead first = getFirstHead();
13581358
if (first != null) {
1359-
first.setNewHead(head);
1359+
first.setNewHead(head, asynchNotifications);
13601360
done = true; // After setting the final head
13611361
heads.clear();
13621362
heads.put(first.getId(), first);

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsThreadGroup.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
import java.nio.file.StandardCopyOption;
6161
import java.util.ArrayList;
6262
import java.util.Collection;
63-
import java.util.Collections;
6463
import java.util.HashMap;
6564
import java.util.List;
6665
import java.util.Map;
@@ -454,7 +453,7 @@ private boolean run() {
454453
runtimeThreads.remove(t.id);
455454
t.cleanUp();
456455
if (runtimeThreads.isEmpty()) {
457-
execution.onProgramEnd(o);
456+
execution.onProgramEnd(o, false);
458457
try {
459458
this.execution.saveOwner();
460459
} catch (Exception ex) {

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/FlowHead.java

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.io.ObjectInputStream;
3232
import java.io.ObjectOutputStream;
3333
import java.io.Serializable;
34-
import java.util.Collections;
3534
import java.util.List;
3635
import java.util.logging.Level;
3736
import java.util.logging.Logger;
@@ -46,6 +45,7 @@
4645
import org.jenkinsci.plugins.workflow.graph.FlowStartNode;
4746

4847
import edu.umd.cs.findbugs.annotations.NonNull;
48+
import jenkins.util.Timer;
4949

5050
/**
5151
* Growing tip of the node graph.
@@ -115,23 +115,15 @@ void newStartNode(FlowStartNode n) throws IOException {
115115
this.head = execution.startNodes.push(n);
116116
}
117117
execution.storage.storeNode(head, false);
118-
119-
CpsThreadGroup c = CpsThreadGroup.current();
120-
if (c !=null) {
121-
// if the manipulation is from within the program executing thread, then
122-
// defer the notification till we get to a safe point.
123-
c.notifyNewHead(head);
124-
} else {
125-
// in recovering from error and such situation, we sometimes need to grow the graph
126-
// without running the program.
127-
// TODO can CpsThreadGroup.notifyNewHead be used instead to notify both kinds of listeners?
128-
execution.notifyListeners(List.of(head), true);
129-
execution.notifyListeners(List.of(head), false);
130-
}
118+
notifyNewHead(head, false);
131119
}
132120

133121
/** Could be better described as "append to Flow graph" except for parallel cases. */
134122
void setNewHead(@NonNull FlowNode v) {
123+
setNewHead(v, false);
124+
}
125+
126+
void setNewHead(@NonNull FlowNode v, boolean asynchNotifications) {
135127
if (v == null) {
136128
// Because Findbugs isn't 100% at catching cases where this can happen and we really need to fail hard-and-fast
137129
throw new IllegalArgumentException("FlowHead.setNewHead called on FlowHead id="+this.id+" with a null FlowNode, execution="+this.execution);
@@ -151,17 +143,31 @@ void setNewHead(@NonNull FlowNode v) {
151143
LOGGER.log(Level.WARNING, "Failed to record new head or persist old: " + v, e);
152144
}
153145
this.head = v;
154-
CpsThreadGroup c = CpsThreadGroup.current();
155-
if (c !=null) {
156-
// if the manipulation is from within the program executing thread, then
157-
// defer the notification till we get to a safe point.
158-
c.notifyNewHead(v);
146+
notifyNewHead(v, asynchNotifications);
147+
}
148+
149+
/**
150+
* Usually calls {@link CpsThreadGroup#notifyNewHead}.
151+
* If the manipulation is from within the program executing thread,
152+
* then defer the notification till we get to a safe point.
153+
* Can also be used for special situations such as a fatal error;
154+
* in those cases we may need to grow the graph without running the program,
155+
* and may also want to avoid holding any locks.
156+
*/
157+
private void notifyNewHead(@NonNull FlowNode head, boolean asynchIfOutsideProgram) {
158+
var g = CpsThreadGroup.current();
159+
if (g !=null) {
160+
g.notifyNewHead(head);
159161
} else {
160-
// in recovering from error and such situation, we sometimes need to grow the graph
161-
// without running the program.
162-
// TODO can CpsThreadGroup.notifyNewHead be used instead to notify both kinds of listeners?
163-
execution.notifyListeners(List.of(v), true);
164-
execution.notifyListeners(List.of(v), false);
162+
Runnable notify = () -> {
163+
execution.notifyListeners(List.of(head), true);
164+
execution.notifyListeners(List.of(head), false);
165+
};
166+
if (asynchIfOutsideProgram) {
167+
Timer.get().submit(notify);
168+
} else {
169+
notify.run();
170+
}
165171
}
166172
}
167173

0 commit comments

Comments
 (0)