37
37
import io .temporal .api .history .v1 .ActivityTaskTimedOutEventAttributes ;
38
38
import io .temporal .workflow .Functions ;
39
39
import java .util .Optional ;
40
+ import javax .annotation .Nonnull ;
40
41
41
42
final class ActivityStateMachine
42
43
extends EntityStateMachineInitialCommand <
@@ -54,7 +55,7 @@ final class ActivityStateMachine
54
55
private final ActivityType activityType ;
55
56
private final ActivityCancellationType cancellationType ;
56
57
57
- private final Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ;
58
+ private final Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ;
58
59
59
60
private ExecuteActivityParameters parameters ;
60
61
@@ -107,7 +108,7 @@ enum State {
107
108
State .SCHEDULE_COMMAND_CREATED ,
108
109
ExplicitEvent .CANCEL ,
109
110
State .CANCELED ,
110
- ActivityStateMachine ::cancelCommandNotifyCanceled )
111
+ ActivityStateMachine ::cancelCommandNotifyCanceledImmediately )
111
112
.add (
112
113
State .SCHEDULED_EVENT_RECORDED ,
113
114
EventType .EVENT_TYPE_ACTIVITY_TASK_STARTED ,
@@ -147,7 +148,7 @@ enum State {
147
148
State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
148
149
CommandType .COMMAND_TYPE_REQUEST_CANCEL_ACTIVITY_TASK ,
149
150
State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
150
- ActivityStateMachine ::notifyCanceledIfTryCancel )
151
+ ActivityStateMachine ::notifyCanceledIfTryCancelImmediately )
151
152
.add (
152
153
State .SCHEDULED_ACTIVITY_CANCEL_COMMAND_CREATED ,
153
154
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
175
176
State .SCHEDULED_ACTIVITY_CANCEL_EVENT_RECORDED ,
176
177
EventType .EVENT_TYPE_ACTIVITY_TASK_CANCELED ,
177
178
State .CANCELED ,
178
- ActivityStateMachine ::notifyCanceled )
179
+ ActivityStateMachine ::notifyCanceledFromEvent )
179
180
.add (
180
181
State .SCHEDULED_ACTIVITY_CANCEL_EVENT_RECORDED ,
181
182
EventType .EVENT_TYPE_ACTIVITY_TASK_STARTED ,
@@ -193,7 +194,7 @@ applied to the state machine (as it is done for all command events before
193
194
State .STARTED_ACTIVITY_CANCEL_COMMAND_CREATED ,
194
195
EventType .EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED ,
195
196
State .STARTED_ACTIVITY_CANCEL_EVENT_RECORDED ,
196
- ActivityStateMachine ::notifyCanceledIfTryCancel )
197
+ ActivityStateMachine ::notifyCanceledIfTryCancelFromEvent )
197
198
/*
198
199
These state transitions are not possible.
199
200
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
247
248
*/
248
249
public static ActivityStateMachine newInstance (
249
250
ExecuteActivityParameters parameters ,
250
- Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ,
251
+ Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ,
251
252
Functions .Proc1 <CancellableCommand > commandSink ,
252
253
Functions .Proc1 <StateMachine > stateMachineSink ) {
253
254
return new ActivityStateMachine (parameters , completionCallback , commandSink , stateMachineSink );
254
255
}
255
256
256
257
private ActivityStateMachine (
257
258
ExecuteActivityParameters parameters ,
258
- Functions .Proc2 <Optional <Payloads >, Failure > completionCallback ,
259
+ Functions .Proc2 <Optional <Payloads >, FailureResult > completionCallback ,
259
260
Functions .Proc1 <CancellableCommand > commandSink ,
260
261
Functions .Proc1 <StateMachine > stateMachineSink ) {
261
262
super (STATE_MACHINE_DEFINITION , commandSink , stateMachineSink );
@@ -276,32 +277,76 @@ public void createScheduleActivityTaskCommand() {
276
277
.build ());
277
278
}
278
279
280
+ private void setStartedCommandEventId () {
281
+ startedCommandEventId = currentEvent .getEventId ();
282
+ }
283
+
279
284
public void cancel () {
280
285
if (cancellationType == ActivityCancellationType .ABANDON ) {
281
- notifyCanceled ();
286
+ notifyCanceled (false );
282
287
} else if (!isFinalState ()) {
283
288
explicitEvent (ExplicitEvent .CANCEL );
284
289
}
285
290
}
286
291
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.
290
294
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 () {
292
300
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?
293
306
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 );
295
318
}
296
319
}
297
320
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 () {
299
330
if (cancellationType == ActivityCancellationType .TRY_CANCEL ) {
300
- notifyCanceled ();
331
+ notifyCanceled (true );
301
332
}
302
333
}
303
334
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 ) {
305
350
Failure canceledFailure =
306
351
Failure .newBuilder ()
307
352
.setSource (JAVA_SDK )
@@ -321,7 +366,7 @@ private void notifyCanceled() {
321
366
.setCause (canceledFailure )
322
367
.setMessage (ACTIVITY_CANCELED_MESSAGE )
323
368
.build ();
324
- completionCallback .apply (Optional .empty (), failure );
369
+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , fromEvent ) );
325
370
}
326
371
327
372
private void notifyCompleted () {
@@ -349,7 +394,7 @@ private void notifyFailed() {
349
394
.setCause (failed .getFailure ())
350
395
.setMessage (ACTIVITY_FAILED_MESSAGE )
351
396
.build ();
352
- completionCallback .apply (Optional .empty (), failure );
397
+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
353
398
}
354
399
355
400
private void notifyTimedOut () {
@@ -370,7 +415,7 @@ private void notifyTimedOut() {
370
415
.setCause (timedOut .getFailure ())
371
416
.setMessage (ACTIVITY_TIMED_OUT_MESSAGE )
372
417
.build ();
373
- completionCallback .apply (Optional .empty (), failure );
418
+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
374
419
}
375
420
376
421
private void notifyCancellationFromEvent () {
@@ -398,7 +443,7 @@ private void notifyCancellationFromEvent() {
398
443
.setMessage (ACTIVITY_CANCELED_MESSAGE )
399
444
.build ();
400
445
401
- completionCallback .apply (Optional .empty (), failure );
446
+ completionCallback .apply (Optional .empty (), new FailureResult ( failure , true ) );
402
447
}
403
448
}
404
449
@@ -412,4 +457,27 @@ private void createRequestCancelActivityTaskCommand() {
412
457
.build ());
413
458
parameters = null ; // avoid retaining large input for the duration of the activity
414
459
}
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
+ }
415
483
}
0 commit comments