@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,4 +1,6 @@
1
+ use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk};
1
2
  use std::{
3
+ collections::{HashSet, VecDeque, hash_map::RandomState},
2
4
  sync::{
3
5
  Arc,
4
6
  atomic::{AtomicBool, Ordering},
@@ -6,12 +8,32 @@ use std::{
6
8
  time::Duration,
7
9
  };
8
10
  use temporal_client::WorkflowClientTrait;
11
+ use temporal_sdk::{ActivityOptions, WfContext, WorkflowResult};
12
+ use temporal_sdk_core::test_help::{CoreInternalFlags, MockPollCfg, ResponseType};
13
+ use temporal_sdk_core_protos::{
14
+ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, VERSION_SEARCH_ATTR_KEY,
15
+ constants::PATCH_MARKER_NAME,
16
+ coresdk::{
17
+ AsJsonPayloadExt, FromJsonPayloadExt,
18
+ common::decode_change_marker_details,
19
+ workflow_activation::{NotifyHasPatch, WorkflowActivationJob, workflow_activation_job},
20
+ },
21
+ temporal::api::{
22
+ command::v1::{
23
+ RecordMarkerCommandAttributes, ScheduleActivityTaskCommandAttributes,
24
+ UpsertWorkflowSearchAttributesCommandAttributes, command::Attributes,
25
+ },
26
+ common::v1::ActivityType,
27
+ enums::v1::{CommandType, EventType, IndexedValueType},
28
+ history::v1::{
29
+ ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes,
30
+ ActivityTaskStartedEventAttributes, TimerFiredEventAttributes,
31
+ },
32
+ },
33
+ };
9
34
  use tokio::{join, sync::Notify};
10
35
  use tokio_stream::StreamExt;
11
36
 
12
- use temporal_sdk::{WfContext, WorkflowResult};
13
- use temporal_sdk_core_test_utils::CoreWfStarter;
14
-
15
37
  const MY_PATCH_ID: &str = "integ_test_change_name";
16
38
 
17
39
  pub(crate) async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> {
@@ -204,3 +226,503 @@ async fn deprecated_patch_removal() {
204
226
  };
205
227
  join!(sig_fut, run_fut);
206
228
  }
229
+
230
+ #[derive(Eq, PartialEq, Copy, Clone, Debug)]
231
+ enum MarkerType {
232
+ Deprecated,
233
+ NotDeprecated,
234
+ NoMarker,
235
+ }
236
+
237
+ const ONE_SECOND: Duration = Duration::from_secs(1);
238
+
239
+ /// EVENT_TYPE_WORKFLOW_EXECUTION_STARTED
240
+ /// EVENT_TYPE_WORKFLOW_TASK_SCHEDULED
241
+ /// EVENT_TYPE_WORKFLOW_TASK_STARTED
242
+ /// EVENT_TYPE_WORKFLOW_TASK_COMPLETED
243
+ /// EVENT_TYPE_MARKER_RECORDED (depending on marker_type)
244
+ /// EVENT_TYPE_ACTIVITY_TASK_SCHEDULED
245
+ /// EVENT_TYPE_ACTIVITY_TASK_STARTED
246
+ /// EVENT_TYPE_ACTIVITY_TASK_COMPLETED
247
+ /// EVENT_TYPE_WORKFLOW_TASK_SCHEDULED
248
+ /// EVENT_TYPE_WORKFLOW_TASK_STARTED
249
+ /// EVENT_TYPE_WORKFLOW_TASK_COMPLETED
250
+ /// EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED
251
+ fn patch_marker_single_activity(
252
+ marker_type: MarkerType,
253
+ version: usize,
254
+ replay: bool,
255
+ ) -> TestHistoryBuilder {
256
+ let mut t = TestHistoryBuilder::default();
257
+ t.add_by_type(EventType::WorkflowExecutionStarted);
258
+ t.add_full_wf_task();
259
+ t.set_flags_first_wft(
260
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
261
+ &[],
262
+ );
263
+ match marker_type {
264
+ MarkerType::Deprecated => {
265
+ t.add_has_change_marker(MY_PATCH_ID, true);
266
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
267
+ }
268
+ MarkerType::NotDeprecated => {
269
+ t.add_has_change_marker(MY_PATCH_ID, false);
270
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
271
+ }
272
+ MarkerType::NoMarker => {}
273
+ };
274
+
275
+ let activity_id = if replay {
276
+ match (marker_type, version) {
277
+ (_, 1) => "no_change",
278
+ (MarkerType::NotDeprecated, 2) => "had_change",
279
+ (MarkerType::Deprecated, 2) => "had_change",
280
+ (MarkerType::NoMarker, 2) => "no_change",
281
+ (_, 3) => "had_change",
282
+ (_, 4) => "had_change",
283
+ v => panic!("Nonsense marker / version combo {v:?}"),
284
+ }
285
+ } else {
286
+ // If the workflow isn't replaying (we're creating history here for a workflow which
287
+ // wasn't replaying at the time of scheduling the activity, and has done that, and now
288
+ // we're feeding back the history it would have produced) then it always has the change,
289
+ // except in v1.
290
+ if version > 1 {
291
+ "had_change"
292
+ } else {
293
+ "no_change"
294
+ }
295
+ };
296
+
297
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
298
+ activity_id: activity_id.to_string(),
299
+ ..Default::default()
300
+ });
301
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
302
+ scheduled_event_id,
303
+ ..Default::default()
304
+ });
305
+ t.add(ActivityTaskCompletedEventAttributes {
306
+ scheduled_event_id,
307
+ started_event_id,
308
+ ..Default::default()
309
+ });
310
+ t.add_full_wf_task();
311
+ t.add_workflow_execution_completed();
312
+ t
313
+ }
314
+
315
+ async fn v1(ctx: &mut WfContext) {
316
+ ctx.activity(ActivityOptions {
317
+ activity_id: Some("no_change".to_owned()),
318
+ ..Default::default()
319
+ })
320
+ .await;
321
+ }
322
+
323
+ async fn v2(ctx: &mut WfContext) -> bool {
324
+ if ctx.patched(MY_PATCH_ID) {
325
+ ctx.activity(ActivityOptions {
326
+ activity_id: Some("had_change".to_owned()),
327
+ ..Default::default()
328
+ })
329
+ .await;
330
+ true
331
+ } else {
332
+ ctx.activity(ActivityOptions {
333
+ activity_id: Some("no_change".to_owned()),
334
+ ..Default::default()
335
+ })
336
+ .await;
337
+ false
338
+ }
339
+ }
340
+
341
+ async fn v3(ctx: &mut WfContext) {
342
+ ctx.deprecate_patch(MY_PATCH_ID);
343
+ ctx.activity(ActivityOptions {
344
+ activity_id: Some("had_change".to_owned()),
345
+ ..Default::default()
346
+ })
347
+ .await;
348
+ }
349
+
350
+ async fn v4(ctx: &mut WfContext) {
351
+ ctx.activity(ActivityOptions {
352
+ activity_id: Some("had_change".to_owned()),
353
+ ..Default::default()
354
+ })
355
+ .await;
356
+ }
357
+
358
+ fn patch_setup(replaying: bool, marker_type: MarkerType, workflow_version: usize) -> MockPollCfg {
359
+ let t = patch_marker_single_activity(marker_type, workflow_version, replaying);
360
+ if replaying {
361
+ MockPollCfg::from_resps(t, [ResponseType::AllHistory])
362
+ } else {
363
+ MockPollCfg::from_hist_builder(t)
364
+ }
365
+ }
366
+
367
+ macro_rules! patch_wf {
368
+ ($workflow_version:ident) => {
369
+ move |mut ctx: WfContext| async move {
370
+ match $workflow_version {
371
+ 1 => {
372
+ v1(&mut ctx).await;
373
+ }
374
+ 2 => {
375
+ v2(&mut ctx).await;
376
+ }
377
+ 3 => {
378
+ v3(&mut ctx).await;
379
+ }
380
+ 4 => {
381
+ v4(&mut ctx).await;
382
+ }
383
+ _ => panic!("Invalid workflow version for test setup"),
384
+ }
385
+ Ok(().into())
386
+ }
387
+ };
388
+ }
389
+
390
+ #[rstest]
391
+ #[case::v1_breaks_on_normal_marker(false, MarkerType::NotDeprecated, 1)]
392
+ #[case::v1_accepts_dep_marker(false, MarkerType::Deprecated, 1)]
393
+ #[case::v1_replay_breaks_on_normal_marker(true, MarkerType::NotDeprecated, 1)]
394
+ #[case::v1_replay_accepts_dep_marker(true, MarkerType::Deprecated, 1)]
395
+ #[case::v4_breaks_on_normal_marker(false, MarkerType::NotDeprecated, 4)]
396
+ #[case::v4_accepts_dep_marker(false, MarkerType::Deprecated, 4)]
397
+ #[case::v4_replay_breaks_on_normal_marker(true, MarkerType::NotDeprecated, 4)]
398
+ #[case::v4_replay_accepts_dep_marker(true, MarkerType::Deprecated, 4)]
399
+ #[tokio::test]
400
+ async fn v1_and_v4_changes(
401
+ #[case] replaying: bool,
402
+ #[case] marker_type: MarkerType,
403
+ #[case] wf_version: usize,
404
+ ) {
405
+ let mut mock_cfg = patch_setup(replaying, marker_type, wf_version);
406
+
407
+ if marker_type != MarkerType::Deprecated {
408
+ // should explode b/c non-dep marker is present
409
+ mock_cfg.num_expected_fails = 1;
410
+ }
411
+
412
+ let mut aai = ActivationAssertionsInterceptor::default();
413
+ aai.skip_one().then(move |a| {
414
+ if marker_type == MarkerType::Deprecated {
415
+ // Activity is resolved
416
+ assert_matches!(
417
+ a.jobs.as_slice(),
418
+ [WorkflowActivationJob {
419
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(_))
420
+ }]
421
+ );
422
+ }
423
+ });
424
+
425
+ if !replaying {
426
+ mock_cfg.completion_asserts_from_expectations(|mut asserts| {
427
+ asserts.then(|wft| {
428
+ assert_eq!(wft.commands.len(), 1);
429
+ assert_eq!(
430
+ wft.commands[0].command_type,
431
+ CommandType::ScheduleActivityTask as i32
432
+ );
433
+ });
434
+ });
435
+ }
436
+
437
+ let mut worker = build_fake_sdk(mock_cfg);
438
+ worker.set_worker_interceptor(aai);
439
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, patch_wf!(wf_version));
440
+ worker.run().await.unwrap();
441
+ }
442
+
443
+ // Note that the not-replaying and no-marker cases don't make sense and hence are absent
444
+ #[rstest]
445
+ #[case::v2_marker_new_path(false, MarkerType::NotDeprecated, 2)]
446
+ #[case::v2_dep_marker_new_path(false, MarkerType::Deprecated, 2)]
447
+ #[case::v2_replay_no_marker_old_path(true, MarkerType::NoMarker, 2)]
448
+ #[case::v2_replay_marker_new_path(true, MarkerType::NotDeprecated, 2)]
449
+ #[case::v2_replay_dep_marker_new_path(true, MarkerType::Deprecated, 2)]
450
+ #[case::v3_marker_new_path(false, MarkerType::NotDeprecated, 3)]
451
+ #[case::v3_dep_marker_new_path(false, MarkerType::Deprecated, 3)]
452
+ #[case::v3_replay_no_marker_old_path(true, MarkerType::NoMarker, 3)]
453
+ #[case::v3_replay_marker_new_path(true, MarkerType::NotDeprecated, 3)]
454
+ #[case::v3_replay_dep_marker_new_path(true, MarkerType::Deprecated, 3)]
455
+ #[tokio::test]
456
+ async fn v2_and_v3_changes(
457
+ #[case] replaying: bool,
458
+ #[case] marker_type: MarkerType,
459
+ #[case] wf_version: usize,
460
+ ) {
461
+ let mut mock_cfg = patch_setup(replaying, marker_type, wf_version);
462
+
463
+ let mut aai = ActivationAssertionsInterceptor::default();
464
+ aai.then(move |act| {
465
+ // replaying cases should immediately get a resolve change activation when marker is
466
+ // present
467
+ if replaying && marker_type != MarkerType::NoMarker {
468
+ assert_matches!(
469
+ &act.jobs[1],
470
+ WorkflowActivationJob {
471
+ variant: Some(workflow_activation_job::Variant::NotifyHasPatch(
472
+ NotifyHasPatch {
473
+ patch_id,
474
+ }
475
+ ))
476
+ } => patch_id == MY_PATCH_ID
477
+ );
478
+ } else {
479
+ assert_eq!(act.jobs.len(), 1);
480
+ }
481
+ })
482
+ .then(move |act| {
483
+ assert_matches!(
484
+ act.jobs.as_slice(),
485
+ [WorkflowActivationJob {
486
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(_))
487
+ }]
488
+ );
489
+ });
490
+
491
+ if !replaying {
492
+ mock_cfg.completion_asserts_from_expectations(|mut asserts| {
493
+ asserts.then(move |wft| {
494
+ let mut commands = VecDeque::from(wft.commands.clone());
495
+ let expected_num_cmds = if marker_type == MarkerType::NoMarker {
496
+ 2
497
+ } else {
498
+ 3
499
+ };
500
+ assert_eq!(commands.len(), expected_num_cmds);
501
+ let dep_flag_expected = wf_version != 2;
502
+ assert_matches!(
503
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
504
+ Attributes::RecordMarkerCommandAttributes(
505
+ RecordMarkerCommandAttributes { marker_name, details,.. })
506
+ if marker_name == PATCH_MARKER_NAME
507
+ && decode_change_marker_details(details).unwrap().1 == dep_flag_expected
508
+ );
509
+ if expected_num_cmds == 3 {
510
+ let mut as_payload = [MY_PATCH_ID].as_json_payload().unwrap();
511
+ as_payload.metadata.insert(
512
+ "type".to_string(),
513
+ IndexedValueType::KeywordList
514
+ .as_str_name()
515
+ .as_bytes()
516
+ .to_vec(),
517
+ );
518
+ assert_matches!(
519
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
520
+ Attributes::UpsertWorkflowSearchAttributesCommandAttributes(
521
+ UpsertWorkflowSearchAttributesCommandAttributes
522
+ { search_attributes: Some(attrs) }
523
+ )
524
+ if attrs.indexed_fields.get(VERSION_SEARCH_ATTR_KEY).unwrap()
525
+ == &as_payload
526
+ );
527
+ }
528
+ // The only time the "old" timer should fire is in v2, replaying, without a marker.
529
+ let expected_activity_id =
530
+ if replaying && marker_type == MarkerType::NoMarker && wf_version == 2 {
531
+ "no_change"
532
+ } else {
533
+ "had_change"
534
+ };
535
+ assert_matches!(
536
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
537
+ Attributes::ScheduleActivityTaskCommandAttributes(
538
+ ScheduleActivityTaskCommandAttributes { activity_id, .. }
539
+ )
540
+ if activity_id == expected_activity_id
541
+ );
542
+ });
543
+ });
544
+ }
545
+
546
+ let mut worker = build_fake_sdk(mock_cfg);
547
+ worker.set_worker_interceptor(aai);
548
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, patch_wf!(wf_version));
549
+ worker.run().await.unwrap();
550
+ }
551
+
552
+ #[rstest]
553
+ #[case::has_change_replay(true, true)]
554
+ #[case::no_change_replay(false, true)]
555
+ #[case::has_change_inc(true, false)]
556
+ // The false-false case doesn't make sense, as the incremental cases act as if working against
557
+ // a sticky queue, and it'd be impossible for a worker with the call to get an incremental
558
+ // history that then suddenly doesn't have the marker.
559
+ #[tokio::test]
560
+ async fn same_change_multiple_spots(#[case] have_marker_in_hist: bool, #[case] replay: bool) {
561
+ let mut t = TestHistoryBuilder::default();
562
+ t.add_by_type(EventType::WorkflowExecutionStarted);
563
+ t.add_full_wf_task();
564
+ t.set_flags_first_wft(
565
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
566
+ &[],
567
+ );
568
+ if have_marker_in_hist {
569
+ t.add_has_change_marker(MY_PATCH_ID, false);
570
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
571
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
572
+ activity_id: "1".to_owned(),
573
+ activity_type: Some(ActivityType {
574
+ name: "".to_string(),
575
+ }),
576
+ ..Default::default()
577
+ });
578
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
579
+ scheduled_event_id,
580
+ ..Default::default()
581
+ });
582
+ t.add(ActivityTaskCompletedEventAttributes {
583
+ scheduled_event_id,
584
+ started_event_id,
585
+ ..Default::default()
586
+ });
587
+ t.add_full_wf_task();
588
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
589
+ t.add(TimerFiredEventAttributes {
590
+ started_event_id: timer_started_event_id,
591
+ timer_id: "1".to_owned(),
592
+ });
593
+ } else {
594
+ let started_event_id = t.add_by_type(EventType::TimerStarted);
595
+ t.add(TimerFiredEventAttributes {
596
+ started_event_id,
597
+ timer_id: "1".to_owned(),
598
+ });
599
+ t.add_full_wf_task();
600
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
601
+ t.add(TimerFiredEventAttributes {
602
+ started_event_id: timer_started_event_id,
603
+ timer_id: "2".to_owned(),
604
+ });
605
+ }
606
+ t.add_full_wf_task();
607
+
608
+ if have_marker_in_hist {
609
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
610
+ activity_id: "2".to_string(),
611
+ activity_type: Some(ActivityType {
612
+ name: "".to_string(),
613
+ }),
614
+ ..Default::default()
615
+ });
616
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
617
+ scheduled_event_id,
618
+ ..Default::default()
619
+ });
620
+ t.add(ActivityTaskCompletedEventAttributes {
621
+ scheduled_event_id,
622
+ started_event_id,
623
+ ..Default::default()
624
+ });
625
+ } else {
626
+ let started_event_id = t.add_by_type(EventType::TimerStarted);
627
+ t.add(TimerFiredEventAttributes {
628
+ started_event_id,
629
+ timer_id: "3".to_owned(),
630
+ });
631
+ }
632
+ t.add_full_wf_task();
633
+ t.add_workflow_execution_completed();
634
+
635
+ let mock_cfg = if replay {
636
+ MockPollCfg::from_resps(t, [ResponseType::AllHistory])
637
+ } else {
638
+ MockPollCfg::from_hist_builder(t)
639
+ };
640
+
641
+ // Errors would appear as nondeterminism problems, so just run it.
642
+ let mut worker = build_fake_sdk(mock_cfg);
643
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
644
+ if ctx.patched(MY_PATCH_ID) {
645
+ ctx.activity(ActivityOptions::default()).await;
646
+ } else {
647
+ ctx.timer(ONE_SECOND).await;
648
+ }
649
+ ctx.timer(ONE_SECOND).await;
650
+ if ctx.patched(MY_PATCH_ID) {
651
+ ctx.activity(ActivityOptions::default()).await;
652
+ } else {
653
+ ctx.timer(ONE_SECOND).await;
654
+ }
655
+ Ok(().into())
656
+ });
657
+ worker.run().await.unwrap();
658
+ }
659
+
660
+ const SIZE_OVERFLOW_PATCH_AMOUNT: usize = 180;
661
+ #[rstest]
662
+ #[case::happy_path(50)]
663
+ // We start exceeding the 2k size limit at 180 patches with this format
664
+ #[case::size_overflow(SIZE_OVERFLOW_PATCH_AMOUNT)]
665
+ #[tokio::test]
666
+ async fn many_patches_combine_in_search_attrib_update(#[case] num_patches: usize) {
667
+ let mut t = TestHistoryBuilder::default();
668
+ t.add_by_type(EventType::WorkflowExecutionStarted);
669
+ t.add_full_wf_task();
670
+ t.set_flags_first_wft(
671
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
672
+ &[],
673
+ );
674
+ for i in 1..=num_patches {
675
+ let id = format!("patch-{i}");
676
+ t.add_has_change_marker(&id, false);
677
+ if i < SIZE_OVERFLOW_PATCH_AMOUNT {
678
+ t.add_upsert_search_attrs_for_patch(&[id]);
679
+ }
680
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
681
+ t.add(TimerFiredEventAttributes {
682
+ started_event_id: timer_started_event_id,
683
+ timer_id: i.to_string(),
684
+ });
685
+ t.add_full_wf_task();
686
+ }
687
+ t.add_workflow_execution_completed();
688
+
689
+ let mut mock_cfg = MockPollCfg::from_hist_builder(t);
690
+ mock_cfg.completion_asserts_from_expectations(|mut asserts| {
691
+ // Iterate through all activations/responses except the final one with complete workflow
692
+ for i in 2..=num_patches + 1 {
693
+ asserts.then(move |wft| {
694
+ let cmds = &wft.commands;
695
+ if i > SIZE_OVERFLOW_PATCH_AMOUNT {
696
+ assert_eq!(2, cmds.len());
697
+ assert_matches!(cmds[1].command_type(), CommandType::StartTimer);
698
+ } else {
699
+ assert_eq!(3, cmds.len());
700
+ let attrs = assert_matches!(
701
+ cmds[1].attributes.as_ref().unwrap(),
702
+ Attributes::UpsertWorkflowSearchAttributesCommandAttributes(
703
+ UpsertWorkflowSearchAttributesCommandAttributes
704
+ { search_attributes: Some(attrs) }
705
+ ) => attrs
706
+ );
707
+ let expected_patches: HashSet<String, _> =
708
+ (1..i).map(|i| format!("patch-{i}")).collect();
709
+ let deserialized = HashSet::<String, RandomState>::from_json_payload(
710
+ attrs.indexed_fields.get(VERSION_SEARCH_ATTR_KEY).unwrap(),
711
+ )
712
+ .unwrap();
713
+ assert_eq!(deserialized, expected_patches);
714
+ }
715
+ });
716
+ }
717
+ });
718
+
719
+ let mut worker = build_fake_sdk(mock_cfg);
720
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
721
+ for i in 1..=num_patches {
722
+ let _dontcare = ctx.patched(&format!("patch-{i}"));
723
+ ctx.timer(ONE_SECOND).await;
724
+ }
725
+ Ok(().into())
726
+ });
727
+ worker.run().await.unwrap();
728
+ }
@@ -1,23 +1,29 @@
1
- use crate::integ_tests::workflow_tests::patches::changes_wf;
1
+ use crate::{
2
+ common::{
3
+ ActivationAssertionsInterceptor, build_fake_sdk, history_from_proto_binary,
4
+ init_core_replay_preloaded, replay_sdk_worker, replay_sdk_worker_stream,
5
+ },
6
+ integ_tests::workflow_tests::patches::changes_wf,
7
+ };
2
8
  use assert_matches::assert_matches;
