@temporalio/core-bridge 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/Cargo.lock +255 -48
  2. package/package.json +4 -4
  3. package/releases/aarch64-apple-darwin/index.node +0 -0
  4. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  5. package/releases/x86_64-apple-darwin/index.node +0 -0
  6. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  7. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  8. package/sdk-core/.buildkite/pipeline.yml +1 -3
  9. package/sdk-core/.cargo/config.toml +5 -2
  10. package/sdk-core/.github/workflows/heavy.yml +28 -0
  11. package/sdk-core/Cargo.toml +1 -1
  12. package/sdk-core/README.md +9 -5
  13. package/sdk-core/client/src/lib.rs +211 -36
  14. package/sdk-core/client/src/raw.rs +1 -1
  15. package/sdk-core/client/src/retry.rs +32 -20
  16. package/sdk-core/core/Cargo.toml +23 -9
  17. package/sdk-core/core/src/abstractions.rs +11 -0
  18. package/sdk-core/core/src/core_tests/activity_tasks.rs +6 -5
  19. package/sdk-core/core/src/core_tests/local_activities.rs +263 -22
  20. package/sdk-core/core/src/core_tests/queries.rs +2 -2
  21. package/sdk-core/core/src/core_tests/workflow_tasks.rs +249 -5
  22. package/sdk-core/core/src/ephemeral_server/mod.rs +5 -6
  23. package/sdk-core/core/src/lib.rs +2 -0
  24. package/sdk-core/core/src/protosext/mod.rs +1 -1
  25. package/sdk-core/core/src/telemetry/log_export.rs +1 -1
  26. package/sdk-core/core/src/telemetry/mod.rs +23 -8
  27. package/sdk-core/core/src/test_help/mod.rs +8 -1
  28. package/sdk-core/core/src/worker/activities/local_activities.rs +259 -125
  29. package/sdk-core/core/src/worker/activities.rs +3 -2
  30. package/sdk-core/core/src/worker/mod.rs +53 -26
  31. package/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
  32. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
  33. package/sdk-core/core/src/worker/workflow/history_update.rs +835 -277
  34. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +9 -17
  35. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +3 -5
  36. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +1 -2
  37. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +3 -5
  38. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +1 -2
  39. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +1 -2
  40. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +1 -2
  41. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +73 -51
  42. package/sdk-core/core/src/worker/workflow/machines/mod.rs +3 -3
  43. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +4 -4
  44. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +1 -2
  45. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +3 -5
  46. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +6 -7
  47. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
  48. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +4 -4
  49. package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
  50. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +89 -58
  51. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +4 -7
  52. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +21 -9
  53. package/sdk-core/core/src/worker/workflow/managed_run.rs +1021 -360
  54. package/sdk-core/core/src/worker/workflow/mod.rs +306 -346
  55. package/sdk-core/core/src/worker/workflow/run_cache.rs +29 -53
  56. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +125 -0
  57. package/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
  58. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +115 -0
  59. package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
  60. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +444 -714
  61. package/sdk-core/core-api/Cargo.toml +2 -0
  62. package/sdk-core/core-api/src/errors.rs +1 -34
  63. package/sdk-core/core-api/src/lib.rs +6 -2
  64. package/sdk-core/core-api/src/worker.rs +14 -1
  65. package/sdk-core/etc/deps.svg +115 -140
  66. package/sdk-core/etc/regen-depgraph.sh +5 -0
  67. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -6
  68. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +7 -3
  69. package/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
  70. package/sdk-core/protos/api_upstream/Makefile +5 -5
  71. package/sdk-core/protos/api_upstream/build/go.mod +7 -0
  72. package/sdk-core/protos/api_upstream/build/go.sum +5 -0
  73. package/sdk-core/protos/api_upstream/build/tools.go +29 -0
  74. package/sdk-core/protos/api_upstream/go.mod +6 -0
  75. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +9 -2
  76. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +12 -19
  77. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +2 -2
  78. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
  79. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +3 -2
  80. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
  81. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +3 -3
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +20 -2
  83. package/sdk-core/protos/api_upstream/temporal/api/{update/v1/message.proto → enums/v1/interaction_type.proto} +11 -18
  84. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
  85. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
  86. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
  87. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
  88. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
  89. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +2 -13
  90. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
  91. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
  92. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  93. package/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
  94. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +13 -19
  95. package/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +87 -0
  96. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -2
  97. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +2 -2
  98. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -2
  99. package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
  100. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
  101. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
  102. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
  103. package/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
  104. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -2
  105. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +13 -8
  106. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
  107. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  108. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
  109. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
  110. package/sdk-core/sdk/Cargo.toml +4 -3
  111. package/sdk-core/sdk/src/lib.rs +87 -21
  112. package/sdk-core/sdk/src/workflow_future.rs +7 -12
  113. package/sdk-core/sdk-core-protos/Cargo.toml +5 -2
  114. package/sdk-core/sdk-core-protos/build.rs +36 -2
  115. package/sdk-core/sdk-core-protos/src/history_builder.rs +26 -19
  116. package/sdk-core/sdk-core-protos/src/history_info.rs +4 -0
  117. package/sdk-core/sdk-core-protos/src/lib.rs +78 -34
  118. package/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
  119. package/sdk-core/test-utils/Cargo.toml +3 -1
  120. package/sdk-core/test-utils/src/histfetch.rs +1 -1
  121. package/sdk-core/test-utils/src/lib.rs +50 -18
  122. package/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
  123. package/sdk-core/test-utils/src/workflows.rs +29 -0
  124. package/sdk-core/tests/fuzzy_workflow.rs +130 -0
  125. package/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +114 -7
  126. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -2
  127. package/sdk-core/tests/integ_tests/metrics_tests.rs +1 -1
  128. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -39
  129. package/sdk-core/tests/integ_tests/queries_tests.rs +2 -127
  130. package/sdk-core/tests/integ_tests/visibility_tests.rs +52 -5
  131. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +74 -1
  132. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +5 -13
  133. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +1 -1
  134. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +2 -10
  135. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +69 -197
  136. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +4 -28
  137. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +12 -7
  138. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +14 -14
  139. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -19
  140. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +3 -19
  141. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +1 -1
  142. package/sdk-core/tests/integ_tests/workflow_tests.rs +5 -6
  143. package/sdk-core/tests/main.rs +2 -12
  144. package/sdk-core/tests/runner.rs +71 -34
  145. package/sdk-core/tests/wf_input_replay.rs +32 -0
  146. package/sdk-core/bridge-ffi/Cargo.toml +0 -24
  147. package/sdk-core/bridge-ffi/LICENSE.txt +0 -23
  148. package/sdk-core/bridge-ffi/build.rs +0 -25
  149. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -224
  150. package/sdk-core/bridge-ffi/src/lib.rs +0 -746
  151. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -221
  152. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
  153. package/sdk-core/sdk/src/conversions.rs +0 -8
