@temporalio/core-bridge 1.6.0 → 1.7.1

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 (138) hide show
  1. package/Cargo.lock +520 -456
  2. package/lib/index.d.ts +8 -6
  3. package/lib/index.js.map +1 -1
  4. package/package.json +8 -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/.buildkite/docker/Dockerfile +2 -2
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.buildkite/pipeline.yml +1 -1
  13. package/sdk-core/.github/workflows/heavy.yml +1 -0
  14. package/sdk-core/README.md +13 -7
  15. package/sdk-core/client/src/lib.rs +27 -9
  16. package/sdk-core/client/src/metrics.rs +17 -8
  17. package/sdk-core/client/src/raw.rs +3 -3
  18. package/sdk-core/core/Cargo.toml +3 -4
  19. package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
  20. package/sdk-core/core/src/abstractions.rs +197 -18
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +137 -45
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
  23. package/sdk-core/core/src/core_tests/determinism.rs +212 -2
  24. package/sdk-core/core/src/core_tests/local_activities.rs +183 -36
  25. package/sdk-core/core/src/core_tests/queries.rs +32 -14
  26. package/sdk-core/core/src/core_tests/workers.rs +8 -5
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +340 -51
  28. package/sdk-core/core/src/ephemeral_server/mod.rs +110 -8
  29. package/sdk-core/core/src/internal_flags.rs +141 -0
  30. package/sdk-core/core/src/lib.rs +14 -9
  31. package/sdk-core/core/src/replay/mod.rs +16 -27
  32. package/sdk-core/core/src/telemetry/metrics.rs +69 -35
  33. package/sdk-core/core/src/telemetry/mod.rs +38 -14
  34. package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
  35. package/sdk-core/core/src/test_help/mod.rs +65 -13
  36. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
  37. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  38. package/sdk-core/core/src/worker/activities/local_activities.rs +122 -6
  39. package/sdk-core/core/src/worker/activities.rs +347 -173
  40. package/sdk-core/core/src/worker/client/mocks.rs +22 -2
  41. package/sdk-core/core/src/worker/client.rs +18 -2
  42. package/sdk-core/core/src/worker/mod.rs +137 -44
  43. package/sdk-core/core/src/worker/workflow/history_update.rs +132 -51
  44. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +207 -166
  45. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +6 -7
  46. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -7
  47. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +157 -82
  48. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +12 -12
  49. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -7
  50. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +13 -15
  51. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +170 -60
  52. package/sdk-core/core/src/worker/workflow/machines/mod.rs +24 -16
  53. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +6 -8
  54. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +320 -204
  55. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +10 -13
  56. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +15 -23
  57. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +187 -46
  58. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +237 -111
  59. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +13 -13
  60. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +10 -6
  61. package/sdk-core/core/src/worker/workflow/managed_run.rs +81 -62
  62. package/sdk-core/core/src/worker/workflow/mod.rs +341 -79
  63. package/sdk-core/core/src/worker/workflow/run_cache.rs +18 -11
  64. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +15 -3
  65. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +2 -0
  66. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +75 -52
  67. package/sdk-core/core-api/Cargo.toml +0 -1
  68. package/sdk-core/core-api/src/lib.rs +13 -7
  69. package/sdk-core/core-api/src/telemetry.rs +4 -6
  70. package/sdk-core/core-api/src/worker.rs +5 -0
  71. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +80 -55
  72. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +22 -68
  73. package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
  74. package/sdk-core/histories/old_change_marker_format.bin +0 -0
  75. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
  76. package/sdk-core/protos/api_upstream/Makefile +1 -1
  77. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
  78. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
  80. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
  81. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
  83. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
  84. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
  85. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
  87. package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  88. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
  89. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
  90. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
  91. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
  92. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
  93. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
  94. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  95. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  96. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
  97. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +65 -60
  98. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
  99. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
  100. package/sdk-core/sdk/Cargo.toml +1 -1
  101. package/sdk-core/sdk/src/lib.rs +21 -5
  102. package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
  103. package/sdk-core/sdk/src/workflow_context.rs +24 -17
  104. package/sdk-core/sdk/src/workflow_future.rs +9 -3
  105. package/sdk-core/sdk-core-protos/src/history_builder.rs +114 -89
  106. package/sdk-core/sdk-core-protos/src/history_info.rs +6 -1
  107. package/sdk-core/sdk-core-protos/src/lib.rs +205 -64
  108. package/sdk-core/test-utils/src/canned_histories.rs +106 -296
  109. package/sdk-core/test-utils/src/lib.rs +32 -5
  110. package/sdk-core/tests/heavy_tests.rs +10 -43
  111. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  112. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
  113. package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  114. package/sdk-core/tests/integ_tests/polling_tests.rs +3 -8
  115. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -2
  116. package/sdk-core/tests/integ_tests/visibility_tests.rs +34 -23
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +97 -81
  118. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -0
  120. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
  121. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +5 -1
  122. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +1 -0
  123. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +25 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
  125. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +30 -0
  126. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +64 -0
  127. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  128. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +4 -0
  129. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -1
  130. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +7 -2
  131. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -7
  132. package/sdk-core/tests/integ_tests/workflow_tests.rs +8 -8
  133. package/sdk-core/tests/main.rs +16 -25
  134. package/sdk-core/tests/runner.rs +11 -9
  135. package/src/conversions.rs +14 -8
  136. package/src/runtime.rs +9 -8
  137. package/ts/index.ts +8 -6
  138. package/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
