Skip to content

Commit 7e3e36d

Browse files
committed
[Fix #1749] Alternative approach
Signed-off-by: Francisco Javier Tirado Sarti <ftirados@ibm.com>
1 parent 8085c7b commit 7e3e36d

2 files changed

Lines changed: 108 additions & 123 deletions

File tree

‎impl/core/src/main/java/io/serverlessworkflow/impl/LifecycleEventsUtils.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ public class LifecycleEventsUtils {
2828

2929
private LifecycleEventsUtils() {}
3030

31+
public static CompletableFuture<Boolean> publishEvent(
32+
boolean statusChanged,
33+
WorkflowContext workflowContext,
34+
Function<WorkflowExecutionCompletableListener, CompletableFuture<?>> function) {
35+
return statusChanged
36+
? publishEvent(workflowContext, function).thenApply(__ -> true)
37+
: CompletableFuture.completedFuture(false);
38+
}
39+
3140
public static CompletableFuture<?> publishEvent(
3241
WorkflowContext workflowContext,
3342
Function<WorkflowExecutionCompletableListener, CompletableFuture<?>> function) {

‎impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java‎

Lines changed: 99 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)