@temporalio/core-bridge 1.10.3 → 1.11.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 (126) hide show
  1. package/Cargo.lock +563 -676
  2. package/Cargo.toml +3 -3
  3. package/lib/index.d.ts +16 -5
  4. package/lib/index.js.map +1 -1
  5. package/lib/worker-tuner.d.ts +57 -0
  6. package/lib/worker-tuner.js +3 -0
  7. package/lib/worker-tuner.js.map +1 -0
  8. package/package.json +3 -3
  9. package/releases/aarch64-apple-darwin/index.node +0 -0
  10. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  11. package/releases/x86_64-apple-darwin/index.node +0 -0
  12. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  13. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  14. package/sdk-core/.github/workflows/heavy.yml +6 -11
  15. package/sdk-core/.github/workflows/per-pr.yml +23 -41
  16. package/sdk-core/Cargo.toml +5 -5
  17. package/sdk-core/README.md +2 -0
  18. package/sdk-core/client/Cargo.toml +4 -2
  19. package/sdk-core/client/src/lib.rs +60 -17
  20. package/sdk-core/client/src/metrics.rs +1 -1
  21. package/sdk-core/client/src/proxy.rs +17 -12
  22. package/sdk-core/client/src/raw.rs +218 -69
  23. package/sdk-core/client/src/retry.rs +19 -9
  24. package/sdk-core/core/Cargo.toml +12 -12
  25. package/sdk-core/core/src/abstractions.rs +3 -3
  26. package/sdk-core/core/src/core_tests/activity_tasks.rs +2 -1
  27. package/sdk-core/core/src/core_tests/determinism.rs +1 -1
  28. package/sdk-core/core/src/core_tests/local_activities.rs +73 -10
  29. package/sdk-core/core/src/core_tests/queries.rs +2 -1
  30. package/sdk-core/core/src/core_tests/updates.rs +162 -4
  31. package/sdk-core/core/src/core_tests/workers.rs +38 -2
  32. package/sdk-core/core/src/core_tests/workflow_tasks.rs +158 -27
  33. package/sdk-core/core/src/internal_flags.rs +17 -7
  34. package/sdk-core/core/src/lib.rs +9 -3
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +1 -10
  36. package/sdk-core/core/src/protosext/mod.rs +0 -1
  37. package/sdk-core/core/src/protosext/protocol_messages.rs +105 -16
  38. package/sdk-core/core/src/retry_logic.rs +22 -2
  39. package/sdk-core/core/src/telemetry/otel.rs +44 -12
  40. package/sdk-core/core/src/test_help/mod.rs +65 -12
  41. package/sdk-core/core/src/worker/activities/local_activities.rs +1 -4
  42. package/sdk-core/core/src/worker/activities.rs +3 -4
  43. package/sdk-core/core/src/worker/client/mocks.rs +7 -6
  44. package/sdk-core/core/src/worker/client.rs +11 -2
  45. package/sdk-core/core/src/worker/mod.rs +49 -24
  46. package/sdk-core/core/src/worker/tuner/resource_based.rs +48 -48
  47. package/sdk-core/core/src/worker/tuner.rs +124 -4
  48. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +1 -1
  49. package/sdk-core/core/src/worker/workflow/history_update.rs +11 -2
  50. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +18 -3
  51. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -0
  52. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +1 -0
  53. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -0
  54. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +1 -0
  55. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +1 -0
  56. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +1 -0
  57. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +4 -4
  58. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +1 -0
  59. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +1 -0
  60. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -0
  61. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +3 -1
  62. package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +38 -28
  63. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +4 -2
  64. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +95 -71
  65. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +1 -1
  66. package/sdk-core/core/src/worker/workflow/managed_run.rs +214 -14
  67. package/sdk-core/core/src/worker/workflow/mod.rs +49 -36
  68. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +1 -2
  69. package/sdk-core/core-api/src/errors.rs +13 -7
  70. package/sdk-core/core-api/src/lib.rs +9 -1
  71. package/sdk-core/sdk/Cargo.toml +1 -1
  72. package/sdk-core/sdk/src/activity_context.rs +3 -4
  73. package/sdk-core/sdk/src/lib.rs +96 -49
  74. package/sdk-core/sdk/src/workflow_context/options.rs +8 -4
  75. package/sdk-core/sdk/src/workflow_context.rs +53 -49
  76. package/sdk-core/sdk/src/workflow_future.rs +10 -4
  77. package/sdk-core/sdk-core-protos/Cargo.toml +4 -3
  78. package/sdk-core/sdk-core-protos/build.rs +2 -0
  79. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/build.yaml +18 -0
  80. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/LICENSE +21 -0
  81. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/Makefile +59 -0
  82. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +25 -0
  83. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -0
  84. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.gen.yaml +14 -0
  85. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +8 -0
  86. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +9 -0
  87. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +520 -0
  88. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +263 -0
  89. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +173 -0
  90. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +164 -0
  91. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +36 -0
  92. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +22 -0
  93. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +50 -8
  94. package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +3 -0
  95. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +132 -54
  96. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +177 -81
  97. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +13 -0
  98. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  99. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +8 -3
  100. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +10 -0
  101. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +3 -3
  102. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +6 -6
  103. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +96 -0
  104. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +49 -0
  105. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -7
  106. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +55 -24
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +7 -0
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +21 -4
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +64 -45
  110. package/sdk-core/sdk-core-protos/src/history_builder.rs +8 -1
  111. package/sdk-core/sdk-core-protos/src/lib.rs +40 -10
  112. package/sdk-core/test-utils/src/canned_histories.rs +1 -1
  113. package/sdk-core/tests/fuzzy_workflow.rs +4 -2
  114. package/sdk-core/tests/heavy_tests.rs +3 -3
  115. package/sdk-core/tests/integ_tests/activity_functions.rs +2 -2
  116. package/sdk-core/tests/integ_tests/client_tests.rs +234 -6
  117. package/sdk-core/tests/integ_tests/update_tests.rs +180 -47
  118. package/sdk-core/tests/integ_tests/worker_tests.rs +32 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +47 -3
  120. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +10 -10
  121. package/sdk-core/tests/main.rs +1 -0
  122. package/src/conversions.rs +84 -11
  123. package/src/runtime.rs +5 -17
  124. package/src/worker.rs +27 -6
  125. package/ts/index.ts +24 -5
  126. package/ts/worker-tuner.ts +71 -0
