Skip to content

Commit cdde71e

Browse files
authored
Fixed getVersion removal and addition (#83)
* Refactored getVersion handling * Fixed getVersion unit tests * Workflow determinism unit tests fixed
1 parent 90cbeaa commit cdde71e

6 files changed

Lines changed: 343 additions & 109 deletions

File tree

src/main/java/io/temporal/internal/replay/ClockDecisionContext.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import java.util.function.BiConsumer;
4545
import java.util.function.BiFunction;
4646
import java.util.function.Consumer;
47-
import java.util.function.Predicate;
4847
import org.slf4j.Logger;
4948
import org.slf4j.LoggerFactory;
5049

@@ -187,7 +186,7 @@ private void timerCancelled(long startEventId, Exception reason) {
187186
}
188187

189188
byte[] sideEffect(Func<byte[]> func) {
190-
decisions.addAllMissingVersionMarker(false, Optional.empty());
189+
decisions.addAllMissingVersionMarker();
191190
long sideEffectEventId = decisions.getNextDecisionEventId();
192191
byte[] result;
193192
if (replaying) {
@@ -216,7 +215,7 @@ byte[] sideEffect(Func<byte[]> func) {
216215
*/
217216
Optional<byte[]> mutableSideEffect(
218217
String id, DataConverter converter, Func1<Optional<byte[]>, Optional<byte[]>> func) {
219-
decisions.addAllMissingVersionMarker(false, Optional.empty());
218+
decisions.addAllMissingVersionMarker();
220219
return mutableSideEffectHandler.handle(id, converter, func);
221220
}
222221

@@ -281,14 +280,24 @@ private void handleLocalActivityMarker(MarkerRecordedEventAttributes attributes)
281280
}
282281
}
283282

283+
/**
284+
* During replay getVersion should account for the following situations at the current eventId.
285+
*
286+
* <ul>
287+
* <li>There is correspondent Marker with the same changeId: return version from the marker.
288+
* <li>There is no Marker with the same changeId: return DEFAULT_VERSION,
289+
* <li>There is marker with a different changeId (possibly more than one) and the marker with
290+
* matching changeId follows them: add fake decisions for all the version markers that
291+
* precede the matching one as the correspondent getVersion calls were removed
292+
* <li>There is marker with a different changeId (possibly more than one) and no marker with
293+
* matching changeId follows them: return DEFAULT_VERSION as it looks like the getVersion
294+
* was added after that part of code has executed
295+
* <li>Another case is when there is no call to getVersion and there is a version marker: insert
296+
* fake decisions for all version markers up to the event that caused the lookup.
297+
* </ul>
298+
*/
284299
int getVersion(String changeId, DataConverter converter, int minSupported, int maxSupported) {
285-
Predicate<MarkerRecordedEventAttributes> changeIdEquals =
286-
(attributes) -> {
287-
MarkerHandler.MarkerInterface markerData =
288-
MarkerHandler.MarkerInterface.fromEventAttributes(attributes, converter);
289-
return markerData.getId().equals(changeId);
290-
};
291-
decisions.addAllMissingVersionMarker(true, Optional.of(changeIdEquals));
300+
decisions.addAllMissingVersionMarker(Optional.of(changeId), Optional.of(converter));
292301

293302
Optional<byte[]> result =
294303
versionHandler.handle(

src/main/java/io/temporal/internal/replay/DecisionsHelper.java

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package io.temporal.internal.replay;
2121

22+
import io.temporal.common.converter.DataConverter;
2223
import io.temporal.internal.common.OptionsUtils;
2324
import io.temporal.internal.common.WorkflowExecutionUtils;
2425
import io.temporal.internal.replay.HistoryHelper.DecisionEvents;
@@ -52,7 +53,6 @@
5253
import io.temporal.proto.event.EventType;
5354
import io.temporal.proto.event.ExternalWorkflowExecutionCancelRequestedEventAttributes;
5455
import io.temporal.proto.event.HistoryEvent;
55-
import io.temporal.proto.event.MarkerRecordedEventAttributes;
5656
import io.temporal.proto.event.RequestCancelActivityTaskFailedEventAttributes;
5757
import io.temporal.proto.event.RequestCancelExternalWorkflowExecutionFailedEventAttributes;
5858
import io.temporal.proto.event.StartChildWorkflowExecutionFailedEventAttributes;
@@ -70,7 +70,6 @@
7070
import java.util.Map;
7171
import java.util.Objects;
7272
import java.util.Optional;
73-
import java.util.function.Predicate;
7473

7574
class DecisionsHelper {
7675

@@ -117,7 +116,7 @@ long getNextDecisionEventId() {
117116
}
118117

119118
long scheduleActivityTask(ScheduleActivityTaskDecisionAttributes schedule) {
120-
addAllMissingVersionMarker(false, Optional.empty());
119+
addAllMissingVersionMarker();
121120

122121
long nextDecisionEventId = getNextDecisionEventId();
123122
DecisionId decisionId = new DecisionId(DecisionTarget.ACTIVITY, nextDecisionEventId);
@@ -198,7 +197,7 @@ boolean handleRequestCancelActivityTaskFailed(HistoryEvent event) {
198197
}
199198

200199
long startChildWorkflowExecution(StartChildWorkflowExecutionDecisionAttributes childWorkflow) {
201-
addAllMissingVersionMarker(false, Optional.empty());
200+
addAllMissingVersionMarker();
202201

203202
long nextDecisionEventId = getNextDecisionEventId();
204203
DecisionId decisionId = new DecisionId(DecisionTarget.CHILD_WORKFLOW, nextDecisionEventId);
@@ -269,7 +268,7 @@ boolean handleStartChildWorkflowExecutionFailed(HistoryEvent event) {
269268
*/
270269
long requestCancelExternalWorkflowExecution(
271270
RequestCancelExternalWorkflowExecutionDecisionAttributes schedule) {
272-
addAllMissingVersionMarker(false, Optional.empty());
271+
addAllMissingVersionMarker();
273272

274273
long nextDecisionEventId = getNextDecisionEventId();
275274
DecisionId decisionId =
@@ -306,7 +305,7 @@ void handleRequestCancelExternalWorkflowExecutionFailed(HistoryEvent event) {
306305
}
307306

308307
long signalExternalWorkflowExecution(SignalExternalWorkflowExecutionDecisionAttributes signal) {
309-
addAllMissingVersionMarker(false, Optional.empty());
308+
addAllMissingVersionMarker();
310309

311310
long nextDecisionEventId = getNextDecisionEventId();
312311
DecisionId decisionId =
@@ -339,7 +338,7 @@ boolean handleExternalWorkflowExecutionSignaled(long initiatedEventId) {
339338
}
340339

341340
long startTimer(StartTimerDecisionAttributes request) {
342-
addAllMissingVersionMarker(false, Optional.empty());
341+
addAllMissingVersionMarker();
343342

344343
long startEventId = getNextDecisionEventId();
345344
DecisionId decisionId = new DecisionId(DecisionTarget.TIMER, startEventId);
@@ -461,7 +460,7 @@ public void handleWorkflowExecutionCompleted(HistoryEvent event) {
461460
}
462461

463462
void completeWorkflowExecution(byte[] output) {
464-
addAllMissingVersionMarker(false, Optional.empty());
463+
addAllMissingVersionMarker();
465464

466465
Decision decision =
467466
Decision.newBuilder()
@@ -475,7 +474,7 @@ void completeWorkflowExecution(byte[] output) {
475474
}
476475

477476
void continueAsNewWorkflowExecution(ContinueAsNewWorkflowExecutionParameters continueParameters) {
478-
addAllMissingVersionMarker(false, Optional.empty());
477+
addAllMissingVersionMarker();
479478

480479
HistoryEvent firstEvent = task.getHistory().getEvents(0);
481480
if (!firstEvent.hasWorkflowExecutionStartedEventAttributes()) {
@@ -522,7 +521,7 @@ void continueAsNewWorkflowExecution(ContinueAsNewWorkflowExecutionParameters con
522521
}
523522

524523
void failWorkflowExecution(WorkflowExecutionException failure) {
525-
addAllMissingVersionMarker(false, Optional.empty());
524+
addAllMissingVersionMarker();
526525

527526
Decision decision =
528527
Decision.newBuilder()
@@ -541,7 +540,7 @@ void failWorkflowExecution(WorkflowExecutionException failure) {
541540
* CancelWorkflowExecution was created.
542541
*/
543542
void cancelWorkflowExecution() {
544-
addAllMissingVersionMarker(false, Optional.empty());
543+
addAllMissingVersionMarker();
545544

546545
Decision decision =
547546
Decision.newBuilder()
@@ -685,60 +684,90 @@ private void addDecision(DecisionId decisionId, DecisionStateMachine decision) {
685684
nextDecisionEventId++;
686685
}
687686

688-
// This is to support the case where a getVersion call presents during workflow execution but
689-
// is removed in replay.
690-
void addAllMissingVersionMarker(
691-
boolean isNextDecisionVersionMarker,
692-
Optional<Predicate<MarkerRecordedEventAttributes>> isDifferentChange) {
693-
boolean added;
694-
do {
695-
added = addMissingVersionMarker(isNextDecisionVersionMarker, isDifferentChange);
696-
} while (added);
687+
void addAllMissingVersionMarker() {
688+
addAllMissingVersionMarker(Optional.empty(), Optional.empty());
697689
}
698690

699-
private boolean addMissingVersionMarker(
700-
boolean isNextDecisionVersionMarker,
701-
Optional<Predicate<MarkerRecordedEventAttributes>> changeIdEquals) {
702-
Optional<HistoryEvent> optionalEvent = getOptionalDecisionEvent(nextDecisionEventId);
691+
Optional<HistoryEvent> getVersionMakerEvent(long eventId) {
692+
Optional<HistoryEvent> optionalEvent = getOptionalDecisionEvent(eventId);
703693
if (!optionalEvent.isPresent()) {
704-
return false;
694+
return Optional.empty();
705695
}
706696

707697
HistoryEvent event = optionalEvent.get();
708698
if (event.getEventType() != EventType.MarkerRecorded) {
709-
return false;
699+
return Optional.empty();
710700
}
711701

712702
if (!event
713703
.getMarkerRecordedEventAttributes()
714704
.getMarkerName()
715705
.equals(ClockDecisionContext.VERSION_MARKER_NAME)) {
716-
return false;
706+
return Optional.empty();
717707
}
708+
return Optional.of(event);
709+
}
718710

719-
// Next decision is for version marker and the event is for the same.
720-
if (isNextDecisionVersionMarker
721-
&& (!changeIdEquals.isPresent()
722-
|| changeIdEquals.get().test(event.getMarkerRecordedEventAttributes()))) {
723-
return false;
711+
/**
712+
* As getVersion calls can be added and removed any time this method inserts missing decision
713+
* events that correspond to removed getVersion calls.
714+
*
715+
* @param changeId optional getVersion change id to compare
716+
* @param converter must be present if changeId is present
717+
*/
718+
void addAllMissingVersionMarker(Optional<String> changeId, Optional<DataConverter> converter) {
719+
Optional<HistoryEvent> markerEvent = getVersionMakerEvent(nextDecisionEventId);
720+
721+
if (!markerEvent.isPresent()) {
722+
return;
724723
}
725724

726-
// If we have a version marker in history event but not in decisions, let's add one.
727-
RecordMarkerDecisionAttributes.Builder marker =
728-
RecordMarkerDecisionAttributes.newBuilder()
729-
.setMarkerName(ClockDecisionContext.VERSION_MARKER_NAME)
730-
.setHeader(event.getMarkerRecordedEventAttributes().getHeader())
731-
.setDetails(event.getMarkerRecordedEventAttributes().getDetails());
732-
Decision markerDecision =
733-
Decision.newBuilder()
734-
.setDecisionType(DecisionType.RecordMarker)
735-
.setRecordMarkerDecisionAttributes(marker)
736-
.build();
737-
DecisionId markerDecisionId = new DecisionId(DecisionTarget.MARKER, nextDecisionEventId);
738-
decisions.put(
739-
markerDecisionId, new MarkerDecisionStateMachine(markerDecisionId, markerDecision));
740-
nextDecisionEventId++;
741-
return true;
725+
// Look ahead to see if there is a marker with changeId following current version marker
726+
// If it is the case then all the markers that precede it should be added as decisions
727+
// as their correspondent getVersion calls were removed.
728+
long changeIdMarkerEventId = -1;
729+
if (changeId.isPresent()) {
730+
String id = changeId.get();
731+
long eventId = nextDecisionEventId;
732+
while (true) {
733+
MarkerHandler.MarkerInterface markerData =
734+
MarkerHandler.MarkerInterface.fromEventAttributes(
735+
markerEvent.get().getMarkerRecordedEventAttributes(), converter.get());
736+
737+
if (id.equals(markerData.getId())) {
738+
changeIdMarkerEventId = eventId;
739+
break;
740+
}
741+
eventId++;
742+
markerEvent = getVersionMakerEvent(eventId);
743+
if (!markerEvent.isPresent()) {
744+
break;
745+
}
746+
}
747+
// There are no version markers preceding a marker with the changeId
748+
if (changeIdMarkerEventId < 0 || changeIdMarkerEventId == nextDecisionEventId) {
749+
return;
750+
}
751+
}
752+
do {
753+
// If we have a version marker in history event but not in decisions, let's add one.
754+
RecordMarkerDecisionAttributes.Builder attributes =
755+
RecordMarkerDecisionAttributes.newBuilder()
756+
.setMarkerName(ClockDecisionContext.VERSION_MARKER_NAME)
757+
.setHeader(markerEvent.get().getMarkerRecordedEventAttributes().getHeader())
758+
.setDetails(markerEvent.get().getMarkerRecordedEventAttributes().getDetails());
759+
Decision markerDecision =
760+
Decision.newBuilder()
761+
.setDecisionType(DecisionType.RecordMarker)
762+
.setRecordMarkerDecisionAttributes(attributes)
763+
.build();
764+
DecisionId markerDecisionId = new DecisionId(DecisionTarget.MARKER, nextDecisionEventId);
765+
decisions.put(
766+
markerDecisionId, new MarkerDecisionStateMachine(markerDecisionId, markerDecision));
767+
nextDecisionEventId++;
768+
markerEvent = getVersionMakerEvent(nextDecisionEventId);
769+
} while (markerEvent.isPresent()
770+
&& (changeIdMarkerEventId < 0 || nextDecisionEventId < changeIdMarkerEventId));
742771
}
743772

744773
private DecisionStateMachine getDecision(DecisionId decisionId) {

src/main/java/io/temporal/internal/replay/ReplayDecisionTaskHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ private Result createCompletedRequest(
256256
.putAllQueryResults(result.getQueryResults())
257257
.setForceCreateNewDecisionTask(result.getForceCreateNewDecisionTask());
258258

259-
if (stickyTaskListName != null) {
259+
if (stickyTaskListName != null && !stickyTaskListScheduleToStartTimeout.isZero()) {
260260
StickyExecutionAttributes.Builder attributes =
261261
StickyExecutionAttributes.newBuilder()
262262
.setWorkerTaskList(createStickyTaskList(stickyTaskListName))

src/main/java/io/temporal/internal/sync/POJOActivityTaskHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,13 @@
4242
import java.util.concurrent.CancellationException;
4343
import java.util.concurrent.ScheduledExecutorService;
4444
import java.util.function.BiFunction;
45+
import org.slf4j.Logger;
46+
import org.slf4j.LoggerFactory;
4547

4648
class POJOActivityTaskHandler implements ActivityTaskHandler {
4749

50+
private static final Logger log = LoggerFactory.getLogger(POJOActivityTaskHandler.class);
51+
4852
private final DataConverter dataConverter;
4953
private final ScheduledExecutorService heartbeatExecutor;
5054
private final Map<String, ActivityTaskExecutor> activities =

src/main/java/io/temporal/worker/WorkerFactoryOptions.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static WorkerFactoryOptions getDefaultInstance() {
4343
}
4444

4545
public static class Builder {
46-
private int stickyDecisionScheduleToStartTimeoutInSeconds;
46+
private int stickyDecisionScheduleToStartTimeoutInSeconds = 10;
4747
private int cacheMaximumSize;
4848
private int maxWorkflowThreadCount;
4949
private WorkflowInterceptor workflowInterceptor;
@@ -144,9 +144,6 @@ private WorkerFactoryOptions(
144144
if (maxWorkflowThreadCount <= 0) {
145145
maxWorkflowThreadCount = 600;
146146
}
147-
if (stickyDecisionScheduleToStartTimeoutInSeconds <= 0) {
148-
stickyDecisionScheduleToStartTimeoutInSeconds = 5;
149-
}
150147
if (workflowInterceptor == null) {
151148
workflowInterceptor = new NoopWorkflowInterceptor();
152149
}

0 commit comments

Comments
 (0)