@@ -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,34 @@ 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+ .whenComplete ((__ , ___ ) -> cleanUp ())
140+ .thenCompose (
141+ __ ->
142+ ex == null
143+ ? CompletableFuture .completedFuture (model )
144+ : CompletableFuture .failedFuture (ex ));
145+ }
146+
147+ private void cleanUp () {
149148 additionalObjects .values ().stream ()
150149 .filter (AutoCloseable .class ::isInstance )
151150 .map (AutoCloseable .class ::cast )
152151 .forEach (WorkflowUtils ::safeClose );
153152 additionalObjects .clear ();
154153 workflowContext .definition ().removeInstance (this );
154+ outOfOrderListeners .clear ();
155155 }
156156
157157 private CompletableFuture <WorkflowModel > handleException (Throwable exception ) {
158158 final Throwable cause =
159159 exception instanceof CompletionException ? exception .getCause () : exception ;
160- if (!(cause instanceof CancellationException )) {
160+ if (cause instanceof CancellationException ) {
161+ return CompletableFuture .failedFuture (cause );
162+ } else {
161163 return status (WorkflowStatus .FAULTED )
162164 .thenCompose (
163165 __ ->
@@ -166,7 +168,6 @@ private CompletableFuture<WorkflowModel> handleException(Throwable exception) {
166168 l -> l .onWorkflowFailed (new WorkflowFailedEvent (workflowContext , cause ))))
167169 .thenCompose (__ -> CompletableFuture .failedFuture (exception ));
168170 }
169- return CompletableFuture .failedFuture (exception );
170171 }
171172
172173 private WorkflowModel filterAndValidate (WorkflowModel model ) {
@@ -232,29 +233,21 @@ public <T> T outputAs(Class<T> clazz) {
232233 : null ;
233234 }
234235
235- public CompletableFuture <Boolean > status (WorkflowStatus state ) {
236+ public CompletableFuture <Boolean > status (WorkflowStatus newState ) {
236237 WorkflowStatus prevState ;
238+ statusLock .lock ();
237239 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 );
240+ prevState = status .get ();
241+ if (prevState == WorkflowStatus .CANCELLED && newState != WorkflowStatus .CANCELLED ) {
242+ return CompletableFuture .failedFuture (
243+ new CancellationException ("Workflow has been cancelled" ));
244+ } else {
245+ status .set (newState );
248246 }
249- this .status .set (state );
250247 } finally {
251248 statusLock .unlock ();
252249 }
253- return publishStatusChange (prevState , state );
254- }
255-
256- private static boolean isTerminal (WorkflowStatus state ) {
257- return state == WorkflowStatus .COMPLETED || state == WorkflowStatus .FAULTED ;
250+ return publishStatusChange (prevState , newState );
258251 }
259252
260253 protected final void setStatus (WorkflowStatus state ) {
@@ -263,14 +256,10 @@ protected final void setStatus(WorkflowStatus state) {
263256
264257 private CompletableFuture <Boolean > publishStatusChange (
265258 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 );
259+ return publishEvent (
260+ prevState != state ,
261+ workflowContext ,
262+ l -> l .onWorkflowStatusChanged (new WorkflowStatusEvent (workflowContext , prevState , state )));
274263 }
275264
276265 @ Override
@@ -291,28 +280,30 @@ public boolean suspend() {
291280 WorkflowStatus prevState = internalSuspend ();
292281 boolean result = prevState != WorkflowStatus .SUSPENDED ;
293282 if (result ) {
294- publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
295- .thenCompose (
296- __ ->
297- publishEvent (
298- workflowContext ,
299- l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext ))));
283+ outOfOrder (
284+ publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
285+ .thenCompose (
286+ changed ->
287+ publishEvent (
288+ changed ,
289+ workflowContext ,
290+ l ->
291+ l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext )))));
300292 }
301293 return result ;
302294 }
303295
304296 @ Override
305297 public CompletableFuture <Boolean > suspendFuture () {
306298 WorkflowStatus prevState = internalSuspend ();
307- return prevState != WorkflowStatus . SUSPENDED
308- ? publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
299+ return outOfOrder (
300+ publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
309301 .thenCompose (
310- __ ->
302+ changed ->
311303 publishEvent (
304+ changed ,
312305 workflowContext ,
313- l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext ))))
314- .thenApply (__ -> true )
315- : CompletableFuture .completedFuture (false );
306+ l -> l .onWorkflowSuspended (new WorkflowSuspendedEvent (workflowContext )))));
316307 }
317308
318309 private WorkflowStatus internalSuspend () {
@@ -338,28 +329,29 @@ public boolean resume() {
338329 WorkflowStatus prevStatus = internalResume ();
339330 boolean result = prevStatus != WorkflowStatus .RUNNING ;
340331 if (result ) {
341- publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
342- .thenCompose (
343- __ ->
344- publishEvent (
345- workflowContext ,
346- l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext ))));
332+ outOfOrder (
333+ publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
334+ .thenCompose (
335+ changed ->
336+ publishEvent (
337+ changed ,
338+ workflowContext ,
339+ l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext )))));
347340 }
348341 return result ;
349342 }
350343
351344 @ Override
352345 public CompletableFuture <Boolean > resumeFuture () {
353346 WorkflowStatus prevStatus = internalResume ();
354- return prevStatus != WorkflowStatus . RUNNING
355- ? publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
347+ return outOfOrder (
348+ publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
356349 .thenCompose (
357- __ ->
350+ change ->
358351 publishEvent (
352+ change ,
359353 workflowContext ,
360- l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext ))))
361- .thenApply (__ -> true )
362- : CompletableFuture .completedFuture (false );
354+ l -> l .onWorkflowResumed (new WorkflowResumedEvent (workflowContext )))));
363355 }
364356
365357 private WorkflowStatus internalResume () {
@@ -382,13 +374,12 @@ private WorkflowStatus internalResume() {
382374 return result ;
383375 }
384376
385- public CompletableFuture <TaskContext > cancelCheck (TaskContext t ) {
377+ public < T > CompletableFuture <T > cancelCheck (T t ) {
386378 try {
387379 statusLock .lock ();
388380 if (status .get () == WorkflowStatus .CANCELLED ) {
389- CompletableFuture <TaskContext > cancelled = new CompletableFuture <TaskContext >();
390- cancelled .completeExceptionally (
391- new CancellationException ("Task " + t .taskName () + " has been cancelled" ));
381+ CompletableFuture <T > cancelled = new CompletableFuture <>();
382+ cancelled .completeExceptionally (new CancellationException (t + " has been cancelled" ));
392383 return cancelled ;
393384 }
394385 } finally {
@@ -419,68 +410,53 @@ public CompletableFuture<TaskContext> suspendedCheck(TaskContext t) {
419410
420411 @ Override
421412 public boolean cancel () {
422- CancelRequest request = internalCancel ();
423- if (request .accepted ()) {
424- publishCancelled (request );
425- }
426- return request .accepted ();
413+ CompletableFuture <Boolean > result = internalCancel ();
414+ return !result .isDone () || result .join ();
427415 }
428416
429417 @ Override
430418 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- }
419+ return internalCancel ();
464420 }
465421
466- private CancelRequest internalCancel () {
422+ private CompletableFuture <Boolean > internalCancel () {
423+ WorkflowStatus prevState ;
424+ Collection <CompletableFuture <?>> toCancel = null ;
467425 try {
468426 statusLock .lock ();
469427 if (TaskExecutorHelper .isActive (status .get ())) {
470- Collection < CompletableFuture <?>> toCancel = new ArrayList <>(cancelables );
428+ toCancel = new ArrayList <>(cancelables );
471429 cancelables .clear ();
472- CompletableFuture <Void > published = new CompletableFuture <>();
473- cancelPublished = published ;
474- return new CancelRequest (status .getAndSet (WorkflowStatus .CANCELLED ), toCancel , published );
430+ prevState = status .getAndSet (WorkflowStatus .CANCELLED );
475431 } else {
476- return new CancelRequest (
477- WorkflowStatus .CANCELLED ,
478- Collections .emptyList (),
479- CompletableFuture .completedFuture (null ));
432+ prevState = WorkflowStatus .CANCELLED ;
480433 }
481434 } finally {
482435 statusLock .unlock ();
483436 }
437+ CompletableFuture <Boolean > result =
438+ outOfOrder (
439+ publishStatusChange (prevState , WorkflowStatus .CANCELLED )
440+ .thenCompose (
441+ changed ->
442+ publishEvent (
443+ changed ,
444+ workflowContext ,
445+ l ->
446+ l .onWorkflowCancelled (
447+ new WorkflowCancelledEvent (workflowContext )))));
448+
449+ if (prevState != WorkflowStatus .CANCELLED && toCancel != null ) {
450+ toCancel .forEach (t -> t .cancel (true ));
451+ }
452+ return result ;
453+ }
454+
455+ private CompletableFuture <Boolean > outOfOrder (CompletableFuture <Boolean > future ) {
456+ if (!future .isDone ()) {
457+ outOfOrderListeners .add (future );
458+ }
459+ return future ;
484460 }
485461
486462 public void addCancelable (CompletableFuture <?> cancelable ) {
0 commit comments