@@ -146,16 +146,26 @@ where
146
146
  throttle_backoff: throttle_cfg.into_exp_backoff(throttle_clock),
147
147
  }
148
148
  }
149
- const fn should_log_retry_warning(&self, cur_attempt: usize) -> bool {
149
+
150
+ fn maybe_log_retry(&self, cur_attempt: usize, err: &tonic::Status) {
151
+ let mut do_log = false;
150
152
  // Warn on more than 5 retries for unlimited retrying
151
153
  if self.max_retries == 0 && cur_attempt > 5 {
152
- return true;
154
+ do_log = true;
153
155
  }
154
156
  // Warn if the attempts are more than 50% of max retries
155
157
  if self.max_retries > 0 && cur_attempt * 2 >= self.max_retries {
156
- return true;
158
+ do_log = true;
159
+ }
160
+
161
+ if do_log {
162
+ // Error if unlimited retries have been going on for a while
163
+ if self.max_retries == 0 && cur_attempt > 15 {
164
+ error!(error=?err, "gRPC call {} retried {} times", self.call_name, cur_attempt);
165
+ } else {
166
+ warn!(error=?err, "gRPC call {} retried {} times", self.call_name, cur_attempt);
167
+ }
157
168
  }
158
- false
159
169
  }
160
170
  }
161
171
  #[doc(hidden)]
@@ -194,8 +204,8 @@ where
194
204
  if RETRYABLE_ERROR_CODES.contains(&e.code()) || long_poll_allowed {
195
205
  if current_attempt == 1 {
196
206
  debug!(error=?e, "gRPC call {} failed on first attempt", self.call_name);
197
- } else if self.should_log_retry_warning(current_attempt) {
198
- warn!(error=?e, "gRPC call {} retried {} times", self.call_name, current_attempt);
207
+ } else {
208
+ self.maybe_log_retry(current_attempt, &e);
199
209
  }
200
210
 
201
211
  match self.backoff.next_backoff() {
@@ -471,7 +481,7 @@ where
471
481
  list_open_workflow_executions,
472
482
  maximum_page_size,
473
483
  next_page_token.clone(),
474
- start_time_filter.clone(),
484
+ start_time_filter,
475
485
  filters.clone()
476
486
  )
477
487
  }
