@temporalio/core-bridge 1.11.2 → 1.11.4

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 (119) hide show
  1. package/Cargo.lock +396 -489
  2. package/Cargo.toml +3 -2
  3. package/lib/errors.d.ts +2 -0
  4. package/lib/errors.js +7 -3
  5. package/lib/errors.js.map +1 -1
  6. package/lib/index.d.ts +8 -2
  7. package/lib/index.js.map +1 -1
  8. package/lib/worker-tuner.d.ts +111 -1
  9. package/package.json +3 -3
  10. package/releases/aarch64-apple-darwin/index.node +0 -0
  11. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  12. package/releases/x86_64-apple-darwin/index.node +0 -0
  13. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  14. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  15. package/sdk-core/.github/workflows/per-pr.yml +3 -3
  16. package/sdk-core/Cargo.toml +0 -1
  17. package/sdk-core/client/Cargo.toml +1 -2
  18. package/sdk-core/client/src/lib.rs +21 -13
  19. package/sdk-core/client/src/metrics.rs +1 -1
  20. package/sdk-core/client/src/raw.rs +46 -1
  21. package/sdk-core/core/Cargo.toml +7 -7
  22. package/sdk-core/core/benches/workflow_replay.rs +1 -1
  23. package/sdk-core/core/src/abstractions/take_cell.rs +1 -1
  24. package/sdk-core/core/src/abstractions.rs +98 -10
  25. package/sdk-core/core/src/core_tests/activity_tasks.rs +8 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +1 -1
  27. package/sdk-core/core/src/core_tests/mod.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +104 -9
  29. package/sdk-core/core/src/core_tests/workers.rs +72 -3
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +6 -7
  31. package/sdk-core/core/src/debug_client.rs +78 -0
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -5
  33. package/sdk-core/core/src/lib.rs +30 -4
  34. package/sdk-core/core/src/pollers/mod.rs +1 -1
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +7 -7
  36. package/sdk-core/core/src/replay/mod.rs +4 -4
  37. package/sdk-core/core/src/telemetry/log_export.rs +2 -2
  38. package/sdk-core/core/src/telemetry/metrics.rs +69 -1
  39. package/sdk-core/core/src/telemetry/otel.rs +2 -2
  40. package/sdk-core/core/src/test_help/mod.rs +3 -3
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +3 -3
  42. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +1 -1
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +68 -24
  44. package/sdk-core/core/src/worker/activities.rs +26 -15
  45. package/sdk-core/core/src/worker/client/mocks.rs +10 -4
  46. package/sdk-core/core/src/worker/client.rs +17 -0
  47. package/sdk-core/core/src/worker/mod.rs +71 -13
  48. package/sdk-core/core/src/worker/slot_provider.rs +5 -7
  49. package/sdk-core/core/src/worker/tuner/fixed_size.rs +4 -3
  50. package/sdk-core/core/src/worker/tuner/resource_based.rs +171 -32
  51. package/sdk-core/core/src/worker/tuner.rs +18 -6
  52. package/sdk-core/core/src/worker/workflow/history_update.rs +43 -13
  53. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +6 -6
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -5
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +3 -3
  56. package/sdk-core/core/src/worker/workflow/mod.rs +13 -7
  57. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +7 -7
  58. package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
  59. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +11 -11
  60. package/sdk-core/core-api/Cargo.toml +1 -0
  61. package/sdk-core/core-api/src/worker.rs +84 -30
  62. package/sdk-core/sdk/Cargo.toml +1 -2
  63. package/sdk-core/sdk/src/lib.rs +1 -1
  64. package/sdk-core/sdk/src/workflow_context.rs +9 -8
  65. package/sdk-core/sdk/src/workflow_future.rs +19 -14
  66. package/sdk-core/sdk-core-protos/Cargo.toml +2 -0
  67. package/sdk-core/sdk-core-protos/build.rs +6 -1
  68. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +1 -0
  69. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +3207 -158
  70. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +2934 -118
  71. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +67 -0
  72. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +47 -1
  73. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -7
  74. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  75. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +5 -3
  76. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +3 -3
  77. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +14 -13
  78. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -3
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +22 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +13 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +26 -6
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +5 -0
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -0
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +46 -12
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +18 -19
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +27 -0
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +192 -19
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +279 -12
  89. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -1
  90. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -1
  91. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -1
  92. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +1 -1
  93. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/core_interface.proto +17 -1
  94. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -1
  95. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +1 -1
  96. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -1
  97. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +1 -1
  98. package/sdk-core/sdk-core-protos/src/lib.rs +30 -6
  99. package/sdk-core/test-utils/Cargo.toml +1 -2
  100. package/sdk-core/test-utils/src/lib.rs +2 -2
  101. package/sdk-core/tests/heavy_tests.rs +1 -1
  102. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +2 -2
  103. package/sdk-core/tests/integ_tests/metrics_tests.rs +144 -7
  104. package/sdk-core/tests/integ_tests/queries_tests.rs +1 -1
  105. package/sdk-core/tests/integ_tests/update_tests.rs +109 -5
  106. package/sdk-core/tests/integ_tests/worker_tests.rs +44 -8
  107. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +1 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -2
  111. package/src/conversions/slot_supplier_bridge.rs +287 -0
  112. package/src/conversions.rs +23 -15
  113. package/src/helpers.rs +35 -1
  114. package/src/runtime.rs +7 -3
  115. package/src/worker.rs +1 -1
  116. package/ts/errors.ts +9 -2
  117. package/ts/index.ts +19 -4
  118. package/ts/worker-tuner.ts +123 -1
  119. package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +0 -3