@@ -1,4 +1,5 @@
1
1
  use crate::{
2
+ internal_flags::CoreInternalFlags,
2
3
  replay::DEFAULT_WORKFLOW_TYPE,
3
4
  test_help::{canned_histories, mock_sdk, mock_sdk_cfg, MockPollCfg, ResponseType},
4
5
  worker::client::mocks::mock_workflow_client,
@@ -8,8 +9,16 @@ use std::{
8
9
  time::Duration,
9
10
  };
10
11
  use temporal_client::WorkflowOptions;
11
- use temporal_sdk::{WfContext, WorkflowResult};
12
- use temporal_sdk_core_protos::temporal::api::enums::v1::WorkflowTaskFailedCause;
12
+ use temporal_sdk::{
13
+ ActivityOptions, ChildWorkflowOptions, LocalActivityOptions, WfContext, WorkflowResult,
14
+ };
15
+ use temporal_sdk_core_protos::{
16
+ temporal::api::{
17
+ enums::v1::{EventType, WorkflowTaskFailedCause},
18
+ failure::v1::Failure,
19
+ },
20
+ TestHistoryBuilder, DEFAULT_ACTIVITY_TYPE,
21
+ };
13
22
 
14
23
  static DID_FAIL: AtomicBool = AtomicBool::new(false);
15
24
  pub async fn timer_wf_fails_once(ctx: WfContext) -> WorkflowResult<()> {
@@ -105,3 +114,204 @@ async fn test_wf_task_rejected_properly_due_to_nondeterminism(#[case] use_cache:
105
114
  // timer and proceed without restarting
106
115
  assert_eq!(2, started_count.load(Ordering::Relaxed));
107
116
  }
117
+
118
+ #[rstest::rstest]
119
+ #[tokio::test]
120
+ async fn activity_id_or_type_change_is_nondeterministic(
121
+ #[values(true, false)] use_cache: bool,
122
+ #[values(true, false)] id_change: bool,
123
+ #[values(true, false)] local_act: bool,
124
+ ) {
125
+ let wf_id = "fakeid";
126
+ let wf_type = DEFAULT_WORKFLOW_TYPE;
127
+ let mut t = if local_act {
128
+ canned_histories::single_local_activity("1")
129
+ } else {
130
+ canned_histories::single_activity("1")
131
+ };
132
+ t.set_flags_first_wft(&[CoreInternalFlags::IdAndTypeDeterminismChecks as u32], &[]);
133
+ let mock = mock_workflow_client();
134
+ let mut mh = MockPollCfg::from_resp_batches(
135
+ wf_id,
136
+ t,
137
+ // Two polls are needed, since the first will fail
138
+ [ResponseType::AllHistory, ResponseType::AllHistory],
139
+ mock,
140
+ );
141
+ // We should see one wft failure which has nondeterminism cause
142
+ mh.num_expected_fails = 1;
143
+ mh.expect_fail_wft_matcher = Box::new(move |_, cause, f| {
144
+ let should_contain = if id_change {
145
+ "does not match activity id"
146
+ } else {
147
+ "does not match activity type"
148
+ };
149
+ matches!(cause, WorkflowTaskFailedCause::NonDeterministicError)
150
+ && matches!(f, Some(Failure {
151
+ message,
152
+ ..
153
+ }) if message.contains(should_contain))
154
+ });
155
+ let mut worker = mock_sdk_cfg(mh, |cfg| {
156
+ if use_cache {
157
+ cfg.max_cached_workflows = 2;
158
+ }
159
+ });
160
+
161
+ worker.register_wf(wf_type.to_owned(), move |ctx: WfContext| async move {
162
+ if local_act {
163
+ ctx.local_activity(if id_change {
164
+ LocalActivityOptions {
165
+ activity_id: Some("I'm bad and wrong!".to_string()),
166
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
167
+ ..Default::default()
168
+ }
169
+ } else {
170
+ LocalActivityOptions {
171
+ activity_type: "not the default act type".to_string(),
172
+ ..Default::default()
173
+ }
174
+ })
175
+ .await;
176
+ } else {
177
+ ctx.activity(if id_change {
178
+ ActivityOptions {
179
+ activity_id: Some("I'm bad and wrong!".to_string()),
180
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
181
+ ..Default::default()
182
+ }
183
+ } else {
184
+ ActivityOptions {
185
+ activity_type: "not the default act type".to_string(),
186
+ ..Default::default()
187
+ }
188
+ })
189
+ .await;
190
+ }
191
+ Ok(().into())
192
+ });
193
+
194
+ worker
195
+ .submit_wf(
196
+ wf_id.to_owned(),
197
+ wf_type.to_owned(),
198
+ vec![],
199
+ WorkflowOptions::default(),
200
+ )
201
+ .await
202
+ .unwrap();
203
+ worker.run_until_done().await.unwrap();
204
+ }
205
+
206
+ #[rstest::rstest]
207
+ #[tokio::test]
208
+ async fn child_wf_id_or_type_change_is_nondeterministic(
209
+ #[values(true, false)] use_cache: bool,
210
+ #[values(true, false)] id_change: bool,
211
+ ) {
212
+ let wf_id = "fakeid";
213
+ let wf_type = DEFAULT_WORKFLOW_TYPE;
214
+ let mut t = canned_histories::single_child_workflow("1");
215
+ t.set_flags_first_wft(&[CoreInternalFlags::IdAndTypeDeterminismChecks as u32], &[]);
216
+ let mock = mock_workflow_client();
217
+ let mut mh = MockPollCfg::from_resp_batches(
218
+ wf_id,
219
+ t,
220
+ // Two polls are needed, since the first will fail
221
+ [ResponseType::AllHistory, ResponseType::AllHistory],
222
+ mock,
223
+ );
224
+ // We should see one wft failure which has nondeterminism cause
225
+ mh.num_expected_fails = 1;
226
+ mh.expect_fail_wft_matcher = Box::new(move |_, cause, f| {
227
+ let should_contain = if id_change {
228
+ "does not match child workflow id"
229
+ } else {
230
+ "does not match child workflow type"
231
+ };
232
+ matches!(cause, WorkflowTaskFailedCause::NonDeterministicError)
233
+ && matches!(f, Some(Failure {
234
+ message,
235
+ ..
236
+ }) if message.contains(should_contain))
237
+ });
238
+ let mut worker = mock_sdk_cfg(mh, |cfg| {
239
+ if use_cache {
240
+ cfg.max_cached_workflows = 2;
241
+ }
242
+ });
243
+
244
+ worker.register_wf(wf_type.to_owned(), move |ctx: WfContext| async move {
245
+ ctx.child_workflow(if id_change {
246
+ ChildWorkflowOptions {
247
+ workflow_id: "I'm bad and wrong!".to_string(),
248
+ workflow_type: DEFAULT_ACTIVITY_TYPE.to_string(),
249
+ ..Default::default()
250
+ }
251
+ } else {
252
+ ChildWorkflowOptions {
253
+ workflow_id: "1".to_string(),
254
+ workflow_type: "not the child wf type".to_string(),
255
+ ..Default::default()
256
+ }
257
+ })
258
+ .start(&ctx)
259
+ .await;
260
+ Ok(().into())
261
+ });
262
+
263
+ worker
264
+ .submit_wf(
265
+ wf_id.to_owned(),
266
+ wf_type.to_owned(),
267
+ vec![],
268
+ WorkflowOptions::default(),
269
+ )
270
+ .await
271
+ .unwrap();
272
+ worker.run_until_done().await.unwrap();
273
+ }
274
+
275
+ /// Repros a situation where if, upon completing a task there is some internal error which causes
276
+ /// us to want to auto-fail the workflow task while there is also an outstanding eviction, the wf
277
+ /// would get evicted but then try to send some info down the completion channel afterward, causing
278
+ /// a panic.
279
+ #[tokio::test]
280
+ async fn repro_channel_missing_because_nondeterminism() {
281
+ for _ in 1..50 {
282
+ let wf_id = "fakeid";
283
+ let wf_type = DEFAULT_WORKFLOW_TYPE;
284
+ let mut t = TestHistoryBuilder::default();
285
+ t.add_by_type(EventType::WorkflowExecutionStarted);
286
+ t.add_full_wf_task();
287
+ t.add_has_change_marker("patch-1", false);
288
+ let _ts = t.add_by_type(EventType::TimerStarted);
289
+ t.add_workflow_task_scheduled_and_started();
290
+
291
+ let mock = mock_workflow_client();
292
+ let mut mh =
293
+ MockPollCfg::from_resp_batches(wf_id, t, [1.into(), ResponseType::AllHistory], mock);
294
+ mh.num_expected_fails = 1;
295
+ let mut worker = mock_sdk_cfg(mh, |cfg| {
296
+ cfg.max_cached_workflows = 2;
297
+ cfg.ignore_evicts_on_shutdown = false;
298
+ });
299
+
300
+ worker.register_wf(wf_type.to_owned(), move |ctx: WfContext| async move {
301
+ ctx.patched("wrongid");
302
+ ctx.timer(Duration::from_secs(1)).await;
303
+ Ok(().into())
304
+ });
305
+
306
+ worker
307
+ .submit_wf(
308
+ wf_id.to_owned(),
309
+ wf_type.to_owned(),
310
+ vec![],
311
+ WorkflowOptions::default(),
312
+ )
313
+ .await
314
+ .unwrap();
315
+ worker.run_until_done().await.unwrap();
316
+ }
317
+ }
@@ -8,6 +8,7 @@ use crate::{
8
8
  worker::{client::mocks::mock_workflow_client, LEGACY_QUERY_ID},
9
9
  };
10
10
  use anyhow::anyhow;
11
+ use crossbeam::queue::SegQueue;
11
12
  use futures::{future::join_all, FutureExt};
12
13
  use std::{
13
14
  collections::HashMap,
@@ -22,26 +23,32 @@ use temporal_client::WorkflowOptions;
22
23
  use temporal_sdk::{
23
24
  ActContext, ActivityCancelledError, LocalActivityOptions, WfContext, WorkflowResult,
24
25
  };
25
- use temporal_sdk_core_api::Worker;
26
+ use temporal_sdk_core_api::{
27
+ errors::{PollActivityError, PollWfError},
28
+ Worker,
29
+ };
26
30
  use temporal_sdk_core_protos::{
27
31
  coresdk::{
28
32
  activity_result::ActivityExecutionResult,
29
33
  workflow_activation::{workflow_activation_job, WorkflowActivationJob},
30
- workflow_commands::{ActivityCancellationType, QueryResult, QuerySuccess},
34
+ workflow_commands::{
35
+ ActivityCancellationType, QueryResult, QuerySuccess, ScheduleLocalActivity,
36
+ },
31
37
  workflow_completion::WorkflowActivationCompletion,
32
38
  ActivityTaskCompletion, AsJsonPayloadExt,
33
39
  },
34
40
  temporal::api::{
35
41
  common::v1::RetryPolicy,
36
42
  enums::v1::{EventType, TimeoutType, WorkflowTaskFailedCause},
37
- failure::v1::Failure,
43
+ failure::v1::{failure::FailureInfo, Failure},
38
44
  query::v1::WorkflowQuery,
39
45
  },
46
+ DEFAULT_ACTIVITY_TYPE,
40
47
  };
41
48
  use temporal_sdk_core_test_utils::{
42
49
  schedule_local_activity_cmd, start_timer_cmd, WorkerTestHelpers,
43
50
  };
44
- use tokio::sync::Barrier;
51
+ use tokio::{join, sync::Barrier};
45
52
 
46
53
  async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
47
54
  Ok(e)
@@ -59,7 +66,7 @@ async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached:
59
66
  let mut t = TestHistoryBuilder::default();
60
67
  t.add_by_type(EventType::WorkflowExecutionStarted);
61
68
  t.add_full_wf_task();
62
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
69
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
63
70
  t.add_full_wf_task();
64
71
  t.add_local_activity_result_marker(1, "1", b"echo".into());
65
72
  t.add_timer_fired(timer_started_event_id, "1".to_string());
@@ -84,7 +91,7 @@ async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached:
84
91
  DEFAULT_WORKFLOW_TYPE.to_owned(),
85
92
  |ctx: WfContext| async move {
86
93
  let la = ctx.local_activity(LocalActivityOptions {
87
- activity_type: "echo".to_string(),
94
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
88
95
  input: "hi".as_json_payload().expect("serializes fine"),
89
96
  ..Default::default()
90
97
  });
@@ -93,7 +100,7 @@ async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached:
93
100
  Ok(().into())
94
101
  },
95
102
  );
96
- worker.register_activity("echo", echo);
103
+ worker.register_activity(DEFAULT_ACTIVITY_TYPE, echo);
97
104
  worker
98
105
  .submit_wf(
99
106
  wf_id.to_owned(),
@@ -125,11 +132,10 @@ pub async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
125
132
 
126
133
  #[tokio::test]
127
134
  async fn local_act_many_concurrent() {
128
- crate::telemetry::test_telem_console();
129
135
  let mut t = TestHistoryBuilder::default();
130
136
  t.add_by_type(EventType::WorkflowExecutionStarted);
131
137
  t.add_full_wf_task();
132
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
138
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
133
139
  t.add_full_wf_task();
134
140
  for i in 1..=50 {
135
141
  t.add_local_activity_result_marker(i, &i.to_string(), b"echo".into());
@@ -301,7 +307,7 @@ async fn local_act_retry_long_backoff_uses_timer() {
301
307
  "1",
302
308
  Failure::application_failure("la failed".to_string(), false),
303
309
  );
304
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
310
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
305
311
  t.add_timer_fired(timer_started_event_id, "1".to_string());
306
312
  t.add_full_wf_task();
307
313
  t.add_local_activity_fail_marker(
@@ -309,7 +315,7 @@ async fn local_act_retry_long_backoff_uses_timer() {
309
315
  "2",
310
316
  Failure::application_failure("la failed".to_string(), false),
311
317
  );
312
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
318
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
313
319
  t.add_timer_fired(timer_started_event_id, "2".to_string());
314
320
  t.add_full_wf_task();
315
321
  t.add_workflow_execution_completed();
@@ -329,7 +335,7 @@ async fn local_act_retry_long_backoff_uses_timer() {
329
335
  |ctx: WfContext| async move {
330
336
  let la_res = ctx
331
337
  .local_activity(LocalActivityOptions {
332
- activity_type: "echo".to_string(),
338
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
333
339
  input: "hi".as_json_payload().expect("serializes fine"),
334
340
  retry_policy: RetryPolicy {
335
341
  initial_interval: Some(prost_dur!(from_millis(65))),
@@ -348,9 +354,12 @@ async fn local_act_retry_long_backoff_uses_timer() {
348
354
  Ok(().into())
349
355
  },
350
356
  );
351
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
352
- Result::<(), _>::Err(anyhow!("Oh no I failed!"))
353
- });
357
+ worker.register_activity(
358
+ DEFAULT_ACTIVITY_TYPE,
359
+ move |_ctx: ActContext, _: String| async move {
360
+ Result::<(), _>::Err(anyhow!("Oh no I failed!"))
361
+ },
362
+ );
354
363
  worker
355
364
  .submit_wf(
356
365
  wf_id.to_owned(),
@@ -410,7 +419,7 @@ async fn local_act_command_immediately_follows_la_marker() {
410
419
  t.add_full_wf_task();
411
420
  t.add_full_wf_task();
412
421
  t.add_local_activity_result_marker(1, "1", "done".into());
413
- t.add_get_event_id(EventType::TimerStarted, None);
422
+ t.add_by_type(EventType::TimerStarted);
414
423
  t.add_full_wf_task();
415
424
 
416
425
  let wf_id = "fakeid";
@@ -498,7 +507,7 @@ async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbe
498
507
  task.run_id,
499
508
  schedule_local_activity_cmd(
500
509
  1,
501
- "act-id",
510
+ "1",
502
511
  ActivityCancellationType::TryCancel,
503
512
  Duration::from_secs(60),
504
513
  ),
@@ -564,15 +573,11 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
564
573
  // never happen, but there was an issue where an LA resolving could trigger that.
565
574
  let wfid = "fake_wf_id";
566
575
  let mut t = TestHistoryBuilder::default();
567
- let wes_short_wft_timeout = default_wes_attribs();
568
- t.add(
569
- EventType::WorkflowExecutionStarted,
570
- wes_short_wft_timeout.into(),
571
- );
576
+ t.add(default_wes_attribs());
572
577
  // Since we don't send queries with start workflow, need one workflow task of something else
573
578
  // b/c we want to get an activation with a job and a nonlegacy query
574
579
  t.add_full_wf_task();
575
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
580
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
576
581
  t.add_timer_fired(timer_started_event_id, "1".to_string());
577
582
 
578
583
  // nonlegacy query got here & LA started here
@@ -760,6 +765,15 @@ async fn test_schedule_to_start_timeout() {
760
765
  })
761
766
  .await;
762
767
  assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
768
+ let rfail = la_res.unwrap_failure();
769
+ assert_matches!(
770
+ rfail.failure_info,
771
+ Some(FailureInfo::ActivityFailureInfo(_))
772
+ );
773
+ assert_matches!(
774
+ rfail.cause.unwrap().failure_info,
775
+ Some(FailureInfo::TimeoutFailureInfo(_))
776
+ );
763
777
  Ok(().into())
764
778
  },
765
779
  );
@@ -808,7 +822,7 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
808
822
  deets.backoff = Some(prost_dur!(from_secs(100)));
809
823
  },
810
824
  );
811
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
825
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
812
826
  t.add_timer_fired(timer_started_event_id, "1".to_string());
813
827
  t.add_workflow_task_scheduled_and_started();
814
828
 
@@ -874,7 +888,7 @@ async fn wft_failure_cancels_running_las() {
874
888
  let mut t = TestHistoryBuilder::default();
875
889
  t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
876
890
  t.add_full_wf_task();
877
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
891
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
878
892
  t.add_timer_fired(timer_started_event_id, "1".to_string());
879
893
  t.add_workflow_task_scheduled_and_started();
880
894
 
@@ -888,7 +902,7 @@ async fn wft_failure_cancels_running_las() {
888
902
  DEFAULT_WORKFLOW_TYPE.to_owned(),
889
903
  |ctx: WfContext| async move {
890
904
  let la_handle = ctx.local_activity(LocalActivityOptions {
891
- activity_type: "echo".to_string(),
905
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
892
906
  input: "hi".as_json_payload().expect("serializes fine"),
893
907
  ..Default::default()
894
908
  });
@@ -902,13 +916,16 @@ async fn wft_failure_cancels_running_las() {
902
916
  Ok(().into())
903
917
  },
904
918
  );
905
- worker.register_activity("echo", move |ctx: ActContext, _: String| async move {
906
- let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
907
- if res.is_err() {
908
- panic!("Activity must be cancelled!!!!");
909
- }
910
- Result::<(), _>::Err(ActivityCancelledError::default().into())
911
- });
919
+ worker.register_activity(
920
+ DEFAULT_ACTIVITY_TYPE,
921
+ move |ctx: ActContext, _: String| async move {
922
+ let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
923
+ if res.is_err() {
924
+ panic!("Activity must be cancelled!!!!");
925
+ }
926
+ Result::<(), _>::Err(ActivityCancelledError::default().into())
927
+ },
928
+ );
912
929
  worker
913
930
  .submit_wf(
914
931
  wf_id.to_owned(),
@@ -944,9 +961,7 @@ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
944
961
  mock,
945
962
  );
946
963
  mh.num_expected_fails = 2;
947
- mh.completion_asserts = Some(Box::new(|_| {
948
- panic!("should never successfully complete a WFT");
949
- }));
964
+ mh.num_expected_completions = Some(0.into());
950
965
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
951
966
 
952
967
  worker.register_wf(
@@ -976,3 +991,135 @@ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
976
991
  .unwrap();
977
992
  worker.run_until_done().await.unwrap();
978
993
  }
994
+
995
+ #[tokio::test]
996
+ async fn local_act_records_nonfirst_attempts_ok() {
997
+ let mut t = TestHistoryBuilder::default();
998
+ let wft_timeout = Duration::from_millis(200);
999
+ t.add_wfe_started_with_wft_timeout(wft_timeout);
1000
+ t.add_full_wf_task();
1001
+ t.add_full_wf_task();
1002
+ t.add_full_wf_task();
1003
+ t.add_workflow_task_scheduled_and_started();
1004
+
1005
+ let wf_id = "fakeid";
1006
+ let mock = mock_workflow_client();
1007
+ let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
1008
+ let nonfirst_counts = Arc::new(SegQueue::new());
1009
+ let nfc_c = nonfirst_counts.clone();
1010
+ mh.completion_asserts = Some(Box::new(move |c| {
1011
+ nfc_c.push(
1012
+ c.metering_metadata
1013
+ .nonfirst_local_activity_execution_attempts,
1014
+ );
1015
+ }));
1016
+ let mut worker = mock_sdk_cfg(mh, |wc| {
1017
+ wc.max_cached_workflows = 1;
1018
+ wc.max_outstanding_workflow_tasks = 1;
1019
+ });
1020
+
1021
+ worker.register_wf(
1022
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
1023
+ |ctx: WfContext| async move {
1024
+ ctx.local_activity(LocalActivityOptions {
1025
+ activity_type: "echo".to_string(),
1026
+ input: "hi".as_json_payload().expect("serializes fine"),
1027
+ retry_policy: RetryPolicy {
1028
+ initial_interval: Some(prost_dur!(from_millis(10))),
1029
+ backoff_coefficient: 1.0,
1030
+ maximum_interval: None,
1031
+ maximum_attempts: 0,
1032
+ non_retryable_error_types: vec![],
1033
+ },
1034
+ ..Default::default()
1035
+ })
1036
+ .await;
1037
+ Ok(().into())
1038
+ },
1039
+ );
1040
+ worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1041
+ Result::<(), _>::Err(anyhow!("I fail"))
1042
+ });
1043
+ worker
1044
+ .submit_wf(
1045
+ wf_id.to_owned(),
1046
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
1047
+ vec![],
1048
+ WorkflowOptions::default(),
1049
+ )
1050
+ .await
1051
+ .unwrap();
1052
+ worker.run_until_done().await.unwrap();
1053
+ // 3 workflow tasks
1054
+ assert_eq!(nonfirst_counts.len(), 3);
1055
+ // First task's non-first count should, of course, be 0
1056
+ assert_eq!(nonfirst_counts.pop().unwrap(), 0);
1057
+ // Next two, some nonzero amount which could vary based on test load
1058
+ assert!(nonfirst_counts.pop().unwrap() > 0);
1059
+ assert!(nonfirst_counts.pop().unwrap() > 0);
1060
+ }
1061
+
1062
+ #[tokio::test]
1063
+ async fn local_activities_can_be_delivered_during_shutdown() {
1064
+ let wfid = "fake_wf_id";
1065
+ let mut t = TestHistoryBuilder::default();
1066
+ t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
1067
+ t.add_full_wf_task();
1068
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1069
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
1070
+ t.add_workflow_task_scheduled_and_started();
1071
+
1072
+ let mock = mock_workflow_client();
1073
+ let mut mock = single_hist_mock_sg(
1074
+ wfid,
1075
+ t,
1076
+ [ResponseType::ToTaskNum(1), ResponseType::AllHistory],
1077
+ mock,
1078
+ true,
1079
+ );
1080
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
1081
+ let core = mock_worker(mock);
1082
+
1083
+ let task = core.poll_workflow_activation().await.unwrap();
1084
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1085
+ task.run_id,
1086
+ start_timer_cmd(1, Duration::from_secs(1)),
1087
+ ))
1088
+ .await
1089
+ .unwrap();
1090
+
1091
+ let task = core.poll_workflow_activation().await.unwrap();
1092
+ // Initiate shutdown once we have the WF activation, but before replying that we want to do an
1093
+ // LA
1094
+ core.initiate_shutdown();
1095
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1096
+ task.run_id,
1097
+ ScheduleLocalActivity {
1098
+ seq: 1,
1099
+ activity_id: "1".to_string(),
1100
+ activity_type: "test_act".to_string(),
1101
+ start_to_close_timeout: Some(prost_dur!(from_secs(30))),
1102
+ ..Default::default()
1103
+ }
1104
+ .into(),
1105
+ ))
1106
+ .await
1107
+ .unwrap();
1108
+
1109
+ let wf_poller = async { core.poll_workflow_activation().await };
1110
+
1111
+ let at_poller = async {
1112
+ let act_task = core.poll_activity_task().await.unwrap();
1113
+ core.complete_activity_task(ActivityTaskCompletion {
1114
+ task_token: act_task.task_token,
1115
+ result: Some(ActivityExecutionResult::ok(vec![1].into())),
1116
+ })
1117
+ .await
1118
+ .unwrap();
1119
+ core.poll_activity_task().await
1120
+ };
1121
+
1122
+ let (wf_r, act_r) = join!(wf_poller, at_poller);
1123
+ assert_matches!(wf_r.unwrap_err(), PollWfError::ShutDown);
1124
+ assert_matches!(act_r.unwrap_err(), PollActivityError::ShutDown);
1125
+ }
@@ -23,7 +23,7 @@ use temporal_sdk_core_protos::{
23
23
  },
24
24
  temporal::api::{
25
25
  common::v1::Payload,
26
- enums::v1::EventType,
26
+ enums::v1::{CommandType, EventType},
27
27
  failure::v1::Failure,
28
28
  history::v1::{history_event, ActivityTaskCancelRequestedEventAttributes, History},
29
29
  query::v1::WorkflowQuery,
@@ -148,10 +148,11 @@ async fn legacy_query(#[case] include_history: bool) {
148
148
  }
149
149
 
150
150
  #[rstest::rstest]
151
- #[case::one_query(1)]
152
- #[case::multiple_queries(3)]
153
151
  #[tokio::test]
154
- async fn new_queries(#[case] num_queries: usize) {
152
+ async fn new_queries(
153
+ #[values(1, 3)] num_queries: usize,
154
+ #[values(false, true)] query_results_after_complete: bool,
155
+ ) {
155
156
  let wfid = "fake_wf_id";
156
157
  let query_resp = "response";
157
158
  let t = canned_histories::single_timer("1");
@@ -174,7 +175,18 @@ async fn new_queries(#[case] num_queries: usize) {
174
175
  }]);
175
176
  let mut mock_client = mock_workflow_client();
176
177
  mock_client.expect_respond_legacy_query().times(0);
177
- let mut mock = single_hist_mock_sg(wfid, t, tasks, mock_client, true);
178
+ let mut mh = MockPollCfg::from_resp_batches(wfid, t, tasks, mock_workflow_client());
179
+ mh.completion_asserts = Some(Box::new(move |c| {
180
+ // If the completion is the one ending the workflow, make sure it includes the query resps
181
+ if c.commands[0].command_type() == CommandType::CompleteWorkflowExecution {
182
+ assert_eq!(c.query_responses.len(), num_queries);
183
+ } else if c.commands[0].command_type() == CommandType::StartTimer {
184
+ // first reply, no queries here.
185
+ } else {
186
+ panic!("Unexpected command in response")
187
+ }
188
+ }));
189
+ let mut mock = build_mock_pollers(mh);
178
190
  mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
179
191
  let core = mock_worker(mock);
180
192
 
@@ -203,8 +215,13 @@ async fn new_queries(#[case] num_queries: usize) {
203
215
  }
204
216
  );
205
217
  }
