@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,9 +1,11 @@
1
1
  use anyhow::anyhow;
2
2
  use assert_matches::assert_matches;
3
+ use futures_util::future::join_all;
3
4
  use std::time::Duration;
4
5
  use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions};
5
6
  use temporal_sdk::{
6
- ActContext, ActExitValue, ActivityOptions, CancellableFuture, WfContext, WorkflowResult,
7
+ ActContext, ActExitValue, ActivityCancelledError, ActivityOptions, CancellableFuture,
8
+ WfContext, WorkflowResult,
7
9
  };
8
10
  use temporal_sdk_core_protos::{
9
11
  coresdk::{
@@ -20,16 +22,17 @@ use temporal_sdk_core_protos::{
20
22
  IntoCompletion,
21
23
  },
22
24
  temporal::api::{
23
- common::v1::{ActivityType, Payload, Payloads},
25
+ common::v1::{ActivityType, Payload, Payloads, RetryPolicy},
24
26
  enums::v1::RetryState,
25
27
  failure::v1::{failure::FailureInfo, ActivityFailureInfo, Failure},
26
28
  },
27
- TaskToken,
29
+ TaskToken, DEFAULT_ACTIVITY_TYPE,
28
30
  };
29
31
  use temporal_sdk_core_test_utils::{
30
- init_core_and_create_wf, schedule_activity_cmd, CoreWfStarter, WorkerTestHelpers,
32
+ drain_pollers_and_shutdown, init_core_and_create_wf, schedule_activity_cmd, CoreWfStarter,
33
+ WorkerTestHelpers,
31
34
  };
32
- use tokio::time::sleep;
35
+ use tokio::{join, sync::Semaphore, time::sleep};
33
36
 
34
37
  pub async fn one_activity_wf(ctx: WfContext) -> WorkflowResult<()> {
35
38
  ctx.activity(ActivityOptions {
@@ -98,7 +101,7 @@ async fn activity_workflow() {
98
101
  assert_matches!(
99
102
  task.variant,
100
103
  Some(act_task::Variant::Start(start_activity)) => {
101
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
104
+ assert_eq!(start_activity.activity_type, DEFAULT_ACTIVITY_TYPE.to_string())
102
105
  }
103
106
  );
104
107
  let response_payload = Payload {
@@ -155,14 +158,9 @@ async fn activity_non_retryable_failure() {
155
158
  )
156
159
  .await
157
160
  .unwrap();
158
- // Poll activity and verify that it's been scheduled with correct parameters
161
+ // Poll activity and verify that it's been scheduled
159
162
  let task = core.poll_activity_task().await.unwrap();
160
- assert_matches!(
161
- task.variant,
162
- Some(act_task::Variant::Start(start_activity)) => {
163
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
164
- }
165
- );
163
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
166
164
  // Fail activity with non-retryable error
167
165
  let failure = Failure::application_failure("activity failed".to_string(), true);
168
166
  core.complete_activity_task(ActivityTaskCompletion {
@@ -192,7 +190,7 @@ async fn activity_non_retryable_failure() {
192
190
  failure_info: Some(FailureInfo::ActivityFailureInfo(ActivityFailureInfo{
193
191
  activity_id: "act-1".to_owned(),
194
192
  activity_type: Some(ActivityType {
195
- name: "test_activity".to_owned(),
193
+ name: DEFAULT_ACTIVITY_TYPE.to_owned(),
196
194
  }),
197
195
  scheduled_event_id: 5,
198
196
  started_event_id: 6,
@@ -227,14 +225,9 @@ async fn activity_non_retryable_failure_with_error() {
227
225
  )
228
226
  .await
229
227
  .unwrap();
230
- // Poll activity and verify that it's been scheduled with correct parameters
228
+ // Poll activity and verify that it's been scheduled
231
229
  let task = core.poll_activity_task().await.unwrap();
232
- assert_matches!(
233
- task.variant,
234
- Some(act_task::Variant::Start(start_activity)) => {
235
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
236
- }
237
- );
230
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
238
231
  // Fail activity with non-retryable error
239
232
  let failure = Failure::application_failure_from_error(anyhow!("activity failed"), true);
240
233
  core.complete_activity_task(ActivityTaskCompletion {
@@ -264,7 +257,7 @@ async fn activity_non_retryable_failure_with_error() {
264
257
  failure_info: Some(FailureInfo::ActivityFailureInfo(ActivityFailureInfo{
265
258
  activity_id: "act-1".to_owned(),
266
259
  activity_type: Some(ActivityType {
267
- name: "test_activity".to_owned(),
260
+ name: DEFAULT_ACTIVITY_TYPE.to_owned(),
268
261
  }),
269
262
  scheduled_event_id: 5,
270
263
  started_event_id: 6,
@@ -301,12 +294,7 @@ async fn activity_retry() {
301
294
  .unwrap();
302
295
  // Poll activity 1st time
303
296
  let task = core.poll_activity_task().await.unwrap();
304
- assert_matches!(
305
- task.variant,
306
- Some(act_task::Variant::Start(start_activity)) => {
307
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
308
- }
309
- );
297
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
310
298
  // Fail activity with retryable error
311
299
  let failure = Failure::application_failure("activity failed".to_string(), false);
312
300
  core.complete_activity_task(ActivityTaskCompletion {
@@ -317,12 +305,7 @@ async fn activity_retry() {
317
305
  .unwrap();
318
306
  // Poll 2nd time
319
307
  let task = core.poll_activity_task().await.unwrap();
320
- assert_matches!(
321
- task.variant,
322
- Some(act_task::Variant::Start(start_activity)) => {
323
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
324
- }
325
- );
308
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
326
309
  // Complete activity successfully
327
310
  let response_payload = Payload {
328
311
  data: b"hello ".to_vec(),
@@ -381,15 +364,10 @@ async fn activity_cancellation_try_cancel() {
381
364
  )
382
365
  .await
383
366
  .unwrap();
384
- // Poll activity and verify that it's been scheduled with correct parameters, we don't expect to
385
- // complete it in this test as activity is try-cancelled.
367
+ // Poll activity and verify that it's been scheduled, we don't expect to complete it in this
368
+ // test as activity is try-cancelled.
386
369
  let activity_task = core.poll_activity_task().await.unwrap();
387
- assert_matches!(
388
- activity_task.variant,
389
- Some(act_task::Variant::Start(start_activity)) => {
390
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
391
- }
392
- );
370
+ assert_matches!(activity_task.variant, Some(act_task::Variant::Start(_)));
393
371
  // Poll workflow task and verify that activity has failed.
394
372
  let task = core.poll_workflow_activation().await.unwrap();
395
373
  assert_matches!(
@@ -526,15 +504,10 @@ async fn started_activity_timeout() {
526
504
  )
527
505
  .await
528
506
  .unwrap();
529
- // Poll activity and verify that it's been scheduled with correct parameters, we don't expect to
530
- // complete it in this test as activity is timed out after 1 second.
507
+ // Poll activity and verify that it's been scheduled, we don't expect to complete it in this
508
+ // test as activity is timed out after 1 second.
531
509
  let activity_task = core.poll_activity_task().await.unwrap();
532
- assert_matches!(
533
- activity_task.variant,
534
- Some(act_task::Variant::Start(start_activity)) => {
535
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
536
- }
537
- );
510
+ assert_matches!(activity_task.variant, Some(act_task::Variant::Start(_)));
538
511
  let task = core.poll_workflow_activation().await.unwrap();
539
512
  assert_matches!(
540
513
  task.jobs.as_slice(),
@@ -590,15 +563,10 @@ async fn activity_cancellation_wait_cancellation_completed() {
590
563
  )
591
564
  .await
592
565
  .unwrap();
593
- // Poll activity and verify that it's been scheduled with correct parameters, we don't expect to
594
- // complete it in this test as activity is wait-cancelled.
566
+ // Poll activity and verify that it's been scheduled, we don't expect to complete it in this
567
+ // test as activity is wait-cancelled.
595
568
  let activity_task = core.poll_activity_task().await.unwrap();
596
- assert_matches!(
597
- activity_task.variant,
598
- Some(act_task::Variant::Start(start_activity)) => {
599
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
600
- }
601
- );
569
+ assert_matches!(activity_task.variant, Some(act_task::Variant::Start(_)));
602
570
  // Poll workflow task and verify that activity has failed.
603
571
  let task = core.poll_workflow_activation().await.unwrap();
604
572
  assert_matches!(
@@ -657,15 +625,10 @@ async fn activity_cancellation_abandon() {
657
625
  )
658
626
  .await
659
627
  .unwrap();
660
- // Poll activity and verify that it's been scheduled with correct parameters, we don't expect to
661
- // complete it in this test as activity is abandoned.
628
+ // Poll activity and verify that it's been scheduled, we don't expect to complete it in this
629
+ // test as activity is abandoned.
662
630
  let activity_task = core.poll_activity_task().await.unwrap();
663
- assert_matches!(
664
- activity_task.variant,
665
- Some(act_task::Variant::Start(start_activity)) => {
666
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
667
- }
668
- );
631
+ assert_matches!(activity_task.variant, Some(act_task::Variant::Start(_)));
669
632
  // Poll workflow task and verify that activity has failed.
670
633
  let task = core.poll_workflow_activation().await.unwrap();
671
634
  assert_matches!(
@@ -713,14 +676,9 @@ async fn async_activity_completion_workflow() {
713
676
  )
714
677
  .await
715
678
  .unwrap();
716
- // Poll activity and verify that it's been scheduled with correct parameters
679
+ // Poll activity and verify that it's been scheduled
717
680
  let task = core.poll_activity_task().await.unwrap();
718
- assert_matches!(
719
- task.variant,
720
- Some(act_task::Variant::Start(start_activity)) => {
721
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
722
- }
723
- );
681
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
724
682
  let response_payload = Payload {
725
683
  data: b"hello ".to_vec(),
726
684
  metadata: Default::default(),
@@ -785,14 +743,9 @@ async fn activity_cancelled_after_heartbeat_times_out() {
785
743
  )
786
744
  .await
787
745
  .unwrap();
788
- // Poll activity and verify that it's been scheduled with correct parameters
746
+ // Poll activity and verify that it's been scheduled
789
747
  let task = core.poll_activity_task().await.unwrap();
790
- assert_matches!(
791
- task.variant,
792
- Some(act_task::Variant::Start(start_activity)) => {
793
- assert_eq!(start_activity.activity_type, "test_activity".to_string())
794
- }
795
- );
748
+ assert_matches!(task.variant, Some(act_task::Variant::Start(_)));
796
749
  // Delay the heartbeat
797
750
  sleep(Duration::from_secs(2)).await;
798
751
  core.record_activity_heartbeat(ActivityHeartbeat {
@@ -814,7 +767,7 @@ async fn activity_cancelled_after_heartbeat_times_out() {
814
767
  .unwrap();
815
768
 
816
769
  // Verify shutdown completes
817
- core.shutdown().await;
770
+ drain_pollers_and_shutdown(&core).await;
818
771
  // Cleanup just in case
819
772
  starter
820
773
  .get_client()
@@ -949,3 +902,66 @@ async fn it_can_complete_async() {
949
902
 
950
903
  worker.run_until_done().await.unwrap();
951
904
  }
905
+
906
+ #[tokio::test]
907
+ async fn graceful_shutdown() {
908
+ let wf_name = "graceful_shutdown";
909
+ let mut starter = CoreWfStarter::new(wf_name);
910
+ starter.worker_config.graceful_shutdown_period = Some(Duration::from_millis(500));
911
+ let mut worker = starter.worker().await;
912
+ let client = starter.get_client().await;
913
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
914
+ let act_futs = (1..=10).map(|_| {
915
+ ctx.activity(ActivityOptions {
916
+ activity_type: "sleeper".to_string(),
917
+ start_to_close_timeout: Some(Duration::from_secs(5)),
918
+ retry_policy: Some(RetryPolicy {
919
+ maximum_attempts: 1,
920
+ ..Default::default()
921
+ }),
922
+ cancellation_type: ActivityCancellationType::WaitCancellationCompleted,
923
+ input: "hi".as_json_payload().unwrap(),
924
+ ..Default::default()
925
+ })
926
+ });
927
+ join_all(act_futs).await;
928
+ Ok(().into())
929
+ });
930
+ static ACTS_STARTED: Semaphore = Semaphore::const_new(0);
931
+ static ACTS_DONE: Semaphore = Semaphore::const_new(0);
932
+ worker.register_activity("sleeper", |ctx: ActContext, _: String| async move {
933
+ ACTS_STARTED.add_permits(1);
934
+ // just wait to be cancelled
935
+ ctx.cancelled().await;
936
+ ACTS_DONE.add_permits(1);
937
+ Result::<(), _>::Err(ActivityCancelledError::default().into())
938
+ });
939
+
940
+ worker
941
+ .submit_wf(
942
+ wf_name.to_owned(),
943
+ wf_name.to_owned(),
944
+ vec![],
945
+ WorkflowOptions::default(),
946
+ )
947
+ .await
948
+ .unwrap();
949
+
950
+ let handle = worker.inner_mut().shutdown_handle();
951
+ let shutdowner = async {
952
+ // Wait for all acts to be started before initiating shutdown
953
+ let _ = ACTS_STARTED.acquire_many(10).await;
954
+ handle();
955
+ // Kill workflow once all acts are cancelled. This also ensures we actually see all the
956
+ // cancels, otherwise run_until_done will hang since the workflow won't complete.
957
+ let _ = ACTS_DONE.acquire_many(10).await;
958
+ client
959
+ .terminate_workflow_execution(wf_name.to_owned(), None)
960
+ .await
961
+ .unwrap();
962
+ };
963
+ let runner = async {
964
+ worker.run_until_done().await.unwrap();
965
+ };
966
+ join!(shutdowner, runner);
967
+ }
@@ -33,6 +33,7 @@ async fn cancel_receiver(mut ctx: WfContext) -> WorkflowResult<()> {
33
33
  #[tokio::test]
34
34
  async fn sends_cancel_to_other_wf() {
35
35
  let mut starter = CoreWfStarter::new("sends_cancel_to_other_wf");
36
+ starter.no_remote_activities();
36
37
  let mut worker = starter.worker().await;
37
38
  worker.register_wf("sender", cancel_sender);
38
39
  worker.register_wf("receiver", cancel_receiver);
@@ -21,6 +21,7 @@ async fn cancelled_wf(mut ctx: WfContext) -> WorkflowResult<()> {
21
21
  async fn cancel_during_timer() {
22
22
  let wf_name = "cancel_during_timer";
23
23
  let mut starter = CoreWfStarter::new(wf_name);
24
+ starter.no_remote_activities();
24
25
  let mut worker = starter.worker().await;
25
26
  let client = starter.get_client().await;
26
27
  worker.register_wf(wf_name.to_string(), cancelled_wf);
@@ -1,8 +1,13 @@
1
1
  use anyhow::anyhow;
2
- use temporal_client::WorkflowOptions;
3
- use temporal_sdk::{ChildWorkflowOptions, WfContext, WorkflowResult};
4
- use temporal_sdk_core_protos::coresdk::child_workflow::{child_workflow_result, Success};
2
+ use std::time::Duration;
3
+ use temporal_client::{WorkflowClientTrait, WorkflowOptions};
4
+ use temporal_sdk::{ChildWorkflowOptions, WfContext, WfExitValue, WorkflowResult};
5
+ use temporal_sdk_core_protos::{
6
+ coresdk::child_workflow::{child_workflow_result, ChildWorkflowCancellationType, Success},
7
+ temporal::api::enums::v1::ParentClosePolicy,
8
+ };
5
9
  use temporal_sdk_core_test_utils::CoreWfStarter;
10
+ use tokio::sync::Barrier;
6
11
 
7
12
  static PARENT_WF_TYPE: &str = "parent_wf";
8
13
  static CHILD_WF_TYPE: &str = "child_wf";
@@ -32,6 +37,7 @@ async fn parent_wf(ctx: WfContext) -> WorkflowResult<()> {
32
37
  #[tokio::test]
33
38
  async fn child_workflow_happy_path() {
34
39
  let mut starter = CoreWfStarter::new("child-workflows");
40
+ starter.no_remote_activities();
35
41
  let mut worker = starter.worker().await;
36
42
 
37
43
  worker.register_wf(PARENT_WF_TYPE.to_string(), parent_wf);
@@ -48,3 +54,74 @@ async fn child_workflow_happy_path() {
48
54
  .unwrap();
49
55
  worker.run_until_done().await.unwrap();
50
56
  }
57
+
58
+ #[tokio::test]
59
+ async fn abandoned_child_bug_repro() {
60
+ let mut starter = CoreWfStarter::new("child-workflow-abandon-bug");
61
+ starter.no_remote_activities();
62
+ let mut worker = starter.worker().await;
63
+ let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
64
+
65
+ worker.register_wf(
66
+ PARENT_WF_TYPE.to_string(),
67
+ move |mut ctx: WfContext| async move {
68
+ let child = ctx.child_workflow(ChildWorkflowOptions {
69
+ workflow_id: "abandoned-child".to_owned(),
70
+ workflow_type: CHILD_WF_TYPE.to_owned(),
71
+ parent_close_policy: ParentClosePolicy::Abandon,
72
+ cancel_type: ChildWorkflowCancellationType::Abandon,
73
+ ..Default::default()
74
+ });
75
+
76
+ let started = child
77
+ .start(&ctx)
78
+ .await
79
+ .into_started()
80
+ .expect("Child chould start OK");
81
+ barr.wait().await;
82
+ // Wait for cancel signal
83
+ ctx.cancelled().await;
84
+ // Cancel the child immediately
85
+ started.cancel(&ctx);
86
+ // Need to do something else, so we'll see the ChildWorkflowExecutionCanceled event
87
+ ctx.timer(Duration::from_secs(1)).await;
88
+ started.result().await;
89
+ Ok(().into())
90
+ },
91
+ );
92
+ worker.register_wf(CHILD_WF_TYPE.to_string(), |mut ctx: WfContext| async move {
93
+ ctx.cancelled().await;
94
+ Ok(WfExitValue::Cancelled)
95
+ });
96
+
97
+ worker
98
+ .submit_wf(
99
+ "parent-abandoner".to_string(),
100
+ PARENT_WF_TYPE.to_owned(),
101
+ vec![],
102
+ WorkflowOptions::default(),
103
+ )
104
+ .await
105
+ .unwrap();
106
+ let client = starter.get_client().await;
107
+ let canceller = async {
108
+ barr.wait().await;
109
+ client
110
+ .cancel_workflow_execution(
111
+ "parent-abandoner".to_string(),
112
+ None,
113
+ "die".to_string(),
114
+ None,
115
+ )
116
+ .await
117
+ .unwrap();
118
+ client
119
+ .cancel_workflow_execution("abandoned-child".to_string(), None, "die".to_string(), None)
120
+ .await
121
+ .unwrap();
122
+ };
123
+ let runner = async move {
124
+ worker.run_until_done().await.unwrap();
125
+ };
126
+ tokio::join!(canceller, runner);
127
+ }
@@ -21,6 +21,7 @@ async fn continue_as_new_wf(ctx: WfContext) -> WorkflowResult<()> {
21
21
  async fn continue_as_new_happy_path() {
22
22
  let wf_name = "continue_as_new_happy_path";
23
23
  let mut starter = CoreWfStarter::new(wf_name);
24
+ starter.no_remote_activities();
24
25
  let mut worker = starter.worker().await;
25
26
  worker.register_wf(wf_name.to_string(), continue_as_new_wf);
26
27
 
@@ -40,7 +41,10 @@ async fn continue_as_new_happy_path() {
40
41
  async fn continue_as_new_multiple_concurrent() {
41
42
  let wf_name = "continue_as_new_multiple_concurrent";
42
43
  let mut starter = CoreWfStarter::new(wf_name);
43
- starter.max_cached_workflows(3).max_wft(3);
44
+ starter
45
+ .no_remote_activities()
46
+ .max_cached_workflows(3)
47
+ .max_wft(3);
44
48
  let mut worker = starter.worker().await;
45
49
  worker.register_wf(wf_name.to_string(), continue_as_new_wf);
46
50
 
@@ -36,6 +36,7 @@ pub async fn timer_wf_nondeterministic(ctx: WfContext) -> WorkflowResult<()> {
36
36
  async fn test_determinism_error_then_recovers() {
37
37
  let wf_name = "test_determinism_error_then_recovers";
38
38
  let mut starter = CoreWfStarter::new(wf_name);
39
+ starter.no_remote_activities();
39
40
  let mut worker = starter.worker().await;
40
41
 
41
42
  worker.register_wf(wf_name.to_owned(), timer_wf_nondeterministic);
@@ -12,7 +12,8 @@ use temporal_sdk::{
12
12
  use temporal_sdk_core::replay::HistoryForReplay;
13
13
  use temporal_sdk_core_protos::{
14
14
  coresdk::{
15
- workflow_commands::ActivityCancellationType,
15
+ workflow_commands::workflow_command::Variant, workflow_commands::ActivityCancellationType,
16
+ workflow_completion, workflow_completion::workflow_activation_completion,
16
17
  workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
17
18
  },
18
19
  temporal::api::{common::v1::RetryPolicy, enums::v1::TimeoutType},
@@ -234,6 +235,7 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
234
235
  starter.start_with_worker(wf_name, &mut worker).await;
235
236
  worker
236
237
  .run_until_done_intercepted(Some(LACancellerInterceptor {
238
+ cancel_on_workflow_completed: false,
237
239
  token: manual_cancel,
238
240
  }))
239
241
  .await
@@ -242,12 +244,29 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
242
244
 
243
245
  struct LACancellerInterceptor {
244
246
  token: CancellationToken,
247
+ cancel_on_workflow_completed: bool,
245
248
  }
246
249
  #[async_trait::async_trait(?Send)]
247
250
  impl WorkerInterceptor for LACancellerInterceptor {
248
- async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {}
251
+ async fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
252
+ if !self.cancel_on_workflow_completed {
253
+ return;
254
+ }
255
+ if let Some(workflow_activation_completion::Status::Successful(
256
+ workflow_completion::Success { commands, .. },
257
+ )) = completion.status.as_ref()
258
+ {
259
+ if let Some(&Variant::CompleteWorkflowExecution(_)) =
260
+ commands.last().and_then(|v| v.variant.as_ref())
261
+ {
262
+ self.token.cancel();
263
+ }
264
+ }
265
+ }
249
266
  fn on_shutdown(&self, _: &temporal_sdk::Worker) {
250
- self.token.cancel()
267
+ if !self.cancel_on_workflow_completed {
268
+ self.token.cancel()
269
+ }
251
270
  }
252
271
  }
253
272
 
@@ -331,6 +350,9 @@ async fn cancel_after_act_starts(
331
350
  worker
332
351
  .run_until_done_intercepted(Some(LACancellerInterceptor {
333
352
  token: manual_cancel,
353
+ // Only needed for this one case since the activity is not drained and prevents worker from shutting down.
354
+ cancel_on_workflow_completed: matches!(cancel_type, ActivityCancellationType::Abandon)
355
+ && cancel_on_backoff.is_none(),
334
356
  }))
335
357
  .await
336
358
  .unwrap();
@@ -20,6 +20,7 @@ async fn sends_modify_wf_props() {
20
20
  let wf_name = "can_upsert_memo";
21
21
  let wf_id = Uuid::new_v4();
22
22
  let mut starter = CoreWfStarter::new(wf_name);
23
+ starter.no_remote_activities();
23
24
  let mut worker = starter.worker().await;
24
25
 
25
26
  worker.register_wf(wf_name, memo_upserter);
@@ -43,10 +44,7 @@ async fn sends_modify_wf_props() {
43
44
  let catname = memo.get(FIELD_A).unwrap();
44
45
  let cuteness = memo.get(FIELD_B).unwrap();
45
46
  for payload in [catname, cuteness] {
46
- assert_eq!(
47
- &b"json/plain".to_vec(),
48
- payload.metadata.get("encoding").unwrap()
49
- );
47
+ assert!(payload.is_json_payload());
50
48
  }
51
49
  assert_eq!("enchi", String::from_json_payload(catname).unwrap());
52
50
  assert_eq!(9001, usize::from_json_payload(cuteness).unwrap());
@@ -27,6 +27,7 @@ pub async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> {
27
27
  async fn writes_change_markers() {
28
28
  let wf_name = "writes_change_markers";
29
29
  let mut starter = CoreWfStarter::new(wf_name);
30
+ starter.no_remote_activities();
30
31
  let mut worker = starter.worker().await;
31
32
  worker.register_wf(wf_name.to_owned(), changes_wf);
32
33
 
@@ -59,6 +60,7 @@ pub async fn no_change_then_change_wf(ctx: WfContext) -> WorkflowResult<()> {
59
60
  async fn can_add_change_markers() {
60
61
  let wf_name = "can_add_change_markers";
61
62
  let mut starter = CoreWfStarter::new(wf_name);
63
+ starter.no_remote_activities();
62
64
  let mut worker = starter.worker().await;
63
65
  worker.register_wf(wf_name.to_owned(), no_change_then_change_wf);
64
66
 
@@ -81,9 +83,37 @@ pub async fn replay_with_change_marker_wf(ctx: WfContext) -> WorkflowResult<()>
81
83
  async fn replaying_with_patch_marker() {
82
84
  let wf_name = "replaying_with_patch_marker";
83
85
  let mut starter = CoreWfStarter::new(wf_name);
86
+ starter.no_remote_activities();
84
87
  let mut worker = starter.worker().await;
85
88
  worker.register_wf(wf_name.to_owned(), replay_with_change_marker_wf);
86
89
 
87
90
  starter.start_with_worker(wf_name, &mut worker).await;
88
91
  worker.run_until_done().await.unwrap();
89
92
  }
93
+
94
+ /// Test that the internal patching mechanism works on the second workflow task when replaying.
95
+ /// Used as regression test for a bug that detected that we did not look ahead far enough to find
96
+ /// the next workflow task completion, which the flags are attached to.
97
+ #[tokio::test]
98
+ async fn patched_on_second_workflow_task_is_deterministic() {
99
+ let wf_name = "timer_patched_timer";
100
+ let mut starter = CoreWfStarter::new(wf_name);
101
+ // Disable caching to force replay from beginning
102
+ starter.max_cached_workflows(0).no_remote_activities();
103
+ let mut worker = starter.worker().await;
104
+ // Include a task failure as well to make sure that works
105
+ static FAIL_ONCE: AtomicBool = AtomicBool::new(true);
106
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
107
+ ctx.timer(Duration::from_millis(1)).await;
108
+ if FAIL_ONCE.load(Ordering::Acquire) {
109
+ FAIL_ONCE.store(false, Ordering::Release);
110
+ panic!("Enchi is hungry!");
111
+ }
112
+ assert!(ctx.patched(MY_PATCH_ID));
113
+ ctx.timer(Duration::from_millis(1)).await;
114
+ Ok(().into())
115
+ });
116
+
117
+ starter.start_with_worker(wf_name, &mut worker).await;
118
+ worker.run_until_done().await.unwrap();
119
+ }
@@ -1,3 +1,4 @@
1
+ use crate::integ_tests::workflow_tests::patches::changes_wf;
1
2
  use assert_matches::assert_matches;
2
3
  use parking_lot::Mutex;
3
4
  use std::{collections::HashSet, sync::Arc, time::Duration};
@@ -10,6 +11,7 @@ use temporal_sdk_core_protos::{
10
11
  workflow_commands::{ScheduleActivity, StartTimer},
11
12
  workflow_completion::WorkflowActivationCompletion,
12
13
  },
14
+ temporal::api::enums::v1::EventType,
13
15
  TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
14
16
  };
15
17
  use temporal_sdk_core_test_utils::{
@@ -129,6 +131,23 @@ async fn replay_using_wf_function() {
129
131
  worker.run().await.unwrap();
130
132
  }
131
133
 
134
+ #[tokio::test]
135
+ async fn replay_ending_wft_complete_with_commands_but_no_scheduled_started() {
136
+ let mut t = TestHistoryBuilder::default();
137
+ t.add_by_type(EventType::WorkflowExecutionStarted);
138
+ t.add_full_wf_task();
139
+
140
+ for i in 1..=2 {
141
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
142
+ t.add_timer_fired(timer_started_event_id, i.to_string());
143
+ t.add_full_wf_task();
144
+ }
145
+ let func = timers_wf(3);
146
+ let mut worker = replay_sdk_worker([test_hist_to_replay(t)]);
147
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, func);
148
+ worker.run().await.unwrap();
149
+ }
150
+
132
151
  async fn replay_abrupt_ending(t: TestHistoryBuilder) {
133
152
  let func = timers_wf(1);
134
153
  let mut worker = replay_sdk_worker([test_hist_to_replay(t)]);
@@ -205,6 +224,51 @@ async fn multiple_histories_can_handle_dupe_run_ids() {
205
224
  worker.run().await.unwrap();
206
225
  }
207
226
 
227
+ // Verifies SDK can decode patch markers before changing them to use json encoding
228
+ #[tokio::test]
229
+ async fn replay_old_patch_format() {
230
+ let mut worker = replay_sdk_worker([HistoryForReplay::new(
231
+ history_from_proto_binary("histories/old_change_marker_format.bin")
232
+ .await
233
+ .unwrap(),
234
+ "fake".to_owned(),
235
+ )]);
236
+ worker.register_wf("writes_change_markers", changes_wf);
237
+ worker.run().await.unwrap();
238
+ }
239
+
240
+ #[tokio::test]
241
+ async fn replay_ends_with_empty_wft() {
242
+ let core = init_core_replay_preloaded(
243
+ "SayHelloWorkflow",
244
+ [HistoryForReplay::new(
245
+ history_from_proto_binary("histories/ends_empty_wft_complete.bin")
246
+ .await
247
+ .unwrap(),
248
+ "fake".to_owned(),
249
+ )],
250
+ );
251
+ let task = core.poll_workflow_activation().await.unwrap();
252
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
253
+ task.run_id,
254
+ vec![ScheduleActivity {
255
+ seq: 1,
256
+ activity_id: "1".to_string(),
257
+ activity_type: "say_hello".to_string(),
258
+ ..Default::default()
259
+ }
260
+ .into()],
261
+ ))
262
+ .await
263
+ .unwrap();
264
+ let task = core.poll_workflow_activation().await.unwrap();
265
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
266
+ .await
267
+ .unwrap();
268
+ let task = core.poll_workflow_activation().await.unwrap();
269
+ assert!(task.eviction_reason().is_some());
270
+ }
271
+
208
272
  fn timers_wf(num_timers: u32) -> WorkflowFunction {
209
273
  WorkflowFunction::new(move |ctx: WfContext| async move {
210
274
  for _ in 1..=num_timers {
@@ -14,6 +14,7 @@ const POST_RESET_SIG: &str = "post-reset";
14
14
  async fn reset_workflow() {
15
15
  let wf_name = "reset_me_wf";
16
16
  let mut starter = CoreWfStarter::new(wf_name);
17
+ starter.no_remote_activities();
17
18
  let mut worker = starter.worker().await;
18
19
  worker.fetch_results = false;
19
20
  let notify = Arc::new(Notify::new());