@@ -488,7 +498,7 @@ where
488
498
  list_closed_workflow_executions,
489
499
  maximum_page_size,
490
500
  next_page_token.clone(),
491
- start_time_filter.clone(),
501
+ start_time_filter,
492
502
  filters.clone()
493
503
  )
494
504
  }
@@ -541,7 +551,7 @@ where
541
551
  workflow_id.clone(),
542
552
  run_id.clone(),
543
553
  name.clone(),
544
- wait_policy.clone(),
554
+ wait_policy,
545
555
  args.clone()
546
556
  )
547
557
  }
@@ -17,16 +17,16 @@ default = ["otel"]
17
17
  otel = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-otlp",
18
18
  "dep:opentelemetry-prometheus", "dep:hyper", "dep:hyper-util", "dep:http-body-util"]
19
19
  tokio-console = ["console-subscriber"]
20
- ephemeral-server = ["dep:flate2", "dep:nix", "dep:reqwest", "dep:tar", "dep:zip"]
20
+ ephemeral-server = ["dep:flate2", "dep:reqwest", "dep:tar", "dep:zip"]
21
21
 
22
22
  [dependencies]
23
23
  anyhow = "1.0"
24
24
  async-trait = "0.1"
25
- console-subscriber = { version = "0.2", optional = true }
25
+ console-subscriber = { version = "0.4", optional = true }
26
26
  crossbeam-channel = "0.5"
27
27
  crossbeam-queue = "0.3"
28
28
  crossbeam-utils = "0.8"
29
- dashmap = "5.5"
29
+ dashmap = "6.0"
30
30
  derive_builder = { workspace = true }
31
31
  derive_more = { workspace = true }
32
32
  enum_dispatch = "0.3"
@@ -38,23 +38,22 @@ governor = "0.6"
38
38
  http-body-util = { version = "0.1", optional = true }
39
39
  hyper = { version = "1.2", optional = true }
40
40
  hyper-util = { version = "0.1", features = ["server", "http1", "http2", "tokio"], optional = true }
41
- itertools = "0.12"
41
+ itertools = "0.13"
42
42
  lru = "0.12"
43
43
  mockall = "0.12"
44
- nix = { version = "0.28", optional = true, features = ["process", "signal"] }
45
44
  once_cell = { workspace = true }
46
45
  opentelemetry = { workspace = true, features = ["metrics"], optional = true }
47
- opentelemetry_sdk = { version = "0.22", features = ["rt-tokio", "metrics"], optional = true }
48
- opentelemetry-otlp = { version = "0.15", features = ["tokio", "metrics"], optional = true }
49
- opentelemetry-prometheus = { version = "0.15", optional = true }
46
+ opentelemetry_sdk = { version = "0.24", features = ["rt-tokio", "metrics"], optional = true }
47
+ opentelemetry-otlp = { version = "0.17", features = ["tokio", "metrics"], optional = true }
48
+ opentelemetry-prometheus = { version = "0.17", optional = true }
50
49
  parking_lot = { version = "0.12", features = ["send_guard"] }
51
50
  pid = "4.0"
52
51
  pin-project = "1.0"
53
52
  prometheus = "0.13"
54
53
  prost = { workspace = true }
55
- prost-types = { version = "0.5", package = "prost-wkt-types" }
54
+ prost-types = { version = "0.6", package = "prost-wkt-types" }
56
55
  rand = "0.8.3"
57
- reqwest = { version = "0.11", features = ["json", "stream", "rustls-tls"], default-features = false, optional = true }
56
+ reqwest = { version = "0.12", features = ["json", "stream", "rustls-tls"], default-features = false, optional = true }
58
57
  ringbuf = "0.4"
59
58
  serde = "1.0"
60
59
  serde_json = "1.0"
@@ -71,7 +70,7 @@ tracing = "0.1"
71
70
  tracing-subscriber = { version = "0.3", features = ["parking_lot", "env-filter", "registry"] }
72
71
  url = "2.2"