@@ -1,7 +1,7 @@
1
1
  syntax = "proto3";
2
2
 
3
3
  package coresdk;
4
- option ruby_package = "Temporalio::Bridge::Api::CoreInterface";
4
+ option ruby_package = "Temporalio::Internal::Bridge::Api::CoreInterface";
5
5
 
6
6
  // Note: Intellij will think the Google imports don't work because of the slightly odd nature of
7
7
  // the include paths. You can make it work by going to the "Protobuf Support" settings section
@@ -29,3 +29,19 @@ message ActivityTaskCompletion {
29
29
  bytes task_token = 1;
30
30
  activity_result.ActivityExecutionResult result = 2;
31
31
  }
32
+
33
+ // Info about workflow task slot usage
34
+ message WorkflowSlotInfo {
35
+ string workflow_type = 1;
36
+ bool is_sticky = 2;
37
+ }
38
+
39
+ // Info about activity task slot usage
40
+ message ActivitySlotInfo {
41
+ string activity_type = 1;
42
+ }
43
+
44
+ // Info about local activity slot usage
45
+ message LocalActivitySlotInfo {
46
+ string activity_type = 1;
47
+ }
@@ -1,7 +1,7 @@
1
1
  syntax = "proto3";
2
2
 
3
3
  package coresdk.external_data;
4
- option ruby_package = "Temporalio::Bridge::Api::ExternalData";
4
+ option ruby_package = "Temporalio::Internal::Bridge::Api::ExternalData";
5
5
 
6
6
  import "google/protobuf/duration.proto";
7
7
  import "google/protobuf/timestamp.proto";
@@ -5,7 +5,7 @@ syntax = "proto3";
5
5
  * lang SDK applies these activation jobs to drive workflows.
6
6
  */
7
7
  package coresdk.workflow_activation;
8
- option ruby_package = "Temporalio::Bridge::Api::WorkflowActivation";
8
+ option ruby_package = "Temporalio::Internal::Bridge::Api::WorkflowActivation";
9
9
 
10
10
  import "google/protobuf/timestamp.proto";
11
11
  import "google/protobuf/duration.proto";
@@ -6,7 +6,7 @@ syntax = "proto3";
6
6
  * activation.
7
7
  */
8
8
  package coresdk.workflow_commands;
9
- option ruby_package = "Temporalio::Bridge::Api::WorkflowCommands";
9
+ option ruby_package = "Temporalio::Internal::Bridge::Api::WorkflowCommands";
10
10
 
11
11
  import "google/protobuf/duration.proto";
12
12
  import "google/protobuf/timestamp.proto";
@@ -1,7 +1,7 @@
1
1
  syntax = "proto3";
2
2
 
3
3
  package coresdk.workflow_completion;
4
- option ruby_package = "Temporalio::Bridge::Api::WorkflowCompletion";
4
+ option ruby_package = "Temporalio::Internal::Bridge::Api::WorkflowCompletion";
5
5
 
6
6
  import "temporal/api/failure/v1/message.proto";
7
7
  import "temporal/api/enums/v1/failed_cause.proto";
@@ -111,6 +111,7 @@ pub mod coresdk {
111
111
  temporal::api::enums::v1::TimeoutType,
112
112
  };
113
113
  use activity_execution_result as aer;
114
+ use anyhow::anyhow;
114
115
  use std::fmt::{Display, Formatter};
115
116
 