3
9
  use parking_lot::Mutex;
4
10
  use std::{collections::HashSet, sync::Arc, time::Duration};
5
11
  use temporal_sdk::{WfContext, Worker, WorkflowFunction, interceptors::WorkerInterceptor};
6
- use temporal_sdk_core::replay::{HistoryFeeder, HistoryForReplay};
12
+ use temporal_sdk_core::{
13
+ replay::{HistoryFeeder, HistoryForReplay},
14
+ test_help::{MockPollCfg, ResponseType, WorkerTestHelpers},
15
+ };
7
16
  use temporal_sdk_core_api::errors::PollError;
8
17
  use temporal_sdk_core_protos::{
9
- DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder,
18
+ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, canned_histories,
10
19
  coresdk::{
11
20
  workflow_activation::remove_from_cache::EvictionReason,
12
21
  workflow_commands::{ScheduleActivity, StartTimer},
13
22
  workflow_completion::WorkflowActivationCompletion,
14
23
  },
24
+ prost_dur,
15
25
  temporal::api::enums::v1::EventType,
16
26
  };
17
- use temporal_sdk_core_test_utils::{
18
- WorkerTestHelpers, canned_histories, history_from_proto_binary, init_core_replay_preloaded,
19
- replay_sdk_worker, replay_sdk_worker_stream,
20
- };
21
27
  use tokio::join;
