@temporalio/core-bridge 1.12.1 → 1.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/Cargo.lock +64 -119
  2. package/Cargo.toml +1 -1
  3. package/package.json +3 -3
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  6. package/releases/x86_64-apple-darwin/index.node +0 -0
  7. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  8. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  9. package/sdk-core/.cargo/config.toml +1 -2
  10. package/sdk-core/.github/workflows/per-pr.yml +2 -0
  11. package/sdk-core/AGENTS.md +7 -0
  12. package/sdk-core/Cargo.toml +9 -5
  13. package/sdk-core/README.md +6 -5
  14. package/sdk-core/client/Cargo.toml +3 -2
  15. package/sdk-core/client/src/callback_based.rs +123 -0
  16. package/sdk-core/client/src/lib.rs +113 -36
  17. package/sdk-core/client/src/metrics.rs +89 -27
  18. package/sdk-core/client/src/raw.rs +73 -16
  19. package/sdk-core/client/src/retry.rs +12 -3
  20. package/sdk-core/core/Cargo.toml +11 -9
  21. package/sdk-core/core/benches/workflow_replay.rs +114 -15
  22. package/sdk-core/core/src/core_tests/activity_tasks.rs +18 -18
  23. package/sdk-core/core/src/core_tests/child_workflows.rs +4 -4
  24. package/sdk-core/core/src/core_tests/determinism.rs +6 -6
  25. package/sdk-core/core/src/core_tests/local_activities.rs +20 -20
  26. package/sdk-core/core/src/core_tests/mod.rs +40 -5
  27. package/sdk-core/core/src/core_tests/queries.rs +25 -16
  28. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -3
  29. package/sdk-core/core/src/core_tests/updates.rs +3 -3
  30. package/sdk-core/core/src/core_tests/workers.rs +9 -7
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +40 -42
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +1 -19
  33. package/sdk-core/core/src/lib.rs +12 -1
  34. package/sdk-core/core/src/pollers/poll_buffer.rs +64 -16
  35. package/sdk-core/core/src/replay/mod.rs +3 -3
  36. package/sdk-core/core/src/telemetry/metrics.rs +306 -152
  37. package/sdk-core/core/src/telemetry/mod.rs +11 -4
  38. package/sdk-core/core/src/telemetry/otel.rs +134 -131
  39. package/sdk-core/core/src/telemetry/prometheus_meter.rs +885 -0
  40. package/sdk-core/core/src/telemetry/prometheus_server.rs +48 -28
  41. package/sdk-core/core/src/test_help/mod.rs +27 -12
  42. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +7 -7
  43. package/sdk-core/core/src/worker/activities.rs +4 -4
  44. package/sdk-core/core/src/worker/client/mocks.rs +10 -3
  45. package/sdk-core/core/src/worker/client.rs +72 -5
  46. package/sdk-core/core/src/worker/heartbeat.rs +231 -0
  47. package/sdk-core/core/src/worker/mod.rs +35 -14
  48. package/sdk-core/core/src/worker/tuner/resource_based.rs +4 -4
  49. package/sdk-core/core/src/worker/workflow/history_update.rs +71 -19
  50. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -2
  51. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -1
  52. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +31 -48
  53. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -2
  54. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +3 -3
  55. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +4 -1
  56. package/sdk-core/core/src/worker/workflow/managed_run.rs +1 -1
  57. package/sdk-core/core/src/worker/workflow/mod.rs +15 -15
  58. package/sdk-core/core-api/Cargo.toml +2 -2
  59. package/sdk-core/core-api/src/envconfig.rs +204 -99
  60. package/sdk-core/core-api/src/lib.rs +9 -0
  61. package/sdk-core/core-api/src/telemetry/metrics.rs +548 -100
  62. package/sdk-core/core-api/src/worker.rs +11 -5
  63. package/sdk-core/core-c-bridge/Cargo.toml +51 -0
  64. package/sdk-core/core-c-bridge/build.rs +26 -0
  65. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +922 -0
  66. package/sdk-core/core-c-bridge/src/client.rs +936 -0
  67. package/sdk-core/core-c-bridge/src/lib.rs +245 -0
  68. package/sdk-core/core-c-bridge/src/metric.rs +682 -0
  69. package/sdk-core/core-c-bridge/src/random.rs +61 -0
  70. package/sdk-core/core-c-bridge/src/runtime.rs +445 -0
  71. package/sdk-core/core-c-bridge/src/testing.rs +282 -0
  72. package/sdk-core/core-c-bridge/src/tests/context.rs +655 -0
  73. package/sdk-core/core-c-bridge/src/tests/mod.rs +354 -0
  74. package/sdk-core/core-c-bridge/src/tests/utils.rs +108 -0
  75. package/sdk-core/core-c-bridge/src/worker.rs +1069 -0
  76. package/sdk-core/etc/deps.svg +64 -64
  77. package/sdk-core/sdk/src/activity_context.rs +6 -4
  78. package/sdk-core/sdk/src/lib.rs +49 -27
  79. package/sdk-core/sdk/src/workflow_future.rs +18 -25
  80. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +1 -1
  81. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +1 -1
  82. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
  83. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +1 -0
  84. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +83 -0
  85. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +37 -0
  86. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/connectivityrule/v1/message.proto +64 -0
  87. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +3 -1
  88. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +10 -0
  89. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +1 -0
  90. package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +4 -0
  91. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +0 -2
  92. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +1285 -103
  93. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +1290 -122
  94. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +64 -6
  95. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +6 -4
  96. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +86 -17
  97. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +32 -2
  98. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -0
  99. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +10 -1
  100. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +26 -0
  101. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  102. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +4 -4
  103. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +11 -0
  104. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  105. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +52 -31
  106. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -4
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +7 -1
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +1 -1
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/worker_config.proto +36 -0
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +29 -0
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +144 -0
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +14 -11
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +268 -39
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +62 -0
  115. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -4
  116. package/sdk-core/sdk-core-protos/src/history_builder.rs +9 -5
  117. package/sdk-core/sdk-core-protos/src/lib.rs +100 -6
  118. package/sdk-core/test-utils/Cargo.toml +1 -0
  119. package/sdk-core/test-utils/src/lib.rs +101 -6
  120. package/sdk-core/tests/cloud_tests.rs +11 -74
  121. package/sdk-core/tests/heavy_tests.rs +11 -3
  122. package/sdk-core/tests/integ_tests/client_tests.rs +22 -19
  123. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +1 -1
  124. package/sdk-core/tests/integ_tests/metrics_tests.rs +188 -83
  125. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
  126. package/sdk-core/tests/integ_tests/queries_tests.rs +56 -40
  127. package/sdk-core/tests/integ_tests/update_tests.rs +2 -7
  128. package/sdk-core/tests/integ_tests/worker_tests.rs +8 -3
  129. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +3 -7
  130. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +13 -0
  131. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +3 -5
  132. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +24 -17
  133. package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +2 -108
  134. package/sdk-core/tests/main.rs +3 -0
  135. package/sdk-core/tests/shared_tests/mod.rs +43 -0
  136. package/sdk-core/tests/shared_tests/priority.rs +155 -0
  137. package/src/client.rs +11 -0
  138. package/src/metrics.rs +6 -6