@@ -11,14 +11,17 @@ use anyhow::anyhow;
11
11
  use futures::{future::join_all, FutureExt};
12
12
  use std::{
13
13
  collections::HashMap,
14
+ ops::Sub,
14
15
  sync::{
15
16
  atomic::{AtomicUsize, Ordering},
16
17
  Arc,
17
18
  },
18
- time::Duration,
19
+ time::{Duration, SystemTime},
19
20
  };
20
21
  use temporal_client::WorkflowOptions;
21
- use temporal_sdk::{ActContext, LocalActivityOptions, WfContext, WorkflowResult};
22
+ use temporal_sdk::{
23
+ ActContext, ActivityCancelledError, LocalActivityOptions, WfContext, WorkflowResult,
24
+ };
22
25
  use temporal_sdk_core_api::Worker;
23
26
  use temporal_sdk_core_protos::{
24
27
  coresdk::{
@@ -29,7 +32,9 @@ use temporal_sdk_core_protos::{
29
32
  ActivityTaskCompletion, AsJsonPayloadExt,
30
33
  },
31
34
  temporal::api::{
32
- common::v1::RetryPolicy, enums::v1::EventType, failure::v1::Failure, history::v1::History,
35
+ common::v1::RetryPolicy,
36
+ enums::v1::{EventType, TimeoutType, WorkflowTaskFailedCause},
37
+ failure::v1::Failure,
33
38
  query::v1::WorkflowQuery,
34
39
  },
35
40
  };
@@ -106,7 +111,7 @@ pub async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
106
111
  .map(|i| {
107
112
  ctx.local_activity(LocalActivityOptions {
108
113
  activity_type: "echo".to_string(),
109
- input: format!("Hi {}", i)
114
+ input: format!("Hi {i}")
110
115
  .as_json_payload()
111
116
  .expect("serializes fine"),
112
117
  ..Default::default()
@@ -120,6 +125,7 @@ pub async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
120
125
 
121
126
  #[tokio::test]
122
127
  async fn local_act_many_concurrent() {
128
+ crate::telemetry::test_telem_console();
123
129
  let mut t = TestHistoryBuilder::default();
124
130
  t.add_by_type(EventType::WorkflowExecutionStarted);
125
131
  t.add_full_wf_task();
@@ -164,12 +170,7 @@ async fn local_act_many_concurrent() {
164
170
  async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
165
171
  let mut t = TestHistoryBuilder::default();
166
172
  let wft_timeout = Duration::from_millis(200);
167
- let mut wes_short_wft_timeout = default_wes_attribs();
168
- wes_short_wft_timeout.workflow_task_timeout = Some(wft_timeout.try_into().unwrap());
169
- t.add(
170
- EventType::WorkflowExecutionStarted,
171
- wes_short_wft_timeout.into(),
172
- );
173
+ t.add_wfe_started_with_wft_timeout(wft_timeout);
173
174
  t.add_full_wf_task();
174
175
  // Task created by WFT heartbeat
175
176
  t.add_full_wf_task();
@@ -367,7 +368,7 @@ async fn local_act_null_result() {
367
368
  let mut t = TestHistoryBuilder::default();
368
369
  t.add_by_type(EventType::WorkflowExecutionStarted);
369
370
  t.add_full_wf_task();
370
- t.add_local_activity_marker(1, "1", None, None, None);
371
+ t.add_local_activity_marker(1, "1", None, None, |_| {});
371
372
  t.add_workflow_execution_completed();
372
373
 
373
374
  let wf_id = "fakeid";
@@ -408,7 +409,7 @@ async fn local_act_command_immediately_follows_la_marker() {
408
409
  t.add_by_type(EventType::WorkflowExecutionStarted);
409
410
  t.add_full_wf_task();
410
411
  t.add_full_wf_task();
411
- t.add_local_activity_marker(1, "1", None, None, None);
412
+ t.add_local_activity_result_marker(1, "1", "done".into());
412
413
  t.add_get_event_id(EventType::TimerStarted, None);
413
414
  t.add_full_wf_task();
414
415
 
@@ -448,16 +449,11 @@ async fn local_act_command_immediately_follows_la_marker() {
448
449
  async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbeat() {
449
450
  let wfid = "fake_wf_id";
450
451
  let mut t = TestHistoryBuilder::default();
451
- let mut wes_short_wft_timeout = default_wes_attribs();
452
- wes_short_wft_timeout.workflow_task_timeout = Some(prost_dur!(from_millis(200)));
453
- t.add(
454
- EventType::WorkflowExecutionStarted,
455
- wes_short_wft_timeout.into(),
456
- );
452
+ t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
457
453
  t.add_full_wf_task();
458
454
  // get query here
459
455
  t.add_full_wf_task();
460
- t.add_local_activity_marker(1, "1", None, None, None);
456
+ t.add_local_activity_result_marker(1, "1", "done".into());
461
457
  t.add_workflow_execution_completed();
462
458
 
463
459
  let query_with_hist_task = {
@@ -582,7 +578,7 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
582
578
  // nonlegacy query got here & LA started here
583
579
  t.add_full_wf_task();
584
580
  // legacy query got here, at the same time that the LA is resolved
585
- t.add_local_activity_marker(1, "1", None, None, None);
581
+ t.add_local_activity_result_marker(1, "1", "whatever".into());
586
582
  t.add_workflow_execution_completed();
587
583
 
588
584
  let barr = Arc::new(Barrier::new(2));
@@ -618,8 +614,12 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
618
614
  2,
619
615
  ),
620
616
  );
621
- // Strip history, we need to look like we hit the cache
622
- pr.history = Some(History { events: vec![] });
617
+ // Strip beginning of history so the only events are WFT sched/started, we need to look
618
+ // like we hit the cache
619
+ {
620
+ let h = pr.history.as_mut().unwrap();
621
+ h.events = h.events.split_off(6);
622
+ }
623
623
  // In the nonsense server response case, we attach a legacy query, otherwise this
624
624
  // response looks like a normal response to a forced WFT heartbeat.
625
625
  if impossible_query_in_task {
@@ -735,3 +735,244 @@ async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_quer
735
735
  tokio::join!(wf_fut, act_fut);
736
736
  core.shutdown().await;
737
737
  }
738
+
739
+ #[tokio::test]
740
+ async fn test_schedule_to_start_timeout() {
741
+ let mut t = TestHistoryBuilder::default();
742
+ t.add_by_type(EventType::WorkflowExecutionStarted);
743
+ t.add_full_wf_task();
744
+
745
+ let wf_id = "fakeid";
746
+ let mock = mock_workflow_client();
747
+ let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::ToTaskNum(1)], mock);
748
+ let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
749
+
750
+ worker.register_wf(
751
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
752
+ |ctx: WfContext| async move {
753
+ let la_res = ctx
754
+ .local_activity(LocalActivityOptions {
755
+ activity_type: "echo".to_string(),
756
+ input: "hi".as_json_payload().expect("serializes fine"),
757
+ // Impossibly small timeout so we timeout in the queue
758
+ schedule_to_start_timeout: prost_dur!(from_nanos(1)),
759
+ ..Default::default()
760
+ })
761
+ .await;
762
+ assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
763
+ Ok(().into())
764
+ },
765
+ );
766
+ worker.register_activity(
767
+ "echo",
768
+ move |_ctx: ActContext, _: String| async move { Ok(()) },
769
+ );
770
+ worker
771
+ .submit_wf(
772
+ wf_id.to_owned(),
773
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
774
+ vec![],
775
+ WorkflowOptions::default(),
776
+ )
777
+ .await
778
+ .unwrap();
779
+ worker.run_until_done().await.unwrap();
780
+ }
781
+
782
+ #[rstest::rstest]
783
+ #[case::sched_to_start(true)]
784
+ #[case::sched_to_close(false)]
785
+ #[tokio::test]
786
+ async fn test_schedule_to_start_timeout_not_based_on_original_time(
787
+ #[case] is_sched_to_start: bool,
788
+ ) {
789
+ // We used to carry over the schedule time of LAs from the "original" schedule time if these LAs
790
+ // created newly after backing off across a timer. That was a mistake, since schedule-to-start
791
+ // timeouts should apply to when the new attempt was scheduled. This test verifies:
792
+ // * we don't time out on s-t-s timeouts because of that, when the param is true.
793
+ // * we do properly time out on s-t-c timeouts when the param is false
794
+
795
+ let mut t = TestHistoryBuilder::default();
796
+ t.add_by_type(EventType::WorkflowExecutionStarted);
797
+ t.add_full_wf_task();
798
+ let orig_sched = SystemTime::now().sub(Duration::from_secs(60 * 20));
799
+ t.add_local_activity_marker(
800
+ 1,
801
+ "1",
802
+ None,
803
+ Some(Failure::application_failure("la failed".to_string(), false)),
804
+ |deets| {
805
+ // Really old schedule time, which should _not_ count against schedule_to_start
806
+ deets.original_schedule_time = Some(orig_sched.into());
807
+ // Backoff value must be present since we're simulating timer backoff
808
+ deets.backoff = Some(prost_dur!(from_secs(100)));
809
+ },
810
+ );
811
+ let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
812
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
813
+ t.add_workflow_task_scheduled_and_started();
814
+
815
+ let wf_id = "fakeid";
816
+ let mock = mock_workflow_client();
817
+ let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
818
+ let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
819
+
820
+ let schedule_to_close_timeout = Some(if is_sched_to_start {
821
+ // This 60 minute timeout will not have elapsed according to the original
822
+ // schedule time in the history.
823
+ Duration::from_secs(60 * 60)
824
+ } else {
825
+ // This 10 minute timeout will have already elapsed
826
+ Duration::from_secs(10 * 60)
827
+ });
828
+
829
+ worker.register_wf(
830
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
831
+ move |ctx: WfContext| async move {
832
+ let la_res = ctx
833
+ .local_activity(LocalActivityOptions {
834
+ activity_type: "echo".to_string(),
835
+ input: "hi".as_json_payload().expect("serializes fine"),
836
+ retry_policy: RetryPolicy {
837
+ initial_interval: Some(prost_dur!(from_millis(50))),
838
+ backoff_coefficient: 1.2,
839
+ maximum_interval: None,
840
+ maximum_attempts: 5,
841
+ non_retryable_error_types: vec![],
842
+ },
843
+ schedule_to_start_timeout: Some(Duration::from_secs(60)),
844
+ schedule_to_close_timeout,
845
+ ..Default::default()
846
+ })
847
+ .await;
848
+ if is_sched_to_start {
849
+ assert!(la_res.completed_ok());
850
+ } else {
851
+ assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToClose));
852
+ }
853
+ Ok(().into())
854
+ },
855
+ );
856
+ worker.register_activity(
857
+ "echo",
858
+ move |_ctx: ActContext, _: String| async move { Ok(()) },
859
+ );
860
+ worker
861
+ .submit_wf(
862
+ wf_id.to_owned(),
863
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
864
+ vec![],
865
+ WorkflowOptions::default(),
866
+ )
867
+ .await
868
+ .unwrap();
869
+ worker.run_until_done().await.unwrap();
870
+ }
871
+
872
+ #[tokio::test]
873
+ async fn wft_failure_cancels_running_las() {
874
+ let mut t = TestHistoryBuilder::default();
875
+ t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
876
+ t.add_full_wf_task();
877
+ let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
878
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
879
+ t.add_workflow_task_scheduled_and_started();
880
+
881
+ let wf_id = "fakeid";
882
+ let mock = mock_workflow_client();
883
+ let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2], mock);
884
+ mh.num_expected_fails = 1;
885
+ let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
886
+
887
+ worker.register_wf(
888
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
889
+ |ctx: WfContext| async move {
890
+ let la_handle = ctx.local_activity(LocalActivityOptions {
891
+ activity_type: "echo".to_string(),
892
+ input: "hi".as_json_payload().expect("serializes fine"),
893
+ ..Default::default()
894
+ });
895
+ tokio::join!(
896
+ async {
897
+ ctx.timer(Duration::from_secs(1)).await;
898
+ panic!("ahhh I'm failing wft")
899
+ },
900
+ la_handle
901
+ );
902
+ Ok(().into())
903
+ },
904
+ );
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
+ });
912
+ worker
913
+ .submit_wf(
914
+ wf_id.to_owned(),
915
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
916
+ vec![],
917
+ WorkflowOptions::default(),
918
+ )
919
+ .await
920
+ .unwrap();
921
+ worker.run_until_done().await.unwrap();
922
+ }
923
+
924
+ #[tokio::test]
925
+ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
926
+ // We shouldn't record any LA results if the workflow activation is repeatedly failing. There
927
+ // was an issue that, because we stop reporting WFT failures after 2 tries, this meant the WFT
928
+ // was not marked as "completed" and the WFT could accidentally be replied to with LA results.
929
+ let mut t = TestHistoryBuilder::default();
930
+ t.add_by_type(EventType::WorkflowExecutionStarted);
931
+ t.add_workflow_task_scheduled_and_started();
932
+ t.add_workflow_task_failed_with_failure(
933
+ WorkflowTaskFailedCause::Unspecified,
934
+ Default::default(),
935
+ );
936
+ t.add_workflow_task_scheduled_and_started();
937
+
938
+ let wf_id = "fakeid";
939
+ let mock = mock_workflow_client();
940
+ let mut mh = MockPollCfg::from_resp_batches(
941
+ wf_id,
942
+ t,
943
+ [1.into(), ResponseType::AllHistory, ResponseType::AllHistory],
944
+ mock,
945
+ );
946
+ mh.num_expected_fails = 2;
947
+ mh.completion_asserts = Some(Box::new(|_| {
948
+ panic!("should never successfully complete a WFT");
949
+ }));
950
+ let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
951
+
952
+ worker.register_wf(
953
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
954
+ |ctx: WfContext| async move {
955
+ ctx.local_activity(LocalActivityOptions {
956
+ activity_type: "echo".to_string(),
957
+ input: "hi".as_json_payload().expect("serializes fine"),
958
+ ..Default::default()
959
+ })
960
+ .await;
961
+ panic!("Oh nooooo")
962
+ },
963
+ );
964
+ worker.register_activity(
965
+ "echo",
966
+ move |_: ActContext, _: String| async move { Ok(()) },
967
+ );
968
+ worker
969
+ .submit_wf(
970
+ wf_id.to_owned(),
971
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
972
+ vec![],
973
+ WorkflowOptions::default(),
974
+ )
975
+ .await
976
+ .unwrap();
977
+ worker.run_until_done().await.unwrap();
978
+ }
@@ -162,7 +162,7 @@ async fn new_queries(#[case] num_queries: usize) {
162
162
  pr.queries = HashMap::new();
163
163
  for i in 1..=num_queries {
164
164
  pr.queries.insert(
165
- format!("q{}", i),
165
+ format!("q{i}"),
166
166
  WorkflowQuery {
167
167
  query_type: "query-type".to_string(),
168
168
  query_args: Some(b"hi".into()),
@@ -206,7 +206,7 @@ async fn new_queries(#[case] num_queries: usize) {
206
206
  let mut qresults: Vec<_> = (1..=num_queries)
207
207
  .map(|i| {
208
208
  QueryResult {
209
- query_id: format!("q{}", i),
209
+ query_id: format!("q{i}"),
210
210
  variant: Some(
211
211
  QuerySuccess {
212
212
  response: Some(query_resp.into()),
@@ -892,7 +892,7 @@ async fn max_wft_respected() {
892
892
  let total_wfs = 100;
893
893
  let wf_ids: Vec<_> = (0..total_wfs)
894
894
  .into_iter()
895
- .map(|i| format!("fake-wf-{}", i))
895
+ .map(|i| format!("fake-wf-{i}"))
896
896
  .collect();
897
897
  let hists = wf_ids.iter().map(|wf_id| {
898
898
  let hist = canned_histories::single_timer("1");
@@ -1022,7 +1022,7 @@ async fn activity_not_canceled_when_also_completed_repro(hist_batches: &'static
1022
1022
  async fn lots_of_workflows() {
1023
1023
  let total_wfs = 500;
1024
1024
  let hists = (0..total_wfs).into_iter().map(|i| {
1025
- let wf_id = format!("fake-wf-{}", i);
1025
+ let wf_id = format!("fake-wf-{i}");
1026
1026
  let hist = canned_histories::single_timer("1");
1027
1027
  FakeWfResponses {
1028
1028
  wf_id,
@@ -1546,9 +1546,8 @@ async fn failing_wft_doesnt_eat_permit_forever() {
1546
1546
  ))
1547
1547
  .await
1548
1548
  .unwrap();
1549
- assert_eq!(worker.available_wft_permits().await, 2);
1550
-
1551
1549
  worker.shutdown().await;
1550
+ assert_eq!(worker.available_wft_permits().await, 2);
1552
1551
  }
1553
1552
 
1554
1553
  #[tokio::test]
@@ -1574,6 +1573,8 @@ async fn cache_miss_will_fetch_history() {
1574
1573
  let mut mock = build_mock_pollers(mh);
1575
1574
  mock.worker_cfg(|cfg| {
1576
1575
  cfg.max_cached_workflows = 1;
1576
+ // Also verifies tying the WFT permit to the fetch request doesn't get us stuck
1577
+ cfg.max_outstanding_workflow_tasks = 1;
1577
1578
  });
1578
1579
  let worker = mock_worker(mock);
1579
1580
 
@@ -1655,6 +1656,55 @@ async fn tasks_from_completion_are_delivered() {
1655
1656
  let complete_resp = RespondWorkflowTaskCompletedResponse {
1656
1657
  workflow_task: Some(hist_to_poll_resp(&t, wfid.to_owned(), 2.into()).resp),
1657
1658
  activity_tasks: vec![],
1659
+ reset_history_event_id: 0,
1660
+ };
1661
+ mock.expect_complete_workflow_task()
1662
+ .times(1)
1663
+ .returning(move |_| Ok(complete_resp.clone()));
1664
+ mock.expect_complete_workflow_task()
1665
+ .times(1)
1666
+ .returning(|_| Ok(Default::default()));
1667
+ let mut mock = single_hist_mock_sg(wfid, t, [1], mock, true);
1668
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
1669
+ let core = mock_worker(mock);
1670
+
1671
+ let wf_task = core.poll_workflow_activation().await.unwrap();
1672
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(wf_task.run_id))
1673
+ .await
1674
+ .unwrap();
1675
+ let wf_task = core.poll_workflow_activation().await.unwrap();
1676
+ assert_matches!(
1677
+ wf_task.jobs.as_slice(),
1678
+ [WorkflowActivationJob {
1679
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
1680
+ },]
1681
+ );
1682
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
1683
+ wf_task.run_id,
1684
+ vec![CompleteWorkflowExecution { result: None }.into()],
1685
+ ))
1686
+ .await
1687
+ .unwrap();
1688
+ core.shutdown().await;
1689
+ }
1690
+
1691
+ #[tokio::test]
1692
+ async fn pagination_works_with_tasks_from_completion() {
1693
+ let wfid = "fake_wf_id";
1694
+ let mut t = TestHistoryBuilder::default();
1695
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1696
+ t.add_full_wf_task();
1697
+ t.add_we_signaled("sig", vec![]);
1698
+ t.add_full_wf_task();
1699
+ t.add_workflow_execution_completed();
1700
+ let get_exec_resp: GetWorkflowExecutionHistoryResponse = t.get_history_info(2).unwrap().into();
1701
+
1702
+ let mut mock = mock_workflow_client();
1703
+ let mut needs_pag_resp = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2)).resp;
1704
+ needs_pag_resp.next_page_token = vec![1];
1705
+ let complete_resp = RespondWorkflowTaskCompletedResponse {
1706
+ workflow_task: Some(needs_pag_resp),
1707
+ ..Default::default()
1658
1708
  };
1659
1709
  mock.expect_complete_workflow_task()
1660
1710
  .times(1)
@@ -1662,6 +1712,9 @@ async fn tasks_from_completion_are_delivered() {
1662
1712
  mock.expect_complete_workflow_task()
1663
1713
  .times(1)
1664
1714
  .returning(|_| Ok(Default::default()));
1715
+ mock.expect_get_workflow_execution_history()
1716
+ .returning(move |_, _, _| Ok(get_exec_resp.clone()))
1717
+ .times(1);
1665
1718
  let mut mock = single_hist_mock_sg(wfid, t, [1], mock, true);
1666
1719
  mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
1667
1720
  let core = mock_worker(mock);
@@ -1691,7 +1744,7 @@ async fn poll_faster_than_complete_wont_overflow_cache() {
1691
1744
  // Make workflow tasks for 5 different runs
1692
1745
  let tasks: Vec<_> = (1..=5)
1693
1746
  .map(|i| FakeWfResponses {
1694
- wf_id: format!("wf-{}", i),
1747
+ wf_id: format!("wf-{i}"),
1695
1748
  hist: canned_histories::single_timer("1"),
1696
1749
  response_batches: vec![ResponseType::ToTaskNum(1)],
1697
1750
  })
@@ -2087,3 +2140,194 @@ async fn ignorable_events_are_ok(#[values(true, false)] attribs_unset: bool) {
2087
2140
  Some(workflow_activation_job::Variant::StartWorkflow(_))
2088
2141
  );
2089
2142
  }
2143
+
2144
+ #[tokio::test]
2145
+ async fn fetching_to_continue_replay_works() {
2146
+ let mut mock_client = mock_workflow_client();
2147
+ let mut t = TestHistoryBuilder::default();
2148
+ t.add_by_type(EventType::WorkflowExecutionStarted);
2149
+ t.add_full_wf_task();
2150
+ t.add_full_wf_task(); // ends 7
2151
+ let mut need_fetch_resp =
2152
+ hist_to_poll_resp(&t, "wfid".to_owned(), ResponseType::AllHistory).resp;
2153
+ need_fetch_resp.next_page_token = vec![1];
2154
+
2155
+ t.add_full_wf_task();
2156
+ t.add_we_signaled("hi", vec![]); // Need to make there be two complete WFTs
2157
+ t.add_full_wf_task(); // end 14
2158
+ let mut fetch_resp: GetWorkflowExecutionHistoryResponse =
2159
+ t.get_full_history_info().unwrap().into();
2160
+ // Should only contain events after 7
2161
+ if let Some(ref mut h) = fetch_resp.history {
2162
+ h.events.retain(|e| e.event_id >= 8);
2163
+ }
2164
+ // And indicate that even *more* needs to be fetched after this, so we see a request for the
2165
+ // next page happen.
2166
+ fetch_resp.next_page_token = vec![2];
2167
+
2168
+ let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
2169
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
2170
+ t.add_full_wf_task();
2171
+ let mut final_fetch_resp: GetWorkflowExecutionHistoryResponse =
2172
+ t.get_full_history_info().unwrap().into();
2173
+ // Should have only the final event
2174
+ if let Some(ref mut h) = final_fetch_resp.history {
2175
+ h.events.retain(|e| e.event_id >= 15);
2176
+ }
2177
+
2178
+ let tasks = vec![
2179
+ ResponseType::ToTaskNum(1),
2180
+ ResponseType::Raw(need_fetch_resp),
2181
+ ];
2182
+ mock_client
2183
+ .expect_get_workflow_execution_history()
2184
+ .returning(move |_, _, _| Ok(fetch_resp.clone()))
2185
+ .times(1);
2186
+ mock_client
2187
+ .expect_get_workflow_execution_history()
2188
+ .returning(move |_, _, _| Ok(final_fetch_resp.clone()))
2189
+ .times(1);
2190
+ let mut mock = single_hist_mock_sg("wfid", t, tasks, mock_client, true);
2191
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
2192
+ let core = mock_worker(mock);
2193
+ let act = core.poll_workflow_activation().await.unwrap();
2194
+ assert_matches!(
2195
+ act.jobs[0].variant,
2196
+ Some(workflow_activation_job::Variant::StartWorkflow(_))
2197
+ );
2198
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
2199
+ .await
2200
+ .unwrap();
2201
+ let act = core.poll_workflow_activation().await.unwrap();
2202
+ assert_matches!(
2203
+ act.jobs.as_slice(),
2204
+ [WorkflowActivationJob {
2205
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
2206
+ }]
2207
+ );
2208
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
2209
+ act.run_id,
2210
+ start_timer_cmd(1, Duration::from_secs(1)),
2211
+ ))
2212
+ .await
2213
+ .unwrap();
2214
+ let act = core.poll_workflow_activation().await.unwrap();
2215
+ assert_matches!(
2216
+ act.jobs.as_slice(),
2217
+ [WorkflowActivationJob {
2218
+ variant: Some(workflow_activation_job::Variant::FireTimer(_)),
2219
+ }]
2220
+ );
2221
+ }
2222
+
2223
+ #[tokio::test]
2224
+ async fn fetching_error_evicts_wf() {
2225
+ let mut mock_client = mock_workflow_client();
2226
+ let mut t = TestHistoryBuilder::default();
2227
+ t.add_by_type(EventType::WorkflowExecutionStarted);
2228
+ t.add_workflow_task_scheduled_and_started();
2229
+ t.add_workflow_task_completed();
2230
+ let mut need_fetch_resp =
2231
+ hist_to_poll_resp(&t, "wfid".to_owned(), ResponseType::AllHistory).resp;
2232
+ need_fetch_resp.next_page_token = vec![1];
2233
+ let tasks = vec![
2234
+ ResponseType::ToTaskNum(1),
2235
+ ResponseType::Raw(need_fetch_resp),
2236
+ ];
2237
+ mock_client
2238
+ .expect_get_workflow_execution_history()
2239
+ .returning(move |_, _, _| Err(tonic::Status::not_found("Ahh broken")))
2240
+ .times(1);
2241
+ let mut mock = single_hist_mock_sg("wfid", t, tasks, mock_client, true);
2242
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
2243
+ let core = mock_worker(mock);
2244
+ let act = core.poll_workflow_activation().await.unwrap();
2245
+ assert_matches!(
2246
+ act.jobs[0].variant,
2247
+ Some(workflow_activation_job::Variant::StartWorkflow(_))
2248
+ );
2249
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
2250
+ .await
2251
+ .unwrap();
2252
+ let evict_act = core.poll_workflow_activation().await.unwrap();
2253
+ assert_matches!(
2254
+ evict_act.jobs.as_slice(),
2255
+ [WorkflowActivationJob {
2256
+ variant: Some(workflow_activation_job::Variant::RemoveFromCache(r)),
2257
+ }] => r.message.contains("Fetching history failed")
2258
+ );
2259
+ }
2260
+
2261
+ /// This test verifies that if we fail to fetch a page during a completion, that we don't get stuck
2262
+ /// in the complete waiting for the completion to finish.
2263
+ #[tokio::test]
2264
+ async fn ensure_fetching_fail_during_complete_sends_task_failure() {
2265
+ let wfid = "fake_wf_id";
2266
+ let mut t = TestHistoryBuilder::default();
2267
+ t.add_by_type(EventType::WorkflowExecutionStarted);
2268
+ t.add_full_wf_task(); // started 3
2269
+ t.add_we_signaled("sig1", vec![]);
2270
+ t.add_full_wf_task(); // started 7
2271
+ t.add_we_signaled("sig2", vec![]);
2272
+ t.add_full_wf_task(); // started 11
2273
+ t.add_workflow_execution_completed();
2274
+
2275
+ let mut first_poll = hist_to_poll_resp(&t, wfid, ResponseType::ToTaskNum(1)).resp;
2276
+ first_poll.next_page_token = vec![1];
2277
+ first_poll.previous_started_event_id = 3;
2278
+
2279
+ let mut next_page: GetWorkflowExecutionHistoryResponse = t.get_history_info(2).unwrap().into();
2280
+ next_page.next_page_token = vec![2];
2281
+
2282
+ let mut mock = mock_workflow_client();
2283
+ mock.expect_get_workflow_execution_history()
2284
+ .returning(move |_, _, _| {
2285
+ error!("Called fetch!");
2286
+ Ok(next_page.clone())
2287
+ })
2288
+ .times(1);
2289
+ let mut really_empty_fetch_resp: GetWorkflowExecutionHistoryResponse =
2290
+ t.get_history_info(1).unwrap().into();
2291
+ really_empty_fetch_resp.history = Some(Default::default());
2292
+ mock.expect_get_workflow_execution_history()
2293
+ .returning(move |_, _, _| {
2294
+ error!("Called fetch second time!");
2295
+ Ok(really_empty_fetch_resp.clone())
2296
+ })
2297
+ .times(1);
2298
+ mock.expect_fail_workflow_task()
2299
+ .returning(|_, _, _| Ok(Default::default()))
2300
+ .times(1);
2301
+
2302
+ let mut mock = single_hist_mock_sg(wfid, t, [ResponseType::Raw(first_poll)], mock, true);
2303
+ mock.make_wft_stream_interminable();
2304
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
2305
+ let core = mock_worker(mock);
2306
+
2307
+ let wf_task = core.poll_workflow_activation().await.unwrap();
2308
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(wf_task.run_id))
2309
+ .await
2310
+ .unwrap();
2311
+
2312
+ let wf_task = core.poll_workflow_activation().await.unwrap();
2313
+ assert_matches!(
2314
+ wf_task.jobs.as_slice(),
2315
+ [WorkflowActivationJob {
2316
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
2317
+ },]
2318
+ );
2319
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(wf_task.run_id))
2320
+ .await
2321
+ .unwrap();
2322
+
2323
+ // Expect to see eviction b/c of history fetching error here.
2324
+ let wf_task = core.poll_workflow_activation().await.unwrap();
2325
+ assert_matches!(
2326
+ wf_task.jobs.as_slice(),
2327
+ [WorkflowActivationJob {
2328
+ variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
2329
+ },]
2330
+ );
2331
+
2332
+ core.shutdown().await;
2333
+ }