116
117
  impl ActivityExecutionResult {
@@ -209,13 +210,23 @@ pub mod coresdk {
209
210
  }
210
211
 
211
212
  impl ActivityResolution {
212
- pub fn unwrap_ok_payload(self) -> Payload {
213
- match self.status.unwrap() {
214
- activity_resolution::Status::Completed(c) => c.result.unwrap(),
215
- e => panic!("Activity was not successful: {e:?}"),
213
+ /// Extract an activity's payload if it completed successfully, or return an error for all
214
+ /// other outcomes.
215
+ pub fn success_payload_or_error(self) -> Result<Option<Payload>, anyhow::Error> {
216
+ let Some(status) = self.status else {
217
+ return Err(anyhow!("Activity completed without a status"));
218
+ };
219
+
220
+ match status {
221
+ activity_resolution::Status::Completed(success) => Ok(success.result),
222
+ e => Err(anyhow!("Activity was not successful: {e:?}")),
216
223
  }
217
224
  }
218
225
 
226
+ pub fn unwrap_ok_payload(self) -> Payload {
227
+ self.success_payload_or_error().unwrap().unwrap()
228
+ }
229
+
219
230
  pub fn completed_ok(&self) -> bool {
220
231
  matches!(self.status, Some(activity_resolution::Status::Completed(_)))
221
232
  }
@@ -1447,6 +1458,11 @@ pub mod coresdk {
1447
1458
  // This is disgusting, but unclear to me how to avoid it. TODO: Discuss w/ prost maintainer
1448
1459
  pub mod temporal {
1449
1460
  pub mod api {
1461
+ pub mod activity {
1462
+ pub mod v1 {
1463
+ tonic::include_proto!("temporal.api.activity.v1");
1464
+ }
1465
+ }
1450
1466
  pub mod batch {
1451
1467
  pub mod v1 {
1452
1468
  tonic::include_proto!("temporal.api.batch.v1");
@@ -1754,9 +1770,11 @@ pub mod temporal {
1754
1770
  }
1755
1771
  }
1756
1772
 
1757
- impl Display for Payload {
1773
+ impl std::fmt::Debug for Payload {
1758
1774
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1759
- if self.data.len() > 64 {
1775
+ if std::env::var("TEMPORAL_PRINT_FULL_PAYLOADS").is_err()
1776
+ && self.data.len() > 64
1777
+ {
1760
1778
  let mut windows = self.data.as_slice().windows(32);
1761
1779
  write!(
1762
1780
  f,
@@ -1770,6 +1788,12 @@ pub mod temporal {
1770
1788
  }
1771
1789
  }
1772
1790
 
1791
+ impl Display for Payload {
1792
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1793
+ write!(f, "{:?}", self)
1794
+ }
1795
+ }
1796
+
1773
1797
  impl Display for Header {
1774
1798
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1775
1799
  write!(f, "Header(")?;
@@ -18,9 +18,8 @@ anyhow = "1.0"
18
18
  async-trait = "0.1"
19
19
  base64 = "0.22"
20
20
  bytes = "1.3"
21
- futures = "0.3"
21
+ futures-util = { version = "0.3", default-features = false }
22
22
  log = "0.4"
23
- once_cell = { workspace = true }
24
23
  parking_lot = "0.12"
25
24
  prost = { workspace = true }
26
25
  prost-types = { workspace = true }
@@ -13,7 +13,7 @@ pub use temporal_sdk_core::replay::HistoryForReplay;
13
13
  use crate::stream::{Stream, TryStreamExt};
14
14
  use anyhow::{Context, Error};
15
15
  use base64::{prelude::BASE64_STANDARD, Engine};
16
- use futures::{future, stream, stream::FuturesUnordered, StreamExt};
16
+ use futures_util::{future, stream, stream::FuturesUnordered, StreamExt};
17
17
  use parking_lot::Mutex;
18
18
  use prost::Message;
19
19
  use rand::{distributions::Standard, Rng};
@@ -154,7 +154,7 @@ pub async fn history_from_proto_binary(path_from_root: &str) -> Result<History,
154
154
  Ok(History::decode(&*bytes)?)
155
155
  }
156
156
 
157
- static INTEG_TESTS_RT: once_cell::sync::OnceCell<CoreRuntime> = once_cell::sync::OnceCell::new();
157
+ static INTEG_TESTS_RT: std::sync::OnceLock<CoreRuntime> = std::sync::OnceLock::new();
158
158
  pub fn init_integ_telem() -> &'static CoreRuntime {
159
159
  INTEG_TESTS_RT.get_or_init(|| {
160
160
  let telemetry_options = get_integ_telem_options();
@@ -1,4 +1,4 @@
1
- use futures::{future::join_all, sink, stream::FuturesUnordered, StreamExt};
1
+ use futures_util::{future::join_all, sink, stream::FuturesUnordered, StreamExt};
2
2
  use std::{
3
3
  sync::Arc,
4
4
  time::{Duration, Instant},
@@ -1,5 +1,5 @@
1
- use futures::stream;
2
- use futures::TryStreamExt;
1
+ use futures_util::stream;
2
+ use futures_util::TryStreamExt;
3
3
  use std::time::{SystemTime, UNIX_EPOCH};
4
4
  use temporal_client::{ClientOptionsBuilder, TestService, WorkflowService};
5
5
  use temporal_sdk_core::ephemeral_server::{
@@ -1,10 +1,14 @@
1
+ use anyhow::anyhow;
1
2
  use assert_matches::assert_matches;
2
3
  use std::{env, net::SocketAddr, sync::Arc, time::Duration};
3
4
  use temporal_client::{WorkflowClientTrait, WorkflowOptions, WorkflowService};
5
+ use temporal_sdk::{
6
+ ActContext, ActivityError, ActivityOptions, CancellableFuture, LocalActivityOptions, WfContext,
7
+ };
4
8
  use temporal_sdk_core::{
5
9
  init_worker,
6
10
  telemetry::{build_otlp_metric_exporter, start_prometheus_metric_exporter},
7
- CoreRuntime,
11
+ CoreRuntime, TokioRuntimeBuilder,
8
12
  };
9
13
  use temporal_sdk_core_api::{
10
14
  telemetry::{
@@ -25,9 +29,10 @@ use temporal_sdk_core_protos::{
25
29
  ScheduleActivity, ScheduleLocalActivity,
26
30
  },
27
31
  workflow_completion::WorkflowActivationCompletion,
28
- ActivityTaskCompletion,
32
+ ActivityTaskCompletion, AsJsonPayloadExt,
29
33
  },
30
34
  temporal::api::{
35
+ common::v1::RetryPolicy,
31
36
  enums::v1::WorkflowIdReusePolicy,
32
37
  failure::v1::Failure,
33
38
  query::v1::WorkflowQuery,
@@ -491,11 +496,8 @@ async fn query_of_closed_workflow_doesnt_tick_terminal_metric(
491
496
 
492
497
  #[test]
493
498
  fn runtime_new() {
494
- let mut rt = CoreRuntime::new(
495
- get_integ_telem_options(),
496
- tokio::runtime::Builder::new_multi_thread(),
497
- )
498
- .unwrap();
499
+ let mut rt =
500
+ CoreRuntime::new(get_integ_telem_options(), TokioRuntimeBuilder::default()).unwrap();
499
501
  let handle = rt.tokio_handle();
500
502
  let _rt = handle.enter();
501
503
  let (telemopts, addr, _aborter) = prom_metrics(false, false);
@@ -620,3 +622,138 @@ async fn request_fail_codes_otel() {
620
622
  tokio::time::sleep(Duration::from_secs(1)).await;
621
623
  }
622
624
  }
625
+
626
+ #[tokio::test]
627
+ async fn activity_metrics() {
628
+ let (telemopts, addr, _aborter) = prom_metrics(false, false);
629
+ let rt = CoreRuntime::new_assume_tokio(telemopts).unwrap();
630
+ let wf_name = "activity_metrics";
631
+ let mut starter = CoreWfStarter::new_with_runtime(wf_name, rt);
632
+ let task_queue = starter.get_task_queue().to_owned();
633
+ let mut worker = starter.worker().await;
634
+
635
+ worker.register_wf(wf_name.to_string(), |ctx: WfContext| async move {
636
+ let normal_act_pass = ctx.activity(ActivityOptions {
637
+ activity_type: "pass_fail_act".to_string(),
638
+ input: "pass".as_json_payload().expect("serializes fine"),
639
+ start_to_close_timeout: Some(Duration::from_secs(1)),
640
+ ..Default::default()
641
+ });
642
+ let local_act_pass = ctx.local_activity(LocalActivityOptions {
643
+ activity_type: "pass_fail_act".to_string(),
644
+ input: "pass".as_json_payload().expect("serializes fine"),
645
+ ..Default::default()
646
+ });
647
+ let normal_act_fail = ctx.activity(ActivityOptions {
648
+ activity_type: "pass_fail_act".to_string(),
649
+ input: "fail".as_json_payload().expect("serializes fine"),
650
+ start_to_close_timeout: Some(Duration::from_secs(1)),
651
+ retry_policy: Some(RetryPolicy {
652
+ maximum_attempts: 1,
653
+ ..Default::default()
654
+ }),
655
+ ..Default::default()
656
+ });
657
+ let local_act_fail = ctx.local_activity(LocalActivityOptions {
658
+ activity_type: "pass_fail_act".to_string(),
659
+ input: "fail".as_json_payload().expect("serializes fine"),
660
+ retry_policy: RetryPolicy {
661
+ maximum_attempts: 1,
662
+ ..Default::default()
663
+ },
664
+ ..Default::default()
665
+ });
666
+ let local_act_cancel = ctx.local_activity(LocalActivityOptions {
667
+ activity_type: "pass_fail_act".to_string(),
668
+ input: "cancel".as_json_payload().expect("serializes fine"),
669
+ retry_policy: RetryPolicy {
670
+ maximum_attempts: 1,
671
+ ..Default::default()
672
+ },
673
+ ..Default::default()
674
+ });
675
+ join!(
676
+ normal_act_pass,
677
+ local_act_pass,
678
+ normal_act_fail,
679
+ local_act_fail
680
+ );
681
+ local_act_cancel.cancel(&ctx);
682
+ local_act_cancel.await;
683
+ Ok(().into())
684
+ });
685
+ worker.register_activity("pass_fail_act", |ctx: ActContext, i: String| async move {
686
+ match i.as_str() {
687
+ "pass" => Ok("pass"),
688
+ "cancel" => {
689
+ // TODO: Cancel is taking until shutdown to come through :|
690
+ ctx.cancelled().await;
691
+ Err(ActivityError::cancelled())
692
+ }
693
+ _ => Err(anyhow!("fail").into()),
694
+ }
695
+ });
696
+
697
+ worker
698
+ .submit_wf(
699
+ wf_name.to_owned(),
700
+ wf_name.to_owned(),
701
+ vec![],
702
+ WorkflowOptions::default(),
703
+ )
704
+ .await
705
+ .unwrap();
706
+ worker.run_until_done().await.unwrap();
707
+
708
+ let body = get_text(format!("http://{addr}/metrics")).await;
709
+ assert!(body.contains(&format!(
710
+ "temporal_activity_execution_failed{{activity_type=\"pass_fail_act\",\
711
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
712
+ task_queue=\"{task_queue}\",workflow_type=\"{wf_name}\"}} 1"
713
+ )));
714
+ assert!(body.contains(&format!(
715
+ "temporal_activity_schedule_to_start_latency_count{{\
716
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
717
+ task_queue=\"{task_queue}\"}} 2"
718
+ )));
719
+ assert!(body.contains(&format!(
720
+ "temporal_activity_execution_latency_count{{activity_type=\"pass_fail_act\",\
721
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
722
+ task_queue=\"{task_queue}\",workflow_type=\"{wf_name}\"}} 2"
723
+ )));
724
+ assert!(body.contains(&format!(
725
+ "temporal_activity_succeed_endtoend_latency_count{{activity_type=\"pass_fail_act\",\
726
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
727
+ task_queue=\"{task_queue}\",workflow_type=\"{wf_name}\"}} 1"
728
+ )));
729
+
730
+ assert!(body.contains(&format!(
731
+ "temporal_local_activity_total{{activity_type=\"pass_fail_act\",namespace=\"{NAMESPACE}\",\
732
+ service_name=\"temporal-core-sdk\",task_queue=\"{task_queue}\",\
733
+ workflow_type=\"{wf_name}\"}} 3"
734
+ )));
735
+ assert!(body.contains(&format!(
736
+ "temporal_local_activity_execution_failed{{activity_type=\"pass_fail_act\",\
737
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
738
+ task_queue=\"{task_queue}\",\
739
+ workflow_type=\"{wf_name}\"}} 1"
740
+ )));
741
+ assert!(body.contains(&format!(
742
+ "temporal_local_activity_execution_cancelled{{activity_type=\"pass_fail_act\",\
743
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
744
+ task_queue=\"{task_queue}\",\
745
+ workflow_type=\"{wf_name}\"}} 1"
746
+ )));
747
+ assert!(body.contains(&format!(
748
+ "temporal_local_activity_execution_latency_count{{activity_type=\"pass_fail_act\",\
749
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
750
+ task_queue=\"{task_queue}\",\
751
+ workflow_type=\"{wf_name}\"}} 3"
752
+ )));
753
+ assert!(body.contains(&format!(
754
+ "temporal_local_activity_succeed_endtoend_latency_count{{activity_type=\"pass_fail_act\",\
755
+ namespace=\"{NAMESPACE}\",service_name=\"temporal-core-sdk\",\
756
+ task_queue=\"{task_queue}\",\
757
+ workflow_type=\"{wf_name}\"}} 1"
758
+ )));
759
+ }
@@ -1,6 +1,6 @@
1
1
  use assert_matches::assert_matches;
2
- use futures::{prelude::stream::FuturesUnordered, FutureExt, StreamExt};
3
2
  use futures_util::future::join_all;
3
+ use futures_util::{stream::FuturesUnordered, FutureExt, StreamExt};
4
4
  use std::time::{Duration, Instant};
5
5
  use temporal_client::WorkflowClientTrait;
6
6
  use temporal_sdk_core_protos::{
@@ -1,11 +1,10 @@
1
1
  use anyhow::anyhow;
2
2
  use assert_matches::assert_matches;
3
3
  use futures_util::{future, future::join_all, StreamExt};
4
- use once_cell::sync::Lazy;
5
4
  use std::{
6
5
  sync::{
7
6
  atomic::{AtomicBool, AtomicUsize, Ordering},
8
- Arc,
7
+ Arc, LazyLock,
9
8
  },
10
9
  time::Duration,
11
10
  };
@@ -78,7 +77,13 @@ async fn update_workflow(#[values(FailUpdate::Yes, FailUpdate::No)] will_fail: F
78
77
  .unwrap();
79
78
  let with_id = HistoryForReplay::new(history, workflow_id.to_string());
80
79
  let replay_worker = init_core_replay_preloaded(workflow_id, [with_id]);
81
- handle_update(will_fail, CompleteWorkflow::Yes, replay_worker.as_ref(), 1).await;
80
+ // Init workflow comes by itself
81
+ let act = replay_worker.poll_workflow_activation().await.unwrap();
82
+ replay_worker
83
+ .complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
84
+ .await
85
+ .unwrap();
86
+ handle_update(will_fail, CompleteWorkflow::Yes, replay_worker.as_ref(), 0).await;
82
87
  }
83
88
 
84
89
  #[tokio::test]
@@ -147,6 +152,12 @@ async fn reapplied_updates_due_to_reset() {
147
152
  let with_id = HistoryForReplay::new(history, workflow_id.to_string());
148
153
 
149
154
  let replay_worker = init_core_replay_preloaded(workflow_id, [with_id]);
155
+ // Init workflow comes by itself
156
+ let act = replay_worker.poll_workflow_activation().await.unwrap();
157
+ replay_worker
158
+ .complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
159
+ .await
160
+ .unwrap();
150
161
  // We now recapitulate the actions that the worker took on first execution above, pretending
151
162
  // that we always followed the post-reset history.
152
163
  // First, we handled the post-reset reapplied update and did not complete the workflow.
@@ -154,9 +165,15 @@ async fn reapplied_updates_due_to_reset() {
154
165
  FailUpdate::No,
155
166
  CompleteWorkflow::No,
156
167
  replay_worker.as_ref(),
157
- 2,
168
+ 1,
158
169
  )
159
170
  .await;
171
+ // Then the timer fires
172
+ let act = replay_worker.poll_workflow_activation().await.unwrap();
173
+ replay_worker
174
+ .complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
175
+ .await
176
+ .unwrap();
160
177
  // Then the client sent a second update; we handled it and completed the workflow.
161
178
  handle_update(
162
179
  FailUpdate::No,
@@ -957,7 +974,7 @@ async fn worker_restarted_in_middle_of_update() {
957
974
  let mut worker = starter.worker().await;
958
975
  let client = starter.get_client().await;
959
976
 
960
- static BARR: Lazy<Barrier> = Lazy::new(|| Barrier::new(2));
977
+ static BARR: LazyLock<Barrier> = LazyLock::new(|| Barrier::new(2));
961
978
  static ACT_RAN: AtomicBool = AtomicBool::new(false);
962
979
  worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
963
980
  ctx.update_handler(
@@ -1044,3 +1061,90 @@ async fn worker_restarted_in_middle_of_update() {
1044
1061
  .await
1045
1062
  .unwrap();
1046
1063
  }
1064
+
1065
+ #[tokio::test]
1066
+ async fn update_after_empty_wft() {
1067
+ let wf_name = "update_after_empty_wft";
1068
+ let mut starter = CoreWfStarter::new(wf_name);
1069
+ let mut worker = starter.worker().await;
1070
+ let client = starter.get_client().await;
1071
+
1072
+ static ACT_STARTED: AtomicBool = AtomicBool::new(false);
1073
+ worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
1074
+ ctx.update_handler(
1075
+ "update",
1076
+ |_: &_, _: ()| Ok(()),
1077
+ move |ctx: UpdateContext, _: ()| async move {
1078
+ if ACT_STARTED.load(Ordering::Acquire) {
1079
+ return Ok(());
1080
+ }
1081
+ ctx.wf_ctx
1082
+ .activity(ActivityOptions {
1083
+ activity_type: "echo".to_string(),
1084
+ input: "hi!".as_json_payload().expect("serializes fine"),
1085
+ start_to_close_timeout: Some(Duration::from_secs(2)),
1086
+ ..Default::default()
1087
+ })
1088
+ .await;
1089
+ Ok(())
1090
+ },
1091
+ );
1092
+ let mut sig = ctx.make_signal_channel("signal");
1093
+ let sig_handle = async {
1094
+ sig.next().await;
1095
+ ACT_STARTED.store(true, Ordering::Release);
1096
+ ctx.activity(ActivityOptions {
1097
+ activity_type: "echo".to_string(),
1098
+ input: "hi!".as_json_payload().expect("serializes fine"),
1099
+ start_to_close_timeout: Some(Duration::from_secs(2)),
1100
+ ..Default::default()
1101
+ })
1102
+ .await;
1103
+ ACT_STARTED.store(false, Ordering::Release);
1104
+ };
1105
+ join!(sig_handle, async {
1106
+ ctx.timer(Duration::from_secs(2)).await;
1107
+ });
1108
+ Ok(().into())
1109
+ });
1110
+ worker.register_activity("echo", |_ctx: ActContext, echo_me: String| async move {
1111
+ Ok(echo_me)
1112
+ });
1113
+
1114
+ let run_id = starter.start_with_worker(wf_name, &mut worker).await;
1115
+ let wf_id = starter.get_task_queue().to_string();
1116
+ let update = async {
1117
+ client
1118
+ .signal_workflow_execution(
1119
+ wf_id.clone(),
1120
+ "".to_string(),
1121
+ "signal".to_string(),
1122
+ None,
1123
+ None,
1124
+ )
1125
+ .await
1126
+ .unwrap();
1127
+ tokio::time::sleep(Duration::from_millis(500)).await;
1128
+ let res = client
1129
+ .update_workflow_execution(
1130
+ wf_id.clone(),
1131
+ "".to_string(),
1132
+ "update".to_string(),
1133
+ WaitPolicy {
1134
+ lifecycle_stage: UpdateWorkflowExecutionLifecycleStage::Completed as i32,
1135
+ },
1136
+ [().as_json_payload().unwrap()].into_payloads(),
1137
+ )
1138
+ .await
1139
+ .unwrap();
1140
+ assert!(res.outcome.unwrap().is_success());
1141
+ };
1142
+ let runner = async {
1143
+ worker.run_until_done().await.unwrap();
1144
+ };
1145
+ join!(update, runner);
1146
+ starter
1147
+ .fetch_history_and_replay(wf_id, run_id, worker.inner_mut())
1148
+ .await
1149
+ .unwrap();
1150
+ }
@@ -1,15 +1,15 @@
1
- use std::cell::Cell;
2
- use std::sync::Arc;
3
-
4
1
  use assert_matches::assert_matches;
2
+ use std::{cell::Cell, sync::Arc, time::Duration};
5
3
  use temporal_client::WorkflowOptions;
6
- use temporal_sdk::interceptors::WorkerInterceptor;
7
- use temporal_sdk_core::{init_worker, CoreRuntime};
4
+ use temporal_sdk::{interceptors::WorkerInterceptor, WfContext};
5
+ use temporal_sdk_core::{init_worker, CoreRuntime, ResourceBasedTuner, ResourceSlotOptions};
8
6
  use temporal_sdk_core_api::{errors::WorkerValidationError, worker::WorkerConfigBuilder, Worker};
9
- use temporal_sdk_core_protos::coresdk::workflow_completion::{
10
- workflow_activation_completion::Status, Failure, WorkflowActivationCompletion,
7
+ use temporal_sdk_core_protos::{
8
+ coresdk::workflow_completion::{
9
+ workflow_activation_completion::Status, Failure, WorkflowActivationCompletion,
10
+ },
11
+ temporal::api::failure::v1::Failure as InnerFailure,
11
12
  };
12
- use temporal_sdk_core_protos::temporal::api::failure::v1::Failure as InnerFailure;
13
13
  use temporal_sdk_core_test_utils::{
14
14
  drain_pollers_and_shutdown, get_integ_server_options, get_integ_telem_options, CoreWfStarter,
15
15
  };
@@ -111,3 +111,39 @@ async fn worker_handles_unknown_workflow_types_gracefully() {
111
111
  drain_pollers_and_shutdown(&worker).await;
112
112
  });
113
113
  }
114
+
115
+ #[tokio::test]
116
+ async fn resource_based_few_pollers_guarantees_non_sticky_poll() {
117
+ let wf_name = "resource_based_few_pollers_guarantees_non_sticky_poll";
118
+ let mut starter = CoreWfStarter::new(wf_name);
119
+ starter
120
+ .worker_config
121
+ .clear_max_outstanding_opts()
122
+ .no_remote_activities(true)
123
+ // 3 pollers so the minimum slots of 2 can both be handed out to a sticky poller
124
+ .max_concurrent_wft_polls(3_usize);
125
+ // Set the limits to zero so it's essentially unwilling to hand out slots
126
+ let mut tuner = ResourceBasedTuner::new(0.0, 0.0);
127
+ tuner.with_workflow_slots_options(ResourceSlotOptions::new(2, 10, Duration::from_millis(0)));
128
+ starter.worker_config.tuner(Arc::new(tuner));
129
+ let mut worker = starter.worker().await;
130
+
131
+ // Workflow doesn't actually need to do anything. We just need to see that we don't get stuck
132
+ // by assigning all slots to sticky pollers.
133
+ worker.register_wf(
134
+ wf_name.to_owned(),
135
+ |_: WfContext| async move { Ok(().into()) },
136
+ );
137
+ for i in 0..20 {
138
+ worker
139
+ .submit_wf(
140
+ format!("{wf_name}_{i}"),
141
+ wf_name.to_owned(),
142
+ vec![],
143
+ WorkflowOptions::default(),
144
+ )
145
+ .await
146
+ .unwrap();
147
+ }
148
+ worker.run_until_done().await.unwrap();
149
+ }
@@ -1,6 +1,6 @@
1
1
  use crate::integ_tests::activity_functions::echo;
2
2
  use anyhow::anyhow;
3
- use futures::future::join_all;
3
+ use futures_util::future::join_all;
4
4
  use std::{
5
5
  sync::atomic::{AtomicU8, Ordering},
6
6
  time::Duration,
@@ -1,5 +1,5 @@
1
1
  use crate::integ_tests::activity_functions::echo;
2
- use futures::StreamExt;
2
+ use futures_util::StreamExt;
3
3
  use std::{
4
4
  sync::{
5
5
  atomic::{AtomicBool, AtomicU64, Ordering},
@@ -1,6 +1,6 @@
1
1
  use std::collections::HashMap;
2
2
 
3
- use futures::StreamExt;
3
+ use futures_util::StreamExt;
4
4
  use temporal_client::{SignalWithStartOptions, WorkflowClientTrait, WorkflowOptions};
5
5
  use temporal_sdk::{
6
6
  ChildWorkflowOptions, Signal, SignalWorkflowOptions, WfContext, WorkflowResult,
@@ -18,7 +18,8 @@ mod upsert_search_attrs;
18
18
 
19
19
  use crate::integ_tests::{activity_functions::echo, metrics_tests};
20
20
  use assert_matches::assert_matches;
21
- use futures::{channel::mpsc::UnboundedReceiver, future, SinkExt, StreamExt};
21
+ use futures_channel::mpsc::UnboundedReceiver;
22
+ use futures_util::{future, SinkExt, StreamExt};
22
23
  use std::{
23
24
  collections::{HashMap, HashSet},
24
25
  sync::{
@@ -92,7 +93,7 @@ async fn parallel_workflows_same_queue() {
92
93
  let handles: Vec<_> = run_ids
93
94
  .iter()
94
95
  .map(|run_id| {
95
- let (tx, rx) = futures::channel::mpsc::unbounded();
96
+ let (tx, rx) = futures_channel::mpsc::unbounded();
96
97
  send_chans.insert(run_id.clone(), tx);
97
98
  tokio::spawn(wf_task(core.clone(), rx))
98
99
  })