@temporalio/core-bridge 0.16.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/Cargo.lock +339 -226
  2. package/Cargo.toml +7 -3
  3. package/common.js +50 -0
  4. package/index.d.ts +7 -0
  5. package/index.js +12 -0
  6. package/package.json +7 -4
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/{index.node → releases/index.node} +0 -0
  10. package/releases/x86_64-apple-darwin/index.node +0 -0
  11. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  12. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  13. package/scripts/build.js +10 -50
  14. package/sdk-core/.buildkite/docker/Dockerfile +1 -1
  15. package/sdk-core/.buildkite/docker/docker-compose.yaml +2 -2
  16. package/sdk-core/.buildkite/pipeline.yml +2 -0
  17. package/sdk-core/Cargo.toml +1 -88
  18. package/sdk-core/README.md +30 -6
  19. package/sdk-core/bridge-ffi/Cargo.toml +24 -0
  20. package/sdk-core/bridge-ffi/LICENSE.txt +23 -0
  21. package/sdk-core/bridge-ffi/build.rs +25 -0
  22. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +216 -0
  23. package/sdk-core/bridge-ffi/src/lib.rs +829 -0
  24. package/sdk-core/bridge-ffi/src/wrappers.rs +193 -0
  25. package/sdk-core/client/Cargo.toml +32 -0
  26. package/sdk-core/{src/pollers/gateway.rs → client/src/lib.rs} +101 -195
  27. package/sdk-core/client/src/metrics.rs +89 -0
  28. package/sdk-core/client/src/mocks.rs +167 -0
  29. package/sdk-core/{src/pollers → client/src}/retry.rs +172 -14
  30. package/sdk-core/core/Cargo.toml +96 -0
  31. package/sdk-core/{src → core/src}/core_tests/activity_tasks.rs +193 -37
  32. package/sdk-core/{src → core/src}/core_tests/child_workflows.rs +14 -14
  33. package/sdk-core/{src → core/src}/core_tests/determinism.rs +8 -8
  34. package/sdk-core/core/src/core_tests/local_activities.rs +328 -0
  35. package/sdk-core/{src → core/src}/core_tests/mod.rs +6 -9
  36. package/sdk-core/{src → core/src}/core_tests/queries.rs +54 -54
  37. package/sdk-core/{src → core/src}/core_tests/replay_flag.rs +8 -12
  38. package/sdk-core/{src → core/src}/core_tests/workers.rs +120 -33
  39. package/sdk-core/{src → core/src}/core_tests/workflow_cancels.rs +16 -26
  40. package/sdk-core/{src → core/src}/core_tests/workflow_tasks.rs +280 -292
  41. package/sdk-core/core/src/lib.rs +374 -0
  42. package/sdk-core/{src → core/src}/log_export.rs +3 -27
  43. package/sdk-core/core/src/pending_activations.rs +162 -0
  44. package/sdk-core/{src → core/src}/pollers/mod.rs +4 -22
  45. package/sdk-core/{src → core/src}/pollers/poll_buffer.rs +1 -1
  46. package/sdk-core/core/src/protosext/mod.rs +396 -0
  47. package/sdk-core/core/src/replay/mod.rs +210 -0
  48. package/sdk-core/core/src/retry_logic.rs +144 -0
  49. package/sdk-core/{src → core/src}/telemetry/metrics.rs +3 -58
  50. package/sdk-core/{src → core/src}/telemetry/mod.rs +8 -8
  51. package/sdk-core/{src → core/src}/telemetry/prometheus_server.rs +0 -0
  52. package/sdk-core/{src → core/src}/test_help/mod.rs +35 -83
  53. package/sdk-core/{src → core/src}/worker/activities/activity_heartbeat_manager.rs +95 -42
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +973 -0
  55. package/sdk-core/{src → core/src}/worker/activities.rs +52 -33
  56. package/sdk-core/{src → core/src}/worker/dispatcher.rs +8 -6
  57. package/sdk-core/{src → core/src}/worker/mod.rs +347 -221
  58. package/sdk-core/core/src/worker/wft_delivery.rs +81 -0
  59. package/sdk-core/{src → core/src}/workflow/bridge.rs +5 -2
  60. package/sdk-core/{src → core/src}/workflow/driven_workflow.rs +17 -7
  61. package/sdk-core/{src → core/src}/workflow/history_update.rs +33 -7
  62. package/sdk-core/{src → core/src/workflow}/machines/activity_state_machine.rs +26 -26
  63. package/sdk-core/{src → core/src/workflow}/machines/cancel_external_state_machine.rs +8 -11
  64. package/sdk-core/{src → core/src/workflow}/machines/cancel_workflow_state_machine.rs +19 -21
  65. package/sdk-core/{src → core/src/workflow}/machines/child_workflow_state_machine.rs +20 -31
  66. package/sdk-core/{src → core/src/workflow}/machines/complete_workflow_state_machine.rs +3 -5
  67. package/sdk-core/{src → core/src/workflow}/machines/continue_as_new_workflow_state_machine.rs +18 -18
  68. package/sdk-core/{src → core/src/workflow}/machines/fail_workflow_state_machine.rs +5 -6
  69. package/sdk-core/core/src/workflow/machines/local_activity_state_machine.rs +1451 -0
  70. package/sdk-core/{src → core/src/workflow}/machines/mod.rs +54 -107
  71. package/sdk-core/{src → core/src/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
  72. package/sdk-core/{src → core/src/workflow}/machines/patch_state_machine.rs +29 -30
  73. package/sdk-core/{src → core/src/workflow}/machines/side_effect_state_machine.rs +0 -0
  74. package/sdk-core/{src → core/src/workflow}/machines/signal_external_state_machine.rs +17 -19
  75. package/sdk-core/{src → core/src/workflow}/machines/timer_state_machine.rs +20 -21
  76. package/sdk-core/{src → core/src/workflow}/machines/transition_coverage.rs +5 -2
  77. package/sdk-core/{src → core/src/workflow}/machines/upsert_search_attributes_state_machine.rs +0 -0
  78. package/sdk-core/core/src/workflow/machines/workflow_machines/local_acts.rs +96 -0
  79. package/sdk-core/{src → core/src/workflow}/machines/workflow_machines.rs +357 -171
  80. package/sdk-core/{src → core/src/workflow}/machines/workflow_task_state_machine.rs +1 -1
  81. package/sdk-core/{src → core/src}/workflow/mod.rs +200 -39
  82. package/sdk-core/{src → core/src}/workflow/workflow_tasks/cache_manager.rs +0 -0
  83. package/sdk-core/{src → core/src}/workflow/workflow_tasks/concurrency_manager.rs +38 -5
  84. package/sdk-core/{src → core/src}/workflow/workflow_tasks/mod.rs +317 -103
  85. package/sdk-core/{test_utils → core-api}/Cargo.toml +10 -7
  86. package/sdk-core/{src → core-api/src}/errors.rs +42 -92
  87. package/sdk-core/core-api/src/lib.rs +158 -0
  88. package/sdk-core/{src/worker/config.rs → core-api/src/worker.rs} +18 -23
  89. package/sdk-core/etc/deps.svg +156 -0
  90. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +5 -5
  91. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +3 -5
  92. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +7 -1
  93. package/sdk-core/histories/fail_wf_task.bin +0 -0
  94. package/sdk-core/histories/timer_workflow_history.bin +0 -0
  95. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +44 -13
  96. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +19 -1
  97. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +1 -1
  98. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +9 -0
  99. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +1 -0
  100. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +1 -0
  101. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +13 -0
  102. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +14 -7
  103. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +176 -18
  104. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  105. package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +11 -0
  106. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +3 -0
  107. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +156 -7
  108. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +135 -104
  109. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +78 -0
  110. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +78 -0
  111. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +205 -0
  112. package/sdk-core/protos/local/temporal/sdk/core/bridge/service.proto +61 -0
  113. package/sdk-core/protos/local/{child_workflow.proto → temporal/sdk/core/child_workflow/child_workflow.proto} +1 -1
  114. package/sdk-core/protos/local/{common.proto → temporal/sdk/core/common/common.proto} +5 -3
  115. package/sdk-core/protos/local/{core_interface.proto → temporal/sdk/core/core_interface.proto} +10 -10
  116. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +30 -0
  117. package/sdk-core/protos/local/{workflow_activation.proto → temporal/sdk/core/workflow_activation/workflow_activation.proto} +35 -11
  118. package/sdk-core/protos/local/{workflow_commands.proto → temporal/sdk/core/workflow_commands/workflow_commands.proto} +55 -4
  119. package/sdk-core/protos/local/{workflow_completion.proto → temporal/sdk/core/workflow_completion/workflow_completion.proto} +3 -3
  120. package/sdk-core/sdk/Cargo.toml +32 -0
  121. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/conversions.rs +0 -0
  122. package/sdk-core/sdk/src/lib.rs +699 -0
  123. package/sdk-core/sdk/src/payload_converter.rs +11 -0
  124. package/sdk-core/sdk/src/workflow_context/options.rs +180 -0
  125. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/workflow_context.rs +201 -124
  126. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/workflow_future.rs +63 -30
  127. package/sdk-core/sdk-core-protos/Cargo.toml +10 -0
  128. package/sdk-core/sdk-core-protos/build.rs +28 -6
  129. package/sdk-core/sdk-core-protos/src/constants.rs +7 -0
  130. package/sdk-core/{src/test_help → sdk-core-protos/src}/history_builder.rs +134 -49
  131. package/sdk-core/sdk-core-protos/src/history_info.rs +216 -0
  132. package/sdk-core/sdk-core-protos/src/lib.rs +601 -168
  133. package/sdk-core/sdk-core-protos/src/task_token.rs +38 -0
  134. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -0
  135. package/sdk-core/test-utils/Cargo.toml +32 -0
  136. package/sdk-core/{src/test_help → test-utils/src}/canned_histories.rs +59 -78
  137. package/sdk-core/test-utils/src/histfetch.rs +28 -0
  138. package/sdk-core/{test_utils → test-utils}/src/lib.rs +131 -68
  139. package/sdk-core/tests/integ_tests/client_tests.rs +1 -1
  140. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -7
  141. package/sdk-core/tests/integ_tests/polling_tests.rs +12 -11
  142. package/sdk-core/tests/integ_tests/queries_tests.rs +82 -78
  143. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +91 -71
  144. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +3 -4
  145. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +2 -4
  146. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +4 -6
  147. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +4 -6
  148. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -4
  149. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +496 -0
  150. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +5 -8
  151. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +125 -0
  152. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +7 -13
  153. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +33 -5
  154. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +12 -16
  155. package/sdk-core/tests/integ_tests/workflow_tests.rs +85 -82
  156. package/sdk-core/tests/load_tests.rs +6 -6
  157. package/sdk-core/tests/main.rs +2 -2
  158. package/src/conversions.rs +24 -21
  159. package/src/errors.rs +8 -0
  160. package/src/lib.rs +323 -211
  161. package/sdk-core/protos/local/activity_result.proto +0 -46
  162. package/sdk-core/protos/local/activity_task.proto +0 -66
  163. package/sdk-core/src/core_tests/retry.rs +0 -147
  164. package/sdk-core/src/lib.rs +0 -403
  165. package/sdk-core/src/machines/local_activity_state_machine.rs +0 -117
  166. package/sdk-core/src/pending_activations.rs +0 -249
  167. package/sdk-core/src/protosext/mod.rs +0 -160
  168. package/sdk-core/src/prototype_rust_sdk.rs +0 -412
  169. package/sdk-core/src/task_token.rs +0 -20
  170. package/sdk-core/src/test_help/history_info.rs +0 -157
@@ -0,0 +1,496 @@
1
+ use anyhow::anyhow;
2
+ use futures::future::join_all;
3
+ use std::time::Duration;
4
+ use temporal_sdk::{
5
+ act_cancelled, act_is_cancelled, ActivityCancelledError, CancellableFuture,
6
+ LocalActivityOptions, WfContext, WorkflowResult,
7
+ };
8
+ use temporal_sdk_core_protos::coresdk::{
9
+ common::RetryPolicy, workflow_commands::ActivityCancellationType, AsJsonPayloadExt,
10
+ };
11
+ use temporal_sdk_core_test_utils::CoreWfStarter;
12
+ use tokio_util::sync::CancellationToken;
13
+
14
+ pub async fn echo(e: String) -> anyhow::Result<String> {
15
+ Ok(e)
16
+ }
17
+
18
+ pub async fn one_local_activity_wf(ctx: WfContext) -> WorkflowResult<()> {
19
+ let initial_workflow_time = ctx.workflow_time().expect("Workflow time should be set");
20
+ ctx.local_activity(LocalActivityOptions {
21
+ activity_type: "echo_activity".to_string(),
22
+ input: "hi!".as_json_payload().expect("serializes fine"),
23
+ ..Default::default()
24
+ })
25
+ .await;
26
+ // Verify LA execution advances the clock
27
+ assert!(initial_workflow_time < ctx.workflow_time().unwrap());
28
+ Ok(().into())
29
+ }
30
+
31
+ #[tokio::test]
32
+ async fn one_local_activity() {
33
+ let wf_name = "one_local_activity";
34
+ let mut starter = CoreWfStarter::new(wf_name);
35
+ let mut worker = starter.worker().await;
36
+ worker.register_wf(wf_name.to_owned(), one_local_activity_wf);
37
+ worker.register_activity("echo_activity", echo);
38
+
39
+ worker
40
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
41
+ .await
42
+ .unwrap();
43
+ worker.run_until_done().await.unwrap();
44
+ }
45
+
46
+ pub async fn local_act_concurrent_with_timer_wf(ctx: WfContext) -> WorkflowResult<()> {
47
+ let la = ctx.local_activity(LocalActivityOptions {
48
+ activity_type: "echo_activity".to_string(),
49
+ input: "hi!".as_json_payload().expect("serializes fine"),
50
+ ..Default::default()
51
+ });
52
+ let timer = ctx.timer(Duration::from_secs(1));
53
+ tokio::join!(la, timer);
54
+ Ok(().into())
55
+ }
56
+
57
+ #[tokio::test]
58
+ async fn local_act_concurrent_with_timer() {
59
+ let wf_name = "local_act_concurrent_with_timer";
60
+ let mut starter = CoreWfStarter::new(wf_name);
61
+ let mut worker = starter.worker().await;
62
+ worker.register_wf(wf_name.to_owned(), local_act_concurrent_with_timer_wf);
63
+ worker.register_activity("echo_activity", echo);
64
+
65
+ worker
66
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
67
+ .await
68
+ .unwrap();
69
+ worker.run_until_done().await.unwrap();
70
+ }
71
+
72
+ pub async fn local_act_then_timer_then_wait(ctx: WfContext) -> WorkflowResult<()> {
73
+ let la = ctx.local_activity(LocalActivityOptions {
74
+ activity_type: "echo_activity".to_string(),
75
+ input: "hi!".as_json_payload().expect("serializes fine"),
76
+ ..Default::default()
77
+ });
78
+ ctx.timer(Duration::from_secs(1)).await;
79
+ let res = la.await;
80
+ assert!(res.completed_ok());
81
+ Ok(().into())
82
+ }
83
+
84
+ #[tokio::test]
85
+ async fn local_act_then_timer_then_wait_result() {
86
+ let wf_name = "local_act_then_timer_then_wait_result";
87
+ let mut starter = CoreWfStarter::new(wf_name);
88
+ let mut worker = starter.worker().await;
89
+ worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
90
+ worker.register_activity("echo_activity", echo);
91
+
92
+ worker
93
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
94
+ .await
95
+ .unwrap();
96
+ worker.run_until_done().await.unwrap();
97
+ }
98
+
99
+ #[tokio::test]
100
+ async fn long_running_local_act_with_timer() {
101
+ let wf_name = "long_running_local_act_with_timer";
102
+ let mut starter = CoreWfStarter::new(wf_name);
103
+ starter.wft_timeout(Duration::from_secs(1));
104
+ let mut worker = starter.worker().await;
105
+ worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
106
+ worker.register_activity("echo_activity", |str: String| async {
107
+ tokio::time::sleep(Duration::from_secs(4)).await;
108
+ Ok(str)
109
+ });
110
+
111
+ worker
112
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
113
+ .await
114
+ .unwrap();
115
+ worker.run_until_done().await.unwrap();
116
+ }
117
+
118
+ pub async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
119
+ let las: Vec<_> = (1..=50)
120
+ .map(|i| {
121
+ ctx.local_activity(LocalActivityOptions {
122
+ activity_type: "echo_activity".to_string(),
123
+ input: format!("Hi {}", i)
124
+ .as_json_payload()
125
+ .expect("serializes fine"),
126
+ ..Default::default()
127
+ })
128
+ })
129
+ .collect();
130
+ ctx.timer(Duration::from_secs(1)).await;
131
+ join_all(las).await;
132
+ Ok(().into())
133
+ }
134
+
135
+ #[tokio::test]
136
+ async fn local_act_fanout() {
137
+ let wf_name = "local_act_fanout";
138
+ let mut starter = CoreWfStarter::new(wf_name);
139
+ starter.max_local_at(1);
140
+ let mut worker = starter.worker().await;
141
+ worker.register_wf(wf_name.to_owned(), local_act_fanout_wf);
142
+ worker.register_activity("echo_activity", echo);
143
+
144
+ worker
145
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
146
+ .await
147
+ .unwrap();
148
+ worker.run_until_done().await.unwrap();
149
+ }
150
+
151
+ #[tokio::test]
152
+ async fn local_act_retry_timer_backoff() {
153
+ let wf_name = "local_act_retry_timer_backoff";
154
+ let mut starter = CoreWfStarter::new(wf_name);
155
+ let mut worker = starter.worker().await;
156
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
157
+ let res = ctx
158
+ .local_activity(LocalActivityOptions {
159
+ activity_type: "echo".to_string(),
160
+ input: "hi".as_json_payload().expect("serializes fine"),
161
+ retry_policy: RetryPolicy {
162
+ initial_interval: Some(Duration::from_micros(15).into()),
163
+ // We want two local backoffs that are short. Third backoff will use timer
164
+ backoff_coefficient: 1_000.,
165
+ maximum_interval: Some(Duration::from_millis(1500).into()),
166
+ maximum_attempts: 4,
167
+ non_retryable_error_types: vec![],
168
+ },
169
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
170
+ ..Default::default()
171
+ })
172
+ .await;
173
+ assert!(res.failed());
174
+ Ok(().into())
175
+ });
176
+ worker.register_activity("echo", |_: String| async {
177
+ Result::<(), _>::Err(anyhow!("Oh no I failed!"))
178
+ });
179
+
180
+ let run_id = worker
181
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
182
+ .await
183
+ .unwrap();
184
+ worker.run_until_done().await.unwrap();
185
+ starter
186
+ .fetch_history_and_replay(wf_name, run_id, &mut worker)
187
+ .await
188
+ .unwrap();
189
+ }
190
+
191
+ #[rstest::rstest]
192
+ #[case::wait(ActivityCancellationType::WaitCancellationCompleted)]
193
+ #[case::try_cancel(ActivityCancellationType::TryCancel)]
194
+ #[case::abandon(ActivityCancellationType::Abandon)]
195
+ #[tokio::test]
196
+ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
197
+ let wf_name = format!("cancel_immediate_{:?}", cancel_type);
198
+ let mut starter = CoreWfStarter::new(&wf_name);
199
+ let mut worker = starter.worker().await;
200
+ worker.register_wf(&wf_name, move |ctx: WfContext| async move {
201
+ let la = ctx.local_activity(LocalActivityOptions {
202
+ activity_type: "echo".to_string(),
203
+ input: "hi".as_json_payload().expect("serializes fine"),
204
+ cancel_type,
205
+ ..Default::default()
206
+ });
207
+ la.cancel(&ctx);
208
+ let resolution = la.await;
209
+ assert!(resolution.cancelled());
210
+ Ok(().into())
211
+ });
212
+
213
+ // If we don't use this, we'd hang on shutdown for abandon cancel modes.
214
+ let manual_cancel = CancellationToken::new();
215
+ let manual_cancel_act = manual_cancel.clone();
216
+
217
+ worker.register_activity("echo", move |_: String| {
218
+ let manual_cancel_act = manual_cancel_act.clone();
219
+ async move {
220
+ tokio::select! {
221
+ _ = tokio::time::sleep(Duration::from_secs(10)) => {},
222
+ _ = act_cancelled() => {
223
+ return Err(anyhow!(ActivityCancelledError::default()))
224
+ }
225
+ _ = manual_cancel_act.cancelled() => {}
226
+ }
227
+ Ok(())
228
+ }
229
+ });
230
+
231
+ worker
232
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
233
+ .await
234
+ .unwrap();
235
+ worker
236
+ .run_until_done_shutdown_hook(|| manual_cancel.cancel())
237
+ .await
238
+ .unwrap();
239
+ }
240
+
241
+ #[rstest::rstest]
242
+ #[case::while_running(None)]
243
+ #[case::while_backing_off(Some(Duration::from_millis(1500)))]
244
+ #[case::while_backing_off_locally(Some(Duration::from_millis(150)))]
245
+ #[tokio::test]
246
+ async fn cancel_after_act_starts(
247
+ #[case] cancel_on_backoff: Option<Duration>,
248
+ #[values(
249
+ ActivityCancellationType::WaitCancellationCompleted,
250
+ ActivityCancellationType::TryCancel,
251
+ ActivityCancellationType::Abandon
252
+ )]
253
+ cancel_type: ActivityCancellationType,
254
+ ) {
255
+ let wf_name = format!(
256
+ "cancel_after_act_starts_timer_{:?}_{:?}",
257
+ cancel_on_backoff, cancel_type
258
+ );
259
+ let mut starter = CoreWfStarter::new(&wf_name);
260
+ starter.wft_timeout(Duration::from_secs(1));
261
+ let mut worker = starter.worker().await;
262
+ let bo_dur = cancel_on_backoff.unwrap_or_else(|| Duration::from_secs(1));
263
+ worker.register_wf(&wf_name, move |ctx: WfContext| async move {
264
+ let la = ctx.local_activity(LocalActivityOptions {
265
+ activity_type: "echo".to_string(),
266
+ input: "hi".as_json_payload().expect("serializes fine"),
267
+ retry_policy: RetryPolicy {
268
+ initial_interval: Some(bo_dur.into()),
269
+ backoff_coefficient: 1.,
270
+ maximum_interval: Some(bo_dur.into()),
271
+ // Retry forever until cancelled
272
+ ..Default::default()
273
+ },
274
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
275
+ cancel_type,
276
+ ..Default::default()
277
+ });
278
+ ctx.timer(Duration::from_secs(1)).await;
279
+ // Note that this cancel can't go through for *two* WF tasks, because we do a full heartbeat
280
+ // before the timer (LA hasn't resolved), and then the timer fired event won't appear in
281
+ // history until *after* the next WFT because we force generated it when we sent the timer
282
+ // command.
283
+ la.cancel(&ctx);
284
+ // This extra timer is here to ensure the presence of another WF task doesn't mess up
285
+ // resolving the LA with cancel on replay
286
+ ctx.timer(Duration::from_secs(1)).await;
287
+ let resolution = la.await;
288
+ assert!(resolution.cancelled());
289
+ Ok(().into())
290
+ });
291
+
292
+ // If we don't use this, we'd hang on shutdown for abandon cancel modes.
293
+ let manual_cancel = CancellationToken::new();
294
+ let manual_cancel_act = manual_cancel.clone();
295
+
296
+ worker.register_activity("echo", move |_: String| {
297
+ let manual_cancel_act = manual_cancel_act.clone();
298
+ async move {
299
+ if cancel_on_backoff.is_some() {
300
+ if act_is_cancelled() {
301
+ return Err(anyhow!(ActivityCancelledError::default()));
302
+ }
303
+ // Just fail constantly so we get stuck on the backoff timer
304
+ return Err(anyhow!("Oh no I failed!"));
305
+ } else {
306
+ tokio::select! {
307
+ _ = tokio::time::sleep(Duration::from_secs(100)) => {},
308
+ _ = act_cancelled() => {
309
+ return Err(anyhow!(ActivityCancelledError::default()))
310
+ }
311
+ _ = manual_cancel_act.cancelled() => {
312
+ return Ok(())
313
+ }
314
+ }
315
+ }
316
+ Err(anyhow!("Oh no I failed!"))
317
+ }
318
+ });
319
+
320
+ worker
321
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
322
+ .await
323
+ .unwrap();
324
+ worker
325
+ .run_until_done_shutdown_hook(|| manual_cancel.cancel())
326
+ .await
327
+ .unwrap();
328
+ }
329
+
330
+ #[rstest::rstest]
331
+ #[case::schedule(true)]
332
+ #[case::start(false)]
333
+ #[tokio::test]
334
+ async fn x_to_close_timeout(#[case] is_schedule: bool) {
335
+ let wf_name = format!(
336
+ "{}_to_close_timeout",
337
+ if is_schedule { "schedule" } else { "start" }
338
+ );
339
+ let mut starter = CoreWfStarter::new(&wf_name);
340
+ let mut worker = starter.worker().await;
341
+ let (sched, start) = if is_schedule {
342
+ (Some(Duration::from_secs(2)), None)
343
+ } else {
344
+ (None, Some(Duration::from_secs(2)))
345
+ };
346
+
347
+ worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
348
+ let res = ctx
349
+ .local_activity(LocalActivityOptions {
350
+ activity_type: "echo".to_string(),
351
+ input: "hi".as_json_payload().expect("serializes fine"),
352
+ retry_policy: RetryPolicy {
353
+ initial_interval: Some(Duration::from_micros(15).into()),
354
+ backoff_coefficient: 1_000.,
355
+ maximum_interval: Some(Duration::from_millis(1500).into()),
356
+ maximum_attempts: 4,
357
+ non_retryable_error_types: vec![],
358
+ },
359
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
360
+ schedule_to_close_timeout: sched,
361
+ start_to_close_timeout: start,
362
+ ..Default::default()
363
+ })
364
+ .await;
365
+ assert!(res.timed_out());
366
+ Ok(().into())
367
+ });
368
+ worker.register_activity("echo", |_: String| async {
369
+ tokio::select! {
370
+ _ = tokio::time::sleep(Duration::from_secs(100)) => {},
371
+ _ = act_cancelled() => {
372
+ return Err(anyhow!(ActivityCancelledError::default()))
373
+ }
374
+ };
375
+ Ok(())
376
+ });
377
+
378
+ worker
379
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
380
+ .await
381
+ .unwrap();
382
+ worker.run_until_done().await.unwrap();
383
+ }
384
+
385
+ #[rstest::rstest]
386
+ #[case::cached(true)]
387
+ #[case::not_cached(false)]
388
+ #[tokio::test]
389
+ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
390
+ let wf_name = format!(
391
+ "schedule_to_close_timeout_across_timer_backoff_{}",
392
+ if cached { "cached" } else { "not_cached" }
393
+ );
394
+ let mut starter = CoreWfStarter::new(&wf_name);
395
+ if !cached {
396
+ starter.max_cached_workflows(0);
397
+ }
398
+ let mut worker = starter.worker().await;
399
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
400
+ let res = ctx
401
+ .local_activity(LocalActivityOptions {
402
+ activity_type: "echo".to_string(),
403
+ input: "hi".as_json_payload().expect("serializes fine"),
404
+ retry_policy: RetryPolicy {
405
+ initial_interval: Some(Duration::from_micros(15).into()),
406
+ backoff_coefficient: 1_000.,
407
+ maximum_interval: Some(Duration::from_millis(1500).into()),
408
+ maximum_attempts: 40,
409
+ non_retryable_error_types: vec![],
410
+ },
411
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
412
+ schedule_to_close_timeout: Some(Duration::from_secs(3)),
413
+ ..Default::default()
414
+ })
415
+ .await;
416
+ assert!(res.timed_out());
417
+ Ok(().into())
418
+ });
419
+ worker.register_activity("echo", |_: String| async {
420
+ Result::<(), _>::Err(anyhow!("Oh no I failed!"))
421
+ });
422
+
423
+ worker
424
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
425
+ .await
426
+ .unwrap();
427
+ worker.run_until_done().await.unwrap();
428
+ }
429
+
430
+ #[tokio::test]
431
+ async fn eviction_wont_make_local_act_get_dropped() {
432
+ let wf_name = "eviction_wont_make_local_act_get_dropped";
433
+ let mut starter = CoreWfStarter::new(wf_name);
434
+ starter.max_cached_workflows(0);
435
+ starter.wft_timeout(Duration::from_secs(1));
436
+ let mut worker = starter.worker().await;
437
+ worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
438
+ worker.register_activity("echo_activity", |str: String| async {
439
+ tokio::time::sleep(Duration::from_secs(4)).await;
440
+ Ok(str)
441
+ });
442
+
443
+ worker
444
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
445
+ .await
446
+ .unwrap();
447
+ worker.run_until_done().await.unwrap();
448
+ }
449
+
450
+ #[tokio::test]
451
+ async fn timer_backoff_concurrent_with_non_timer_backoff() {
452
+ let wf_name = "timer_backoff_concurrent_with_non_timer_backoff";
453
+ let mut starter = CoreWfStarter::new(wf_name);
454
+ let mut worker = starter.worker().await;
455
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
456
+ let r1 = ctx.local_activity(LocalActivityOptions {
457
+ activity_type: "echo".to_string(),
458
+ input: "hi".as_json_payload().expect("serializes fine"),
459
+ retry_policy: RetryPolicy {
460
+ initial_interval: Some(Duration::from_micros(15).into()),
461
+ backoff_coefficient: 1_000.,
462
+ maximum_interval: Some(Duration::from_millis(1500).into()),
463
+ maximum_attempts: 4,
464
+ non_retryable_error_types: vec![],
465
+ },
466
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
467
+ ..Default::default()
468
+ });
469
+ let r2 = ctx.local_activity(LocalActivityOptions {
470
+ activity_type: "echo".to_string(),
471
+ input: "hi".as_json_payload().expect("serializes fine"),
472
+ retry_policy: RetryPolicy {
473
+ initial_interval: Some(Duration::from_millis(15).into()),
474
+ backoff_coefficient: 10.,
475
+ maximum_interval: Some(Duration::from_millis(1500).into()),
476
+ maximum_attempts: 4,
477
+ non_retryable_error_types: vec![],
478
+ },
479
+ timer_backoff_threshold: Some(Duration::from_secs(10)),
480
+ ..Default::default()
481
+ });
482
+ let (r1, r2) = tokio::join!(r1, r2);
483
+ assert!(r1.failed());
484
+ assert!(r2.failed());
485
+ Ok(().into())
486
+ });
487
+ worker.register_activity("echo", |_: String| async {
488
+ Result::<(), _>::Err(anyhow!("Oh no I failed!"))
489
+ });
490
+
491
+ worker
492
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![])
493
+ .await
494
+ .unwrap();
495
+ worker.run_until_done().await.unwrap();
496
+ }
@@ -2,12 +2,12 @@ use std::{
2
2
  sync::atomic::{AtomicBool, Ordering},
3
3
  time::Duration,
4
4
  };