73
72
  uuid = { version = "1.1", features = ["v4"] }
74
- zip = { version = "1.2", optional = true }
73
+ zip = { version = "2.0", optional = true }
75
74
 
76
75
  # 1st party local deps
77
76
  [dependencies.temporal-sdk-core-api]
@@ -93,10 +92,11 @@ assert_matches = "1.4"
93
92
  bimap = "0.6.1"
94
93
  clap = { version = "4.0", features = ["derive"] }
95
94
  criterion = "0.5"
96
- rstest = "0.19.0"
95
+ rstest = "0.21"
97
96
  sysinfo = "0.30"
98
97
  temporal-sdk-core-test-utils = { path = "../test-utils" }
99
98
  temporal-sdk = { path = "../sdk" }
99
+ tokio-stream = { version = "0.1", features = ["net"] }
100
100
 
101
101
  [build-dependencies]
102
102
  tonic-build = { workspace = true }
@@ -66,7 +66,7 @@ where
66
66
  .map(|ap| ap + self.unused_claimants.load(Ordering::Acquire))
67
67
  }
68
68
 
69
- pub(crate) async fn acquire_owned(&self) -> Result<OwnedMeteredSemPermit<SK>, ()> {
69
+ pub(crate) async fn acquire_owned(&self) -> OwnedMeteredSemPermit<SK> {
70
70
  if let Some(max) = self.max_permits {
71
71
  self.extant_permits
72
72
  .1
@@ -76,7 +76,7 @@ where
76
76
  .expect("Extant permit channel is never closed");
77
77
  }
78
78
  let res = self.supplier.reserve_slot(self).await;
79
- Ok(self.build_owned(res))
79
+ self.build_owned(res)
80
80
  }
81
81
 
82
82
  pub(crate) fn try_acquire_owned(&self) -> Result<OwnedMeteredSemPermit<SK>, ()> {
@@ -351,6 +351,6 @@ pub(crate) mod tests {
351
351
  advance_fut!(acquire_fut);
352
352
  drop(perm);
353
353
  // Now it'll proceed
354
- acquire_fut.await.unwrap();
354
+ acquire_fut.await;
355
355
  }
356
356
  }
@@ -900,7 +900,7 @@ async fn activity_tasks_from_completion_reserve_slots() {
900
900
  ],
901
901
  mock,
902
902
  );
