99import io .temporal .common .interceptors .WorkflowInboundCallsInterceptorBase ;
1010import io .temporal .testing .internal .SDKTestWorkflowRule ;
1111import io .temporal .worker .WorkerFactoryOptions ;
12+ import io .temporal .worker .WorkerOptions ;
1213import io .temporal .workflow .unsafe .WorkflowUnsafe ;
1314import java .util .Map ;
1415import java .util .concurrent .ConcurrentHashMap ;
1516import org .junit .Before ;
1617import org .junit .Rule ;
1718import org .junit .Test ;
1819
19- public class WorkflowUnsafeReadOnlyTest {
20+ public class WorkflowUnsafeSubjectToReplayTest {
2021 private static final Map <String , Boolean > calls = new ConcurrentHashMap <>();
2122
2223 @ Rule
2324 public SDKTestWorkflowRule testWorkflowRule =
2425 SDKTestWorkflowRule .newBuilder ()
25- .setWorkflowTypes (ReadOnlyWorkflowImpl .class )
26+ .setWorkflowTypes (SubjectToReplayWorkflowImpl .class )
2627 .setWorkerFactoryOptions (
2728 WorkerFactoryOptions .newBuilder ()
28- .setWorkerInterceptors (new ReadOnlyRecordingInterceptor ())
29+ .setWorkerInterceptors (new SubjectToReplayRecordingInterceptor ())
30+ .build ())
31+ .setWorkerOptions (
32+ WorkerOptions .newBuilder ()
33+ .setPreferredVersionProvider (
34+ input -> {
35+ record ("versionProvider" );
36+ return null ;
37+ })
2938 .build ())
3039 .build ();
3140
@@ -34,10 +43,14 @@ public void setUp() {
3443 calls .clear ();
3544 }
3645
46+ /**
47+ * Subjection to replay is a property of the calling context rather than of the Workflow's current
48+ * state, so running once live covers the whole contract.
49+ */
3750 @ Test
38- public void isReadOnly () {
39- ReadOnlyWorkflow workflow =
40- testWorkflowRule .newWorkflowStubTimeoutOptions (ReadOnlyWorkflow .class );
51+ public void isSubjectToReplay () {
52+ SubjectToReplayWorkflow workflow =
53+ testWorkflowRule .newWorkflowStubTimeoutOptions (SubjectToReplayWorkflow .class );
4154 WorkflowClient .start (workflow ::run );
4255
4356 workflow .query ();
@@ -46,26 +59,32 @@ public void isReadOnly() {
4659 WorkflowStub .fromTyped (workflow ).getResult (Void .class );
4760
4861 Map <String , Boolean > expected = new ConcurrentHashMap <>();
49- expected .put ("ExecuteWorkflow" , false );
50- expected .put ("workflowTask" , false );
51- expected .put ("ExecuteUpdate" , false );
52- expected .put ("updateHandler" , false );
53- expected .put ("HandleSignal" , false );
54- expected .put ("sideEffect" , true );
62+ // The durable Workflow path re-executes on every replay.
63+ expected .put ("ExecuteWorkflow" , true );
64+ expected .put ("workflowTask" , true );
65+ expected .put ("ExecuteUpdate" , true );
66+ expected .put ("updateHandler" , true );
67+ expected .put ("HandleSignal" , true );
68+ // An Await condition is read-only yet still re-evaluated on replay. This is the one context
69+ // where subjection to replay and read-only disagree, and the reason the two are separate.
5570 expected .put ("await" , true );
56- expected .put ("HandleQuery" , true );
57- expected .put ("query" , true );
58- expected .put ("ValidateUpdate" , true );
59- expected .put ("validator" , true );
71+ // Live callbacks run once against current state and are never re-executed from history.
72+ expected .put ("sideEffect" , false );
73+ expected .put ("mutableSideEffect" , false );
74+ expected .put ("versionProvider" , false );
75+ expected .put ("HandleQuery" , false );
76+ expected .put ("query" , false );
77+ expected .put ("ValidateUpdate" , false );
78+ expected .put ("validator" , false );
6079 assertEquals (expected , calls );
6180 }
6281
6382 private static void record (String name ) {
64- calls .put (name , WorkflowUnsafe .isReadOnly ());
83+ calls .put (name , WorkflowUnsafe .isSubjectToReplay ());
6584 }
6685
6786 @ WorkflowInterface
68- public interface ReadOnlyWorkflow {
87+ public interface SubjectToReplayWorkflow {
6988 @ WorkflowMethod
7089 void run ();
7190
@@ -82,18 +101,27 @@ public interface ReadOnlyWorkflow {
82101 void finish ();
83102 }
84103
85- public static class ReadOnlyWorkflowImpl implements ReadOnlyWorkflow {
104+ public static class SubjectToReplayWorkflowImpl implements SubjectToReplayWorkflow {
86105 private boolean finished ;
87106
88107 @ Override
89108 public void run () {
90109 record ("workflowTask" );
110+ Workflow .getVersion ("change" , Workflow .DEFAULT_VERSION , 1 );
91111 Workflow .sideEffect (
92112 Void .class ,
93113 () -> {
94114 record ("sideEffect" );
95115 return null ;
96116 });
117+ Workflow .mutableSideEffect (
118+ "id" ,
119+ Integer .class ,
120+ Integer ::equals ,
121+ () -> {
122+ record ("mutableSideEffect" );
123+ return 1 ;
124+ });
97125 Workflow .await (
98126 () -> {
99127 record ("await" );
@@ -123,7 +151,7 @@ public void finish() {
123151 }
124152 }
125153
126- private static class ReadOnlyRecordingInterceptor extends WorkerInterceptorBase {
154+ private static class SubjectToReplayRecordingInterceptor extends WorkerInterceptorBase {
127155 @ Override
128156 public WorkflowInboundCallsInterceptor interceptWorkflow (WorkflowInboundCallsInterceptor next ) {
129157 return new WorkflowInboundCallsInterceptorBase (next ) {
0 commit comments