206
- let mut qresults: Vec<_> = (1..=num_queries)
207
- .map(|i| {
218
+
219
+ let mut commands = vec![];
220
+ if query_results_after_complete {
221
+ commands.push(CompleteWorkflowExecution { result: None }.into());
222
+ }
223
+ for i in 1..=num_queries {
224
+ commands.push(
208
225
  QueryResult {
209
226
  query_id: format!("q{i}"),
210
227
  variant: Some(
@@ -214,13 +231,15 @@ async fn new_queries(#[case] num_queries: usize) {
214
231
  .into(),
215
232
  ),
216
233
  }
217
- .into()
218
- })
219
- .collect();
220
- qresults.push(CompleteWorkflowExecution { result: None }.into());
234
+ .into(),
235
+ );
236
+ }
237
+ if !query_results_after_complete {
238
+ commands.push(CompleteWorkflowExecution { result: None }.into());
239
+ }
221
240
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
222
241
  task.run_id,
223
- qresults,
242
+ commands,
224
243
  ))
225
244
  .await
226
245
  .unwrap();
@@ -769,11 +788,10 @@ async fn legacy_query_combined_with_timer_fire_repro() {
769
788
  t.add_by_type(EventType::WorkflowExecutionStarted);
770
789
  t.add_full_wf_task();
771
790
  let scheduled_event_id = t.add_activity_task_scheduled("1");
772
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
791
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
773
792
  t.add_timer_fired(timer_started_event_id, "1".to_string());
774
793
  t.add_full_wf_task();
775
794
  t.add(
776
- EventType::ActivityTaskCancelRequested,
777
795
  history_event::Attributes::ActivityTaskCancelRequestedEventAttributes(
778
796
  ActivityTaskCancelRequestedEventAttributes {
779
797
  scheduled_event_id,