@@ -1,14 +1,48 @@
1
+ use crate::telemetry::prometheus_meter::Registry;
1
2
  use http_body_util::Full;
2
3
  use hyper::{Method, Request, Response, body::Bytes, header::CONTENT_TYPE, service::service_fn};
3
4
  use hyper_util::{
4
5
  rt::{TokioExecutor, TokioIo},
5
6
  server::conn::auto,
6
7
  };
7
- use opentelemetry_prometheus::PrometheusExporter;
8
- use prometheus::{Encoder, Registry, TextEncoder};
9
- use std::net::{SocketAddr, TcpListener};
8
+ use prometheus::{Encoder, TextEncoder};
9
+ use std::{
10
+ net::{SocketAddr, TcpListener},
11
+ sync::Arc,
12
+ };
10
13
  use temporal_sdk_core_api::telemetry::PrometheusExporterOptions;
11
- use tokio::io;
14
+ use tokio::{io, task::AbortHandle};
15
+
16
+ pub struct StartedPromServer {
17
+ pub meter: Arc<crate::telemetry::prometheus_meter::CorePrometheusMeter>,
18
+ pub bound_addr: SocketAddr,
19
+ pub abort_handle: AbortHandle,
20
+ }
21
+
22
+ /// Builds and runs a prometheus endpoint which can be scraped by prom instances for metrics export.
23
+ /// Returns the meter that can be used as a [CoreMeter].
24
+ ///
25
+ /// Requires a Tokio runtime to exist, and will block briefly while binding the server endpoint.
26
+ pub fn start_prometheus_metric_exporter(
27
+ opts: PrometheusExporterOptions,
28
+ ) -> Result<StartedPromServer, anyhow::Error> {
29
+ let srv = PromServer::new(&opts)?;
30
+ let meter = Arc::new(
31
+ crate::telemetry::prometheus_meter::CorePrometheusMeter::new(
32
+ srv.registry().clone(),
33
+ opts.use_seconds_for_durations,
34
+ opts.unit_suffix,
35
+ opts.histogram_bucket_overrides,
36
+ ),
37
+ );
38
+ let bound_addr = srv.bound_addr()?;
39
+ let handle = tokio::spawn(async move { srv.run().await });
40
+ Ok(StartedPromServer {
41
+ meter,
42
+ bound_addr,
43
+ abort_handle: handle.abort_handle(),
44
+ })
45
+ }
12
46
 