903
- mh.completion_asserts = Some(Box::new(|wftc| {
903
+ mh.completion_mock_fn = Some(Box::new(|wftc| {
904
904
  // Make sure when we see the completion with the schedule act command that it does
905
905
  // not have the eager execution flag set the first time, and does the second.
906
906
  if let Some(Attributes::ScheduleActivityTaskCommandAttributes(attrs)) = wftc
@@ -914,6 +914,7 @@ async fn activity_tasks_from_completion_reserve_slots() {
914
914
  assert!(attrs.request_eager_execution);
915
915
  }
916
916
  }
917
+ Ok(Default::default())
917
918
  }));
918
919
  let mut mock = build_mock_pollers(mh);
919
920
  mock.worker_cfg(|cfg| {
@@ -125,7 +125,7 @@ async fn activity_id_or_type_change_is_nondeterministic(
125
125
  ) {
126
126
  let wf_id = "fakeid";
127
127
  let wf_type = DEFAULT_WORKFLOW_TYPE;
128
- let mut t = if local_act {
128
+ let mut t: TestHistoryBuilder = if local_act {
129
129
  canned_histories::single_local_activity("1")
130
130
  } else {
131
131
  canned_histories::single_activity("1")
@@ -17,12 +17,11 @@ use std::{
17
17
  atomic::{AtomicUsize, Ordering},
18
18
  Arc,
19
19
  },
20
- time::{Duration, SystemTime},
20
+ time::{Duration, Instant, SystemTime},
21
21
  };
22
22
  use temporal_client::WorkflowOptions;
23
23
  use temporal_sdk::{
24
- ActContext, ActivityCancelledError, LocalActivityOptions, WfContext, WorkflowFunction,
25
- WorkflowResult,
24
+ ActContext, ActivityError, LocalActivityOptions, WfContext, WorkflowFunction, WorkflowResult,
26
25
  };
27
26
  use temporal_sdk_core_api::{
28
27
  errors::{PollActivityError, PollWfError},
@@ -49,7 +48,7 @@ use temporal_sdk_core_test_utils::{
49
48
  };
50
49
  use tokio::{join, select, sync::Barrier};
51
50
 
52
- async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
51
+ async fn echo(_ctx: ActContext, e: String) -> Result<String, ActivityError> {
53
52
  Ok(e)
54
53
  }
55
54
 
@@ -279,7 +278,7 @@ async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
279
278
  if 2 == attempts.fetch_add(1, Ordering::Relaxed) && eventually_pass {
280
279
  Ok(())
281
280
  } else {
282
- Err(anyhow!("Oh no I failed!"))
281
+ Err(anyhow!("Oh no I failed!").into())
283
282
  }
284
283
  });
285
284
  worker
@@ -356,7 +355,7 @@ async fn local_act_retry_long_backoff_uses_timer() {
356
355
  worker.register_activity(
357
356
  DEFAULT_ACTIVITY_TYPE,
358
357
  move |_ctx: ActContext, _: String| async move {
359
- Result::<(), _>::Err(anyhow!("Oh no I failed!"))
358
+ Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
360
359
  },
361
360
  );
362
361
  worker
@@ -928,7 +927,7 @@ async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_complet
928
927
  _ = tokio::time::sleep(Duration::from_millis(100)) => (),
929
928
  _ = ctx.cancelled() => {
930
929
  cancels.fetch_add(1, Ordering::AcqRel);
931
- return Err(anyhow!(ActivityCancelledError::default()));
930
+ return Err(ActivityError::cancelled());
932
931
  }
933
932
  }
934
933
  }
@@ -991,7 +990,7 @@ async fn wft_failure_cancels_running_las() {
991
990
  if res.is_err() {
992
991
  panic!("Activity must be cancelled!!!!");
993
992
  }
994
- Result::<(), _>::Err(ActivityCancelledError::default().into())
993
+ Result::<(), _>::Err(ActivityError::cancelled())
995
994
  },
996
995
  );
997
996
  worker
@@ -1076,11 +1075,12 @@ async fn local_act_records_nonfirst_attempts_ok() {
1076
1075
  let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
1077
1076
  let nonfirst_counts = Arc::new(SegQueue::new());
1078
1077
  let nfc_c = nonfirst_counts.clone();
1079
- mh.completion_asserts = Some(Box::new(move |c| {
1078
+ mh.completion_mock_fn = Some(Box::new(move |c| {
1080
1079
  nfc_c.push(
1081
1080
  c.metering_metadata
1082
1081
  .nonfirst_local_activity_execution_attempts,
1083
1082
  );
1083
+ Ok(Default::default())
1084
1084
  }));
1085
1085
  let mut worker = mock_sdk_cfg(mh, |wc| {
1086
1086
  wc.max_cached_workflows = 1;
@@ -1107,7 +1107,7 @@ async fn local_act_records_nonfirst_attempts_ok() {
1107
1107
  },
1108
1108
  );
1109
1109
  worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1110
- Result::<(), _>::Err(anyhow!("I fail"))
1110
+ Result::<(), _>::Err(anyhow!("I fail").into())
1111
1111
  });
1112
1112
  worker
1113
1113
  .submit_wf(
@@ -1390,3 +1390,66 @@ async fn local_activity_after_wf_complete_is_discarded() {
1390
1390
  join!(wf_poller, at_poller);
1391
1391
  core.drain_pollers_and_shutdown().await;
1392
1392
  }
1393
+
1394
+ #[tokio::test]
1395
+ async fn local_act_retry_explicit_delay() {
1396
+ let mut t = TestHistoryBuilder::default();
1397
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1398
+ t.add_workflow_task_scheduled_and_started();
1399
+
1400
+ let wf_id = "fakeid";
1401
+ let mock = mock_workflow_client();
1402
+ let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
1403
+ let mut worker = mock_sdk(mh);
1404
+
1405
+ worker.register_wf(
1406
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
1407
+ move |ctx: WfContext| async move {
1408
+ let la_res = ctx
1409
+ .local_activity(LocalActivityOptions {
1410
+ activity_type: "echo".to_string(),
1411
+ input: "hi".as_json_payload().expect("serializes fine"),
1412
+ retry_policy: RetryPolicy {
1413
+ initial_interval: Some(prost_dur!(from_millis(50))),
1414
+ backoff_coefficient: 1.0,
1415
+ maximum_attempts: 5,
1416
+ ..Default::default()
1417
+ },
1418
+ ..Default::default()
1419
+ })
1420
+ .await;
1421
+ assert!(la_res.completed_ok());
1422
+ Ok(().into())
1423
+ },
1424
+ );
1425
+ let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1426
+ worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1427
+ // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
1428
+ let last_attempt = attempts.fetch_add(1, Ordering::Relaxed);
1429
+ if 0 == last_attempt {
1430
+ Err(ActivityError::Retryable {
1431
+ source: anyhow!("Explicit backoff error"),
1432
+ explicit_delay: Some(Duration::from_millis(300)),
1433
+ })
1434
+ } else if 2 == last_attempt {
1435
+ Ok(())
1436
+ } else {
1437
+ Err(anyhow!("Oh no I failed!").into())
1438
+ }
1439
+ });
1440
+ worker
1441
+ .submit_wf(
1442
+ wf_id.to_owned(),
1443
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
1444
+ vec![],
1445
+ WorkflowOptions::default(),
1446
+ )
1447
+ .await
1448
+ .unwrap();
1449
+ let start = Instant::now();
1450
+ worker.run_until_done().await.unwrap();
1451
+ let expected_attempts = 3;
1452
+ assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
1453
+ // There will be one 300ms backoff and one 50s backoff, so things should take at least that long
1454
+ assert!(start.elapsed() > Duration::from_millis(350));
1455
+ }
@@ -167,7 +167,7 @@ async fn new_queries(#[values(1, 3)] num_queries: usize) {
167
167
  let mut mock_client = mock_workflow_client();
168
168
  mock_client.expect_respond_legacy_query().times(0);
169
169
  let mut mh = MockPollCfg::from_resp_batches(wfid, t, tasks, mock_workflow_client());
170
- mh.completion_asserts = Some(Box::new(move |c| {
170
+ mh.completion_mock_fn = Some(Box::new(move |c| {
171
171
  // If the completion is the one ending the workflow, make sure it includes the query resps
172
172
  if c.commands[0].command_type() == CommandType::CompleteWorkflowExecution {
173
173
  assert_eq!(c.query_responses.len(), num_queries);
@@ -176,6 +176,7 @@ async fn new_queries(#[values(1, 3)] num_queries: usize) {
176
176
  } else {
177
177
  panic!("Unexpected command in response")
178
178
  }
179
+ Ok(Default::default())
179
180
  }));
180
181
  let mut mock = build_mock_pollers(mh);
181
182
  mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
@@ -1,14 +1,28 @@
1
- use crate::test_help::{build_mock_pollers, mock_worker, MockPollCfg, ResponseType};
1
+ use crate::{
2
+ test_help::{
3
+ build_mock_pollers, hist_to_poll_resp, mock_worker, MockPollCfg, PollWFTRespExt,
4
+ ResponseType,
5
+ },
6
+ worker::client::mocks::mock_workflow_client,
7
+ };
2
8
  use temporal_sdk_core_api::Worker;
3
9
  use temporal_sdk_core_protos::{
4
10
  coresdk::{
5
11
  workflow_activation::{workflow_activation_job, WorkflowActivationJob},
6
- workflow_commands::{update_response::Response, CompleteWorkflowExecution, UpdateResponse},
12
+ workflow_commands::{
13
+ update_response::Response, CompleteWorkflowExecution, ScheduleActivity, UpdateResponse,
14
+ },
7
15
  workflow_completion::WorkflowActivationCompletion,
8
16
  },
9
- temporal::api::{common::v1::Payload, enums::v1::EventType},
10
- TestHistoryBuilder,
17
+ temporal::api::{
18
+ common::v1::Payload,
19
+ enums::v1::EventType,
20
+ update::v1::{Acceptance, Rejection},
21
+ workflowservice::v1::RespondWorkflowTaskCompletedResponse,
22
+ },
23
+ TestHistoryBuilder, DEFAULT_ACTIVITY_TYPE,
11
24
  };
25
+ use temporal_sdk_core_test_utils::WorkerTestHelpers;
12
26
 
13
27
  #[tokio::test]
14
28
  async fn replay_with_empty_first_task() {
@@ -71,3 +85,147 @@ async fn replay_with_empty_first_task() {
71
85
  .await
72
86
  .unwrap();
73
87
  }
88
+
89
+ #[rstest::rstest]
90
+ #[tokio::test]
91
+ async fn initial_request_sent_back(#[values(false, true)] reject: bool) {
92
+ let wfid = "fakeid";
93
+ let mut t = TestHistoryBuilder::default();
94
+ t.add_by_type(EventType::WorkflowExecutionStarted);
95
+ t.add_workflow_task_scheduled_and_started();
96
+
97
+ let update_id = "upd-1";
98
+ let mut poll_resp = hist_to_poll_resp(&t, wfid, ResponseType::AllHistory);
99
+ let upd_req_body = poll_resp.add_update_request(update_id, 1);
100
+
101
+ let mut mock_client = mock_workflow_client();
102
+ mock_client
103
+ .expect_complete_workflow_task()
104
+ .times(1)
105
+ .returning(move |mut resp| {
106
+ let msg = resp.messages.pop().unwrap();
107
+ let orig_req = if reject {
108
+ let acceptance = msg.body.unwrap().unpack_as(Rejection::default()).unwrap();
109
+ acceptance.rejected_request.unwrap()
110
+ } else {
111
+ let acceptance = msg.body.unwrap().unpack_as(Acceptance::default()).unwrap();
112
+ acceptance.accepted_request.unwrap()
113
+ };
114
+ assert_eq!(orig_req, upd_req_body);
115
+ Ok(RespondWorkflowTaskCompletedResponse::default())
116
+ });
117
+ let mh = MockPollCfg::from_resp_batches(wfid, t, [poll_resp], mock_client);
118
+ let mut mock = build_mock_pollers(mh);
119
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
120
+ let core = mock_worker(mock);
121
+
122
+ let task = core.poll_workflow_activation().await.unwrap();
123
+ let resp = if reject {
124
+ Response::Rejected(Default::default())
125
+ } else {
126
+ Response::Accepted(())
127
+ };
128
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
129
+ task.run_id,
130
+ UpdateResponse {
131
+ protocol_instance_id: update_id.to_string(),
132
+ response: Some(resp),
133
+ }
134
+ .into(),
135
+ ))
136
+ .await
137
+ .unwrap();
138
+ }
139
+
140
+ #[tokio::test]
141
+ async fn speculative_wft_with_command_event() {
142
+ let wfid = "fakeid";
143
+ let mut t = TestHistoryBuilder::default();
144
+ t.add_by_type(EventType::WorkflowExecutionStarted);
145
+ t.add_full_wf_task();
146
+ t.add_activity_task_scheduled("act1");
147
+
148
+ let mut spec_task_hist = t.clone();
149
+ spec_task_hist.add_workflow_task_scheduled_and_started();
150
+
151
+ let mut real_hist = t.clone();
152
+ real_hist.add_we_signaled("hi", vec![]);
153
+ real_hist.add_workflow_task_scheduled_and_started();
154
+
155
+ let update_id = "upd-1";
156
+ let mut speculative_task = hist_to_poll_resp(&spec_task_hist, wfid, ResponseType::OneTask(2));
157
+ speculative_task.add_update_request(update_id, 1);
158
+ // Verify the speculative task contains the activity scheduled event
159
+ assert_eq!(
160
+ speculative_task.history.as_ref().unwrap().events[1].event_type,
161
+ EventType::ActivityTaskScheduled as i32
162
+ );
163
+
164
+ let mock_client = mock_workflow_client();
165
+ let mut mh = MockPollCfg::from_resp_batches(
166
+ wfid,
167
+ real_hist,
168
+ [
169
+ ResponseType::ToTaskNum(1),
170
+ speculative_task.into(),
171
+ ResponseType::OneTask(3),
172
+ ],
173
+ mock_client,
174
+ );
175
+ let mut completes = 0;
176
+ mh.completion_mock_fn = Some(Box::new(move |_| {
177
+ completes += 1;
178
+ let mut r = RespondWorkflowTaskCompletedResponse::default();
179
+ if completes == 2 {
180
+ // The second response (the update rejection) needs to indicate that the last started
181
+ // wft ID should be reset.
182
+ r.reset_history_event_id = 3;
183
+ }
184
+ Ok(r)
185
+ }));
186
+ let mut mock = build_mock_pollers(mh);
187
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
188
+ let core = mock_worker(mock);
189
+
190
+ let task = core.poll_workflow_activation().await.unwrap();
191
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
192
+ task.run_id,
193
+ ScheduleActivity {
194
+ activity_id: "act1".to_string(),
195
+ activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
196
+ ..Default::default()
197
+ }
198
+ .into(),
199
+ ))
200
+ .await
201
+ .unwrap();
202
+
203
+ // Receive the task containing and reject the update
204
+ let task = core.poll_workflow_activation().await.unwrap();
205
+ assert_matches!(
206
+ task.jobs.as_slice(),
207
+ [WorkflowActivationJob {
208
+ variant: Some(workflow_activation_job::Variant::DoUpdate(_)),
209
+ }]
210
+ );
211
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
212
+ task.run_id,
213
+ UpdateResponse {
214
+ protocol_instance_id: update_id.to_string(),
215
+ response: Some(Response::Rejected(Default::default())),
216
+ }
217
+ .into(),
218
+ ))
219
+ .await
220
+ .unwrap();
221
+
222
+ // Now we'll get another task with the "real" history containing the signal
223
+ let task = core.poll_workflow_activation().await.unwrap();
224
+ assert_matches!(
225
+ task.jobs.as_slice(),
226
+ [WorkflowActivationJob {
227
+ variant: Some(workflow_activation_job::Variant::SignalWorkflow(_)),
228
+ }]
229
+ );
230
+ core.complete_execution(&task.run_id).await;
231
+ }
@@ -1,9 +1,10 @@
1
1
  use crate::{
2
2
  prost_dur,
3
3
  test_help::{
4
- build_fake_worker, build_mock_pollers, canned_histories, mock_worker, MockPollCfg,
5
- MockWorkerInputs, MocksHolder, ResponseType, WorkerExt,
4
+ build_fake_worker, build_mock_pollers, canned_histories, mock_worker, test_worker_cfg,
5
+ MockPollCfg, MockWorkerInputs, MocksHolder, ResponseType, WorkerExt,
6
6
  },
7
+ worker,
7
8
  worker::client::mocks::mock_workflow_client,
8
9
  PollActivityError, PollWfError,
9
10
  };
@@ -259,3 +260,38 @@ async fn worker_does_not_panic_on_retry_exhaustion_of_nonfatal_net_err() {
259
260
  Some(workflow_activation_job::Variant::RemoveFromCache(_))
260
261
  );
261
262
  }
263
+
264
+ #[rstest::rstest]
265
+ #[tokio::test]
266
+ async fn worker_can_shutdown_after_never_polling_ok(#[values(true, false)] poll_workflow: bool) {
267
+ let mut mock = mock_workflow_client();
268
+ mock.expect_poll_activity_task()
269
+ .returning(|_, _| Err(tonic::Status::permission_denied("you shall not pass")));
270
+ if poll_workflow {
271
+ mock.expect_poll_workflow_task()
272
+ .returning(|_| Err(tonic::Status::permission_denied("you shall not pass")));
273
+ }
274
+ let core = worker::Worker::new_test(
275
+ test_worker_cfg()
276
+ .max_concurrent_at_polls(1_usize)
277
+ .build()
278
+ .unwrap(),
279
+ mock,
280
+ );
281
+
282
+ loop {
283
+ // Must continue polling until polls return shutdown.
284
+ if poll_workflow {
285
+ let res = core.poll_workflow_activation().await.unwrap_err();
286
+ if !matches!(res, PollWfError::ShutDown) {
287
+ continue;
288
+ }
289
+ }
290
+ let res = core.poll_activity_task().await.unwrap_err();
291
+ if !matches!(res, PollActivityError::ShutDown) {
292
+ continue;
293
+ }
294
+ core.finalize_shutdown().await;
295
+ break;
296
+ }
297
+ }