Skip to content

Commit 16755a1

Browse files
Block mutating workflow state in a read only context (#1821)
Block mutating workflow state in a read only context
1 parent c5cf7bb commit 16755a1

16 files changed

+513
-44
lines changed

temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityInvocationHandler.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.temporal.common.MethodRetry;
2626
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
2727
import io.temporal.workflow.ActivityStub;
28+
import io.temporal.workflow.Functions;
2829
import java.lang.reflect.InvocationHandler;
2930
import java.lang.reflect.Method;
3031
import java.util.HashMap;
@@ -36,26 +37,30 @@ public class ActivityInvocationHandler extends ActivityInvocationHandlerBase {
3637
private final ActivityOptions options;
3738
private final Map<String, ActivityOptions> activityMethodOptions;
3839
private final WorkflowOutboundCallsInterceptor activityExecutor;
40+
private final Functions.Proc assertReadOnly;
3941

4042
@VisibleForTesting
4143
public static InvocationHandler newInstance(
4244
Class<?> activityInterface,
4345
ActivityOptions options,
4446
Map<String, ActivityOptions> methodOptions,
45-
WorkflowOutboundCallsInterceptor activityExecutor) {
47+
WorkflowOutboundCallsInterceptor activityExecutor,
48+
Functions.Proc assertReadOnly) {
4649
return new ActivityInvocationHandler(
47-
activityInterface, activityExecutor, options, methodOptions);
50+
activityInterface, activityExecutor, options, methodOptions, assertReadOnly);
4851
}
4952

5053
private ActivityInvocationHandler(
5154
Class<?> activityInterface,
5255
WorkflowOutboundCallsInterceptor activityExecutor,
5356
ActivityOptions options,
54-
Map<String, ActivityOptions> methodOptions) {
57+
Map<String, ActivityOptions> methodOptions,
58+
Functions.Proc assertReadOnly) {
5559
super(activityInterface);
5660
this.options = options;
5761
this.activityMethodOptions = (methodOptions == null) ? new HashMap<>() : methodOptions;
5862
this.activityExecutor = activityExecutor;
63+
this.assertReadOnly = assertReadOnly;
5964
}
6065

6166
@Override
@@ -73,7 +78,7 @@ protected Function<Object[], Object> getActivityFunc(
7378
+ activityName
7479
+ " activity. Please set at least one of the above through the ActivityStub or WorkflowImplementationOptions.");
7580
}
76-
ActivityStub stub = ActivityStubImpl.newInstance(merged, activityExecutor);
81+
ActivityStub stub = ActivityStubImpl.newInstance(merged, activityExecutor, assertReadOnly);
7782
function =
7883
(a) -> stub.execute(activityName, method.getReturnType(), method.getGenericReturnType(), a);
7984
return function;

temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityStubImpl.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,37 @@
2424
import io.temporal.common.interceptors.Header;
2525
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
2626
import io.temporal.workflow.ActivityStub;
27+
import io.temporal.workflow.Functions;
2728
import io.temporal.workflow.Promise;
2829
import java.lang.reflect.Type;
2930

