@temporalio/core-bridge 1.12.0 → 1.12.2

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 (116) hide show
  1. package/Cargo.lock +64 -119
  2. package/Cargo.toml +1 -1
  3. package/index.js +3 -2
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +1 -2
  11. package/sdk-core/.github/workflows/per-pr.yml +2 -0
  12. package/sdk-core/AGENTS.md +7 -0
  13. package/sdk-core/Cargo.toml +9 -5
  14. package/sdk-core/README.md +6 -5
  15. package/sdk-core/client/Cargo.toml +3 -2
  16. package/sdk-core/client/src/lib.rs +17 -8
  17. package/sdk-core/client/src/metrics.rs +57 -23
  18. package/sdk-core/client/src/raw.rs +33 -15
  19. package/sdk-core/core/Cargo.toml +11 -9
  20. package/sdk-core/core/benches/workflow_replay.rs +114 -15
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +18 -18
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +4 -4
  23. package/sdk-core/core/src/core_tests/determinism.rs +6 -6
  24. package/sdk-core/core/src/core_tests/local_activities.rs +20 -20
  25. package/sdk-core/core/src/core_tests/mod.rs +40 -5
  26. package/sdk-core/core/src/core_tests/queries.rs +25 -16
  27. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +3 -3
  29. package/sdk-core/core/src/core_tests/workers.rs +9 -7
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +40 -42
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +1 -19
  32. package/sdk-core/core/src/lib.rs +10 -1
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  34. package/sdk-core/core/src/replay/mod.rs +3 -3
  35. package/sdk-core/core/src/telemetry/metrics.rs +306 -152
  36. package/sdk-core/core/src/telemetry/mod.rs +11 -4
  37. package/sdk-core/core/src/telemetry/otel.rs +134 -131
  38. package/sdk-core/core/src/telemetry/prometheus_meter.rs +885 -0
  39. package/sdk-core/core/src/telemetry/prometheus_server.rs +48 -28
  40. package/sdk-core/core/src/test_help/mod.rs +27 -12
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +7 -7
  42. package/sdk-core/core/src/worker/activities.rs +4 -4
  43. package/sdk-core/core/src/worker/client/mocks.rs +10 -3
  44. package/sdk-core/core/src/worker/client.rs +68 -5
  45. package/sdk-core/core/src/worker/heartbeat.rs +229 -0
  46. package/sdk-core/core/src/worker/mod.rs +35 -14
  47. package/sdk-core/core/src/worker/tuner/resource_based.rs +4 -4
  48. package/sdk-core/core/src/worker/workflow/history_update.rs +71 -19
  49. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -2
  50. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -1
  51. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +31 -48
  52. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -2
  53. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +3 -3
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +4 -1
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +1 -1
  56. package/sdk-core/core/src/worker/workflow/mod.rs +15 -15
  57. package/sdk-core/core-api/Cargo.toml +2 -2
  58. package/sdk-core/core-api/src/envconfig.rs +204 -99
  59. package/sdk-core/core-api/src/lib.rs +9 -0
  60. package/sdk-core/core-api/src/telemetry/metrics.rs +548 -100
  61. package/sdk-core/core-api/src/worker.rs +11 -5
  62. package/sdk-core/core-c-bridge/Cargo.toml +49 -0
  63. package/sdk-core/core-c-bridge/build.rs +26 -0
  64. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +817 -0
  65. package/sdk-core/core-c-bridge/src/client.rs +679 -0
  66. package/sdk-core/core-c-bridge/src/lib.rs +245 -0
  67. package/sdk-core/core-c-bridge/src/metric.rs +682 -0
  68. package/sdk-core/core-c-bridge/src/random.rs +61 -0
  69. package/sdk-core/core-c-bridge/src/runtime.rs +445 -0
  70. package/sdk-core/core-c-bridge/src/testing.rs +282 -0
  71. package/sdk-core/core-c-bridge/src/tests/context.rs +644 -0
  72. package/sdk-core/core-c-bridge/src/tests/mod.rs +178 -0
  73. package/sdk-core/core-c-bridge/src/tests/utils.rs +108 -0
  74. package/sdk-core/core-c-bridge/src/worker.rs +1069 -0
  75. package/sdk-core/etc/deps.svg +64 -64
  76. package/sdk-core/sdk/src/activity_context.rs +6 -4
  77. package/sdk-core/sdk/src/lib.rs +49 -27
  78. package/sdk-core/sdk/src/workflow_future.rs +18 -25
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +4 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +0 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +630 -83
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +632 -78
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +4 -4
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +6 -4
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +2 -2
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +32 -2
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +10 -1
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +26 -0
  89. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  90. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +4 -4
  91. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  92. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +47 -31
  93. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -4
  94. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +7 -1
  95. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +134 -0
  96. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +14 -11
  97. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +148 -37
  98. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +21 -0
  99. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -4
  100. package/sdk-core/sdk-core-protos/src/history_builder.rs +9 -5
  101. package/sdk-core/sdk-core-protos/src/lib.rs +96 -6
  102. package/sdk-core/test-utils/src/lib.rs +11 -3
  103. package/sdk-core/tests/cloud_tests.rs +3 -3
  104. package/sdk-core/tests/heavy_tests.rs +11 -3
  105. package/sdk-core/tests/integ_tests/client_tests.rs +12 -13
  106. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +1 -1
  107. package/sdk-core/tests/integ_tests/metrics_tests.rs +188 -83
  108. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
  109. package/sdk-core/tests/integ_tests/queries_tests.rs +56 -40
  110. package/sdk-core/tests/integ_tests/update_tests.rs +2 -7
  111. package/sdk-core/tests/integ_tests/worker_tests.rs +3 -4
  112. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +3 -7
  113. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +3 -5
  114. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +24 -17
  115. package/src/client.rs +6 -0
  116. 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: 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: 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,15 @@ impl WorkerClientBag {
114
127
  None
115
128
  }
