Skip to content

Commit 6054c31

Browse files
Make GetVersion more deterministic (#1807)
Make GetVersion more deterministic
1 parent 3be060b commit 6054c31

22 files changed

+1609
-71
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3+
*
4+
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this material except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package io.temporal.internal.common;
22+
23+
/**
24+
* SdkFlag represents a flag used to help version the sdk internally to make breaking changes in
25+
* workflow logic.
26+
*/
27+
public enum SdkFlag {
28+
UNSET(0),
29+
/*
30+
* Changes behavior of GetVersion to not yield if no previous call existed in history.
31+
*/
32+
SKIP_YIELD_ON_DEFAULT_VERSION(1),
33+
UNKNOWN(Integer.MAX_VALUE);
34+
35+
private final int value;
36+
37+
SdkFlag(int value) {
38+
this.value = value;
39+
}
40+
41+
public boolean compare(int i) {
42+
return value == i;
43+
}
44+
45+
public static SdkFlag getValue(int id) {
46+
SdkFlag[] as = SdkFlag.values();
47+
for (int i = 0; i < as.length; i++) {
48+
if (as[i].compare(id)) return as[i];
49+
}
50+
return SdkFlag.UNKNOWN;
51+
}
52+
53+
public int getValue() {
54+
return value;
55+
}
56+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3+
*
4+
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this material except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package io.temporal.internal.common;
22+
23+
import io.temporal.workflow.Functions;
24+
import java.util.EnumSet;
25+
26+
/** Represents all the flags that are currently set in a workflow execution. */
27+
public final class SdkFlags {
28+
private final boolean supportSdkMetadata;
29+
private final Functions.Func<Boolean> replaying;
30+
// Flags that have been received from the server or have not been sent yet.
31+
private final EnumSet<SdkFlag> sdkFlags = EnumSet.noneOf(SdkFlag.class);
32+
// Flags that have been set this WFT that have not been sent to the server.
33+
// Keep track of them separately, so we know what to send to the server.
34+
private final EnumSet<SdkFlag> unsentSdkFlags = EnumSet.noneOf(SdkFlag.class);
35+
36+
public SdkFlags(boolean supportSdkMetadata, Functions.Func<Boolean> replaying) {
37+
this.supportSdkMetadata = supportSdkMetadata;
38+
this.replaying = replaying;
39+
}
40+
41+
/**
42+
* Marks a flag as usable regardless of replay status.
43+
*
44+
* @return True, as long as the server supports SDK flags
45+
*/
46+
public boolean setSdkFlag(SdkFlag flag) {
47+
if (!supportSdkMetadata) {
48+
return false;
49+
}
50+
sdkFlags.add(flag);
51+
return true;
52+
}
53+
54+
/**
55+
* @return True if this flag may currently be used.
56+
*/
57+
public boolean tryUseSdkFlag(SdkFlag flag) {
58+
if (!supportSdkMetadata) {
59+
return false;
60+
}
61+
62+
if (!replaying.apply()) {
63+
sdkFlags.add(flag);
64+
unsentSdkFlags.add(flag);
65+
return true;
66+
} else {
67+
return sdkFlags.contains(flag);
68+
}
69+
}
70+
71+
/**
72+
* @return All flags set since the last call to takeNewSdkFlags.
73+
*/
74+
public EnumSet<SdkFlag> takeNewSdkFlags() {
75+
EnumSet<SdkFlag> result = unsentSdkFlags.clone();
76+
unsentSdkFlags.clear();
77+
return result;
78+
}
79+
}

temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContext.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.temporal.api.common.v1.*;
2727
import io.temporal.api.failure.v1.Failure;
2828
import io.temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponse;
29+
import io.temporal.internal.common.SdkFlag;
2930
import io.temporal.internal.statemachines.ExecuteActivityParameters;
3031
import io.temporal.internal.statemachines.ExecuteLocalActivityParameters;
3132
import io.temporal.internal.statemachines.LocalActivityCallback;
@@ -269,8 +270,9 @@ void mutableSideEffect(
269270
* @param minSupported min version supported for the change
270271
* @param maxSupported max version supported for the change
271272
* @param callback used to return version
273+
* @return True if the identifier is not present in history
272274
*/
273-
void getVersion(
275+
boolean getVersion(
274276
String changeId,
275277
int minSupported,
276278
int maxSupported,
@@ -382,4 +384,9 @@ void getVersion(
382384

383385
/** Updates or inserts search attributes used to index workflows. */
384386
void upsertSearchAttributes(@Nonnull SearchAttributes searchAttributes);
387+
388+
/**
389+
* @return true if this flag may currently be used.
390+
*/
391+
boolean tryUseSdkFlag(SdkFlag flag);
385392
}

temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowContextImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.temporal.api.history.v1.WorkflowExecutionStartedEventAttributes;
3333
import io.temporal.failure.CanceledFailure;
3434
import io.temporal.internal.common.ProtobufTimeUtils;
35+
import io.temporal.internal.common.SdkFlag;
3536
import io.temporal.internal.statemachines.*;
3637
import io.temporal.internal.worker.SingleWorkerOptions;
3738
import io.temporal.workflow.Functions;
@@ -240,6 +241,11 @@ public boolean isReplaying() {
240241
return workflowStateMachines.isReplaying();
241242
}
242243

244+
@Override
245+
public boolean tryUseSdkFlag(SdkFlag flag) {
246+
return workflowStateMachines.tryUseSdkFlag(flag);
247+
}
248+
243249
@Override
244250
public Functions.Proc1<RuntimeException> newTimer(
245251
Duration delay, Functions.Proc1<RuntimeException> callback) {
@@ -290,12 +296,12 @@ public void mutableSideEffect(
290296
}
291297

292298
@Override
293-
public void getVersion(
299+
public boolean getVersion(
294300
String changeId,
295301
int minSupported,
296302
int maxSupported,
297303
Functions.Proc2<Integer, RuntimeException> callback) {
298-
workflowStateMachines.getVersion(changeId, minSupported, maxSupported, callback);
304+
return workflowStateMachines.getVersion(changeId, minSupported, maxSupported, callback);
299305
}
300306

301307
@Override

temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowRunTaskHandler.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
import io.temporal.api.protocol.v1.Message;
4040
import io.temporal.api.query.v1.WorkflowQuery;
4141
import io.temporal.api.query.v1.WorkflowQueryResult;
42+
import io.temporal.api.workflowservice.v1.GetSystemInfoResponse;
4243
import io.temporal.api.workflowservice.v1.PollWorkflowTaskQueueResponseOrBuilder;
4344
import io.temporal.internal.Config;
45+
import io.temporal.internal.common.SdkFlag;
4446
import io.temporal.internal.common.UpdateMessage;
4547
import io.temporal.internal.statemachines.ExecuteLocalActivityParameters;
4648
import io.temporal.internal.statemachines.StatesMachinesCallback;
@@ -90,13 +92,16 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler {
9092

9193
private final ReplayWorkflowExecutor replayWorkflowExecutor;
9294

95+
private final GetSystemInfoResponse.Capabilities capabilities;
96+
9397
ReplayWorkflowRunTaskHandler(
9498
String namespace,
9599
ReplayWorkflow workflow,
96100
PollWorkflowTaskQueueResponseOrBuilder workflowTask,
97101
SingleWorkerOptions workerOptions,
98102
Scope metricsScope,
99-
LocalActivityDispatcher localActivityDispatcher) {
103+
LocalActivityDispatcher localActivityDispatcher,
104+
GetSystemInfoResponse.Capabilities capabilities) {
100105
HistoryEvent startedEvent = workflowTask.getHistory().getEvents(0);
101106
if (!startedEvent.hasWorkflowExecutionStartedEventAttributes()) {
102107
throw new IllegalArgumentException(
@@ -107,7 +112,8 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler {
107112
this.localActivityDispatcher = localActivityDispatcher;
108113
this.workflow = workflow;
109114

110-
this.workflowStateMachines = new WorkflowStateMachines(new StatesMachinesCallbackImpl());
115+
this.workflowStateMachines =
116+
new WorkflowStateMachines(new StatesMachinesCallbackImpl(), capabilities);
111117
String fullReplayDirectQueryType =
112118
workflowTask.hasQuery() ? workflowTask.getQuery().getQueryType() : null;
113119
this.context =
@@ -125,6 +131,7 @@ class ReplayWorkflowRunTaskHandler implements WorkflowRunTaskHandler {
125131
new ReplayWorkflowExecutor(workflow, workflowStateMachines, context);
126132
this.localActivityCompletionSink = localActivityCompletionQueue::add;
127133
this.localActivityMeteringHelper = new LocalActivityMeteringHelper();
134+
this.capabilities = capabilities;
128135
}
129136

130137
@Override
@@ -159,6 +166,11 @@ public WorkflowTaskResult handleWorkflowTask(
159166
processLocalActivityRequests(wftHearbeatDeadline);
160167
List<Command> commands = workflowStateMachines.takeCommands();
161168
List<Message> messages = workflowStateMachines.takeMessages();
169+
EnumSet<SdkFlag> newFlags = workflowStateMachines.takeNewSdkFlags();
170+
List<Integer> newSdkFlags = new ArrayList<>(newFlags.size());
171+
for (SdkFlag flag : newFlags) {
172+
newSdkFlags.add(flag.getValue());
173+
}
162174
if (context.isWorkflowMethodCompleted()) {
163175
// it's important for query, otherwise the WorkflowTaskHandler is responsible for closing
164176
// and invalidation
@@ -175,6 +187,7 @@ public WorkflowTaskResult handleWorkflowTask(
175187
.setFinalCommand(context.isWorkflowMethodCompleted())
176188
.setForceWorkflowTask(localActivityTaskCount > 0 && !context.isWorkflowMethodCompleted())
177189
.setNonfirstLocalActivityAttempts(localActivityMeteringHelper.getNonfirstAttempts())
190+
.setSdkFlags(newSdkFlags)
178191
.build();
179192
} finally {
180193
lock.unlock();

temporal-sdk/src/main/java/io/temporal/internal/replay/ReplayWorkflowTaskHandler.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.temporal.api.failure.v1.Failure;
3737
import io.temporal.api.history.v1.HistoryEvent;
3838
import io.temporal.api.query.v1.WorkflowQuery;
39+
import io.temporal.api.sdk.v1.WorkflowTaskCompletedMetadata;
3940
import io.temporal.api.taskqueue.v1.StickyExecutionAttributes;
4041
import io.temporal.api.taskqueue.v1.TaskQueue;
4142
import io.temporal.api.workflowservice.v1.*;
@@ -235,6 +236,13 @@ private Result createCompletedWFTRequest(
235236
}
236237
completedRequest.setStickyAttributes(attributes);
237238
}
239+
if (!result.getSdkFlags().isEmpty()) {
240+
completedRequest =
241+
completedRequest.setSdkMetadata(
242+
WorkflowTaskCompletedMetadata.newBuilder()
243+
.addAllLangUsedFlags(result.getSdkFlags())
244+
.build());
245+
}
238246
return new Result(
239247
workflowType,
240248
completedRequest.build(),
@@ -383,7 +391,13 @@ private WorkflowRunTaskHandler createStatefulHandler(
383391
}
384392
ReplayWorkflow workflow = workflowFactory.getWorkflow(workflowType, workflowExecution);
385393
return new ReplayWorkflowRunTaskHandler(
386-
namespace, workflow, workflowTask, options, metricsScope, localActivityDispatcher);
394+
namespace,
395+
workflow,
396+
workflowTask,
397+
options,
398+
metricsScope,
399+
localActivityDispatcher,
400+
service.getServerCapabilities().get());
387401
}
388402

389403
private void resetStickyTaskQueue(WorkflowExecution execution) {

temporal-sdk/src/main/java/io/temporal/internal/replay/WorkflowTaskResult.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static final class Builder {
4040
private Map<String, WorkflowQueryResult> queryResults;
4141
private boolean forceWorkflowTask;
4242
private int nonfirstLocalActivityAttempts;
43+
private List<Integer> sdkFlags;
4344

4445
public Builder setCommands(List<Command> commands) {
4546
this.commands = commands;
@@ -71,14 +72,20 @@ public Builder setNonfirstLocalActivityAttempts(int nonfirstLocalActivityAttempt
7172
return this;
7273
}
7374

75+
public Builder setSdkFlags(List<Integer> sdkFlags) {
76+
this.sdkFlags = sdkFlags;
77+
return this;
78+
}
79+
7480
public WorkflowTaskResult build() {
7581
return new WorkflowTaskResult(
7682
commands == null ? Collections.emptyList() : commands,
7783
messages == null ? Collections.emptyList() : messages,
7884
queryResults == null ? Collections.emptyMap() : queryResults,
7985
finalCommand,
8086
forceWorkflowTask,
81-
nonfirstLocalActivityAttempts);
87+
nonfirstLocalActivityAttempts,
88+
sdkFlags == null ? Collections.emptyList() : sdkFlags);
8289
}
8390
}
8491

@@ -88,14 +95,16 @@ public WorkflowTaskResult build() {
8895
private final Map<String, WorkflowQueryResult> queryResults;
8996
private final boolean forceWorkflowTask;
9097
private final int nonfirstLocalActivityAttempts;
98+
private final List<Integer> sdkFlags;
9199

92100
private WorkflowTaskResult(
93101
List<Command> commands,
94102
List<Message> messages,
95103
Map<String, WorkflowQueryResult> queryResults,
96104
boolean finalCommand,
97105
boolean forceWorkflowTask,
98-
int nonfirstLocalActivityAttempts) {
106+
int nonfirstLocalActivityAttempts,
107+
List<Integer> sdkFlags) {
99108
this.commands = commands;
100109
this.messages = messages;
101110
this.nonfirstLocalActivityAttempts = nonfirstLocalActivityAttempts;
@@ -105,6 +114,7 @@ private WorkflowTaskResult(
105114
this.queryResults = queryResults;
106115
this.finalCommand = finalCommand;
107116
this.forceWorkflowTask = forceWorkflowTask;
117+
this.sdkFlags = sdkFlags;
108118
}
109119

110120
public List<Command> getCommands() {
@@ -131,4 +141,8 @@ public boolean isForceWorkflowTask() {
131141
public int getNonfirstLocalActivityAttempts() {
132142
return nonfirstLocalActivityAttempts;
133143
}
144+
145+
public List<Integer> getSdkFlags() {
146+
return sdkFlags;
147+
}
134148
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,12 @@ private VersionStateMachine(
369369
this.stateMachineSink = stateMachineSink;
370370
}
371371

372-
public void getVersion(
372+
public State getVersion(
373373
int minSupported, int maxSupported, Functions.Proc2<Integer, RuntimeException> callback) {
374374
InvocationStateMachine ism = new InvocationStateMachine(minSupported, maxSupported, callback);
375375
ism.explicitEvent(ExplicitEvent.CHECK_EXECUTION_STATE);
376376
ism.explicitEvent(ExplicitEvent.SCHEDULE);
377+
return ism.getState();
377378
}
378379

379380
public void handleNonMatchingEvent(HistoryEvent event) {

0 commit comments

Comments
 (0)