13
47
  /// Exposes prometheus metrics for scraping
14
48
  pub(super) struct PromServer {
@@ -17,30 +51,16 @@ pub(super) struct PromServer {
17
51
  }
18
52
 
19
53
  impl PromServer {
20
- pub(super) fn new(
21
- opts: &PrometheusExporterOptions,
22
- ) -> Result<(Self, PrometheusExporter), anyhow::Error> {
23
- let registry = Registry::new();
24
- let exporter = opentelemetry_prometheus::exporter()
25
- .without_scope_info()
26
- .with_registry(registry.clone());
27
- let exporter = if !opts.counters_total_suffix {
28
- exporter.without_counter_suffixes()
29
- } else {
30
- exporter
31
- };
32
- let exporter = if !opts.unit_suffix {
33
- exporter.without_units()
34
- } else {
35
- exporter
36
- };
37
- Ok((
38
- Self {
39
- listener: TcpListener::bind(opts.socket_addr)?,
40
- registry,
41
- },
42
- exporter.build()?,
43
- ))
54
+ pub(super) fn new(opts: &PrometheusExporterOptions) -> Result<Self, anyhow::Error> {
55
+ let registry = Registry::new(opts.global_tags.clone());
56
+ Ok(Self {
57
+ listener: TcpListener::bind(opts.socket_addr)?,
58
+ registry,
59
+ })
60
+ }
61
+
62
+ pub(super) fn registry(&self) -> &Registry {
63
+ &self.registry
44
64
  }
45
65
 
46
66
  pub(super) async fn run(self) -> Result<(), anyhow::Error> {
@@ -9,7 +9,8 @@ use crate::{
9
9
  worker::{
10
10
  TaskPollers,
11
11
  client::{
12
- MockWorkerClient, WorkerClient, WorkflowTaskCompletion, mocks::mock_workflow_client,
12
+ LegacyQueryResult, MockWorkerClient, WorkerClient, WorkflowTaskCompletion,
13
+ mocks::mock_worker_client,
13
14
  },
14
15
  },
15
16
  };
@@ -179,6 +180,7 @@ pub(crate) fn mock_worker(mocks: MocksHolder) -> Worker {
179
180
  .unwrap_or_else(|| mock_poller_from_resps([])),
180
181
  },
181
182
  None,
183
+ None,
182
184
  )
183
185
  }
184
186
 
@@ -406,6 +408,9 @@ pub(crate) struct MockPollCfg {
406
408
  /// All calls to fail WFTs must match this predicate
407
409
  pub(crate) expect_fail_wft_matcher:
408
410
  Box<dyn Fn(&TaskToken, &WorkflowTaskFailedCause, &Option<Failure>) -> bool + Send>,
411
+ /// All calls to legacy query responses must match this predicate
412
+ pub(crate) expect_legacy_query_matcher:
413
+ Box<dyn Fn(&TaskToken, &LegacyQueryResult) -> bool + Send>,
409
414
  pub(crate) completion_mock_fn: Option<Box<WFTCompletionMockFn>>,
410
415
  pub(crate) num_expected_completions: Option<TimesRange>,
411
416
  /// If being used with the Rust SDK, this is set true. It ensures pollers will not error out
@@ -426,8 +431,9 @@ impl MockPollCfg {
426
431
  enforce_correct_number_of_polls,
427
432
  num_expected_fails,
428
433
  num_expected_legacy_query_resps: 0,
429
- mock_client: mock_workflow_client(),
434
+ mock_client: mock_worker_client(),
430
435
  expect_fail_wft_matcher: Box::new(|_, _, _| true),
436
+ expect_legacy_query_matcher: Box::new(|_, _| true),
431
437
  completion_mock_fn: None,
432
438
  num_expected_completions: None,
433
439
  using_rust_sdk: false,
@@ -439,14 +445,14 @@ impl MockPollCfg {
439
445
  pub(crate) fn from_hist_builder(t: TestHistoryBuilder) -> Self {
440
446
  let full_hist_info = t.get_full_history_info().unwrap();
441
447
  let tasks = 1..=full_hist_info.wf_task_count();
442
- Self::from_resp_batches("fake_wf_id", t, tasks, mock_workflow_client())
448
+ Self::from_resp_batches("fake_wf_id", t, tasks, mock_worker_client())
443
449
  }
444
450
 
445
451
  pub(crate) fn from_resps(
446
452
  t: TestHistoryBuilder,
447
453
  resps: impl IntoIterator<Item = impl Into<ResponseType>>,
448
454
  ) -> Self {
449
- Self::from_resp_batches("fake_wf_id", t, resps, mock_workflow_client())
455
+ Self::from_resp_batches("fake_wf_id", t, resps, mock_worker_client())
450
456
  }
451
457
 
452
458
  pub(crate) fn from_resp_batches(
@@ -466,6 +472,7 @@ impl MockPollCfg {
466
472
  num_expected_legacy_query_resps: 0,
467
473
  mock_client,
468
474
  expect_fail_wft_matcher: Box::new(|_, _, _| true),
475
+ expect_legacy_query_matcher: Box::new(|_, _| true),
469
476
  completion_mock_fn: None,
470
477
  num_expected_completions: None,
471
478
  using_rust_sdk: false,
@@ -709,6 +716,7 @@ pub(crate) fn build_mock_pollers(mut cfg: MockPollCfg) -> MocksHolder {
709
716
  let outstanding = outstanding_wf_task_tokens.clone();
710
717
  cfg.mock_client
711
718
  .expect_respond_legacy_query()
719
+ .withf(cfg.expect_legacy_query_matcher)
712
720
  .times::<TimesRange>(cfg.num_expected_legacy_query_resps.into())
713
721
  .returning(move |tt, _| {
714
722
  outstanding.release_token(&tt);
@@ -948,10 +956,8 @@ pub(crate) async fn poll_and_reply_clears_outstanding_evicts<'a>(
948
956
 
949
957
  worker.complete_workflow_activation(reply).await.unwrap();
950
958
 
951
- if do_release {
952
- if let Some(omap) = outstanding_map.as_ref() {
953
- omap.release_run(&res.run_id);
954
- }
959
+ if do_release && let Some(omap) = outstanding_map.as_ref() {
960
+ omap.release_run(&res.run_id);
955
961
  }
956
962
  // Restart assertions from the beginning if it was an eviction (and workflow execution
957
963
  // isn't over)
@@ -1068,10 +1074,19 @@ impl WorkerExt for Worker {
1068
1074
  );
1069
1075
  },
1070
1076
  async {
1071
- assert_matches!(
1072
- self.poll_workflow_activation().await.unwrap_err(),
1073
- PollError::ShutDown
1074
- );
1077
+ loop {
1078
+ match self.poll_workflow_activation().await {
1079
+ Err(PollError::ShutDown) => break,
1080
+ Ok(a) if a.is_only_eviction() => {
1081
+ self.complete_workflow_activation(WorkflowActivationCompletion::empty(
1082
+ a.run_id,
1083
+ ))
1084
+ .await
1085
+ .unwrap();
1086
+ }
1087
+ o => panic!("Unexpected activation while draining: {o:?}"),
1088
+ }
1089
+ }
1075
1090
  }
1076
1091
  );
1077
1092
  self.finalize_shutdown().await;
@@ -423,7 +423,7 @@ impl HeartbeatStreamState {
423
423
  mod test {
424
424
  use super::*;
425
425
 
426
- use crate::worker::client::mocks::mock_workflow_client;
426
+ use crate::worker::client::mocks::mock_worker_client;
427
427
  use std::time::Duration;
428
428
  use temporal_sdk_core_protos::temporal::api::{
429
429
  common::v1::Payload, workflowservice::v1::RecordActivityTaskHeartbeatResponse,
@@ -434,7 +434,7 @@ mod test {
434
434
  /// every 1/2 of the heartbeat timeout.
435
435
  #[tokio::test]
436
436
  async fn process_heartbeats_and_shutdown() {
437
- let mut mock_client = mock_workflow_client();
437
+ let mut mock_client = mock_worker_client();
438
438
  mock_client
439
439
  .expect_record_activity_heartbeat()
440
440
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -456,7 +456,7 @@ mod test {
456
456
 
457
457
  #[tokio::test]
458
458
  async fn send_heartbeats_less_frequently_than_throttle_interval() {
459
- let mut mock_client = mock_workflow_client();
459
+ let mut mock_client = mock_worker_client();
460
460
  mock_client
461
461
  .expect_record_activity_heartbeat()
462
462
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -475,7 +475,7 @@ mod test {
475
475
  /// Ensure that heartbeat can be called from a tight loop and correctly throttle
476
476
  #[tokio::test]
477
477
  async fn process_tight_loop_and_shutdown() {
478
- let mut mock_client = mock_workflow_client();
478
+ let mut mock_client = mock_worker_client();
479
479
  mock_client
480
480
  .expect_record_activity_heartbeat()
481
481
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -495,7 +495,7 @@ mod test {
495
495
  /// This test reports one heartbeat and waits for the throttle_interval to elapse before sending another
496
496
  #[tokio::test]
497
497
  async fn report_heartbeat_after_timeout() {
498
- let mut mock_client = mock_workflow_client();
498
+ let mut mock_client = mock_worker_client();
499
499
  mock_client
500
500
  .expect_record_activity_heartbeat()
501
501
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -513,7 +513,7 @@ mod test {
513
513
 
514
514
  #[tokio::test]
515
515
  async fn evict_works() {
516
- let mut mock_client = mock_workflow_client();
516
+ let mut mock_client = mock_worker_client();
517
517
  mock_client
518
518
  .expect_record_activity_heartbeat()
519
519
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -534,7 +534,7 @@ mod test {
534
534
 
535
535
  #[tokio::test]
536
536
  async fn evict_immediate_after_record() {
537
- let mut mock_client = mock_workflow_client();
537
+ let mut mock_client = mock_worker_client();
538
538
  mock_client
539
539
  .expect_record_activity_heartbeat()
540
540
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
@@ -726,14 +726,14 @@ mod tests {
726
726
  abstractions::tests::fixed_size_permit_dealer,
727
727
  pollers::{ActivityTaskOptions, LongPollBuffer},
728
728
  prost_dur,
729
- worker::client::mocks::mock_workflow_client,
729
+ worker::client::mocks::mock_worker_client,
730
730
  };
731
731
  use temporal_sdk_core_api::worker::PollerBehavior;
732
732
  use temporal_sdk_core_protos::coresdk::activity_result::ActivityExecutionResult;
733
733
 
734
734
  #[tokio::test]
735
735
  async fn per_worker_ratelimit() {
736
- let mut mock_client = mock_workflow_client();
736
+ let mut mock_client = mock_worker_client();
737
737
  mock_client
738
738
  .expect_poll_activity_task()
739
739
  .times(1)
@@ -812,7 +812,7 @@ mod tests {
812
812
 
813
813
  #[tokio::test]
814
814
  async fn local_timeouts() {
815
- let mut mock_client = mock_workflow_client();
815
+ let mut mock_client = mock_worker_client();
816
816
  mock_client
817
817
  .expect_poll_activity_task()
818
818
  .times(1)
@@ -902,7 +902,7 @@ mod tests {
902
902
 
903
903
  #[tokio::test]
904
904
  async fn local_timeout_heartbeating() {
905
- let mut mock_client = mock_workflow_client();
905
+ let mut mock_client = mock_worker_client();
906
906
  mock_client
907
907
  .expect_poll_activity_task()
908
908
  .times(1)
@@ -22,7 +22,7 @@ pub(crate) static DEFAULT_TEST_CAPABILITIES: &Capabilities = &Capabilities {
22
22
 
23
23
  #[cfg(test)]
24
24
  /// Create a mock client primed with basic necessary expectations
25
- pub(crate) fn mock_workflow_client() -> MockWorkerClient {
25
+ pub(crate) fn mock_worker_client() -> MockWorkerClient {
26
26
  let mut r = MockWorkerClient::new();
27
27
  r.expect_capabilities()
28
28
  .returning(|| Some(*DEFAULT_TEST_CAPABILITIES));
@@ -33,11 +33,13 @@ pub(crate) fn mock_workflow_client() -> MockWorkerClient {
33
33
  .returning(|_| Ok(ShutdownWorkerResponse {}));
34
34
  r.expect_sdk_name_and_version()
35
35
  .returning(|| ("test-core".to_string(), "0.0.0".to_string()));
36
+ r.expect_get_identity()
37
+ .returning(|| "test-identity".to_string());
36
38
  r
37
39
  }
38
40
 
39
41
  /// Create a mock manual client primed with basic necessary expectations
40
- pub(crate) fn mock_manual_workflow_client() -> MockManualWorkerClient {
42
+ pub(crate) fn mock_manual_worker_client() -> MockManualWorkerClient {
41
43
  let mut r = MockManualWorkerClient::new();
42
44
  r.expect_capabilities()
43
45
  .returning(|| Some(*DEFAULT_TEST_CAPABILITIES));
@@ -46,6 +48,8 @@ pub(crate) fn mock_manual_workflow_client() -> MockManualWorkerClient {
46
48
  r.expect_is_mock().returning(|| true);
47
49
  r.expect_sdk_name_and_version()
48
50
  .returning(|| ("test-core".to_string(), "0.0.0".to_string()));
51
+ r.expect_get_identity()
52
+ .returning(|| "test-identity".to_string());
49
53
  r
50
54
  }
51
55
 
@@ -135,7 +139,7 @@ mockall::mock! {
135
139
  fn respond_legacy_query<'a, 'b>(
136
140
  &self,
137
141
  task_token: TaskToken,
138
- query_result: QueryResult,
142
+ query_result: LegacyQueryResult,
139
143
  ) -> impl Future<Output = Result<RespondQueryTaskCompletedResponse>> + Send + 'b
140
144
  where 'a: 'b, Self: 'b;
141
145
 
@@ -146,10 +150,13 @@ mockall::mock! {
146
150
  fn shutdown_worker<'a, 'b>(&self, sticky_task_queue: String) -> impl Future<Output = Result<ShutdownWorkerResponse>> + Send + 'b
147
151
  where 'a: 'b, Self: 'b;
148
152
 
153
+ fn record_worker_heartbeat<'a, 'b>(&self, heartbeat: WorkerHeartbeat) -> impl Future<Output = Result<RecordWorkerHeartbeatResponse>> + Send + 'b where 'a: 'b, Self: 'b;
154
+
149
155
  fn replace_client(&self, new_client: RetryClient<Client>);
150
156
  fn capabilities(&self) -> Option<Capabilities>;
151
157
  fn workers(&self) -> Arc<SlotManager>;
152
158
  fn is_mock(&self) -> bool;
153
159
  fn sdk_name_and_version(&self) -> (String, String);
160
+ fn get_identity(&self) -> String;
154
161
  }
155
162
  }
@@ -1,16 +1,21 @@
1
1
  //! Worker-specific client needs
2
2
 
3
3
  pub(crate) mod mocks;
4
+ use crate::abstractions::dbg_panic;
5
+ use crate::protosext::legacy_query_failure;
6
+ use crate::worker::heartbeat::HeartbeatFn;
4
7
  use parking_lot::RwLock;
8
+ use std::sync::OnceLock;
5
9
  use std::{sync::Arc, time::Duration};
6
10
  use temporal_client::{
7
11
  Client, IsWorkerTaskLongPoll, Namespace, NamespacedClient, NoRetryOnMatching, RetryClient,
8
12
  SlotManager, WorkflowService,
9
13
  };
10
14
  use temporal_sdk_core_api::worker::WorkerVersioningStrategy;
15
+ use temporal_sdk_core_protos::temporal::api::worker::v1::WorkerHeartbeat;
11
16
  use temporal_sdk_core_protos::{
12
17
  TaskToken,
13
- coresdk::workflow_commands::QueryResult,
18
+ coresdk::{workflow_commands::QueryResult, workflow_completion},
14
19
  temporal::api::{
15
20
  command::v1::Command,
16
21
  common::v1::{
@@ -34,12 +39,18 @@ use tonic::IntoRequest;
34
39
 
35
40
  type Result<T, E = tonic::Status> = std::result::Result<T, E>;
36
41
 
42
+ pub enum LegacyQueryResult {
43
+ Succeeded(QueryResult),
44
+ Failed(workflow_completion::Failure),
45
+ }
46
+
37
47
  /// Contains everything a worker needs to interact with the server
38
48
  pub(crate) struct WorkerClientBag {
39
49
  replaceable_client: RwLock<RetryClient<Client>>,
40
50
  namespace: String,
41
51
  identity: String,
42
52
  worker_versioning_strategy: WorkerVersioningStrategy,
53
+ heartbeat_data: Option<Arc<OnceLock<HeartbeatFn>>>,
43
54
  }
44
55
 
45
56
  impl WorkerClientBag {
@@ -48,12 +59,14 @@ impl WorkerClientBag {
48
59
  namespace: String,
49
60
  identity: String,
50
61
  worker_versioning_strategy: WorkerVersioningStrategy,
62
+ heartbeat_data: Option<Arc<OnceLock<HeartbeatFn>>>,
51
63
  ) -> Self {
52
64
  Self {
53
65
  replaceable_client: RwLock::new(client),
54
66
  namespace,
55
67
  identity,
56
68
  worker_versioning_strategy,
69
+ heartbeat_data,
57
70
  }
58
71
  }
59
72
 
@@ -114,6 +127,19 @@ impl WorkerClientBag {
114
127
  None
115
128
  }
116
129
  }
130
+
131
+ fn capture_heartbeat(&self) -> Option<WorkerHeartbeat> {
132
+ if let Some(heartbeat_data) = self.heartbeat_data.as_ref() {
133
+ if let Some(hb) = heartbeat_data.get() {
134
+ hb()
135
+ } else {
136
+ dbg_panic!("Heartbeat function never set");
137
+ None
138
+ }
139
+ } else {
140
+ None
141
+ }
142
+ }
117
143
  }
118
144
 
119
145
  /// This trait contains everything workers need to interact with Temporal, and hence provides a
@@ -197,12 +223,17 @@ pub trait WorkerClient: Sync + Send {
197
223
  async fn respond_legacy_query(
198
224
  &self,
199
225
  task_token: TaskToken,
200
- query_result: QueryResult,
226
+ query_result: LegacyQueryResult,
201
227
  ) -> Result<RespondQueryTaskCompletedResponse>;
202
228
  /// Describe the namespace
203
229
  async fn describe_namespace(&self) -> Result<DescribeNamespaceResponse>;
204
230
  /// Shutdown the worker
205
231
  async fn shutdown_worker(&self, sticky_task_queue: String) -> Result<ShutdownWorkerResponse>;
232
+ /// Record a worker heartbeat
233
+ async fn record_worker_heartbeat(
234
+ &self,
235
+ heartbeat: WorkerHeartbeat,
236
+ ) -> Result<RecordWorkerHeartbeatResponse>;
206
237
 
207
238
  /// Replace the underlying client
208
239
  fn replace_client(&self, new_client: RetryClient<Client>);
@@ -214,6 +245,8 @@ pub trait WorkerClient: Sync + Send {
214
245
  fn is_mock(&self) -> bool;
215
246
  /// Return name and version of the SDK
216
247
  fn sdk_name_and_version(&self) -> (String, String);
248
+ /// Get worker identity
249
+ fn get_identity(&self) -> String;
217
250
  }
218
251
 
219
252
  /// Configuration options shared by workflow, activity, and Nexus polling calls
@@ -267,6 +300,7 @@ impl WorkerClient for WorkerClientBag {
267
300
  binary_checksum: self.binary_checksum(),
268
301
  worker_version_capabilities: self.worker_version_capabilities(),
269
302
  deployment_options: self.deployment_options(),
303
+ worker_heartbeat: None,
270
304
  }
271
305
  .into_request();
272
306
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -303,6 +337,7 @@ impl WorkerClient for WorkerClientBag {
303
337
  }),
304
338
  worker_version_capabilities: self.worker_version_capabilities(),
305
339
  deployment_options: self.deployment_options(),
340
+ worker_heartbeat: None,
306
341
  }
307
342
  .into_request();
308
343
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -335,6 +370,7 @@ impl WorkerClient for WorkerClientBag {
335
370
  identity: self.identity.clone(),
336
371
  worker_version_capabilities: self.worker_version_capabilities(),
337
372
  deployment_options: self.deployment_options(),
373
+ worker_heartbeat: self.capture_heartbeat().into_iter().collect(),
338
374
  }
339
375
  .into_request();
340
376
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -578,9 +614,20 @@ impl WorkerClient for WorkerClientBag {
578
614
  async fn respond_legacy_query(
579
615
  &self,
580
616
  task_token: TaskToken,
581
- query_result: QueryResult,
617
+ query_result: LegacyQueryResult,
582
618
  ) -> Result<RespondQueryTaskCompletedResponse> {
619
+ let mut failure = None;
620
+ let (query_result, cause) = match query_result {
621
+ LegacyQueryResult::Succeeded(s) => (s, WorkflowTaskFailedCause::Unspecified),
622
+ #[allow(deprecated)]
623
+ LegacyQueryResult::Failed(f) => {
624
+ let cause = f.force_cause();
625
+ failure = f.failure.clone();
626
+ (legacy_query_failure(f), cause)
627
+ }
628
+ };
583
629
  let (_, completed_type, query_result, error_message) = query_result.into_components();
630
+
584
631
  Ok(self
585
632
  .cloned_client()
586
633
  .respond_query_task_completed(RespondQueryTaskCompletedRequest {
@@ -589,8 +636,8 @@ impl WorkerClient for WorkerClientBag {
589
636
  query_result,
590
637
  error_message,
591
638
  namespace: self.namespace.clone(),
592
- // TODO: https://github.com/temporalio/sdk-core/issues/867
593
- failure: None,
639
+ failure,
640
+ cause: cause.into(),
594
641
  })
595
642
  .await?
596
643
  .into_inner())
@@ -612,6 +659,7 @@ impl WorkerClient for WorkerClientBag {
612
659
  identity: self.identity.clone(),
613
660
  sticky_task_queue,
614
661
  reason: "graceful shutdown".to_string(),
662
+ worker_heartbeat: self.capture_heartbeat(),
615
663
  };
616
664
 
617
665
  Ok(
@@ -626,6 +674,21 @@ impl WorkerClient for WorkerClientBag {
626
674
  *replaceable_client = new_client;
627
675
  }
628
676
 
677
+ async fn record_worker_heartbeat(
678
+ &self,
679
+ heartbeat: WorkerHeartbeat,
680
+ ) -> Result<RecordWorkerHeartbeatResponse> {
681
+ Ok(self
682
+ .cloned_client()
683
+ .record_worker_heartbeat(RecordWorkerHeartbeatRequest {
684
+ namespace: self.namespace.clone(),
685
+ identity: self.identity.clone(),
686
+ worker_heartbeat: vec![heartbeat],
687
+ })
688
+ .await?
689
+ .into_inner())
690
+ }
691
+
629
692
  fn capabilities(&self) -> Option<Capabilities> {
630
693
  let client = self.replaceable_client.read();
631
694
  client.get_client().inner().capabilities().cloned()
@@ -645,6 +708,10 @@ impl WorkerClient for WorkerClientBag {
645
708
  let opts = lock.get_client().inner().options();
646
709
  (opts.client_name.clone(), opts.client_version.clone())
647
710
  }
711
+
712
+ fn get_identity(&self) -> String {
713
+ self.identity.clone()
714
+ }
648
715
  }
649
716
 
650
717
  impl NamespacedClient for WorkerClientBag {