116
129
  }
130
+
131
+ fn capture_heartbeat(&self) -> Option<WorkerHeartbeat> {
132
+ if let Some(hb) = self.heartbeat_data.get() {
133
+ hb()
134
+ } else {
135
+ dbg_panic!("Heartbeat function never set");
136
+ None
137
+ }
138
+ }
117
139
  }
118
140
 
119
141
  /// This trait contains everything workers need to interact with Temporal, and hence provides a
@@ -197,12 +219,17 @@ pub trait WorkerClient: Sync + Send {
197
219
  async fn respond_legacy_query(
198
220
  &self,
199
221
  task_token: TaskToken,
200
- query_result: QueryResult,
222
+ query_result: LegacyQueryResult,
201
223
  ) -> Result<RespondQueryTaskCompletedResponse>;
202
224
  /// Describe the namespace
203
225
  async fn describe_namespace(&self) -> Result<DescribeNamespaceResponse>;
204
226
  /// Shutdown the worker
205
227
  async fn shutdown_worker(&self, sticky_task_queue: String) -> Result<ShutdownWorkerResponse>;
228
+ /// Record a worker heartbeat
229
+ async fn record_worker_heartbeat(
230
+ &self,
231
+ heartbeat: WorkerHeartbeat,
232
+ ) -> Result<RecordWorkerHeartbeatResponse>;
206
233
 
207
234
  /// Replace the underlying client
208
235
  fn replace_client(&self, new_client: RetryClient<Client>);
@@ -214,6 +241,8 @@ pub trait WorkerClient: Sync + Send {
214
241
  fn is_mock(&self) -> bool;
215
242
  /// Return name and version of the SDK
216
243
  fn sdk_name_and_version(&self) -> (String, String);
244
+ /// Get worker identity
245
+ fn get_identity(&self) -> String;
217
246
  }
218
247
 
219
248
  /// Configuration options shared by workflow, activity, and Nexus polling calls
@@ -267,6 +296,7 @@ impl WorkerClient for WorkerClientBag {
267
296
  binary_checksum: self.binary_checksum(),
268
297
  worker_version_capabilities: self.worker_version_capabilities(),
269
298
  deployment_options: self.deployment_options(),
299
+ worker_heartbeat: None,
270
300
  }
271
301
  .into_request();
272
302
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -303,6 +333,7 @@ impl WorkerClient for WorkerClientBag {
303
333
  }),