5
- use temporal_sdk_core::prototype_rust_sdk::{WfContext, WorkflowResult};
6
- use test_utils::CoreWfStarter;
5
+ use temporal_sdk::{WfContext, WorkflowResult};
6
+ use temporal_sdk_core_test_utils::CoreWfStarter;
7
7
 
8
8
  const MY_PATCH_ID: &str = "integ_test_change_name";
9
9
 
10
- pub async fn changes_wf(mut ctx: WfContext) -> WorkflowResult<()> {
10
+ pub async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> {
11
11
  if ctx.patched(MY_PATCH_ID) {
12
12
  ctx.timer(Duration::from_millis(100)).await;
13
13
  } else {
@@ -34,13 +34,12 @@ async fn writes_change_markers() {
34
34
  .await
35
35
  .unwrap();
36
36
  worker.run_until_done().await.unwrap();
37
- starter.shutdown().await;
38
37
  }
39
38
 
40
39
  /// This one simulates a run as if the worker had the "old" code, then it fails at the end as
41
40
  /// a cheapo way of being re-run, at which point it runs with change checks and the "new" code.
42
41
  static DID_DIE: AtomicBool = AtomicBool::new(false);
43
- pub async fn no_change_then_change_wf(mut ctx: WfContext) -> WorkflowResult<()> {
42
+ pub async fn no_change_then_change_wf(ctx: WfContext) -> WorkflowResult<()> {
44
43
  if DID_DIE.load(Ordering::Acquire) {
45
44
  assert!(!ctx.patched(MY_PATCH_ID));
46
45
  }
@@ -70,11 +69,10 @@ async fn can_add_change_markers() {
70
69
  .await
71
70
  .unwrap();
72
71
  worker.run_until_done().await.unwrap();
73
- starter.shutdown().await;
74
72
  }
75
73
 
76
74
  static DID_DIE_2: AtomicBool = AtomicBool::new(false);
77
- pub async fn replay_with_change_marker_wf(mut ctx: WfContext) -> WorkflowResult<()> {
75
+ pub async fn replay_with_change_marker_wf(ctx: WfContext) -> WorkflowResult<()> {
78
76
  assert!(ctx.patched(MY_PATCH_ID));
79
77
  ctx.timer(Duration::from_millis(200)).await;
80
78
  if !DID_DIE_2.load(Ordering::Acquire) {
@@ -96,5 +94,4 @@ async fn replaying_with_patch_marker() {
96
94
  .await
97
95
  .unwrap();
98
96
  worker.run_until_done().await.unwrap();
99
- starter.shutdown().await;
100
97
  }
@@ -0,0 +1,125 @@
1
+ use assert_matches::assert_matches;
2
+ use std::time::Duration;
3
+ use temporal_sdk_core::{replay::mock_gateway_from_history, ServerGatewayApis};
4
+ use temporal_sdk_core_api::errors::{PollActivityError, PollWfError};
5
+ use temporal_sdk_core_protos::{
6
+ coresdk::{
7
+ workflow_activation::remove_from_cache::EvictionReason,
8
+ workflow_commands::{ScheduleActivity, StartTimer},
9
+ workflow_completion::WorkflowActivationCompletion,
10
+ },
11
+ temporal::api::workflowservice::v1::PollWorkflowTaskQueueResponse,
12
+ };
13
+ use temporal_sdk_core_test_utils::{
14
+ history_from_proto_binary, init_core_replay_preloaded, CoreTestHelpers,
15
+ };
16
+ use tokio::join;
17
+
18
+ #[tokio::test]
19
+ async fn timer_workflow_replay() {
20
+ let (core, task_q) = init_core_replay_preloaded(
21
+ "timer_workflow_replay",
22
+ &history_from_proto_binary("histories/timer_workflow_history.bin")
23
+ .await
24
+ .unwrap(),
25
+ );
26
+ let task = core.poll_workflow_activation(&task_q).await.unwrap();
27
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
28
+ &task_q,
29
+ task.run_id,
30
+ vec![StartTimer {
31
+ seq: 0,
32
+ start_to_fire_timeout: Some(Duration::from_secs(1).into()),
33
+ }
34
+ .into()],
35
+ ))
36
+ .await
37
+ .unwrap();
38
+ let task = core.poll_workflow_activation(&task_q).await.unwrap();
39
+ // Verify that an in-progress poll is interrupted by completion finishing processing history
40
+ let act_poll_fut = async {
41
+ assert_matches!(
42
+ core.poll_activity_task(&task_q).await,
43
+ Err(PollActivityError::ShutDown)
44
+ );
45
+ };
46
+ let poll_fut = async {
47
+ assert_matches!(
48
+ core.poll_workflow_activation(&task_q).await,
49
+ Err(PollWfError::ShutDown)
50
+ );
51
+ };
52
+ let complete_fut = async {
53
+ core.complete_execution(&task_q, &task.run_id).await;
54
+ };
55
+ join!(act_poll_fut, poll_fut, complete_fut);
56
+
57
+ // Subsequent polls should still return shutdown
58
+ assert_matches!(
59
+ core.poll_workflow_activation(&task_q).await,
60
+ Err(PollWfError::ShutDown)
61
+ );
62
+
63
+ core.shutdown().await;
64
+ }
65
+
66
+ // Regression test to verify mock replayers don't interfere with each other
67
+ #[tokio::test]
68
+ async fn two_cores_replay() {
69
+ let hist = history_from_proto_binary("histories/fail_wf_task.bin")
70
+ .await
71
+ .unwrap();
72
+
73
+ let mock_1 = mock_gateway_from_history(&hist, "a");
74
+ let mock_2 = mock_gateway_from_history(&hist, "b");
75
+ assert_ne!(
76
+ mock_1
77
+ .poll_workflow_task("a".to_string(), false)
78
+ .await
79
+ .unwrap(),
80
+ PollWorkflowTaskQueueResponse::default()
81
+ );
82
+ assert_ne!(
83
+ mock_2
84
+ .poll_workflow_task("b".to_string(), false)
85
+ .await
86
+ .unwrap(),
87
+ PollWorkflowTaskQueueResponse::default()
88
+ );
89
+ }
90
+
91
+ #[tokio::test]
92
+ async fn workflow_nondeterministic_replay() {
93
+ let (core, task_q) = init_core_replay_preloaded(
94
+ "timer_workflow_replay",
95
+ &history_from_proto_binary("histories/timer_workflow_history.bin")
96
+ .await
97
+ .unwrap(),
98
+ );
99
+ let task = core.poll_workflow_activation(&task_q).await.unwrap();
100
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
101
+ &task_q,
102
+ task.run_id,
103
+ vec![ScheduleActivity {
104
+ seq: 0,
105
+ activity_id: "0".to_string(),
106
+ activity_type: "fake_act".to_string(),
107
+ ..Default::default()
108
+ }
109
+ .into()],
110
+ ))
111
+ .await
112
+ .unwrap();
113
+ let task = core.poll_workflow_activation(&task_q).await.unwrap();
114
+ assert_eq!(task.eviction_reason(), Some(EvictionReason::Nondeterminism));
115
+ // Complete eviction
116
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(&task_q, task.run_id))
117
+ .await
118
+ .unwrap();
119
+ // Call shutdown explicitly because we saw a nondeterminism eviction
120
+ core.shutdown().await;
121
+ assert_matches!(
122
+ core.poll_workflow_activation(&task_q).await,
123
+ Err(PollWfError::ShutDown)
124
+ );
125
+ }