@@ -63,7 +63,7 @@ public class WorkflowMutableInstance implements WorkflowInstance {
6363
6464 private Collection <CompletableFuture <?>> cancelables = new ArrayList <>();
6565
66- private volatile CompletableFuture <?> cancelPublished = CompletableFuture . completedFuture ( null );
66+ private Collection < CompletableFuture <Boolean >> outOfOrderListeners = new ArrayList <>( );
6767
6868 protected WorkflowMutableInstance (WorkflowDefinition definition , String id , WorkflowModel input ) {
6969 this .id = id ;
@@ -107,35 +107,22 @@ protected final CompletableFuture<WorkflowModel> startExecution(
107107 .orElse (input ))
108108 .whenComplete (this ::setCompleteDate )
109109 .thenApply (this ::filterAndValidate )
110- .thenCompose (this ::publishEvents )
110+ .thenCompose (this ::cancelCheck )
111+ .thenCompose (this ::publishCompletionEvents )
111112 .exceptionallyCompose (this ::handleException ))
112- .handle (this ::cleanUpAfterCancelPublished )
113+ .handle (this ::cleanUpWaitingListeners )
113114 .thenCompose (Function .identity ());
114115 futureRef .set (future );
115116 }
116117 return future ;
117118 }
118119
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 ) {
120+ private CompletableFuture <WorkflowModel > publishCompletionEvents (WorkflowModel model ) {
135121 return status (WorkflowStatus .COMPLETED )
136122 .thenCompose (
137- __ ->
123+ changed ->
138124 publishEvent (
125+ changed ,
139126 workflowContext ,
140127 l -> l .onWorkflowCompleted (new WorkflowCompletedEvent (workflowContext , model ))))
141128 .thenApply (__ -> model );
@@ -145,7 +132,19 @@ 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 )
@@ -157,16 +156,23 @@ private void cleanUp(WorkflowModel result, Throwable ex) {
157156 private CompletableFuture <WorkflowModel > handleException (Throwable exception ) {
158157 final Throwable cause =
159158 exception instanceof CompletionException ? exception .getCause () : exception ;
160- if (!(cause instanceof CancellationException )) {
159+ if (cause instanceof CancellationException ) {
160+ return CompletableFuture .failedFuture (cause );
161+ } else {
161162 return status (WorkflowStatus .FAULTED )
162163 .thenCompose (
163- __ ->
164+ changed ->
164165 publishEvent (
166+ changed ,
165167 workflowContext ,
166168 l -> l .onWorkflowFailed (new WorkflowFailedEvent (workflowContext , cause ))))
167- .thenCompose (__ -> CompletableFuture .failedFuture (exception ));
169+ .thenCompose (
170+ failed ->
171+ CompletableFuture .failedFuture (
172+ failed
173+ ? exception
174+ : new CancellationException ("Workflow has been cancelled" )));
168175 }
169- return CompletableFuture .failedFuture (exception );
170176 }
171177
172178 private WorkflowModel filterAndValidate (WorkflowModel model ) {
@@ -232,45 +238,43 @@ public <T> T outputAs(Class<T> clazz) {
232238 : null ;
233239 }
234240
235- public CompletableFuture <Boolean > status (WorkflowStatus state ) {
241+ public CompletableFuture <Boolean > status (WorkflowStatus requestedState ) {
236242 WorkflowStatus prevState ;
243+ WorkflowStatus state ;
244+ statusLock .lock ();
237245 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 );
246+ state = prevState = this .status .get ();
247+ if (prevState != WorkflowStatus .CANCELLED
248+ && prevState != WorkflowStatus .COMPLETED
249+ && prevState != WorkflowStatus .FAULTED ) {
250+ state = requestedState ;
251+ status .set (requestedState );
248252 }
249- this .status .set (state );
250253 } finally {
251254 statusLock .unlock ();
252255 }
253256 return publishStatusChange (prevState , state );
254257 }
255258
256- private static boolean isTerminal (WorkflowStatus state ) {
257- return state == WorkflowStatus .COMPLETED || state == WorkflowStatus .FAULTED ;
258- }
259-
260259 protected final void setStatus (WorkflowStatus state ) {
261260 this .status .set (state );
262261 }
263262
263+ private CompletableFuture <Boolean > publishStatusChangeRequest (
264+ WorkflowStatus prevState , WorkflowStatus state ) {
265+ CompletableFuture <Boolean > future = publishStatusChange (prevState , state );
266+ if (!future .isDone ()) {
267+ outOfOrderListeners .add (future );
268+ }
269+ return future ;
270+ }
271+
264272 private CompletableFuture <Boolean > publishStatusChange (
265273 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 );
274+ return publishEvent (
275+ prevState != state ,
276+ workflowContext ,
277+ l -> l .onWorkflowStatusChanged (new WorkflowStatusEvent (workflowContext , prevState , state )));
274278 }
275279
276280 @ Override
@@ -291,7 +295,7 @@ public boolean suspend() {
291295 WorkflowStatus prevState = internalSuspend ();
292296 boolean result = prevState != WorkflowStatus .SUSPENDED ;
293297 if (result ) {
294- publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
298+ publishStatusChangeRequest (prevState , WorkflowStatus .SUSPENDED )
295299 .thenCompose (
296300 __ ->
297301 publishEvent (
@@ -305,7 +309,7 @@ public boolean suspend() {
305309 public CompletableFuture <Boolean > suspendFuture () {
306310 WorkflowStatus prevState = internalSuspend ();
307311 return prevState != WorkflowStatus .SUSPENDED
308- ? publishStatusChange (prevState , WorkflowStatus .SUSPENDED )
312+ ? publishStatusChangeRequest (prevState , WorkflowStatus .SUSPENDED )
309313 .thenCompose (
310314 __ ->
311315 publishEvent (
@@ -338,7 +342,7 @@ public boolean resume() {
338342 WorkflowStatus prevStatus = internalResume ();
339343 boolean result = prevStatus != WorkflowStatus .RUNNING ;
340344 if (result ) {
341- publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
345+ publishStatusChangeRequest (prevStatus , WorkflowStatus .RUNNING )
342346 .thenCompose (
343347 __ ->
344348 publishEvent (
@@ -352,7 +356,7 @@ public boolean resume() {
352356 public CompletableFuture <Boolean > resumeFuture () {
353357 WorkflowStatus prevStatus = internalResume ();
354358 return prevStatus != WorkflowStatus .RUNNING
355- ? publishStatusChange (prevStatus , WorkflowStatus .RUNNING )
359+ ? publishStatusChangeRequest (prevStatus , WorkflowStatus .RUNNING )
356360 .thenCompose (
357361 __ ->
358362 publishEvent (
@@ -382,13 +386,12 @@ private WorkflowStatus internalResume() {
382386 return result ;
383387 }
384388
385- public CompletableFuture <TaskContext > cancelCheck (TaskContext t ) {
389+ public < T > CompletableFuture <T > cancelCheck (T t ) {
386390 try {
387391 statusLock .lock ();
388392 if (status .get () == WorkflowStatus .CANCELLED ) {
389- CompletableFuture <TaskContext > cancelled = new CompletableFuture <TaskContext >();
390- cancelled .completeExceptionally (
391- new CancellationException ("Task " + t .taskName () + " has been cancelled" ));
393+ CompletableFuture <T > cancelled = new CompletableFuture <>();
394+ cancelled .completeExceptionally (new CancellationException (t + " has been cancelled" ));
392395 return cancelled ;
393396 }
394397 } finally {
@@ -419,68 +422,50 @@ public CompletableFuture<TaskContext> suspendedCheck(TaskContext t) {
419422
420423 @ Override
421424 public boolean cancel () {
422- CancelRequest request = internalCancel ();
423- if (request .accepted ()) {
424- publishCancelled (request );
425+ WorkflowStatus prevStatus = internalCancel ();
426+ boolean result = prevStatus != WorkflowStatus .CANCELLED ;
427+ if (result ) {
428+ publishStatusChangeRequest (prevStatus , WorkflowStatus .CANCELLED )
429+ .thenCompose (
430+ changed ->
431+ publishEvent (
432+ workflowContext ,
433+ l -> l .onWorkflowCancelled (new WorkflowCancelledEvent (workflowContext ))));
425434 }
426- return request . accepted () ;
435+ return result ;
427436 }
428437
429438 @ Override
430439 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 )
440+ WorkflowStatus prevState = internalCancel ();
441+ return publishStatusChangeRequest (prevState , WorkflowStatus .CANCELLED )
445442 .thenCompose (
446- __ ->
443+ changed ->
447444 publishEvent (
445+ changed ,
448446 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- }
447+ l -> l .onWorkflowCancelled (new WorkflowCancelledEvent (workflowContext ))));
464448 }
465449
466- private CancelRequest internalCancel () {
450+ private WorkflowStatus internalCancel () {
451+ WorkflowStatus result ;
452+ Collection <CompletableFuture <?>> toCancel = null ;
467453 try {
468454 statusLock .lock ();
469455 if (TaskExecutorHelper .isActive (status .get ())) {
470- Collection < CompletableFuture <?>> toCancel = new ArrayList <>(cancelables );
456+ toCancel = new ArrayList <>(cancelables );
471457 cancelables .clear ();
472- CompletableFuture <Void > published = new CompletableFuture <>();
473- cancelPublished = published ;
474- return new CancelRequest (status .getAndSet (WorkflowStatus .CANCELLED ), toCancel , published );
458+ result = status .getAndSet (WorkflowStatus .CANCELLED );
475459 } else {
476- return new CancelRequest (
477- WorkflowStatus .CANCELLED ,
478- Collections .emptyList (),
479- CompletableFuture .completedFuture (null ));
460+ result = WorkflowStatus .CANCELLED ;
480461 }
481462 } finally {
482463 statusLock .unlock ();
483464 }
465+ if (result != WorkflowStatus .CANCELLED && toCancel != null ) {
466+ toCancel .forEach (t -> t .cancel (true ));
467+ }
468+ return result ;
484469 }
485470
486471 public void addCancelable (CompletableFuture <?> cancelable ) {
0 commit comments