304
334
  worker_version_capabilities: self.worker_version_capabilities(),
305
335
  deployment_options: self.deployment_options(),
336
+ worker_heartbeat: None,
306
337
  }
307
338
  .into_request();
308
339
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -335,6 +366,7 @@ impl WorkerClient for WorkerClientBag {
335
366
  identity: self.identity.clone(),
336
367
  worker_version_capabilities: self.worker_version_capabilities(),
337
368
  deployment_options: self.deployment_options(),
369
+ worker_heartbeat: self.capture_heartbeat().into_iter().collect(),
338
370
  }
339
371
  .into_request();
340
372
  request.extensions_mut().insert(IsWorkerTaskLongPoll);
@@ -578,9 +610,20 @@ impl WorkerClient for WorkerClientBag {
578
610
  async fn respond_legacy_query(
579
611
  &self,
580
612
  task_token: TaskToken,
581
- query_result: QueryResult,
613
+ query_result: LegacyQueryResult,
582
614
  ) -> Result<RespondQueryTaskCompletedResponse> {
615
+ let mut failure = None;
616
+ let (query_result, cause) = match query_result {
617
+ LegacyQueryResult::Succeeded(s) => (s, WorkflowTaskFailedCause::Unspecified),
618
+ #[allow(deprecated)]
619
+ LegacyQueryResult::Failed(f) => {
620
+ let cause = f.force_cause();
621
+ failure = f.failure.clone();
622
+ (legacy_query_failure(f), cause)
623
+ }
624
+ };
583
625
  let (_, completed_type, query_result, error_message) = query_result.into_components();
626
+
584
627
  Ok(self
585
628
  .cloned_client()
586
629
  .respond_query_task_completed(RespondQueryTaskCompletedRequest {
@@ -589,8 +632,8 @@ impl WorkerClient for WorkerClientBag {
589
632
  query_result,
590
633
  error_message,
591
634
  namespace: self.namespace.clone(),
592
- // TODO: https://github.com/temporalio/sdk-core/issues/867
593
- failure: None,
635
+ failure,
636
+ cause: cause.into(),
594
637
  })
595
638
  .await?
596
639
  .into_inner())
@@ -612,6 +655,7 @@ impl WorkerClient for WorkerClientBag {
612
655
  identity: self.identity.clone(),
613
656
  sticky_task_queue,
614
657
  reason: "graceful shutdown".to_string(),
658
+ worker_heartbeat: self.capture_heartbeat(),
615
659
  };
616
660
 
617
661
  Ok(
@@ -626,6 +670,21 @@ impl WorkerClient for WorkerClientBag {
626
670
  *replaceable_client = new_client;
627
671
  }
628
672
 
673
+ async fn record_worker_heartbeat(
674
+ &self,
675
+ heartbeat: WorkerHeartbeat,
676
+ ) -> Result<RecordWorkerHeartbeatResponse> {
677
+ Ok(self
678
+ .cloned_client()
679
+ .record_worker_heartbeat(RecordWorkerHeartbeatRequest {
680
+ namespace: self.namespace.clone(),
681
+ identity: self.identity.clone(),
682
+ worker_heartbeat: vec![heartbeat],
683
+ })
684
+ .await?
685
+ .into_inner())
686
+ }
687
+
629
688
  fn capabilities(&self) -> Option<Capabilities> {
630
689
  let client = self.replaceable_client.read();
631
690
  client.get_client().inner().capabilities().cloned()
@@ -645,6 +704,10 @@ impl WorkerClient for WorkerClientBag {
645
704
  let opts = lock.get_client().inner().options();
646
705
  (opts.client_name.clone(), opts.client_version.clone())
647
706
  }
707
+
708
+ fn get_identity(&self) -> String {
709
+ self.identity.clone()
710
+ }
648
711
  }
649
712
 
650
713
  impl NamespacedClient for WorkerClientBag {