Skip to content

Commit a41c64e

Browse files
Add support for update admitted event (#2041)
Add support for update admitted event
1 parent 0013675 commit a41c64e

File tree

5 files changed

+605
-42
lines changed

5 files changed

+605
-42
lines changed

temporal-sdk/src/main/java/io/temporal/internal/statemachines/WFTBuffer.java

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020

2121
package io.temporal.internal.statemachines;
2222

23-
import com.google.common.base.Preconditions;
2423
import io.temporal.api.enums.v1.EventType;
2524
import io.temporal.api.history.v1.HistoryEvent;
2625
import io.temporal.internal.common.WorkflowExecutionUtils;
2726
import java.util.ArrayList;
2827
import java.util.Collections;
2928
import java.util.List;
29+
import java.util.Optional;
3030

3131
/**
3232
* This class buffers events between WorkflowTaskStarted events and return them in one chunk so any
@@ -42,9 +42,29 @@ private enum WFTState {
4242
Closed,
4343
}
4444

45+
public static class EventBatch {
46+
private final List<HistoryEvent> events;
47+
private final Optional<HistoryEvent> workflowTaskCompletedEvent;
48+
49+
public EventBatch(
50+
Optional<HistoryEvent> workflowTaskCompletedEvent, List<HistoryEvent> events) {
51+
this.workflowTaskCompletedEvent = workflowTaskCompletedEvent;
52+
this.events = events;
53+
}
54+
55+
public List<HistoryEvent> getEvents() {
56+
return events;
57+
}
58+
59+
public Optional<HistoryEvent> getWorkflowTaskCompletedEvent() {
60+
return workflowTaskCompletedEvent;
61+
}
62+
}
63+
4564
private WFTState wftSequenceState = WFTState.None;
4665

4766
private final List<HistoryEvent> wftBuffer = new ArrayList<>();
67+
private Optional<HistoryEvent> workflowTaskCompletedEvent = Optional.empty();
4868
private final List<HistoryEvent> readyToFetch = new ArrayList<>();
4969

5070
/**
@@ -73,17 +93,20 @@ private void handleEvent(HistoryEvent event, boolean hasNextEvent) {
7393
// This is the only way to enter into the WFT sequence -
7494
// any WorkflowTaskStarted event that it not the last in the history
7595
if (EventType.EVENT_TYPE_WORKFLOW_TASK_STARTED.equals(event.getEventType())) {
76-
// if there is something in wftBuffer, let's flush it
77-
flushBuffer();
78-
// and init a new sequence
96+
// Init a new sequence
7997
wftSequenceState = WFTState.Started;
8098
addToBuffer(event);
8199
return;
82100
}
83101

84102
if (WFTState.Started.equals(wftSequenceState)
85103
&& WorkflowExecutionUtils.isWorkflowTaskClosedEvent(event)) {
86-
wftSequenceState = WFTState.Closed;
104+
if (event.getEventType().equals(EventType.EVENT_TYPE_WORKFLOW_TASK_COMPLETED)) {
105+
workflowTaskCompletedEvent = Optional.of(event);
106+
wftSequenceState = WFTState.Closed;
107+
} else {
108+
wftSequenceState = WFTState.None;
109+
}
87110
addToBuffer(event);
88111
return;
89112
}
@@ -94,14 +117,19 @@ private void handleEvent(HistoryEvent event, boolean hasNextEvent) {
94117
// If event is WFT_STARTED or any of the Closing events, it's handled by if statements
95118
// earlier, so it's safe to switch to None here, we are not inside WFT sequence
96119
wftSequenceState = WFTState.None;
97-
// no open WFT sequence, can't add to buffer, it's ok to add directly to readyToFetch, this
98-
// event can't be EVENT_TYPE_WORKFLOW_TASK_STARTED because we checked it above.
99-
readyToFetch.add(event);
120+
121+
addToBuffer(event);
122+
return;
123+
}
124+
if (WFTState.Closed.equals(wftSequenceState) && WorkflowExecutionUtils.isCommandEvent(event)) {
125+
// we are inside a closed WFT sequence, we can add to buffer
126+
addToBuffer(event);
100127
return;
101128
}
102129

103-
if (WFTState.None.equals(wftSequenceState)) {
104-
// we should be returning the events one by one, we are not inside a WFT sequence
130+
if (WorkflowExecutionUtils.isCommandEvent(event)
131+
|| WorkflowExecutionUtils.isWorkflowTaskClosedEvent(event)) {
132+
flushBuffer();
105133
readyToFetch.add(event);
106134
} else {
107135
addToBuffer(event);
@@ -114,21 +142,22 @@ private void flushBuffer() {
114142
}
115143

116144
private void addToBuffer(HistoryEvent event) {
117-
Preconditions.checkState(
118-
!WFTState.None.equals(wftSequenceState),
119-
"We should be inside an open WFT sequence to add to the buffer");
120145
wftBuffer.add(event);
121146
}
122147

123-
public List<HistoryEvent> fetch() {
148+
public EventBatch fetch() {
124149
if (readyToFetch.size() == 1) {
125150
HistoryEvent event = readyToFetch.get(0);
151+
Optional<HistoryEvent> wftStarted = workflowTaskCompletedEvent;
152+
workflowTaskCompletedEvent = Optional.empty();
126153
readyToFetch.clear();
127-
return Collections.singletonList(event);
154+
return new EventBatch(wftStarted, Collections.singletonList(event));
128155
} else {
129156
List<HistoryEvent> result = new ArrayList<>(readyToFetch);
157+
Optional<HistoryEvent> wftStarted = workflowTaskCompletedEvent;
158+
workflowTaskCompletedEvent = Optional.empty();
130159
readyToFetch.clear();
131-
return result;
160+
return new EventBatch(wftStarted, result);
132161
}
133162
}
134163
}

temporal-sdk/src/main/java/io/temporal/internal/statemachines/WorkflowStateMachines.java

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ enum HandleEventStatus {
113113

114114
private final Map<Long, EntityStateMachine> stateMachines = new HashMap<>();
115115

116+
/** Key is the protocol instance id */
116117
private final Map<String, EntityStateMachine> protocolStateMachines = new HashMap<>();
117118

118119
private final Queue<Message> messageOutbox = new ArrayDeque<>();
@@ -126,9 +127,19 @@ enum HandleEventStatus {
126127
private final Queue<CancellableCommand> cancellableCommands = new ArrayDeque<>();
127128

128129
/**
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.
132143
*/
133144
private boolean replaying;
134145

@@ -160,6 +171,12 @@ enum HandleEventStatus {
160171

161172
private List<Message> messages = new ArrayList<>();
162173

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+
163180
private final SdkFlags flags;
164181

165182
public WorkflowStateMachines(
@@ -277,18 +294,19 @@ public void handleEvent(HistoryEvent event, boolean hasNextEvent) {
277294
* Handle an events batch for one workflow task. Events that are related to one workflow task
278295
* during replay should be prefetched and supplied in one batch.
279296
*
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
282299
* this batch contains the last events of the history
283300
*/
284-
private void handleEventsBatch(List<HistoryEvent> events, boolean hasNextEvent) {
301+
private void handleEventsBatch(WFTBuffer.EventBatch eventBatch, boolean hasNextBatch) {
302+
List<HistoryEvent> events = eventBatch.getEvents();
285303
if (EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED.equals(events.get(0).getEventType())) {
286304
for (SdkFlag flag : initialFlags) {
287305
flags.tryUseSdkFlag(flag);
288306
}
289307
}
290308

291-
if (EventType.EVENT_TYPE_WORKFLOW_TASK_STARTED.equals(events.get(0).getEventType())) {
309+
if (eventBatch.getWorkflowTaskCompletedEvent().isPresent()) {
292310
for (HistoryEvent event : events) {
293311
handleSingleEventLookahead(event);
294312
}
@@ -304,7 +322,10 @@ private void handleEventsBatch(List<HistoryEvent> events, boolean hasNextEvent)
304322
}
305323

306324
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);
308329
} catch (RuntimeException e) {
309330
throw createEventProcessingException(e, event);
310331
}
@@ -330,13 +351,20 @@ private void handleSingleEventLookahead(HistoryEvent event) {
330351
// Look ahead to infer protocol messages
331352
WorkflowExecutionUpdateAcceptedEventAttributes updateEvent =
332353
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+
}
340368
break;
341369
case EVENT_TYPE_WORKFLOW_TASK_COMPLETED:
342370
WorkflowTaskCompletedEventAttributes completedEvent =
@@ -352,6 +380,9 @@ private void handleSingleEventLookahead(HistoryEvent event) {
352380
}
353381
flags.setSdkFlag(sdkFlag);
354382
}
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());
355386
break;
356387
}
357388
}
@@ -416,16 +447,17 @@ private void handleSingleMessage(Message message) {
416447
stateMachine.handleMessage(message);
417448
}
418449

419-
private void handleSingleEvent(HistoryEvent event, boolean hasNextEvent) {
450+
private void handleSingleEvent(HistoryEvent event, boolean lastTask, boolean hasNextEvent) {
420451
if (isCommandEvent(event)) {
421452
handleCommandEvent(event);
422453
return;
423454
}
424455

456+
// We don't explicitly check if the event is a command event here because it's already handled
457+
// above.
425458
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) {
429461
replaying = false;
430462
}
431463

@@ -705,6 +737,20 @@ private void handleNonStatefulEvent(HistoryEvent event, boolean hasNextEvent) {
705737
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
706738
callbacks.cancel(event);
707739
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;
708754
case EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
709755
case UNRECOGNIZED:
710756
break;
@@ -1028,9 +1074,6 @@ public Functions.Proc scheduleLocalActivityTask(
10281074

10291075
/** Validates that command matches the event during replay. */
10301076
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-
10341077
// ProtocolMessageCommand is different from other commands because it can be associated with
10351078
// multiple types of events
10361079
// TODO(#1781) Validate protocol message is expected type.
@@ -1291,6 +1334,7 @@ private OptionalLong getInitialCommandEventId(HistoryEvent event) {
12911334
case EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT:
12921335
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED:
12931336
case EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED:
1337+
case EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED:
12941338
return OptionalLong.of(event.getEventId());
12951339

12961340
default:

temporal-sdk/src/test/java/io/temporal/internal/statemachines/TestHistoryBuilder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,10 @@ private HistoryEvent newAttributes(EventType type, Object attributes) {
537537
result.setChildWorkflowExecutionTerminatedEventAttributes(
538538
(ChildWorkflowExecutionTerminatedEventAttributes) attributes);
539539
break;
540+
case EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED:
541+
result.setWorkflowExecutionUpdateAdmittedEventAttributes(
542+
(WorkflowExecutionUpdateAdmittedEventAttributes) attributes);
543+
break;
540544
case EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ACCEPTED:
541545
result.setWorkflowExecutionUpdateAcceptedEventAttributes(
542546
(WorkflowExecutionUpdateAcceptedEventAttributes) attributes);

0 commit comments

Comments
 (0)