@temporalio/core-bridge 1.13.0 → 1.13.2

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 (181) hide show
  1. package/Cargo.lock +239 -382
  2. package/Cargo.toml +11 -11
  3. package/lib/native.d.ts +10 -3
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +71 -11
  11. package/sdk-core/.clippy.toml +1 -0
  12. package/sdk-core/.github/workflows/heavy.yml +2 -0
  13. package/sdk-core/.github/workflows/per-pr.yml +50 -18
  14. package/sdk-core/ARCHITECTURE.md +44 -48
  15. package/sdk-core/Cargo.toml +26 -7
  16. package/sdk-core/README.md +4 -0
  17. package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
  18. package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
  19. package/sdk-core/arch_docs/sdks_intro.md +299 -0
  20. package/sdk-core/client/Cargo.toml +8 -7
  21. package/sdk-core/client/src/callback_based.rs +1 -2
  22. package/sdk-core/client/src/lib.rs +485 -299
  23. package/sdk-core/client/src/metrics.rs +32 -8
  24. package/sdk-core/client/src/proxy.rs +124 -5
  25. package/sdk-core/client/src/raw.rs +598 -307
  26. package/sdk-core/client/src/replaceable.rs +253 -0
  27. package/sdk-core/client/src/retry.rs +9 -6
  28. package/sdk-core/client/src/worker_registry/mod.rs +19 -3
  29. package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
  30. package/sdk-core/core/Cargo.toml +100 -31
  31. package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
  32. package/sdk-core/core/src/core_tests/mod.rs +2 -8
  33. package/sdk-core/core/src/core_tests/queries.rs +3 -5
  34. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
  35. package/sdk-core/core/src/core_tests/updates.rs +4 -5
  36. package/sdk-core/core/src/core_tests/workers.rs +4 -3
  37. package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
  38. package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
  39. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
  40. package/sdk-core/core/src/internal_flags.rs +11 -1
  41. package/sdk-core/core/src/lib.rs +50 -36
  42. package/sdk-core/core/src/pollers/mod.rs +5 -5
  43. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  44. package/sdk-core/core/src/protosext/mod.rs +13 -5
  45. package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
  46. package/sdk-core/core/src/retry_logic.rs +256 -108
  47. package/sdk-core/core/src/telemetry/metrics.rs +1 -0
  48. package/sdk-core/core/src/telemetry/mod.rs +8 -2
  49. package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
  50. package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
  51. package/sdk-core/core/src/test_help/mod.rs +10 -1100
  52. package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
  53. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
  55. package/sdk-core/core/src/worker/activities.rs +10 -3
  56. package/sdk-core/core/src/worker/client/mocks.rs +3 -3
  57. package/sdk-core/core/src/worker/client.rs +130 -93
  58. package/sdk-core/core/src/worker/heartbeat.rs +12 -13
  59. package/sdk-core/core/src/worker/mod.rs +31 -21
  60. package/sdk-core/core/src/worker/nexus.rs +14 -3
  61. package/sdk-core/core/src/worker/slot_provider.rs +9 -0
  62. package/sdk-core/core/src/worker/tuner.rs +159 -0
  63. package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
  64. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
  65. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
  66. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
  67. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
  68. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
  69. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
  70. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
  71. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
  72. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
  73. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
  74. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
  75. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
  76. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
  77. package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
  78. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
  79. package/sdk-core/core-api/Cargo.toml +4 -4
  80. package/sdk-core/core-api/src/envconfig.rs +153 -54
  81. package/sdk-core/core-api/src/lib.rs +68 -0
  82. package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
  83. package/sdk-core/core-api/src/telemetry.rs +13 -0
  84. package/sdk-core/core-c-bridge/Cargo.toml +13 -8
  85. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
  86. package/sdk-core/core-c-bridge/src/client.rs +462 -184
  87. package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
  88. package/sdk-core/core-c-bridge/src/lib.rs +1 -0
  89. package/sdk-core/core-c-bridge/src/random.rs +4 -4
  90. package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
  91. package/sdk-core/core-c-bridge/src/testing.rs +1 -4
  92. package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
  93. package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
  94. package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
  95. package/sdk-core/core-c-bridge/src/worker.rs +319 -66
  96. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
  97. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
  98. package/sdk-core/sdk/Cargo.toml +8 -2
  99. package/sdk-core/sdk/src/activity_context.rs +1 -1
  100. package/sdk-core/sdk/src/app_data.rs +1 -1
  101. package/sdk-core/sdk/src/interceptors.rs +1 -4
  102. package/sdk-core/sdk/src/lib.rs +1 -5
  103. package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
  104. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  105. package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
  106. package/sdk-core/sdk-core-protos/build.rs +10 -23
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
  115. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  116. package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
  117. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
  118. package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
  119. package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
  120. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
  121. package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
  122. package/sdk-core/tests/cloud_tests.rs +10 -8
  123. package/sdk-core/tests/common/http_proxy.rs +134 -0
  124. package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
  125. package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
  126. package/sdk-core/tests/fuzzy_workflow.rs +1 -1
  127. package/sdk-core/tests/global_metric_tests.rs +8 -7
  128. package/sdk-core/tests/heavy_tests.rs +7 -3
  129. package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
  130. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
  131. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
  132. package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
  133. package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
  134. package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
  135. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
  136. package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
  137. package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
  138. package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
  139. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
  140. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
  141. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
  142. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
  143. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
  144. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
  145. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
  146. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
  147. package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
  148. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
  149. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
  150. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
  151. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
  152. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
  153. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
  154. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
  155. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
  156. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
  157. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
  158. package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
  159. package/sdk-core/tests/main.rs +26 -17
  160. package/sdk-core/tests/manual_tests.rs +5 -1
  161. package/sdk-core/tests/runner.rs +22 -40
  162. package/sdk-core/tests/shared_tests/mod.rs +1 -1
  163. package/sdk-core/tests/shared_tests/priority.rs +1 -1
  164. package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
  165. package/src/client.rs +97 -20
  166. package/src/helpers/callbacks.rs +4 -4
  167. package/src/helpers/errors.rs +7 -1
  168. package/src/helpers/handles.rs +1 -0
  169. package/src/helpers/try_from_js.rs +4 -3
  170. package/src/lib.rs +3 -2
  171. package/src/metrics.rs +3 -0
  172. package/src/runtime.rs +5 -2
  173. package/src/worker.rs +9 -12
  174. package/ts/native.ts +13 -3
  175. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
  176. package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
  177. package/sdk-core/core/src/core_tests/determinism.rs +0 -318
  178. package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
  179. package/sdk-core/test-utils/Cargo.toml +0 -38
  180. package/sdk-core/test-utils/src/histfetch.rs +0 -28
  181. package/sdk-core/test-utils/src/interceptors.rs +0 -46
