@@ -61,9 +61,11 @@ public class WorkflowMutableInstance implements WorkflowInstance {
6161 private Lock statusLock = new ReentrantLock ();
6262 private Map <CompletableFuture <TaskContext >, TaskContext > suspended ;
6363
64- private Collection <CompletableFuture <?>> cancelables = new ArrayList <>();
64+ private Collection <CompletableFuture <?>> cancelables =
65+ Collections .synchronizedList (new ArrayList <>());
6566
66- private volatile CompletableFuture <?> cancelPublished = CompletableFuture .completedFuture (null );
67+ private Collection <CompletableFuture <Boolean >> outOfOrderListeners =
68+ Collections .synchronizedList (new ArrayList <>());
6769
6870 protected WorkflowMutableInstance (WorkflowDefinition definition , String id , WorkflowModel input ) {
6971 this .id = id ;
@@ -107,31 +109,16 @@ protected final CompletableFuture<WorkflowModel> startExecution(
107109 .orElse (input ))
108110 .whenComplete (this ::setCompleteDate )
109111 .thenApply (this ::filterAndValidate )
110- .thenCompose (this ::publishEvents )
112+ .thenCompose (this ::publishCompletionEvents )
111113 .exceptionallyCompose (this ::handleException ))
112- .handle (this ::cleanUpAfterCancelPublished )
114+ .handle (this ::cleanUpWaitingListeners )
113115 .thenCompose (Function .identity ());
114116 futureRef .set (future );
115117 }
116118 return future ;
117119 }
118120
119- private CompletableFuture <WorkflowModel > cleanUpAfterCancelPublished (
120- WorkflowModel result , Throwable ex ) {
121- return cancelPublished
122- .handle (
123- (__ , ___ ) -> {
124- cleanUp (result , ex );
125- return null ;
126- })
127- .thenCompose (
128- __ ->
129- ex == null
130- ? CompletableFuture .completedFuture (result )
131- : CompletableFuture .failedFuture (ex ));
132- }
133-
134- private CompletableFuture <WorkflowModel > publishEvents (WorkflowModel model ) {
121+ private CompletableFuture <WorkflowModel > publishCompletionEvents (WorkflowModel model ) {
135122 return status (WorkflowStatus .COMPLETED )
136123 .thenCompose (
137124 __ ->
@@ -145,19 +132,37 @@ private void setCompleteDate(WorkflowModel result, Throwable ex) {
145132 completedAt = Instant .now ();
146133 }
147134
148- private void cleanUp (WorkflowModel result , Throwable ex ) {
135+ private CompletableFuture <WorkflowModel > cleanUpWaitingListeners (
136+ WorkflowModel model , Throwable ex ) {
137+ return CompletableFuture .allOf (
138+ outOfOrderListeners .toArray (new CompletableFuture [outOfOrderListeners .size ()]))
139+ .handle (this ::cleanUp )
140+ .thenCompose (__ -> fromResult (model , ex ));
141+ }
142+
143+ private Object cleanUp (Object ignored , Throwable ex ) {
149144 additionalObjects .values ().stream ()
150145 .filter (AutoCloseable .class ::isInstance )
151146 .map (AutoCloseable .class ::cast )
152147 .forEach (WorkflowUtils ::safeClose );
153148 additionalObjects .clear ();
154149 workflowContext .definition ().removeInstance (this );
150+ outOfOrderListeners .clear ();
151+ return ignored ;
152+ }
153+
154+ private CompletableFuture <WorkflowModel > fromResult (WorkflowModel model , Throwable ex ) {
155+ return ex == null
156+ ? CompletableFuture .completedFuture (model )
157+ : CompletableFuture .failedFuture (ex );
155158 }
156159
157160 private CompletableFuture <WorkflowModel > handleException (Throwable exception ) {
158161 final Throwable cause =
159162 exception instanceof CompletionException ? exception .getCause () : exception ;
160- if (!(cause instanceof CancellationException )) {
163+ if (cause instanceof CancellationException ) {
164+ return CompletableFuture .failedFuture (cause );
165+ } else {
161166 return status (WorkflowStatus .FAULTED )
162167 .thenCompose (
163168 __ ->
@@ -166,7 +171,6 @@ private CompletableFuture<WorkflowModel> handleException(Throwable exception) {
166171 l -> l .onWorkflowFailed (new WorkflowFailedEvent (workflowContext , cause ))))
167172 .thenCompose (__ -> CompletableFuture .failedFuture (exception ));
168173 }
169- return CompletableFuture .failedFuture (exception );
170174 }
171175
172176 private WorkflowModel filterAndValidate (WorkflowModel model ) {
@@ -232,29 +236,21 @@ public <T> T outputAs(Class<T> clazz) {
232236 : null ;
233237 }
234238
235- public CompletableFuture <Boolean > status (WorkflowStatus state ) {
239+ public CompletableFuture <Boolean > status (WorkflowStatus newState ) {
236240 WorkflowStatus prevState ;
241+ statusLock .lock ();
237242 try {
238- statusLock .lock ();
239- prevState = this .status .get ();
240- if (prevState == WorkflowStatus .CANCELLED ) {
241- // cancellation is final: late callbacks (e.g. an event received by a listen task) must
242- // not move the instance out of it, and a terminal transition must fail so that neither
243- // onWorkflowCompleted nor onWorkflowFailed is published for a cancelled instance
244- return isTerminal (state )
245- ? CompletableFuture .failedFuture (
246- new CancellationException ("Workflow instance " + id + " has been cancelled" ))
247- : CompletableFuture .completedFuture (false );
243+ prevState = status .get ();
244+ if (prevState == WorkflowStatus .CANCELLED && newState != WorkflowStatus .CANCELLED ) {
245+ return CompletableFuture .failedFuture (
246+ new CancellationException ("Workflow has been cancelled" ));
247+ } else {
248+ status .set (newState );
248249 }
249- this .status .set (state );
250250 } finally {
251251 statusLock .unlock ();
252252 }
253- return publishStatusChange (prevState , state );
254- }
255-
256- private static boolean isTerminal (WorkflowStatus state ) {
257- return state == WorkflowStatus .COMPLETED || state == WorkflowStatus .FAULTED ;
253+ return publishStatusChange (prevState , newState );
258254 }
259255
260256 protected final void setStatus (WorkflowStatus state ) {
@@ -263,14 +259,10 @@ protected final void setStatus(WorkflowStatus state) {
263259
264260 private CompletableFuture <Boolean > publishStatusChange (
265261 WorkflowStatus prevState , WorkflowStatus state ) {
266- return prevState != state
267- ? publishEvent (
268- workflowContext ,
269- l ->
270- l .onWorkflowStatusChanged (
271- new WorkflowStatusEvent (workflowContext , prevState , state )))
272- .thenApply (__ -> true )
273- : CompletableFuture .completedFuture (false );
262+ return publishEvent (
263+ prevState != state ,
264+ workflowContext ,
265+ l -> l .onWorkflowStatusChanged (new WorkflowStatusEvent (workflowContext , prevState , state )));
274266 }
275267
276268 @ Override
@@ -291,28 +283,30 @@ public boolean suspend() {
291283 WorkflowStatus prevState = internalSuspend ();
292284 boolean result = prevState != WorkflowStatus .SUSPENDED ;
293285 if (result ) {
294- publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
295- .thenCompose (
296- __ ->
297- publishEvent (
298- workflowContext ,
299- l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext ))));
286+ outOfOrder (
287+ publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
288+ .thenCompose (
289+ changed ->
290+ publishEvent (
291+ changed ,
292+ workflowContext ,
293+ l ->
294+ l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext )))));
300295 }
301296 return result ;
302297 }
303298
304299 @ Override
305300 public CompletableFuture <Boolean > suspendFuture () {
306301 WorkflowStatus prevState = internalSuspend ();
307- return prevState != WorkflowStatus . SUSPENDED
308- ? publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
302+ return outOfOrder (
303+ publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
309304 .thenCompose (
310- __ ->
305+ changed ->
311306 publishEvent (
307+ changed ,
312308 workflowContext ,
313- l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext ))))
314- .thenApply (__ -> true )
315- : CompletableFuture .completedFuture (false );
309+ l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext )))));
316310 }
317311
318312 private WorkflowStatus internalSuspend () {
@@ -338,28 +332,29 @@ public boolean resume() {
338332 WorkflowStatus prevStatus = internalResume ();
339333 boolean result = prevStatus != WorkflowStatus .RUNNING ;
340334 if (result ) {
341- publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
342- .thenCompose (
343- __ ->
344- publishEvent (
345- workflowContext ,
346- l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext ))));
335+ outOfOrder (
336+ publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
337+ .thenCompose (
338+ changed ->
339+ publishEvent (
340+ changed ,
341+ workflowContext ,
342+ l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext )))));
347343 }
348344 return result ;
349345 }
350346
351347 @ Override
352348 public CompletableFuture <Boolean > resumeFuture () {
353349 WorkflowStatus prevStatus = internalResume ();
354- return prevStatus != WorkflowStatus . RUNNING
355- ? publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
350+ return outOfOrder (
351+ publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
356352 .thenCompose (
357- __ ->
353+ change ->
358354 publishEvent (
355+ change ,
359356 workflowContext ,
360- l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext ))))
361- .thenApply (__ -> true )
362- : CompletableFuture .completedFuture (false );
357+ l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext )))));
363358 }
364359
365360 private WorkflowStatus internalResume () {
@@ -382,13 +377,12 @@ private WorkflowStatus internalResume() {
382377 return result ;
383378 }
384379
385- public CompletableFuture <TaskContext > cancelCheck (TaskContext t ) {
380+ public < T > CompletableFuture <T > cancelCheck (T t ) {
386381 try {
387382 statusLock .lock ();
388383 if (status .get () == WorkflowStatus .CANCELLED ) {
389- CompletableFuture <TaskContext > cancelled = new CompletableFuture <TaskContext >();
390- cancelled .completeExceptionally (
391- new CancellationException ("Task " + t .taskName () + " has been cancelled" ));
384+ CompletableFuture <T > cancelled = new CompletableFuture <>();
385+ cancelled .completeExceptionally (new CancellationException (t + " has been cancelled" ));
392386 return cancelled ;
393387 }
394388 } finally {
@@ -419,68 +413,53 @@ public CompletableFuture<TaskContext> suspendedCheck(TaskContext t) {
419413
420414 @ Override
421415 public boolean cancel () {
422- CancelRequest request = internalCancel ();
423- if (request .accepted ()) {
424- publishCancelled (request );
425- }
426- return request .accepted ();
416+ CompletableFuture <Boolean > result = internalCancel ();
417+ return !result .isDone () || result .join ();
427418 }
428419
429420 @ Override
430421 public CompletableFuture <Boolean > cancelFuture () {
431- CancelRequest request = internalCancel ();
432- return request .accepted ()
433- ? publishCancelled (request ).thenApply (__ -> true )
434- : CompletableFuture .completedFuture (false );
435- }
436-
437- /**
438- * Publishes the cancellation and only then cancels the pending futures. Cancelling them may
439- * complete the execution pipeline synchronously, and the pipeline may also end on its own while
440- * the cancellation is being published, so {@link #cleanUpAfterCancelPublished} waits for {@link
441- * CancelRequest#published()} before clearing the instance metadata.
442- */
443- private CompletableFuture <?> publishCancelled (CancelRequest request ) {
444- return publishStatusChange (request .prevStatus (), WorkflowStatus .CANCELLED )
445- .thenCompose (
446- __ ->
447- publishEvent (
448- workflowContext ,
449- l -> l .onWorkflowCancelled (new WorkflowCancelledEvent (workflowContext ))))
450- .whenComplete (
451- (__ , ex ) -> {
452- request .published ().complete (null );
453- request .toCancel ().forEach (t -> t .cancel (true ));
454- });
455- }
456-
457- private record CancelRequest (
458- WorkflowStatus prevStatus ,
459- Collection <CompletableFuture <?>> toCancel ,
460- CompletableFuture <Void > published ) {
461- boolean accepted () {
462- return prevStatus != WorkflowStatus .CANCELLED ;
463- }
422+ return internalCancel ();
464423 }
465424
466- private CancelRequest internalCancel () {
425+ private CompletableFuture <Boolean > internalCancel () {
426+ WorkflowStatus prevState ;
427+ Collection <CompletableFuture <?>> toCancel = null ;
467428 try {
468429 statusLock .lock ();
469430 if (TaskExecutorHelper .isActive (status .get ())) {
470- Collection < CompletableFuture <?>> toCancel = new ArrayList <>(cancelables );
431+ toCancel = new ArrayList <>(cancelables );
471432 cancelables .clear ();
472- CompletableFuture <Void > published = new CompletableFuture <>();
473- cancelPublished = published ;
474- return new CancelRequest (status .getAndSet (WorkflowStatus .CANCELLED ), toCancel , published );
433+ prevState = status .getAndSet (WorkflowStatus .CANCELLED );
475434 } else {
476- return new CancelRequest (
477- WorkflowStatus .CANCELLED ,
478- Collections .emptyList (),
479- CompletableFuture .completedFuture (null ));
435+ prevState = WorkflowStatus .CANCELLED ;
480436 }
481437 } finally {
482438 statusLock .unlock ();
483439 }
440+ CompletableFuture <Boolean > result =
441+ outOfOrder (
442+ publishStatusChange (prevState , WorkflowStatus .CANCELLED )
443+ .thenCompose (
444+ changed ->
445+ publishEvent (
446+ changed ,
447+ workflowContext ,
448+ l ->
449+ l .onWorkflowCancelled (
450+ new WorkflowCancelledEvent (workflowContext )))));
451+
452+ if (prevState != WorkflowStatus .CANCELLED && toCancel != null ) {
453+ toCancel .forEach (t -> t .cancel (true ));
454+ }
455+ return result ;
456+ }
457+
458+ private CompletableFuture <Boolean > outOfOrder (CompletableFuture <Boolean > future ) {
459+ if (!future .isDone ()) {
460+ outOfOrderListeners .add (future );
461+ }
462+ return future ;
484463 }
485464
486465 public void addCancelable (CompletableFuture <?> cancelable ) {
0 commit comments