3131import java .io .ObjectInputStream ;
3232import java .io .ObjectOutputStream ;
3333import java .io .Serializable ;
34- import java .util .Collections ;
3534import java .util .List ;
3635import java .util .logging .Level ;
3736import java .util .logging .Logger ;
4645import org .jenkinsci .plugins .workflow .graph .FlowStartNode ;
4746
4847import 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