3737import io .temporal .api .history .v1 .ActivityTaskTimedOutEventAttributes ;
3838import io .temporal .workflow .Functions ;
3939import java .util .Optional ;
40+ import javax .annotation .Nonnull ;
4041
4142final class ActivityStateMachine
4243 extends EntityStateMachineInitialCommand <
@@ -54,7 +55,7 @@ final class ActivityStateMachine
5455 private final ActivityType activityType ;
5556 private final ActivityCancellationType cancellationType ;
5657
57- private final Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ;
58+ private final Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ;
5859
5960 private ExecuteActivityParameters parameters ;
6061
@@ -107,7 +108,7 @@ enum State {
107108 State .SCHEDULE_COMMAND_CREATED ,
108109 ExplicitEvent .CANCEL ,
109110 State .CANCELED ,
110- ActivityStateMachine ::cancelCommandNotifyCanceled )
111+ ActivityStateMachine ::cancelCommandNotifyCanceledImmediately )
111112 .add (
112113 State .SCHEDULED_EVENT_RECORDED ,
113114 EventType .EVENT_TYPE_ACTIVITY_TASK_STARTED ,
@@ -147,7 +148,7 @@ enum State {
147148 State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
148149 CommandType .COMMAND_TYPE_REQUEST_CANCEL_ACTIVITY_TASK ,
149150 State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
150- ActivityStateMachine ::notifyCanceledIfTryCancel )
151+ ActivityStateMachine ::notifyCanceledIfTryCancelImmediately )
151152 .add (
152153 State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
153154 EventType .EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED ,
@@ -175,7 +176,7 @@ applied to the state machine (as it is done for all command events before
175176 State .SCHEDULED_ACTIVITY_CANCEL_EVENT_RECORDED ,
176177 EventType .EVENT_TYPE_ACTIVITY_TASK_CANCELED ,
177178 State .CANCELED ,
178- ActivityStateMachine ::notifyCanceled )
179+ ActivityStateMachine ::notifyCanceledFromEvent )
179180 .add (
180181 State .SCHEDULED_ACTIVITY_CANCEL_EVENT_RECORDED ,
181182 EventType .EVENT_TYPE_ACTIVITY_TASK_STARTED ,
@@ -193,7 +194,7 @@ applied to the state machine (as it is done for all command events before
193194 State .STARTED_ACTIVITY_CANCEL_COMMAND_CREATED ,
194195 EventType .EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED ,
195196 State .STARTED_ACTIVITY_CANCEL_EVENT_RECORDED ,
196- ActivityStateMachine ::notifyCanceledIfTryCancel )
197+ ActivityStateMachine ::notifyCanceledIfTryCancelFromEvent )
197198 /*
198199 These state transitions are not possible.
199200 It looks like it is valid when an event, handling of which requests activity
@@ -247,15 +248,15 @@ applied to the state machine (as it is done for all command events before
247248 */
248249 public static ActivityStateMachine newInstance (
249250 ExecuteActivityParameters parameters ,
250- Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ,
251+ Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ,
251252 Functions .Proc1 <CancellableCommand > commandSink ,
252253 Functions .Proc1 <StateMachine > stateMachineSink ) {
253254 return new ActivityStateMachine (parameters , completionCallback , commandSink , stateMachineSink );
254255 }
255256
256257 private ActivityStateMachine (
257258 ExecuteActivityParameters parameters ,
258- Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ,
259+ Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ,
259260 Functions .Proc1 <CancellableCommand > commandSink ,
260261 Functions .Proc1 <StateMachine > stateMachineSink ) {
261262 super (STATE_MACHINE_DEFINITION , commandSink , stateMachineSink );
@@ -276,32 +277,76 @@ public void createScheduleActivityTaskCommand() {
276277 .build ());
277278 }
278279
280+ private void setStartedCommandEventId () {
281+ startedCommandEventId = currentEvent .getEventId ();
282+ }
283+
279284 public void cancel () {
280285 if (cancellationType == ActivityCancellationType .ABANDON ) {
281- notifyCanceled ();
286+ notifyCanceled (false );
282287 } else if (!isFinalState ()) {
283288 explicitEvent (ExplicitEvent .CANCEL );
284289 }
285290 }
286291
287- private void setStartedCommandEventId () {
288- startedCommandEventId = currentEvent .getEventId ();
289- }
292+ // *Immediately versions don't wait for a matching command, underlying callback will trigger an
293+ // event loop so the workflow can make progress, because promise gets filled.
290294
291- private void cancelCommandNotifyCanceled () {
295+ /**
296+ * {@link CommandType#COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK} command is not yet left to the server.
297+ * Cancel it in place and immediately notify the workflow code.
298+ */
299+ private void cancelCommandNotifyCanceledImmediately () {
292300 cancelCommand ();
301+ // TODO With {@link ActivityCancellationType#ABANDON} we shouldn't even get here as it gets
302+ // handled in #cancel.
303+ // It's a code path for TRY_CANCEL and WAIT_CANCELLATION_COMPLETED only.
304+ // Was the original design to cancel a not-yet-sent
305+ // {@link CommandType#COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK} in case of ABANDON too?
293306 if (cancellationType != ActivityCancellationType .ABANDON ) {
294- notifyCanceled ();
307+ notifyCanceled (false );
308+ }
309+ }
310+
311+ /**
312+ * Workflow code doesn't need to wait for the cancellation event if {@link
313+ * ActivityCancellationType#TRY_CANCEL}, immediately notify the workflow code.
314+ */
315+ private void notifyCanceledIfTryCancelImmediately () {
316+ if (cancellationType == ActivityCancellationType .TRY_CANCEL ) {
317+ notifyCanceled (false );
295318 }
296319 }
297320
298- private void notifyCanceledIfTryCancel () {
321+ // *FromEvent versions will not trigger event loop as they need to wait for all events to be
322+ // applied before and there will be WorkflowTaskStarted to trigger the event loop.
323+
324+ /**
325+ * if {@link EventType#EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED} is observed, notify the workflow
326+ * code if {@link ActivityCancellationType#TRY_CANCEL}, this mode doesn't need a confirmation of
327+ * cancellation.
328+ */
329+ private void notifyCanceledIfTryCancelFromEvent () {
299330 if (cancellationType == ActivityCancellationType .TRY_CANCEL ) {
300- notifyCanceled ();
331+ notifyCanceled (true );
301332 }
302333 }
303334
304- private void notifyCanceled () {
335+ /**
336+ * Notify workflow code of the cancellation from the {@link
337+ * EventType#EVENT_TYPE_ACTIVITY_TASK_CANCELED} event.
338+ *
339+ * <p>There is no harm in notifying {@link ActivityCancellationType#TRY_CANCEL} again, but it
340+ * should not be needed as it should be already done by {@link
341+ * #notifyCanceledIfTryCancelFromEvent} as there should be no {@link
342+ * EventType#EVENT_TYPE_ACTIVITY_TASK_CANCELED} without {@link
343+ * EventType#EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED}.
344+ */
345+ private void notifyCanceledFromEvent () {
346+ notifyCanceled (true );
347+ }
348+
349+ private void notifyCanceled (boolean fromEvent ) {
305350 Failure canceledFailure =
306351 Failure .newBuilder ()
307352 .setSource (JAVA_SDK )
@@ -321,7 +366,7 @@ private void notifyCanceled() {
321366 .setCause (canceledFailure )
322367 .setMessage (ACTIVITY_CANCELED_MESSAGE )
323368 .build ();
324- completionCallback .apply (Optional .empty (), failure );
369+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , fromEvent ) );
325370 }
326371
327372 private void notifyCompleted () {
@@ -349,7 +394,7 @@ private void notifyFailed() {
349394 .setCause (failed .getFailure ())
350395 .setMessage (ACTIVITY_FAILED_MESSAGE )
351396 .build ();
352- completionCallback .apply (Optional .empty (), failure );
397+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
353398 }
354399
355400 private void notifyTimedOut () {
@@ -370,7 +415,7 @@ private void notifyTimedOut() {
370415 .setCause (timedOut .getFailure ())
371416 .setMessage (ACTIVITY_TIMED_OUT_MESSAGE )
372417 .build ();
373- completionCallback .apply (Optional .empty (), failure );
418+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
374419 }
375420
376421 private void notifyCancellationFromEvent () {
@@ -398,7 +443,7 @@ private void notifyCancellationFromEvent() {
398443 .setMessage (ACTIVITY_CANCELED_MESSAGE )
399444 .build ();
400445
401- completionCallback .apply (Optional .empty (), failure );
446+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
402447 }
403448 }
404449
@@ -412,4 +457,27 @@ private void createRequestCancelActivityTaskCommand() {
412457 .build ());
413458 parameters = null ; // avoid retaining large input for the duration of the activity
414459 }
460+
461+ public static class FailureResult {
462+ private final @ Nonnull Failure failure ;
463+ private final boolean fromEvent ;
464+
465+ public FailureResult (@ Nonnull Failure failure , boolean fromEvent ) {
466+ this .failure = failure ;
467+ this .fromEvent = fromEvent ;
468+ }
469+
470+ @ Nonnull
471+ public Failure getFailure () {
472+ return failure ;
473+ }
474+
475+ /**
476+ * @return true if this failure is created from the event during event <-> command matching
477+ * phase.
478+ */
479+ public boolean isFromEvent () {
480+ return fromEvent ;
481+ }
482+ }
415483}
0 commit comments