22
28
 
23
29
  fn test_hist_to_replay(t: TestHistoryBuilder) -> HistoryForReplay {
@@ -25,6 +31,58 @@ fn test_hist_to_replay(t: TestHistoryBuilder) -> HistoryForReplay {
25
31
  HistoryForReplay::new(hi, "fake".to_string())
26
32
  }
27
33
 
34
+ fn timers_wf(num_timers: u32) -> WorkflowFunction {
35
+ WorkflowFunction::new(move |ctx: WfContext| async move {
36
+ for _ in 1..=num_timers {
37
+ ctx.timer(Duration::from_secs(1)).await;
38
+ }
39
+ Ok(().into())
40
+ })
41
+ }
42
+
43
+ #[fixture(num_timers = 1)]
44
+ fn fire_happy_hist(num_timers: u32) -> Worker {
45
+ let func = timers_wf(num_timers);
46
+ // Add 1 b/c history takes # wf tasks, not timers
47
+ let t = canned_histories::long_sequential_timers(num_timers as usize);
48
+ let mut worker = build_fake_sdk(MockPollCfg::from_resps(t, [ResponseType::AllHistory]));
49
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, func);
50
+ worker
51
+ }
52
+
53
+ #[rstest]
54
+ #[case::one_timer(fire_happy_hist(1), 1)]
55
+ #[case::five_timers(fire_happy_hist(5), 5)]
56
+ #[tokio::test]
57
+ async fn replay_flag_is_correct(#[case] mut worker: Worker, #[case] num_timers: usize) {
58
+ // Verify replay flag is correct by constructing a workflow manager that already has a complete
59
+ // history fed into it. It should always be replaying, because history is complete.
60
+
61
+ let mut aai = ActivationAssertionsInterceptor::default();
62
+
63
+ for _ in 1..=num_timers + 1 {
64
+ aai.then(|a| assert!(a.is_replaying));
65
+ }
66
+
67
+ worker.set_worker_interceptor(aai);
68
+ worker.run().await.unwrap();
69
+ }
70
+
71
+ #[tokio::test(flavor = "multi_thread")]
72
+ async fn replay_flag_is_correct_partial_history() {
73
+ let func = timers_wf(1);
74
+ // Add 1 b/c history takes # wf tasks, not timers
75
+ let t = canned_histories::long_sequential_timers(2);
76
+ let mut worker = build_fake_sdk(MockPollCfg::from_resps(t, [1]));
77
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, func);
78
+
79
+ let mut aai = ActivationAssertionsInterceptor::default();
80
+ aai.then(|a| assert!(!a.is_replaying));
81
+
82
+ worker.set_worker_interceptor(aai);
83
+ worker.run().await.unwrap();
84
+ }
85
+
28
86
  #[tokio::test]
29
87
  async fn timer_workflow_replay() {
30
88
  let core = init_core_replay_preloaded(
@@ -285,15 +343,6 @@ async fn replay_ends_with_empty_wft() {
285
343
  assert!(task.eviction_reason().is_some());
286
344
  }
287
345
 
288
- fn timers_wf(num_timers: u32) -> WorkflowFunction {
289
- WorkflowFunction::new(move |ctx: WfContext| async move {
290
- for _ in 1..=num_timers {
291
- ctx.timer(Duration::from_secs(1)).await;
292
- }
293
- Ok(().into())
294
- })
295
- }
296
-
297
346
  #[derive(Default)]
298
347
  struct UniqueRunsCounter {
299
348
  runs: Arc<Mutex<HashSet<String>>>,