@@ -1,1442 +0,0 @@
1
- use crate::{
2
- prost_dur,
3
- replay::{DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, default_wes_attribs},
4
- test_help::{
5
- MockPollCfg, ResponseType, WorkerExt, build_mock_pollers, hist_to_poll_resp, mock_sdk,
6
- mock_sdk_cfg, mock_worker, single_hist_mock_sg,
7
- },
8
- worker::{LEGACY_QUERY_ID, client::mocks::mock_worker_client},
9
- };
10
- use anyhow::anyhow;
11
- use crossbeam_queue::SegQueue;
12
- use futures_util::{FutureExt, future::join_all};
13
- use std::{
14
- collections::HashMap,
15
- ops::Sub,
16
- sync::{
17
- Arc,
18
- atomic::{AtomicUsize, Ordering},
19
- },
20
- time::{Duration, Instant, SystemTime},
21
- };
22
- use temporal_client::WorkflowOptions;
23
- use temporal_sdk::{
24
- ActContext, ActivityError, LocalActivityOptions, WfContext, WorkflowFunction, WorkflowResult,
25
- };
26
- use temporal_sdk_core_api::{Worker, errors::PollError};
27
- use temporal_sdk_core_protos::{
28
- DEFAULT_ACTIVITY_TYPE,
29
- coresdk::{
30
- ActivityTaskCompletion, AsJsonPayloadExt,
31
- activity_result::ActivityExecutionResult,
32
- workflow_activation::{WorkflowActivationJob, workflow_activation_job},
33
- workflow_commands::{ActivityCancellationType, ScheduleLocalActivity},
34
- workflow_completion::WorkflowActivationCompletion,
35
- },
36
- temporal::api::{
37
- common::v1::RetryPolicy,
38
- enums::v1::{CommandType, EventType, TimeoutType, WorkflowTaskFailedCause},
39
- failure::v1::{Failure, failure::FailureInfo},
40
- query::v1::WorkflowQuery,
41
- },
42
- };
43
- use temporal_sdk_core_test_utils::{
44
- WorkerTestHelpers, query_ok, schedule_local_activity_cmd, start_timer_cmd,
45
- };
46
- use tokio::{join, select, sync::Barrier};
47
-
48
- async fn echo(_ctx: ActContext, e: String) -> Result<String, ActivityError> {
49
- Ok(e)
50
- }
51
-
52
- /// This test verifies that when replaying we are able to resolve local activities whose data we
53
- /// don't see until after the workflow issues the command
54
- #[rstest::rstest]
55
- #[case::replay(true, true)]
56
- #[case::not_replay(false, true)]
57
- #[case::replay_cache_off(true, false)]
58
- #[case::not_replay_cache_off(false, false)]
59
- #[tokio::test]
60
- async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached: bool) {
61
- let mut t = TestHistoryBuilder::default();
62
- t.add_by_type(EventType::WorkflowExecutionStarted);
63
- t.add_full_wf_task();
64
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
65
- t.add_full_wf_task();
66
- t.add_local_activity_result_marker(1, "1", b"echo".into());
67
- t.add_timer_fired(timer_started_event_id, "1".to_string());
68
- t.add_full_wf_task();
69
- t.add_workflow_execution_completed();
70
-
71
- let wf_id = "fakeid";
72
- let mock = mock_worker_client();
73
- let resps = if replay {
74
- vec![ResponseType::AllHistory]
75
- } else {
76
- vec![1.into(), 2.into(), ResponseType::AllHistory]
77
- };
78
- let mh = MockPollCfg::from_resp_batches(wf_id, t, resps, mock);
79
- let mut worker = mock_sdk_cfg(mh, |cfg| {
80
- if cached {
81
- cfg.max_cached_workflows = 1;
82
- }
83
- });
84
-
85
- worker.register_wf(
86
- DEFAULT_WORKFLOW_TYPE.to_owned(),
87
- |ctx: WfContext| async move {
88
- let la = ctx.local_activity(LocalActivityOptions {
89
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
90
- input: "hi".as_json_payload().expect("serializes fine"),
91
- ..Default::default()
92
- });
93
- ctx.timer(Duration::from_secs(1)).await;
94
- la.await;
95
- Ok(().into())
96
- },
97
- );
98
- worker.register_activity(DEFAULT_ACTIVITY_TYPE, echo);
99
- worker
100
- .submit_wf(
101
- wf_id.to_owned(),
102
- DEFAULT_WORKFLOW_TYPE.to_owned(),
103
- vec![],
104
- WorkflowOptions::default(),
105
- )
106
- .await
107
- .unwrap();
108
- worker.run_until_done().await.unwrap();
109
- }
110
-
111
- pub(crate) async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
112
- let las: Vec<_> = (1..=50)
113
- .map(|i| {
114
- ctx.local_activity(LocalActivityOptions {
115
- activity_type: "echo".to_string(),
116
- input: format!("Hi {i}")
117
- .as_json_payload()
118
- .expect("serializes fine"),
119
- ..Default::default()
120
- })
121
- })
122
- .collect();
123
- ctx.timer(Duration::from_secs(1)).await;
124
- join_all(las).await;
125
- Ok(().into())
126
- }
127
-
128
- #[tokio::test]
129
- async fn local_act_many_concurrent() {
130
- let mut t = TestHistoryBuilder::default();
131
- t.add_by_type(EventType::WorkflowExecutionStarted);
132
- t.add_full_wf_task();
133
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
134
- t.add_full_wf_task();
135
- for i in 1..=50 {
136
- t.add_local_activity_result_marker(i, &i.to_string(), b"echo".into());
137
- }
138
- t.add_timer_fired(timer_started_event_id, "1".to_string());
139
- t.add_full_wf_task();
140
- t.add_workflow_execution_completed();
141
-
142
- let wf_id = "fakeid";
143
- let mock = mock_worker_client();
144
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
145
- let mut worker = mock_sdk(mh);
146
-
147
- worker.register_wf(DEFAULT_WORKFLOW_TYPE.to_owned(), local_act_fanout_wf);
148
- worker.register_activity("echo", echo);
149
- worker
150
- .submit_wf(
151
- wf_id.to_owned(),
152
- DEFAULT_WORKFLOW_TYPE.to_owned(),
153
- vec![],
154
- WorkflowOptions::default(),
155
- )
156
- .await
157
- .unwrap();
158
- worker.run_until_done().await.unwrap();
159
- }
160
-
161
- /// Verifies that local activities which take more than a workflow task timeout will cause
162
- /// us to issue additional (empty) WFT completions with the force flag on, thus preventing timeout
163
- /// of WFT while the local activity continues to execute.
164
- ///
165
- /// The test with shutdown verifies if we call shutdown while the local activity is running that
166
- /// shutdown does not complete until it's finished.
167
- #[rstest::rstest]
168
- #[case::with_shutdown(true)]
169
- #[case::normal_complete(false)]
170
- #[tokio::test]
171
- async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
172
- let mut t = TestHistoryBuilder::default();
173
- let wft_timeout = Duration::from_millis(200);
174
- t.add_wfe_started_with_wft_timeout(wft_timeout);
175
- t.add_full_wf_task();
176
- // Task created by WFT heartbeat
177
- t.add_full_wf_task();
178
- t.add_workflow_task_scheduled_and_started();
179
-
180
- let wf_id = "fakeid";
181
- let mock = mock_worker_client();
182
- let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 2, 2], mock);
183
- mh.enforce_correct_number_of_polls = false;
184
- let mut worker = mock_sdk_cfg(mh, |wc| {
185
- wc.max_cached_workflows = 1;
186
- wc.max_outstanding_workflow_tasks = Some(1);
187
- });
188
- let core = worker.core_worker.clone();
189
-
190
- let shutdown_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
191
-
192
- worker.register_wf(
193
- DEFAULT_WORKFLOW_TYPE.to_owned(),
194
- |ctx: WfContext| async move {
195
- ctx.local_activity(LocalActivityOptions {
196
- activity_type: "echo".to_string(),
197
- input: "hi".as_json_payload().expect("serializes fine"),
198
- ..Default::default()
199
- })
200
- .await;
201
- Ok(().into())
202
- },
203
- );
204
- worker.register_activity("echo", move |_ctx: ActContext, str: String| async move {
205
- if shutdown_middle {
206
- shutdown_barr.wait().await;
207
- }
208
- // Take slightly more than two workflow tasks
209
- tokio::time::sleep(wft_timeout.mul_f32(2.2)).await;
210
- Ok(str)
211
- });
212
- worker
213
- .submit_wf(
214
- wf_id.to_owned(),
215
- DEFAULT_WORKFLOW_TYPE.to_owned(),
216
- vec![],
217
- WorkflowOptions::default(),
218
- )
219
- .await
220
- .unwrap();
221
- let (_, runres) = tokio::join!(
222
- async {
223
- if shutdown_middle {
224
- shutdown_barr.wait().await;
225
- core.shutdown().await;
226
- }
227
- },
228
- worker.run_until_done()
229
- );
230
- runres.unwrap();
231
- }
232
-
233
- #[rstest::rstest]
234
- #[case::retry_then_pass(true)]
235
- #[case::retry_until_fail(false)]
236
- #[tokio::test]
237
- async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
238
- let mut t = TestHistoryBuilder::default();
239
- t.add_by_type(EventType::WorkflowExecutionStarted);
240
- t.add_workflow_task_scheduled_and_started();
241
-
242
- let wf_id = "fakeid";
243
- let mock = mock_worker_client();
244
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
245
- let mut worker = mock_sdk(mh);
246
-
247
- worker.register_wf(
248
- DEFAULT_WORKFLOW_TYPE.to_owned(),
249
- move |ctx: WfContext| async move {
250
- let la_res = ctx
251
- .local_activity(LocalActivityOptions {
252
- activity_type: "echo".to_string(),
253
- input: "hi".as_json_payload().expect("serializes fine"),
254
- retry_policy: RetryPolicy {
255
- initial_interval: Some(prost_dur!(from_millis(50))),
256
- backoff_coefficient: 1.2,
257
- maximum_interval: None,
258
- maximum_attempts: 5,
259
- non_retryable_error_types: vec![],
260
- },
261
- ..Default::default()
262
- })
263
- .await;
264
- if eventually_pass {
265
- assert!(la_res.completed_ok())
266
- } else {
267
- assert!(la_res.failed())
268
- }
269
- Ok(().into())
270
- },
271
- );
272
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
273
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
274
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
275
- if 2 == attempts.fetch_add(1, Ordering::Relaxed) && eventually_pass {
276
- Ok(())
277
- } else {
278
- Err(anyhow!("Oh no I failed!").into())
279
- }
280
- });
281
- worker
282
- .submit_wf(
283
- wf_id.to_owned(),
284
- DEFAULT_WORKFLOW_TYPE.to_owned(),
285
- vec![],
286
- WorkflowOptions::default(),
287
- )
288
- .await
289
- .unwrap();
290
- worker.run_until_done().await.unwrap();
291
- let expected_attempts = if eventually_pass { 3 } else { 5 };
292
- assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
293
- }
294
-
295
- #[tokio::test]
296
- async fn local_act_retry_long_backoff_uses_timer() {
297
- let mut t = TestHistoryBuilder::default();
298
- t.add_by_type(EventType::WorkflowExecutionStarted);
299
- t.add_full_wf_task();
300
- t.add_local_activity_fail_marker(
301
- 1,
302
- "1",
303
- Failure::application_failure("la failed".to_string(), false),
304
- );
305
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
306
- t.add_timer_fired(timer_started_event_id, "1".to_string());
307
- t.add_full_wf_task();
308
- t.add_local_activity_fail_marker(
309
- 2,
310
- "2",
311
- Failure::application_failure("la failed".to_string(), false),
312
- );
313
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
314
- t.add_timer_fired(timer_started_event_id, "2".to_string());
315
- t.add_full_wf_task();
316
- t.add_workflow_execution_completed();
317
-
318
- let wf_id = "fakeid";
319
- let mock = mock_worker_client();
320
- let mh = MockPollCfg::from_resp_batches(
321
- wf_id,
322
- t,
323
- [1.into(), 2.into(), ResponseType::AllHistory],
324
- mock,
325
- );
326
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
327
-
328
- worker.register_wf(
329
- DEFAULT_WORKFLOW_TYPE.to_owned(),
330
- |ctx: WfContext| async move {
331
- let la_res = ctx
332
- .local_activity(LocalActivityOptions {
333
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
334
- input: "hi".as_json_payload().expect("serializes fine"),
335
- retry_policy: RetryPolicy {
336
- initial_interval: Some(prost_dur!(from_millis(65))),
337
- // This will make the second backoff 65 seconds, plenty to use timer
338
- backoff_coefficient: 1_000.,
339
- maximum_interval: Some(prost_dur!(from_secs(600))),
340
- maximum_attempts: 3,
341
- non_retryable_error_types: vec![],
342
- },
343
- ..Default::default()
344
- })
345
- .await;
346
- assert!(la_res.failed());
347
- // Extra timer just to have an extra workflow task which we can return full history for
348
- ctx.timer(Duration::from_secs(1)).await;
349
- Ok(().into())
350
- },
351
- );
352
- worker.register_activity(
353
- DEFAULT_ACTIVITY_TYPE,
354
- move |_ctx: ActContext, _: String| async move {
355
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
356
- },
357
- );
358
- worker
359
- .submit_wf(
360
- wf_id.to_owned(),
361
- DEFAULT_WORKFLOW_TYPE.to_owned(),
362
- vec![],
363
- WorkflowOptions::default(),
364
- )
365
- .await
366
- .unwrap();
367
- worker.run_until_done().await.unwrap();
368
- }
369
-
370
- #[tokio::test]
371
- async fn local_act_null_result() {
372
- let mut t = TestHistoryBuilder::default();
373
- t.add_by_type(EventType::WorkflowExecutionStarted);
374
- t.add_full_wf_task();
375
- t.add_local_activity_marker(1, "1", None, None, |_| {});
376
- t.add_workflow_execution_completed();
377
-
378
- let wf_id = "fakeid";
379
- let mock = mock_worker_client();
380
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
381
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
382
-
383
- worker.register_wf(
384
- DEFAULT_WORKFLOW_TYPE.to_owned(),
385
- |ctx: WfContext| async move {
386
- ctx.local_activity(LocalActivityOptions {
387
- activity_type: "nullres".to_string(),
388
- input: "hi".as_json_payload().expect("serializes fine"),
389
- ..Default::default()
390
- })
391
- .await;
392
- Ok(().into())
393
- },
394
- );
395
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
396
- worker
397
- .submit_wf(
398
- wf_id.to_owned(),
399
- DEFAULT_WORKFLOW_TYPE.to_owned(),
400
- vec![],
401
- WorkflowOptions::default(),
402
- )
403
- .await
404
- .unwrap();
405
- worker.run_until_done().await.unwrap();
406
- }
407
-
408
- #[tokio::test]
409
- async fn local_act_command_immediately_follows_la_marker() {
410
- // This repro only works both when cache is off, and there is at least one heartbeat wft
411
- // before the marker & next command are recorded.
412
- let mut t = TestHistoryBuilder::default();
413
- t.add_by_type(EventType::WorkflowExecutionStarted);
414
- t.add_full_wf_task();
415
- t.add_full_wf_task();
416
- t.add_local_activity_result_marker(1, "1", "done".into());
417
- t.add_by_type(EventType::TimerStarted);
418
- t.add_full_wf_task();
419
-
420
- let wf_id = "fakeid";
421
- let mock = mock_worker_client();
422
- // Bug only repros when seeing history up to third wft
423
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [3], mock);
424
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 0);
425
-
426
- worker.register_wf(
427
- DEFAULT_WORKFLOW_TYPE.to_owned(),
428
- |ctx: WfContext| async move {
429
- ctx.local_activity(LocalActivityOptions {
430
- activity_type: "nullres".to_string(),
431
- input: "hi".as_json_payload().expect("serializes fine"),
432
- ..Default::default()
433
- })
434
- .await;
435
- ctx.timer(Duration::from_secs(1)).await;
436
- Ok(().into())
437
- },
438
- );
439
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
440
- worker
441
- .submit_wf(
442
- wf_id.to_owned(),
443
- DEFAULT_WORKFLOW_TYPE.to_owned(),
444
- vec![],
445
- WorkflowOptions::default(),
446
- )
447
- .await
448
- .unwrap();
449
- worker.run_until_done().await.unwrap();
450
- }
451
-
452
- #[tokio::test]
453
- async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbeat() {
454
- let wfid = "fake_wf_id";
455
- let mut t = TestHistoryBuilder::default();
456
- t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
457
- t.add_full_wf_task();
458
- // get query here
459
- t.add_full_wf_task();
460
- t.add_local_activity_result_marker(1, "1", "done".into());
461
- t.add_workflow_execution_completed();
462
-
463
- let query_with_hist_task = {
464
- let mut pr = hist_to_poll_resp(&t, wfid, ResponseType::ToTaskNum(1));
465
- pr.queries = HashMap::new();
466
- pr.queries.insert(
467
- "the-query".to_string(),
468
- WorkflowQuery {
469
- query_type: "query-type".to_string(),
470
- query_args: Some(b"hi".into()),
471
- header: None,
472
- },
473
- );
474
- pr
475
- };
476
- let after_la_resolved = Arc::new(Barrier::new(2));
477
- let poll_barr = after_la_resolved.clone();
478
- let tasks = [
479
- query_with_hist_task,
480
- hist_to_poll_resp(
481
- &t,
482
- wfid,
483
- ResponseType::UntilResolved(
484
- async move {
485
- poll_barr.wait().await;
486
- }
487
- .boxed(),
488
- 3,
489
- ),
490
- ),
491
- ];
492
- let mock = mock_worker_client();
493
- let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
494
- mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
495
- let core = mock_worker(mock);
496
-
497
- let barrier = Barrier::new(2);
498
-
499
- let wf_fut = async {
500
- let task = core.poll_workflow_activation().await.unwrap();
501
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
502
- task.run_id,
503
- schedule_local_activity_cmd(
504
- 1,
505
- "1",
506
- ActivityCancellationType::TryCancel,
507
- Duration::from_secs(60),
508
- ),
509
- ))
510
- .await
511
- .unwrap();
512
- let task = core.poll_workflow_activation().await.unwrap();
513
- // Get query, and complete it
514
- let query = assert_matches!(
515
- task.jobs.as_slice(),
516
- [WorkflowActivationJob {
517
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(q)),
518
- }] => q
519
- );
520
- // Now complete the LA
521
- barrier.wait().await;
522
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
523
- task.run_id,
524
- query_ok(&query.query_id, "whatev"),
525
- ))
526
- .await
527
- .unwrap();
528
- // Activation with it resolving:
529
- let task = core.poll_workflow_activation().await.unwrap();
530
- assert_matches!(
531
- task.jobs.as_slice(),
532
- [WorkflowActivationJob {
533
- variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
534
- }]
535
- );
536
- core.complete_execution(&task.run_id).await;
537
- };
538
- let act_fut = async {
539
- let act_task = core.poll_activity_task().await.unwrap();
540
- barrier.wait().await;
541
- core.complete_activity_task(ActivityTaskCompletion {
542
- task_token: act_task.task_token,
543
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
544
- })
545
- .await
546
- .unwrap();
547
- after_la_resolved.wait().await;
548
- };
549
-
550
- tokio::join!(wf_fut, act_fut);
551
- }
552
-
553
- #[rstest::rstest]
554
- #[case::impossible_query_in_task(true)]
555
- #[case::real_history(false)]
556
- #[tokio::test]
557
- async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_query_in_task: bool) {
558
- // Ensures we do not send an activation with a legacy query and any other work, which should
559
- // never happen, but there was an issue where an LA resolving could trigger that.
560
- let wfid = "fake_wf_id";
561
- let mut t = TestHistoryBuilder::default();
562
- t.add(default_wes_attribs());
563
- // Since we don't send queries with start workflow, need one workflow task of something else
564
- // b/c we want to get an activation with a job and a nonlegacy query
565
- t.add_full_wf_task();
566
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
567
- t.add_timer_fired(timer_started_event_id, "1".to_string());
568
-
569
- // nonlegacy query got here & LA started here
570
- // then next task is incremental w/ legacy query (for impossible query case)
571
- t.add_full_wf_task();
572
-
573
- let tasks = [
574
- hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(1)),
575
- {
576
- let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2));
577
- pr.queries = HashMap::new();
578
- pr.queries.insert(
579
- "q1".to_string(),
580
- WorkflowQuery {
581
- query_type: "query-type".to_string(),
582
- query_args: Some(b"hi".into()),
583
- header: None,
584
- },
585
- );
586
- pr
587
- },
588
- {
589
- let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(2));
590
- // Strip beginning of history so the only events are WFT sched/started, we need to look
591
- // like we hit the cache
592
- {
593
- let h = pr.history.as_mut().unwrap();
594
- h.events = h.events.split_off(6);
595
- }
596
- // In the nonsense server response case, we attach a legacy query, otherwise this
597
- // response looks like a normal response to a forced WFT heartbeat.
598
- if impossible_query_in_task {
599
- pr.query = Some(WorkflowQuery {
600
- query_type: "query-type".to_string(),
601
- query_args: Some(b"hi".into()),
602
- header: None,
603
- });
604
- }
605
- pr
606
- },
607
- ];
608
- let mut mock = mock_worker_client();
609
- if impossible_query_in_task {
610
- mock.expect_respond_legacy_query()
611
- .times(1)
612
- .returning(move |_, _| Ok(Default::default()));
613
- }
614
- let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
615
- mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
616
- let taskmap = mock.outstanding_task_map.clone().unwrap();
617
- let core = mock_worker(mock);
618
-
619
- let wf_fut = async {
620
- let task = core.poll_workflow_activation().await.unwrap();
621
- assert_matches!(
622
- task.jobs.as_slice(),
623
- &[WorkflowActivationJob {
624
- variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
625
- },]
626
- );
627
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
628
- task.run_id,
629
- start_timer_cmd(1, Duration::from_secs(1)),
630
- ))
631
- .await
632
- .unwrap();
633
-
634
- let task = core.poll_workflow_activation().await.unwrap();
635
- assert_matches!(
636
- task.jobs.as_slice(),
637
- &[WorkflowActivationJob {
638
- variant: Some(workflow_activation_job::Variant::FireTimer(_)),
639
- },]
640
- );
641
- // We want to make sure the weird-looking query gets received while we're working on other
642
- // stuff, so that we don't see the workflow complete and choose to evict.
643
- taskmap.release_run(&task.run_id);
644
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
645
- task.run_id,
646
- schedule_local_activity_cmd(
647
- 1,
648
- "act-id",
649
- ActivityCancellationType::TryCancel,
650
- Duration::from_secs(60),
651
- ),
652
- ))
653
- .await
654
- .unwrap();
655
-
656
- let task = core.poll_workflow_activation().await.unwrap();
657
- // The next task needs to be resolve, since the LA is completed immediately
658
- assert_matches!(
659
- task.jobs.as_slice(),
660
- [WorkflowActivationJob {
661
- variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
662
- }]
663
- );
664
- // Complete workflow
665
- core.complete_execution(&task.run_id).await;
666
-
667
- // Now we will get the query
668
- let task = core.poll_workflow_activation().await.unwrap();
669
- assert_matches!(
670
- task.jobs.as_slice(),
671
- &[WorkflowActivationJob {
672
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
673
- }]
674
- if q.query_id == "q1"
675
- );
676
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
677
- task.run_id,
678
- query_ok("q1", "whatev"),
679
- ))
680
- .await
681
- .unwrap();
682
-
683
- if impossible_query_in_task {
684
- // finish last query
685
- let task = core.poll_workflow_activation().await.unwrap();
686
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
687
- task.run_id,
688
- query_ok(LEGACY_QUERY_ID, "whatev"),
689
- ))
690
- .await
691
- .unwrap();
692
- }
693
- };
694
- let act_fut = async {
695
- let act_task = core.poll_activity_task().await.unwrap();
696
- core.complete_activity_task(ActivityTaskCompletion {
697
- task_token: act_task.task_token,
698
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
699
- })
700
- .await
701
- .unwrap();
702
- };
703
-
704
- join!(wf_fut, act_fut);
705
- core.drain_pollers_and_shutdown().await;
706
- }
707
-
708
- #[tokio::test]
709
- async fn test_schedule_to_start_timeout() {
710
- let mut t = TestHistoryBuilder::default();
711
- t.add_by_type(EventType::WorkflowExecutionStarted);
712
- t.add_full_wf_task();
713
-
714
- let wf_id = "fakeid";
715
- let mock = mock_worker_client();
716
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::ToTaskNum(1)], mock);
717
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
718
-
719
- worker.register_wf(
720
- DEFAULT_WORKFLOW_TYPE.to_owned(),
721
- |ctx: WfContext| async move {
722
- let la_res = ctx
723
- .local_activity(LocalActivityOptions {
724
- activity_type: "echo".to_string(),
725
- input: "hi".as_json_payload().expect("serializes fine"),
726
- // Impossibly small timeout so we timeout in the queue
727
- schedule_to_start_timeout: prost_dur!(from_nanos(1)),
728
- ..Default::default()
729
- })
730
- .await;
731
- assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
732
- let rfail = la_res.unwrap_failure();
733
- assert_matches!(
734
- rfail.failure_info,
735
- Some(FailureInfo::ActivityFailureInfo(_))
736
- );
737
- assert_matches!(
738
- rfail.cause.unwrap().failure_info,
739
- Some(FailureInfo::TimeoutFailureInfo(_))
740
- );
741
- Ok(().into())
742
- },
743
- );
744
- worker.register_activity(
745
- "echo",
746
- move |_ctx: ActContext, _: String| async move { Ok(()) },
747
- );
748
- worker
749
- .submit_wf(
750
- wf_id.to_owned(),
751
- DEFAULT_WORKFLOW_TYPE.to_owned(),
752
- vec![],
753
- WorkflowOptions::default(),
754
- )
755
- .await
756
- .unwrap();
757
- worker.run_until_done().await.unwrap();
758
- }
759
-
760
- #[rstest::rstest]
761
- #[case::sched_to_start(true)]
762
- #[case::sched_to_close(false)]
763
- #[tokio::test]
764
- async fn test_schedule_to_start_timeout_not_based_on_original_time(
765
- #[case] is_sched_to_start: bool,
766
- ) {
767
- // We used to carry over the schedule time of LAs from the "original" schedule time if these LAs
768
- // created newly after backing off across a timer. That was a mistake, since schedule-to-start
769
- // timeouts should apply to when the new attempt was scheduled. This test verifies:
770
- // * we don't time out on s-t-s timeouts because of that, when the param is true.
771
- // * we do properly time out on s-t-c timeouts when the param is false
772
-
773
- let mut t = TestHistoryBuilder::default();
774
- t.add_by_type(EventType::WorkflowExecutionStarted);
775
- t.add_full_wf_task();
776
- let orig_sched = SystemTime::now().sub(Duration::from_secs(60 * 20));
777
- t.add_local_activity_marker(
778
- 1,
779
- "1",
780
- None,
781
- Some(Failure::application_failure("la failed".to_string(), false)),
782
- |deets| {
783
- // Really old schedule time, which should _not_ count against schedule_to_start
784
- deets.original_schedule_time = Some(orig_sched.into());
785
- // Backoff value must be present since we're simulating timer backoff
786
- deets.backoff = Some(prost_dur!(from_secs(100)));
787
- },
788
- );
789
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
790
- t.add_timer_fired(timer_started_event_id, "1".to_string());
791
- t.add_workflow_task_scheduled_and_started();
792
-
793
- let wf_id = "fakeid";
794
- let mock = mock_worker_client();
795
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
796
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
797
-
798
- let schedule_to_close_timeout = Some(if is_sched_to_start {
799
- // This 60 minute timeout will not have elapsed according to the original
800
- // schedule time in the history.
801
- Duration::from_secs(60 * 60)
802
- } else {
803
- // This 10 minute timeout will have already elapsed
804
- Duration::from_secs(10 * 60)
805
- });
806
-
807
- worker.register_wf(
808
- DEFAULT_WORKFLOW_TYPE.to_owned(),
809
- move |ctx: WfContext| async move {
810
- let la_res = ctx
811
- .local_activity(LocalActivityOptions {
812
- activity_type: "echo".to_string(),
813
- input: "hi".as_json_payload().expect("serializes fine"),
814
- retry_policy: RetryPolicy {
815
- initial_interval: Some(prost_dur!(from_millis(50))),
816
- backoff_coefficient: 1.2,
817
- maximum_interval: None,
818
- maximum_attempts: 5,
819
- non_retryable_error_types: vec![],
820
- },
821
- schedule_to_start_timeout: Some(Duration::from_secs(60)),
822
- schedule_to_close_timeout,
823
- ..Default::default()
824
- })
825
- .await;
826
- if is_sched_to_start {
827
- assert!(la_res.completed_ok());
828
- } else {
829
- assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToClose));
830
- }
831
- Ok(().into())
832
- },
833
- );
834
- worker.register_activity(
835
- "echo",
836
- move |_ctx: ActContext, _: String| async move { Ok(()) },
837
- );
838
- worker
839
- .submit_wf(
840
- wf_id.to_owned(),
841
- DEFAULT_WORKFLOW_TYPE.to_owned(),
842
- vec![],
843
- WorkflowOptions::default(),
844
- )
845
- .await
846
- .unwrap();
847
- worker.run_until_done().await.unwrap();
848
- }
849
-
850
- #[rstest::rstest]
851
- #[tokio::test]
852
- async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_completes: bool) {
853
- let mut t = TestHistoryBuilder::default();
854
- t.add_by_type(EventType::WorkflowExecutionStarted);
855
- t.add_full_wf_task();
856
- if la_completes {
857
- t.add_local_activity_marker(1, "1", Some("hi".into()), None, |_| {});
858
- } else {
859
- t.add_local_activity_marker(
860
- 1,
861
- "1",
862
- None,
863
- Some(Failure::timeout(TimeoutType::StartToClose)),
864
- |_| {},
865
- );
866
- }
867
- t.add_full_wf_task();
868
- t.add_workflow_execution_completed();
869
-
870
- let wf_id = "fakeid";
871
- let mock = mock_worker_client();
872
- let mh = MockPollCfg::from_resp_batches(
873
- wf_id,
874
- t,
875
- [ResponseType::ToTaskNum(1), ResponseType::AllHistory],
876
- mock,
877
- );
878
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
879
-
880
- worker.register_wf(
881
- DEFAULT_WORKFLOW_TYPE.to_owned(),
882
- move |ctx: WfContext| async move {
883
- let la_res = ctx
884
- .local_activity(LocalActivityOptions {
885
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
886
- input: "hi".as_json_payload().expect("serializes fine"),
887
- retry_policy: RetryPolicy {
888
- initial_interval: Some(prost_dur!(from_millis(20))),
889
- backoff_coefficient: 1.0,
890
- maximum_interval: None,
891
- maximum_attempts: 5,
892
- non_retryable_error_types: vec![],
893
- },
894
- start_to_close_timeout: Some(prost_dur!(from_millis(25))),
895
- ..Default::default()
896
- })
897
- .await;
898
- if la_completes {
899
- assert!(la_res.completed_ok());
900
- } else {
901
- assert_eq!(la_res.timed_out(), Some(TimeoutType::StartToClose));
902
- }
903
- Ok(().into())
904
- },
905
- );
906
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
907
- let cancels: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
908
- worker.register_activity(
909
- DEFAULT_ACTIVITY_TYPE,
910
- move |ctx: ActContext, _: String| async move {
911
- // Timeout the first 4 attempts, or all of them if we intend to fail
912
- if attempts.fetch_add(1, Ordering::AcqRel) < 4 || !la_completes {
913
- select! {
914
- _ = tokio::time::sleep(Duration::from_millis(100)) => (),
915
- _ = ctx.cancelled() => {
916
- cancels.fetch_add(1, Ordering::AcqRel);
917
- return Err(ActivityError::cancelled());
918
- }
919
- }
920
- }
921
- Ok(())
922
- },
923
- );
924
- worker
925
- .submit_wf(
926
- wf_id.to_owned(),
927
- DEFAULT_WORKFLOW_TYPE.to_owned(),
928
- vec![],
929
- WorkflowOptions::default(),
930
- )
931
- .await
932
- .unwrap();
933
- worker.run_until_done().await.unwrap();
934
- // Activity should have been attempted all 5 times
935
- assert_eq!(attempts.load(Ordering::Acquire), 5);
936
- let num_cancels = if la_completes { 4 } else { 5 };
937
- assert_eq!(cancels.load(Ordering::Acquire), num_cancels);
938
- }
939
-
940
- #[tokio::test]
941
- async fn wft_failure_cancels_running_las() {
942
- let mut t = TestHistoryBuilder::default();
943
- t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
944
- t.add_full_wf_task();
945
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
946
- t.add_timer_fired(timer_started_event_id, "1".to_string());
947
- t.add_workflow_task_scheduled_and_started();
948
-
949
- let wf_id = "fakeid";
950
- let mock = mock_worker_client();
951
- let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2], mock);
952
- mh.num_expected_fails = 1;
953
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
954
-
955
- worker.register_wf(
956
- DEFAULT_WORKFLOW_TYPE.to_owned(),
957
- |ctx: WfContext| async move {
958
- let la_handle = ctx.local_activity(LocalActivityOptions {
959
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
960
- input: "hi".as_json_payload().expect("serializes fine"),
961
- ..Default::default()
962
- });
963
- tokio::join!(
964
- async {
965
- ctx.timer(Duration::from_secs(1)).await;
966
- panic!("ahhh I'm failing wft")
967
- },
968
- la_handle
969
- );
970
- Ok(().into())
971
- },
972
- );
973
- worker.register_activity(
974
- DEFAULT_ACTIVITY_TYPE,
975
- move |ctx: ActContext, _: String| async move {
976
- let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
977
- if res.is_err() {
978
- panic!("Activity must be cancelled!!!!");
979
- }
980
- Result::<(), _>::Err(ActivityError::cancelled())
981
- },
982
- );
983
- worker
984
- .submit_wf(
985
- wf_id.to_owned(),
986
- DEFAULT_WORKFLOW_TYPE.to_owned(),
987
- vec![],
988
- WorkflowOptions::default(),
989
- )
990
- .await
991
- .unwrap();
992
- worker.run_until_done().await.unwrap();
993
- }
994
-
995
- #[tokio::test]
996
- async fn resolved_las_not_recorded_if_wft_fails_many_times() {
997
- // We shouldn't record any LA results if the workflow activation is repeatedly failing. There
998
- // was an issue that, because we stop reporting WFT failures after 2 tries, this meant the WFT
999
- // was not marked as "completed" and the WFT could accidentally be replied to with LA results.
1000
- let mut t = TestHistoryBuilder::default();
1001
- t.add_by_type(EventType::WorkflowExecutionStarted);
1002
- t.add_workflow_task_scheduled_and_started();
1003
- t.add_workflow_task_failed_with_failure(
1004
- WorkflowTaskFailedCause::Unspecified,
1005
- Default::default(),
1006
- );
1007
- t.add_workflow_task_scheduled_and_started();
1008
-
1009
- let wf_id = "fakeid";
1010
- let mock = mock_worker_client();
1011
- let mut mh = MockPollCfg::from_resp_batches(
1012
- wf_id,
1013
- t,
1014
- [1.into(), ResponseType::AllHistory, ResponseType::AllHistory],
1015
- mock,
1016
- );
1017
- mh.num_expected_fails = 2;
1018
- mh.num_expected_completions = Some(0.into());
1019
- let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1020
-
1021
- #[allow(unreachable_code)]
1022
- worker.register_wf(
1023
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1024
- WorkflowFunction::new::<_, _, ()>(|ctx: WfContext| async move {
1025
- ctx.local_activity(LocalActivityOptions {
1026
- activity_type: "echo".to_string(),
1027
- input: "hi".as_json_payload().expect("serializes fine"),
1028
- ..Default::default()
1029
- })
1030
- .await;
1031
- panic!()
1032
- }),
1033
- );
1034
- worker.register_activity(
1035
- "echo",
1036
- move |_: ActContext, _: String| async move { Ok(()) },
1037
- );
1038
- worker
1039
- .submit_wf(
1040
- wf_id.to_owned(),
1041
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1042
- vec![],
1043
- WorkflowOptions::default(),
1044
- )
1045
- .await
1046
- .unwrap();
1047
- worker.run_until_done().await.unwrap();
1048
- }
1049
-
1050
- #[tokio::test]
1051
- async fn local_act_records_nonfirst_attempts_ok() {
1052
- let mut t = TestHistoryBuilder::default();
1053
- let wft_timeout = Duration::from_millis(200);
1054
- t.add_wfe_started_with_wft_timeout(wft_timeout);
1055
- t.add_full_wf_task();
1056
- t.add_full_wf_task();
1057
- t.add_full_wf_task();
1058
- t.add_workflow_task_scheduled_and_started();
1059
-
1060
- let wf_id = "fakeid";
1061
- let mock = mock_worker_client();
1062
- let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
1063
- let nonfirst_counts = Arc::new(SegQueue::new());
1064
- let nfc_c = nonfirst_counts.clone();
1065
- mh.completion_mock_fn = Some(Box::new(move |c| {
1066
- nfc_c.push(
1067
- c.metering_metadata
1068
- .nonfirst_local_activity_execution_attempts,
1069
- );
1070
- Ok(Default::default())
1071
- }));
1072
- let mut worker = mock_sdk_cfg(mh, |wc| {
1073
- wc.max_cached_workflows = 1;
1074
- wc.max_outstanding_workflow_tasks = Some(1);
1075
- });
1076
-
1077
- worker.register_wf(
1078
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1079
- |ctx: WfContext| async move {
1080
- ctx.local_activity(LocalActivityOptions {
1081
- activity_type: "echo".to_string(),
1082
- input: "hi".as_json_payload().expect("serializes fine"),
1083
- retry_policy: RetryPolicy {
1084
- initial_interval: Some(prost_dur!(from_millis(10))),
1085
- backoff_coefficient: 1.0,
1086
- maximum_interval: None,
1087
- maximum_attempts: 0,
1088
- non_retryable_error_types: vec![],
1089
- },
1090
- ..Default::default()
1091
- })
1092
- .await;
1093
- Ok(().into())
1094
- },
1095
- );
1096
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1097
- Result::<(), _>::Err(anyhow!("I fail").into())
1098
- });
1099
- worker
1100
- .submit_wf(
1101
- wf_id.to_owned(),
1102
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1103
- vec![],
1104
- WorkflowOptions::default(),
1105
- )
1106
- .await
1107
- .unwrap();
1108
- worker.run_until_done().await.unwrap();
1109
- // 3 workflow tasks
1110
- assert_eq!(nonfirst_counts.len(), 3);
1111
- // First task's non-first count should, of course, be 0
1112
- assert_eq!(nonfirst_counts.pop().unwrap(), 0);
1113
- // Next two, some nonzero amount which could vary based on test load
1114
- assert!(nonfirst_counts.pop().unwrap() > 0);
1115
- assert!(nonfirst_counts.pop().unwrap() > 0);
1116
- }
1117
-
1118
- #[tokio::test]
1119
- async fn local_activities_can_be_delivered_during_shutdown() {
1120
- let wfid = "fake_wf_id";
1121
- let mut t = TestHistoryBuilder::default();
1122
- t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
1123
- t.add_full_wf_task();
1124
- let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1125
- t.add_timer_fired(timer_started_event_id, "1".to_string());
1126
- t.add_workflow_task_scheduled_and_started();
1127
-
1128
- let mock = mock_worker_client();
1129
- let mut mock = single_hist_mock_sg(
1130
- wfid,
1131
- t,
1132
- [ResponseType::ToTaskNum(1), ResponseType::AllHistory],
1133
- mock,
1134
- true,
1135
- );
1136
- mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
1137
- let core = mock_worker(mock);
1138
-
1139
- let task = core.poll_workflow_activation().await.unwrap();
1140
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1141
- task.run_id,
1142
- start_timer_cmd(1, Duration::from_secs(1)),
1143
- ))
1144
- .await
1145
- .unwrap();
1146
-
1147
- let task = core.poll_workflow_activation().await.unwrap();
1148
- // Initiate shutdown once we have the WF activation, but before replying that we want to do an
1149
- // LA
1150
- core.initiate_shutdown();
1151
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1152
- task.run_id,
1153
- ScheduleLocalActivity {
1154
- seq: 1,
1155
- activity_id: "1".to_string(),
1156
- activity_type: "test_act".to_string(),
1157
- start_to_close_timeout: Some(prost_dur!(from_secs(30))),
1158
- ..Default::default()
1159
- }
1160
- .into(),
1161
- ))
1162
- .await
1163
- .unwrap();
1164
-
1165
- let wf_poller = async { core.poll_workflow_activation().await };
1166
-
1167
- let at_poller = async {
1168
- let act_task = core.poll_activity_task().await.unwrap();
1169
- core.complete_activity_task(ActivityTaskCompletion {
1170
- task_token: act_task.task_token,
1171
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
1172
- })
1173
- .await
1174
- .unwrap();
1175
- core.poll_activity_task().await
1176
- };
1177
-
1178
- let (wf_r, act_r) = join!(wf_poller, at_poller);
1179
- assert_matches!(wf_r.unwrap_err(), PollError::ShutDown);
1180
- assert_matches!(act_r.unwrap_err(), PollError::ShutDown);
1181
- }
1182
-
1183
- #[tokio::test]
1184
- async fn queries_can_be_received_while_heartbeating() {
1185
- let wfid = "fake_wf_id";
1186
- let mut t = TestHistoryBuilder::default();
1187
- t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
1188
- t.add_full_wf_task();
1189
- t.add_full_wf_task();
1190
- t.add_full_wf_task();
1191
-
1192
- let tasks = [
1193
- hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(1)),
1194
- {
1195
- let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2));
1196
- pr.queries = HashMap::new();
1197
- pr.queries.insert(
1198
- "q1".to_string(),
1199
- WorkflowQuery {
1200
- query_type: "query-type".to_string(),
1201
- query_args: Some(b"hi".into()),
1202
- header: None,
1203
- },
1204
- );
1205
- pr
1206
- },
1207
- {
1208
- let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(3));
1209
- pr.query = Some(WorkflowQuery {
1210
- query_type: "query-type".to_string(),
1211
- query_args: Some(b"hi".into()),
1212
- header: None,
1213
- });
1214
- pr
1215
- },
1216
- ];
1217
- let mut mock = mock_worker_client();
1218
- mock.expect_respond_legacy_query()
1219
- .times(1)
1220
- .returning(move |_, _| Ok(Default::default()));
1221
- let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
1222
- mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
1223
- let core = mock_worker(mock);
1224
-
1225
- let task = core.poll_workflow_activation().await.unwrap();
1226
- assert_matches!(
1227
- task.jobs.as_slice(),
1228
- &[WorkflowActivationJob {
1229
- variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
1230
- },]
1231
- );
1232
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1233
- task.run_id,
1234
- schedule_local_activity_cmd(
1235
- 1,
1236
- "act-id",
1237
- ActivityCancellationType::TryCancel,
1238
- Duration::from_secs(60),
1239
- ),
1240
- ))
1241
- .await
1242
- .unwrap();
1243
-
1244
- let task = core.poll_workflow_activation().await.unwrap();
1245
- assert_matches!(
1246
- task.jobs.as_slice(),
1247
- &[WorkflowActivationJob {
1248
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
1249
- }]
1250
- if q.query_id == "q1"
1251
- );
1252
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1253
- task.run_id,
1254
- query_ok("q1", "whatev"),
1255
- ))
1256
- .await
1257
- .unwrap();
1258
-
1259
- let task = core.poll_workflow_activation().await.unwrap();
1260
- assert_matches!(
1261
- task.jobs.as_slice(),
1262
- &[WorkflowActivationJob {
1263
- variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
1264
- }]
1265
- if q.query_id == LEGACY_QUERY_ID
1266
- );
1267
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1268
- task.run_id,
1269
- query_ok(LEGACY_QUERY_ID, "whatev"),
1270
- ))
1271
- .await
1272
- .unwrap();
1273
-
1274
- // Handle the activity so we can shut down cleanly
1275
- let act_task = core.poll_activity_task().await.unwrap();
1276
- core.complete_activity_task(ActivityTaskCompletion {
1277
- task_token: act_task.task_token,
1278
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
1279
- })
1280
- .await
1281
- .unwrap();
1282
-
1283
- core.drain_pollers_and_shutdown().await;
1284
- }
1285
-
1286
- #[tokio::test]
1287
- async fn local_activity_after_wf_complete_is_discarded() {
1288
- let wfid = "fake_wf_id";
1289
- let mut t = TestHistoryBuilder::default();
1290
- t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
1291
- t.add_full_wf_task();
1292
- t.add_workflow_task_scheduled_and_started();
1293
-
1294
- let mock = mock_worker_client();
1295
- let mut mock_cfg = MockPollCfg::from_resp_batches(
1296
- wfid,
1297
- t,
1298
- [ResponseType::ToTaskNum(1), ResponseType::ToTaskNum(2)],
1299
- mock,
1300
- );
1301
- mock_cfg.make_poll_stream_interminable = true;
1302
- mock_cfg.completion_asserts_from_expectations(|mut asserts| {
1303
- asserts
1304
- .then(move |wft| {
1305
- assert_eq!(wft.commands.len(), 0);
1306
- })
1307
- .then(move |wft| {
1308
- assert_eq!(wft.commands.len(), 2);
1309
- assert_eq!(wft.commands[0].command_type(), CommandType::RecordMarker);
1310
- assert_eq!(
1311
- wft.commands[1].command_type(),
1312
- CommandType::CompleteWorkflowExecution
1313
- );
1314
- });
1315
- });
1316
- let mut mock = build_mock_pollers(mock_cfg);
1317
- mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
1318
- let core = mock_worker(mock);
1319
-
1320
- let barr = Barrier::new(2);
1321
-
1322
- let task = core.poll_workflow_activation().await.unwrap();
1323
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
1324
- task.run_id,
1325
- vec![
1326
- ScheduleLocalActivity {
1327
- seq: 1,
1328
- activity_id: "1".to_string(),
1329
- activity_type: "test_act".to_string(),
1330
- start_to_close_timeout: Some(prost_dur!(from_secs(30))),
1331
- ..Default::default()
1332
- }
1333
- .into(),
1334
- ScheduleLocalActivity {
1335
- seq: 2,
1336
- activity_id: "2".to_string(),
1337
- activity_type: "test_act".to_string(),
1338
- start_to_close_timeout: Some(prost_dur!(from_secs(30))),
1339
- ..Default::default()
1340
- }
1341
- .into(),
1342
- ],
1343
- ))
1344
- .await
1345
- .unwrap();
1346
-
1347
- let wf_poller = async {
1348
- let task = core.poll_workflow_activation().await.unwrap();
1349
- assert_matches!(
1350
- task.jobs.as_slice(),
1351
- [WorkflowActivationJob {
1352
- variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
1353
- }]
1354
- );
1355
- barr.wait().await;
1356
- core.complete_execution(&task.run_id).await;
1357
- };
1358
-
1359
- let at_poller = async {
1360
- let act_task = core.poll_activity_task().await.unwrap();
1361
- core.complete_activity_task(ActivityTaskCompletion {
1362
- task_token: act_task.task_token,
1363
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
1364
- })
1365
- .await
1366
- .unwrap();
1367
- let act_task = core.poll_activity_task().await.unwrap();
1368
- barr.wait().await;
1369
- core.complete_activity_task(ActivityTaskCompletion {
1370
- task_token: act_task.task_token,
1371
- result: Some(ActivityExecutionResult::ok(vec![2].into())),
1372
- })
1373
- .await
1374
- .unwrap();
1375
- };
1376
-
1377
- join!(wf_poller, at_poller);
1378
- core.drain_pollers_and_shutdown().await;
1379
- }
1380
-
1381
- #[tokio::test]
1382
- async fn local_act_retry_explicit_delay() {
1383
- let mut t = TestHistoryBuilder::default();
1384
- t.add_by_type(EventType::WorkflowExecutionStarted);
1385
- t.add_workflow_task_scheduled_and_started();
1386
-
1387
- let wf_id = "fakeid";
1388
- let mock = mock_worker_client();
1389
- let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
1390
- let mut worker = mock_sdk(mh);
1391
-
1392
- worker.register_wf(
1393
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1394
- move |ctx: WfContext| async move {
1395
- let la_res = ctx
1396
- .local_activity(LocalActivityOptions {
1397
- activity_type: "echo".to_string(),
1398
- input: "hi".as_json_payload().expect("serializes fine"),
1399
- retry_policy: RetryPolicy {
1400
- initial_interval: Some(prost_dur!(from_millis(50))),
1401
- backoff_coefficient: 1.0,
1402
- maximum_attempts: 5,
1403
- ..Default::default()
1404
- },
1405
- ..Default::default()
1406
- })
1407
- .await;
1408
- assert!(la_res.completed_ok());
1409
- Ok(().into())
1410
- },
1411
- );
1412
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1413
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1414
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
1415
- let last_attempt = attempts.fetch_add(1, Ordering::Relaxed);
1416
- if 0 == last_attempt {
1417
- Err(ActivityError::Retryable {
1418
- source: anyhow!("Explicit backoff error"),
1419
- explicit_delay: Some(Duration::from_millis(300)),
1420
- })
1421
- } else if 2 == last_attempt {
1422
- Ok(())
1423
- } else {
1424
- Err(anyhow!("Oh no I failed!").into())
1425
- }
1426
- });
1427
- worker
1428
- .submit_wf(
1429
- wf_id.to_owned(),
1430
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1431
- vec![],
1432
- WorkflowOptions::default(),
1433
- )
1434
- .await
1435
- .unwrap();
1436
- let start = Instant::now();
1437
- worker.run_until_done().await.unwrap();
1438
- let expected_attempts = 3;
1439
- assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
1440
- // There will be one 300ms backoff and one 50s backoff, so things should take at least that long
1441
- assert!(start.elapsed() > Duration::from_millis(350));
1442
- }