3031
final class ActivityStubImpl extends ActivityStubBase {
3132
protected final ActivityOptions options;
3233
private final WorkflowOutboundCallsInterceptor activityExecutor;
34+
private final Functions.Proc assertReadOnly;
3335

3436
static ActivityStub newInstance(
35-
ActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor) {
37+
ActivityOptions options,
38+
WorkflowOutboundCallsInterceptor activityExecutor,
39+
Functions.Proc assertReadOnly) {
3640
ActivityOptions validatedOptions =
3741
ActivityOptions.newBuilder(options).validateAndBuildWithDefaults();
38-
return new ActivityStubImpl(validatedOptions, activityExecutor);
42+
return new ActivityStubImpl(validatedOptions, activityExecutor, assertReadOnly);
3943
}
4044

41-
ActivityStubImpl(ActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor) {
45+
ActivityStubImpl(
46+
ActivityOptions options,
47+
WorkflowOutboundCallsInterceptor activityExecutor,
48+
Functions.Proc assertReadOnly) {
4249
this.options = options;
4350
this.activityExecutor = activityExecutor;
51+
this.assertReadOnly = assertReadOnly;
4452
}
4553

4654
@Override
4755
public <R> Promise<R> executeAsync(
4856
String activityName, Class<R> resultClass, Type resultType, Object... args) {
57+
this.assertReadOnly.apply();
4958
return activityExecutor
5059
.executeActivity(
5160
new WorkflowOutboundCallsInterceptor.ActivityInput<>(

temporal-sdk/src/main/java/io/temporal/internal/sync/ChildWorkflowInvocationHandler.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import io.temporal.common.metadata.WorkflowMethodType;
3131
import io.temporal.workflow.ChildWorkflowOptions;
3232
import io.temporal.workflow.ChildWorkflowStub;
33+
import io.temporal.workflow.Functions;
3334
import java.lang.reflect.InvocationHandler;
3435
import java.lang.reflect.Method;
3536
import java.util.Optional;
@@ -43,7 +44,8 @@ class ChildWorkflowInvocationHandler implements InvocationHandler {
4344
ChildWorkflowInvocationHandler(
4445
Class<?> workflowInterface,
4546
ChildWorkflowOptions options,
46-
WorkflowOutboundCallsInterceptor outboundCallsInterceptor) {
47+
WorkflowOutboundCallsInterceptor outboundCallsInterceptor,
48+
Functions.Proc1<String> assertReadOnly) {
4749
workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
4850
Optional<POJOWorkflowMethodMetadata> workflowMethodMetadata =
4951
workflowMetadata.getWorkflowMethod();
@@ -61,7 +63,10 @@ class ChildWorkflowInvocationHandler implements InvocationHandler {
6163
.validateAndBuildWithDefaults();
6264
this.stub =
6365
new ChildWorkflowStubImpl(
64-
workflowMethodMetadata.get().getName(), merged, outboundCallsInterceptor);
66+
workflowMethodMetadata.get().getName(),
67+
merged,
68+
outboundCallsInterceptor,
69+
assertReadOnly);
6570
}
6671

6772
@Override

temporal-sdk/src/main/java/io/temporal/internal/sync/ChildWorkflowStubImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ class ChildWorkflowStubImpl implements ChildWorkflowStub {
3636
private final ChildWorkflowOptions options;
3737
private final WorkflowOutboundCallsInterceptor outboundCallsInterceptor;
3838
private final CompletablePromise<WorkflowExecution> execution;
39+
private final Functions.Proc1<String> assertReadOnly;
3940

4041
ChildWorkflowStubImpl(
4142
String workflowType,
4243
ChildWorkflowOptions options,
43-
WorkflowOutboundCallsInterceptor outboundCallsInterceptor) {
44+
WorkflowOutboundCallsInterceptor outboundCallsInterceptor,
45+
Functions.Proc1<String> assertReadOnly) {
4446
this.workflowType = Objects.requireNonNull(workflowType);
4547
this.options = ChildWorkflowOptions.newBuilder(options).validateAndBuildWithDefaults();
4648
this.outboundCallsInterceptor = Objects.requireNonNull(outboundCallsInterceptor);
@@ -50,6 +52,7 @@ class ChildWorkflowStubImpl implements ChildWorkflowStub {
5052
// The "main" Child Workflow promise is the one returned from the execute method and that
5153
// promise will always be logged if not accessed.
5254
this.execution.handle((ex, failure) -> null);
55+
this.assertReadOnly = assertReadOnly;
5356
}
5457

5558
@Override
@@ -77,6 +80,7 @@ public <R> R execute(Class<R> resultClass, Object... args) {
7780

7881
@Override
7982
public <R> R execute(Class<R> resultClass, Type resultType, Object... args) {
83+
assertReadOnly.apply("schedule child workflow");
8084
Promise<R> result = executeAsync(resultClass, resultType, args);
8185
if (AsyncInternal.isAsync()) {
8286
AsyncInternal.setAsyncResult(result);
@@ -99,6 +103,7 @@ public <R> Promise<R> executeAsync(Class<R> resultClass, Object... args) {
99103

100104
@Override
101105
public <R> Promise<R> executeAsync(Class<R> resultClass, Type resultType, Object... args) {
106+
assertReadOnly.apply("schedule child workflow");
102107
ChildWorkflowOutput<R> result =
103108
outboundCallsInterceptor.executeChildWorkflow(
104109
new WorkflowOutboundCallsInterceptor.ChildWorkflowInput<>(
@@ -115,6 +120,7 @@ public <R> Promise<R> executeAsync(Class<R> resultClass, Type resultType, Object
115120

116121
@Override
117122
public void signal(String signalName, Object... args) {
123+
assertReadOnly.apply("signal workflow");
118124
Promise<Void> signaled =
119125
outboundCallsInterceptor
120126
.signalExternalWorkflow(

temporal-sdk/src/main/java/io/temporal/internal/sync/ExternalWorkflowInvocationHandler.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.temporal.common.metadata.POJOWorkflowInterfaceMetadata;
2626
import io.temporal.common.metadata.POJOWorkflowMethodMetadata;
2727
import io.temporal.workflow.ExternalWorkflowStub;
28+
import io.temporal.workflow.Functions;
2829
import java.lang.reflect.InvocationHandler;
2930
import java.lang.reflect.Method;
3031

@@ -37,9 +38,11 @@ class ExternalWorkflowInvocationHandler implements InvocationHandler {
3738
public ExternalWorkflowInvocationHandler(
3839
Class<?> workflowInterface,
3940
WorkflowExecution execution,
40-
WorkflowOutboundCallsInterceptor workflowOutboundCallsInterceptor) {
41-
workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
42-
stub = new ExternalWorkflowStubImpl(execution, workflowOutboundCallsInterceptor);
41+
WorkflowOutboundCallsInterceptor workflowOutboundCallsInterceptor,
42+
Functions.Proc1<String> assertReadOnly) {
43+
this.workflowMetadata = POJOWorkflowInterfaceMetadata.newInstance(workflowInterface);
44+
this.stub =
45+
new ExternalWorkflowStubImpl(execution, workflowOutboundCallsInterceptor, assertReadOnly);
4346
}
4447

4548
@Override

temporal-sdk/src/main/java/io/temporal/internal/sync/ExternalWorkflowStubImpl.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,23 @@
2222

2323
import io.temporal.api.common.v1.WorkflowExecution;
2424
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
25-
import io.temporal.workflow.CancelExternalWorkflowException;
26-
import io.temporal.workflow.ExternalWorkflowStub;
27-
import io.temporal.workflow.Promise;
28-
import io.temporal.workflow.SignalExternalWorkflowException;
25+
import io.temporal.workflow.*;
2926
import java.util.Objects;
3027

3128
/** Dynamic implementation of a strongly typed child workflow interface. */
3229
class ExternalWorkflowStubImpl implements ExternalWorkflowStub {
3330

3431
private final WorkflowOutboundCallsInterceptor outboundCallsInterceptor;
3532
private final WorkflowExecution execution;
33+
private Functions.Proc1<String> assertReadOnly;
3634

3735
public ExternalWorkflowStubImpl(
38-
WorkflowExecution execution, WorkflowOutboundCallsInterceptor outboundCallsInterceptor) {
36+
WorkflowExecution execution,
37+
WorkflowOutboundCallsInterceptor outboundCallsInterceptor,
38+
Functions.Proc1<String> assertReadOnly) {
3939
this.outboundCallsInterceptor = Objects.requireNonNull(outboundCallsInterceptor);
4040
this.execution = Objects.requireNonNull(execution);
41+
this.assertReadOnly = assertReadOnly;
4142
}
4243

4344
@Override
@@ -47,6 +48,7 @@ public WorkflowExecution getExecution() {
4748

4849
@Override
4950
public void signal(String signalName, Object... args) {
51+
assertReadOnly.apply("signal external workflow");
5052
Promise<Void> signaled =
5153
outboundCallsInterceptor
5254
.signalExternalWorkflow(
@@ -69,6 +71,7 @@ public void signal(String signalName, Object... args) {
6971

7072
@Override
7173
public void cancel() {
74+
assertReadOnly.apply("cancel external workflow");
7275
Promise<Void> cancelRequested =
7376
outboundCallsInterceptor
7477
.cancelWorkflow(new WorkflowOutboundCallsInterceptor.CancelWorkflowInput(execution))

temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityInvocationHandler.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.temporal.common.MethodRetry;
2626
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
2727
import io.temporal.workflow.ActivityStub;
28+
import io.temporal.workflow.Functions;
2829
import java.lang.reflect.InvocationHandler;
2930
import java.lang.reflect.Method;
3031
import java.util.HashMap;
@@ -36,26 +37,30 @@ public class LocalActivityInvocationHandler extends ActivityInvocationHandlerBas
3637
private final LocalActivityOptions options;
3738
private final Map<String, LocalActivityOptions> activityMethodOptions;
3839
private final WorkflowOutboundCallsInterceptor activityExecutor;
40+
private final Functions.Proc assertReadOnly;
3941

4042
@VisibleForTesting
4143
public static InvocationHandler newInstance(
4244
Class<?> activityInterface,
4345
LocalActivityOptions options,
4446
Map<String, LocalActivityOptions> methodOptions,
45-
WorkflowOutboundCallsInterceptor activityExecutor) {
47+
WorkflowOutboundCallsInterceptor activityExecutor,
48+
Functions.Proc assertReadOnly) {
4649
return new LocalActivityInvocationHandler(
47-
activityInterface, activityExecutor, options, methodOptions);
50+
activityInterface, activityExecutor, options, methodOptions, assertReadOnly);
4851
}
4952

5053
private LocalActivityInvocationHandler(
5154
Class<?> activityInterface,
5255
WorkflowOutboundCallsInterceptor activityExecutor,
5356
LocalActivityOptions options,
54-
Map<String, LocalActivityOptions> methodOptions) {
57+
Map<String, LocalActivityOptions> methodOptions,
58+
Functions.Proc assertReadOnly) {
5559
super(activityInterface);
5660
this.options = options;
5761
this.activityMethodOptions = (methodOptions == null) ? new HashMap<>() : methodOptions;
5862
this.activityExecutor = activityExecutor;
63+
this.assertReadOnly = assertReadOnly;
5964
}
6065

6166
@VisibleForTesting
@@ -68,7 +73,8 @@ public Function<Object[], Object> getActivityFunc(
6873
.mergeActivityOptions(activityMethodOptions.get(activityName))
6974
.setMethodRetry(methodRetry)
7075
.build();
71-
ActivityStub stub = LocalActivityStubImpl.newInstance(mergedOptions, activityExecutor);
76+
ActivityStub stub =
77+
LocalActivityStubImpl.newInstance(mergedOptions, activityExecutor, assertReadOnly);
7278
function =
7379
(a) -> stub.execute(activityName, method.getReturnType(), method.getGenericReturnType(), a);
7480
return function;

temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityStubImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,37 @@
2424
import io.temporal.common.interceptors.Header;
2525
import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor;
2626
import io.temporal.workflow.ActivityStub;
27+
import io.temporal.workflow.Functions;
2728
import io.temporal.workflow.Promise;
2829
import java.lang.reflect.Type;
2930

3031
class LocalActivityStubImpl extends ActivityStubBase {
3132
protected final LocalActivityOptions options;
3233
private final WorkflowOutboundCallsInterceptor activityExecutor;
34+
private final Functions.Proc assertReadOnly;
3335

3436
static ActivityStub newInstance(
35-
LocalActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor) {
37+
LocalActivityOptions options,
38+
WorkflowOutboundCallsInterceptor activityExecutor,
39+
Functions.Proc assertReadOnly) {
3640
LocalActivityOptions validatedOptions =
3741
LocalActivityOptions.newBuilder(options).validateAndBuildWithDefaults();
38-
return new LocalActivityStubImpl(validatedOptions, activityExecutor);
42+
return new LocalActivityStubImpl(validatedOptions, activityExecutor, assertReadOnly);
3943
}
4044

4145
private LocalActivityStubImpl(
42-
LocalActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor) {
46+
LocalActivityOptions options,
47+
WorkflowOutboundCallsInterceptor activityExecutor,
48+
Functions.Proc assertReadOnly) {
4349
this.options = options;
4450
this.activityExecutor = activityExecutor;
51+
this.assertReadOnly = assertReadOnly;
4552
}
4653

4754
@Override
4855
public <R> Promise<R> executeAsync(
4956
String activityName, Class<R> resultClass, Type resultType, Object... args) {
57+
this.assertReadOnly.apply();
5058
return activityExecutor
5159
.executeLocalActivity(
5260
new WorkflowOutboundCallsInterceptor.LocalActivityInput<>(

temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflow.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ public void handleUpdate(
157157
// TODO(https://github.com/temporalio/sdk-java/issues/1748) handleValidateUpdate
158158
// should not just be run
159159
// in a workflow thread
160+
workflowContext.setReadOnly(true);
160161
workflowProc.handleValidateUpdate(updateName, input, eventId);
161162
} catch (Exception e) {
162163
callbacks.reject(this.dataConverter.exceptionToFailure(e));
163164
return;
165+
} finally {
166+
workflowContext.setReadOnly(false);
164167
}
165168
}
166169
callbacks.accept();

0 commit comments

Comments
 (0)