@@ -113,6 +113,7 @@ enum HandleEventStatus {
113
113
114
114
private final Map <Long , EntityStateMachine > stateMachines = new HashMap <>();
115
115
116
+ /** Key is the protocol instance id */
116
117
private final Map <String , EntityStateMachine > protocolStateMachines = new HashMap <>();
117
118
118
119
private final Queue <Message > messageOutbox = new ArrayDeque <>();
@@ -126,9 +127,19 @@ enum HandleEventStatus {
126
127
private final Queue <CancellableCommand > cancellableCommands = new ArrayDeque <>();
127
128
128
129
/**
129
- * Is workflow executing new code or replaying from the history. Note that this flag ALWAYS flips
130
- * to true for the time when we apply events from the server even if the commands were created by
131
- * an actual execution with replaying=false.
130
+ * Is workflow executing new code or replaying from the history. The definition of replaying here
131
+ * is that we are no longer replaying as soon as we see new events that have never been seen or
132
+ * produced by the SDK.
133
+ *
134
+ * <p>Specifically, replay ends once we have seen any non-command event (IE: events that aren't a
135
+ * result of something we produced in the SDK) on a WFT which has the final event in history
136
+ * (meaning we are processing the most recent WFT and there are no more subsequent WFTs). WFT
137
+ * Completed in this case does not count as a non-command event, because that will typically show
138
+ * up as the first event in an incremental history, and we want to ignore it and its associated
139
+ * commands since we "produced" them.
140
+ *
141
+ * <p>Note: that this flag ALWAYS flips to true for the time when we apply events from the server
142
+ * even if the commands were created by an actual execution with replaying=false.
132
143
*/
133
144
private boolean replaying ;
134
145
@@ -160,6 +171,12 @@ enum HandleEventStatus {
160
171
161
172
private List <Message > messages = new ArrayList <>();
162
173
174
+ /**
175
+ * Set of accepted durably admitted updates by update id a "durably admitted" update is one with
176
+ * an UPDATE_ADMITTED event.
177
+ */
178
+ private final Set <String > acceptedUpdates = new HashSet <>();
179
+
163
180
private final SdkFlags flags ;
164
181
165
182
public WorkflowStateMachines (
@@ -277,18 +294,19 @@ public void handleEvent(HistoryEvent event, boolean hasNextEvent) {
277
294
* Handle an events batch for one workflow task. Events that are related to one workflow task
278
295
* during replay should be prefetched and supplied in one batch.
279
296
*
280
- * @param events events belong to one workflow task
281
- * @param hasNextEvent true if there are more events in the history follow this batch, false if
297
+ * @param eventBatch events belong to one workflow task
298
+ * @param hasNextBatch true if there are more events in the history follow this batch, false if
282
299
* this batch contains the last events of the history
283
300
*/
284
- private void handleEventsBatch (List <HistoryEvent > events , boolean hasNextEvent ) {
301
+ private void handleEventsBatch (WFTBuffer .EventBatch eventBatch , boolean hasNextBatch ) {
302
+ List <HistoryEvent > events = eventBatch .getEvents ();
285
303
if (EventType .EVENT_TYPE_WORKFLOW_EXECUTION_STARTED .equals (events .get (0 ).getEventType ())) {
286
304
for (SdkFlag flag : initialFlags ) {
287
305
flags .tryUseSdkFlag (flag );
288
306
}
289
307
}
290
308
291
- if (EventType . EVENT_TYPE_WORKFLOW_TASK_STARTED . equals ( events . get ( 0 ). getEventType () )) {
309
+ if (eventBatch . getWorkflowTaskCompletedEvent (). isPresent ( )) {
292
310
for (HistoryEvent event : events ) {
293
311
handleSingleEventLookahead (event );
294
312
}
@@ -304,7 +322,10 @@ private void handleEventsBatch(List<HistoryEvent> events, boolean hasNextEvent)
304
322
}
305
323
306
324
try {
307
- handleSingleEvent (event , iterator .hasNext () || hasNextEvent );
325
+ boolean isLastTask =
326
+ !hasNextBatch && !eventBatch .getWorkflowTaskCompletedEvent ().isPresent ();
327
+ boolean hasNextEvent = iterator .hasNext () || hasNextBatch ;
328
+ handleSingleEvent (event , isLastTask , hasNextEvent );
308
329
} catch (RuntimeException e ) {
309
330
throw createEventProcessingException (e , event );
310
331
}
@@ -330,13 +351,20 @@ private void handleSingleEventLookahead(HistoryEvent event) {
330
351
// Look ahead to infer protocol messages
331
352
WorkflowExecutionUpdateAcceptedEventAttributes updateEvent =
332
353
event .getWorkflowExecutionUpdateAcceptedEventAttributes ();
333
- this .messages .add (
334
- Message .newBuilder ()
335
- .setId (updateEvent .getAcceptedRequestMessageId ())
336
- .setProtocolInstanceId (updateEvent .getProtocolInstanceId ())
337
- .setEventId (updateEvent .getAcceptedRequestSequencingEventId ())
338
- .setBody (Any .pack (updateEvent .getAcceptedRequest ()))
339
- .build ());
354
+ // If an EXECUTION_UPDATE_ACCEPTED event does not have an accepted request, then it
355
+ // must be from an admitted update. This is the only way to infer an admitted update was
356
+ // accepted.
357
+ if (!updateEvent .hasAcceptedRequest ()) {
358
+ acceptedUpdates .add (updateEvent .getProtocolInstanceId ());
359
+ } else {
360
+ messages .add (
361
+ Message .newBuilder ()
362
+ .setId (updateEvent .getAcceptedRequestMessageId ())
363
+ .setProtocolInstanceId (updateEvent .getProtocolInstanceId ())
364
+ .setEventId (updateEvent .getAcceptedRequestSequencingEventId ())
365
+ .setBody (Any .pack (updateEvent .getAcceptedRequest ()))
366
+ .build ());
367
+ }
340
368
break ;
341
369
case EVENT_TYPE_WORKFLOW_TASK_COMPLETED :
342
370
WorkflowTaskCompletedEventAttributes completedEvent =
@@ -352,6 +380,9 @@ private void handleSingleEventLookahead(HistoryEvent event) {
352
380
}
353
381
flags .setSdkFlag (sdkFlag );
354
382
}
383
+ // Remove any finished update protocol state machines. We can't remove them on an event like
384
+ // other state machines because a rejected update produces no event in history.
385
+ protocolStateMachines .entrySet ().removeIf (entry -> entry .getValue ().isFinalState ());
355
386
break ;
356
387
}
357
388
}
@@ -416,16 +447,17 @@ private void handleSingleMessage(Message message) {
416
447
stateMachine .handleMessage (message );
417
448
}
418
449
419
- private void handleSingleEvent (HistoryEvent event , boolean hasNextEvent ) {
450
+ private void handleSingleEvent (HistoryEvent event , boolean lastTask , boolean hasNextEvent ) {
420
451
if (isCommandEvent (event )) {
421
452
handleCommandEvent (event );
422
453
return ;
423
454
}
424
455
456
+ // We don't explicitly check if the event is a command event here because it's already handled
457
+ // above.
425
458
if (replaying
426
- && !hasNextEvent
427
- && (event .getEventType () == EventType .EVENT_TYPE_WORKFLOW_TASK_STARTED
428
- || WorkflowExecutionUtils .isWorkflowTaskClosedEvent (event ))) {
459
+ && lastTask
460
+ && event .getEventType () != EventType .EVENT_TYPE_WORKFLOW_TASK_COMPLETED ) {
429
461
replaying = false ;
430
462
}
431
463
@@ -705,6 +737,20 @@ private void handleNonStatefulEvent(HistoryEvent event, boolean hasNextEvent) {
705
737
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED :
706
738
callbacks .cancel (event );
707
739
break ;
740
+ case EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED :
741
+ WorkflowExecutionUpdateAdmittedEventAttributes admittedEvent =
742
+ event .getWorkflowExecutionUpdateAdmittedEventAttributes ();
743
+ Message msg =
744
+ Message .newBuilder ()
745
+ .setId (admittedEvent .getRequest ().getMeta ().getUpdateId () + "/request" )
746
+ .setProtocolInstanceId (admittedEvent .getRequest ().getMeta ().getUpdateId ())
747
+ .setEventId (event .getEventId ())
748
+ .setBody (Any .pack (admittedEvent .getRequest ()))
749
+ .build ();
750
+ if (replaying && acceptedUpdates .remove (msg .getProtocolInstanceId ()) || !replaying ) {
751
+ messages .add (msg );
752
+ }
753
+ break ;
708
754
case EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT :
709
755
case UNRECOGNIZED :
710
756
break ;
@@ -1028,9 +1074,6 @@ public Functions.Proc scheduleLocalActivityTask(
1028
1074
1029
1075
/** Validates that command matches the event during replay. */
1030
1076
private void validateCommand (Command command , HistoryEvent event ) {
1031
- // TODO(maxim): Add more thorough validation logic. For example check if activity IDs are
1032
- // matching.
1033
-
1034
1077
// ProtocolMessageCommand is different from other commands because it can be associated with
1035
1078
// multiple types of events
1036
1079
// TODO(#1781) Validate protocol message is expected type.
@@ -1291,6 +1334,7 @@ private OptionalLong getInitialCommandEventId(HistoryEvent event) {
1291
1334
case EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT :
1292
1335
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED :
1293
1336
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED :
1337
+ case EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED :
1294
1338
return OptionalLong .of (event .getEventId ());
1295
1339
1296
1340
default :
0 commit comments