@temporalio/core-bridge 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/Cargo.lock +754 -473
  2. package/Cargo.toml +3 -3
  3. package/lib/index.d.ts +33 -2
  4. package/lib/index.js.map +1 -1
  5. package/package.json +4 -4
  6. package/releases/aarch64-apple-darwin/index.node +0 -0
  7. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  8. package/releases/x86_64-apple-darwin/index.node +0 -0
  9. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  10. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  11. package/scripts/build.js +4 -3
  12. package/sdk-core/.cargo/config.toml +2 -4
  13. package/sdk-core/.github/workflows/heavy.yml +1 -1
  14. package/sdk-core/.github/workflows/per-pr.yml +6 -4
  15. package/sdk-core/Cargo.toml +10 -3
  16. package/sdk-core/README.md +4 -6
  17. package/sdk-core/client/Cargo.toml +13 -5
  18. package/sdk-core/client/src/lib.rs +123 -34
  19. package/sdk-core/client/src/metrics.rs +70 -18
  20. package/sdk-core/client/src/proxy.rs +85 -0
  21. package/sdk-core/client/src/raw.rs +67 -5
  22. package/sdk-core/client/src/worker_registry/mod.rs +5 -3
  23. package/sdk-core/client/src/workflow_handle/mod.rs +3 -1
  24. package/sdk-core/core/Cargo.toml +31 -37
  25. package/sdk-core/core/src/abstractions/take_cell.rs +3 -3
  26. package/sdk-core/core/src/abstractions.rs +176 -108
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +4 -13
  28. package/sdk-core/core/src/core_tests/determinism.rs +2 -1
  29. package/sdk-core/core/src/core_tests/local_activities.rs +3 -3
  30. package/sdk-core/core/src/core_tests/mod.rs +3 -3
  31. package/sdk-core/core/src/core_tests/queries.rs +42 -5
  32. package/sdk-core/core/src/core_tests/workers.rs +2 -3
  33. package/sdk-core/core/src/core_tests/workflow_tasks.rs +115 -15
  34. package/sdk-core/core/src/ephemeral_server/mod.rs +109 -136
  35. package/sdk-core/core/src/internal_flags.rs +8 -8
  36. package/sdk-core/core/src/lib.rs +16 -11
  37. package/sdk-core/core/src/pollers/mod.rs +11 -5
  38. package/sdk-core/core/src/pollers/poll_buffer.rs +48 -29
  39. package/sdk-core/core/src/protosext/mod.rs +32 -32
  40. package/sdk-core/core/src/protosext/protocol_messages.rs +14 -24
  41. package/sdk-core/core/src/retry_logic.rs +2 -2
  42. package/sdk-core/core/src/telemetry/log_export.rs +10 -9
  43. package/sdk-core/core/src/telemetry/metrics.rs +233 -330
  44. package/sdk-core/core/src/telemetry/mod.rs +11 -38
  45. package/sdk-core/core/src/telemetry/otel.rs +355 -0
  46. package/sdk-core/core/src/telemetry/prometheus_server.rs +36 -23
  47. package/sdk-core/core/src/test_help/mod.rs +80 -59
  48. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +6 -6
  49. package/sdk-core/core/src/worker/activities/local_activities.rs +46 -43
  50. package/sdk-core/core/src/worker/activities.rs +45 -46
  51. package/sdk-core/core/src/worker/client/mocks.rs +8 -7
  52. package/sdk-core/core/src/worker/client.rs +40 -39
  53. package/sdk-core/core/src/worker/mod.rs +72 -42
  54. package/sdk-core/core/src/worker/slot_provider.rs +28 -28
  55. package/sdk-core/core/src/worker/slot_supplier.rs +1 -0
  56. package/sdk-core/core/src/worker/tuner/fixed_size.rs +52 -0
  57. package/sdk-core/core/src/worker/tuner/resource_based.rs +561 -0
  58. package/sdk-core/core/src/worker/tuner.rs +122 -0
  59. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +6 -6
  60. package/sdk-core/core/src/worker/workflow/history_update.rs +27 -53
  61. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +4 -17
  62. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -10
  63. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +4 -11
  64. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +17 -35
  65. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +0 -8
  66. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +1 -5
  67. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +0 -5
  68. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +0 -5
  69. package/sdk-core/core/src/worker/workflow/machines/mod.rs +0 -14
  70. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -5
  71. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +0 -5
  72. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -10
  73. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +3 -10
  74. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +12 -8
  75. package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +0 -10
  76. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -13
  77. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +27 -37
  78. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +3 -14
  79. package/sdk-core/core/src/worker/workflow/managed_run.rs +84 -54
  80. package/sdk-core/core/src/worker/workflow/mod.rs +63 -160
  81. package/sdk-core/core/src/worker/workflow/run_cache.rs +22 -13
  82. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +16 -3
  83. package/sdk-core/core/src/worker/workflow/wft_poller.rs +15 -12
  84. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +39 -78
  85. package/sdk-core/core-api/Cargo.toml +6 -5
  86. package/sdk-core/core-api/src/errors.rs +8 -0
  87. package/sdk-core/core-api/src/telemetry/metrics.rs +75 -4
  88. package/sdk-core/core-api/src/telemetry.rs +7 -1
  89. package/sdk-core/core-api/src/worker.rs +212 -56
  90. package/sdk-core/fsm/Cargo.toml +3 -0
  91. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +1 -1
  92. package/sdk-core/sdk/Cargo.toml +5 -7
  93. package/sdk-core/sdk/src/app_data.rs +3 -3
  94. package/sdk-core/sdk/src/lib.rs +5 -3
  95. package/sdk-core/sdk/src/workflow_context/options.rs +1 -1
  96. package/sdk-core/sdk/src/workflow_context.rs +10 -9
  97. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  98. package/sdk-core/sdk-core-protos/Cargo.toml +8 -6
  99. package/sdk-core/sdk-core-protos/build.rs +1 -10
  100. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/PULL_REQUEST_TEMPLATE.md +3 -0
  101. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/ci.yml +26 -0
  102. package/sdk-core/sdk-core-protos/protos/api_upstream/Makefile +42 -20
  103. package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +2 -0
  104. package/sdk-core/sdk-core-protos/protos/api_upstream/api-linter.yaml +36 -26
  105. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.lock +2 -0
  106. package/sdk-core/sdk-core-protos/protos/api_upstream/google/protobuf/struct.proto +95 -0
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +9632 -0
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +7337 -0
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/payload_description.txt +2 -0
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +45 -11
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +22 -4
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/command_type.proto +2 -0
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +44 -0
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +18 -3
  115. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +20 -0
  116. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +30 -0
  117. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +7 -8
  118. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/workflow.proto +23 -5
  119. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/errordetails/v1/message.proto +20 -0
  120. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +25 -0
  121. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +141 -15
  122. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +12 -0
  123. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +193 -0
  124. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +73 -6
  125. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +46 -4
  126. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +4 -0
  127. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +2 -2
  128. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +116 -0
  129. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +134 -0
  130. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +274 -29
  131. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +57 -1
  132. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +10 -12
  133. package/sdk-core/sdk-core-protos/src/history_builder.rs +1 -1
  134. package/sdk-core/sdk-core-protos/src/lib.rs +54 -51
  135. package/sdk-core/sdk-core-protos/src/task_token.rs +11 -2
  136. package/sdk-core/test-utils/Cargo.toml +7 -4
  137. package/sdk-core/test-utils/src/histfetch.rs +1 -1
  138. package/sdk-core/test-utils/src/lib.rs +44 -62
  139. package/sdk-core/tests/fuzzy_workflow.rs +5 -2
  140. package/sdk-core/tests/heavy_tests.rs +114 -17
  141. package/sdk-core/tests/integ_tests/activity_functions.rs +1 -1
  142. package/sdk-core/tests/integ_tests/client_tests.rs +2 -2
  143. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +38 -26
  144. package/sdk-core/tests/integ_tests/metrics_tests.rs +126 -17
  145. package/sdk-core/tests/integ_tests/polling_tests.rs +118 -2
  146. package/sdk-core/tests/integ_tests/update_tests.rs +3 -5
  147. package/sdk-core/tests/integ_tests/visibility_tests.rs +3 -3
  148. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +1 -1
  149. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
  150. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -1
  151. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  152. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +3 -3
  153. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +5 -4
  154. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -2
  155. package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +6 -10
  156. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +9 -7
  157. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +1 -1
  158. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +14 -9
  159. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  160. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +6 -13
  161. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +9 -6
  162. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +5 -5
  163. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +1 -1
  164. package/sdk-core/tests/integ_tests/workflow_tests.rs +115 -11
  165. package/sdk-core/tests/main.rs +2 -2
  166. package/src/conversions.rs +57 -0
  167. package/src/lib.rs +1 -0
  168. package/src/runtime.rs +51 -35
  169. package/ts/index.ts +67 -3
  170. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +0 -117
  171. package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +0 -24
  172. package/sdk-core/sdk/src/payload_converter.rs +0 -11
  173. package/sdk-core/sdk-core-protos/protos/api_upstream/.buildkite/Dockerfile +0 -2
  174. package/sdk-core/sdk-core-protos/protos/api_upstream/.buildkite/docker-compose.yml +0 -15
  175. package/sdk-core/sdk-core-protos/protos/api_upstream/.buildkite/pipeline.yml +0 -10
  176. package/sdk-core/test-utils/src/wf_input_saver.rs +0 -50
  177. package/sdk-core/tests/wf_input_replay.rs +0 -32
@@ -10,7 +10,10 @@ use crate::{
10
10
  test_worker_cfg, FakeWfResponses, MockPollCfg, MocksHolder, ResponseType, WorkerExt,
11
11
  WorkflowCachingPolicy::{self, AfterEveryReply, NonSticky},
12
12
  },
13
- worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
13
+ worker::{
14
+ client::mocks::{mock_manual_workflow_client, mock_workflow_client},
15
+ TunerBuilder,
16
+ },
14
17
  Worker,
15
18
  };
16
19
  use futures::{stream, FutureExt};
@@ -26,7 +29,13 @@ use std::{
26
29
  };
27
30
  use temporal_client::WorkflowOptions;
28
31
  use temporal_sdk::{ActivityOptions, CancellableFuture, WfContext};
29
- use temporal_sdk_core_api::{errors::PollWfError, Worker as WorkerTrait};
32
+ use temporal_sdk_core_api::{
33
+ errors::PollWfError,
34
+ worker::{
35
+ SlotKind, SlotReservationContext, SlotSupplier, SlotSupplierPermit, WorkflowSlotKind,
36
+ },
37
+ Worker as WorkerTrait,
38
+ };
30
39
  use temporal_sdk_core_protos::{
31
40
  coresdk::{
32
41
  activity_result::{self as ar, activity_resolution, ActivityResolution},
@@ -911,7 +920,7 @@ async fn max_wft_respected() {
911
920
  let mh = MockPollCfg::new(hists.into_iter().collect(), true, 0);
912
921
  let mut worker = mock_sdk_cfg(mh, |cfg| {
913
922
  cfg.max_cached_workflows = total_wfs as usize;
914
- cfg.max_outstanding_workflow_tasks = 1;
923
+ cfg.max_outstanding_workflow_tasks = Some(1);
915
924
  });
916
925
  let active_count: &'static _ = Box::leak(Box::new(Semaphore::new(1)));
917
926
  worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
@@ -1506,7 +1515,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
1506
1515
  let mut mock = build_mock_pollers(mock);
1507
1516
  mock.worker_cfg(|cfg| {
1508
1517
  cfg.max_cached_workflows = 2;
1509
- cfg.max_outstanding_workflow_tasks = 2;
1518
+ cfg.max_outstanding_workflow_tasks = Some(2);
1510
1519
  });
1511
1520
  let outstanding_mock_tasks = mock.outstanding_task_map.clone();
1512
1521
  let worker = mock_worker(mock);
@@ -1531,7 +1540,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
1531
1540
  variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
1532
1541
  },]
1533
1542
  );
1534
- run_id = activation.run_id.clone();
1543
+ run_id.clone_from(&activation.run_id);
1535
1544
  worker
1536
1545
  .complete_workflow_activation(WorkflowActivationCompletion::empty(activation.run_id))
1537
1546
  .await
@@ -1546,7 +1555,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
1546
1555
  outstanding_mock_tasks.unwrap().release_run(&run_id);
1547
1556
  let activation = worker.poll_workflow_activation().await.unwrap();
1548
1557
  // There should be no change in permits, since this just unbuffered the buffered task
1549
- assert_eq!(worker.available_wft_permits(), 1);
1558
+ assert_eq!(worker.available_wft_permits(), Some(1));
1550
1559
  worker
1551
1560
  .complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
1552
1561
  activation.run_id,
@@ -1555,7 +1564,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
1555
1564
  .await
1556
1565
  .unwrap();
1557
1566
  worker.shutdown().await;
1558
- assert_eq!(worker.available_wft_permits(), 2);
1567
+ assert_eq!(worker.available_wft_permits(), Some(2));
1559
1568
  }
1560
1569
 
1561
1570
  #[tokio::test]
@@ -1582,7 +1591,7 @@ async fn cache_miss_will_fetch_history() {
1582
1591
  mock.worker_cfg(|cfg| {
1583
1592
  cfg.max_cached_workflows = 1;
1584
1593
  // Also verifies tying the WFT permit to the fetch request doesn't get us stuck
1585
- cfg.max_outstanding_workflow_tasks = 1;
1594
+ cfg.max_outstanding_workflow_tasks = Some(1);
1586
1595
  });
1587
1596
  let worker = mock_worker(mock);
1588
1597
 
@@ -1808,7 +1817,7 @@ async fn poll_faster_than_complete_wont_overflow_cache() {
1808
1817
  let mut mock = build_mock_pollers(mock_cfg);
1809
1818
  mock.worker_cfg(|wc| {
1810
1819
  wc.max_cached_workflows = 3;
1811
- wc.max_outstanding_workflow_tasks = 3;
1820
+ wc.max_outstanding_workflow_tasks = Some(3);
1812
1821
  });
1813
1822
  let core = mock_worker(mock);
1814
1823
  // Poll 4 times, completing once, such that max tasks are never exceeded
@@ -1915,7 +1924,7 @@ async fn poll_faster_than_complete_wont_overflow_cache() {
1915
1924
 
1916
1925
  join!(blocking_poll, complete_evict);
1917
1926
  // p5 outstanding and final poll outstanding -- hence one permit available
1918
- assert_eq!(core.available_wft_permits(), 1);
1927
+ assert_eq!(core.available_wft_permits(), Some(1));
1919
1928
  assert_eq!(core.cached_workflows().await, 3);
1920
1929
  }
1921
1930
 
@@ -2663,8 +2672,8 @@ async fn poller_wont_run_ahead_of_task_slots() {
2663
2672
  }
2664
2673
 
2665
2674
  assert_eq!(worker.outstanding_workflow_tasks().await, 10);
2666
- assert_eq!(worker.available_wft_permits(), 0);
2667
- assert_eq!(worker.unused_wft_permits(), 0);
2675
+ assert_eq!(worker.available_wft_permits(), Some(0));
2676
+ assert_eq!(worker.unused_wft_permits(), Some(0));
2668
2677
 
2669
2678
  // This one should hang until we complete some tasks since we're at the limit
2670
2679
  let hung_poll = async {
@@ -2752,13 +2761,13 @@ async fn use_compatible_version_flag(
2752
2761
  let can_cmd = c.commands.pop().unwrap().attributes.unwrap();
2753
2762
  match can_cmd {
2754
2763
  Attributes::ContinueAsNewWorkflowExecutionCommandAttributes(a) => {
2755
- assert_eq!(a.use_compatible_version, compat_flag_expected);
2764
+ assert_eq!(a.inherit_build_id, compat_flag_expected);
2756
2765
  }
2757
2766
  Attributes::ScheduleActivityTaskCommandAttributes(a) => {
2758
- assert_eq!(a.use_compatible_version, compat_flag_expected);
2767
+ assert_eq!(a.use_workflow_build_id, compat_flag_expected);
2759
2768
  }
2760
2769
  Attributes::StartChildWorkflowExecutionCommandAttributes(a) => {
2761
- assert_eq!(a.use_compatible_version, compat_flag_expected);
2770
+ assert_eq!(a.inherit_build_id, compat_flag_expected);
2762
2771
  }
2763
2772
  _ => panic!("invalid attributes type"),
2764
2773
  }
@@ -2855,3 +2864,94 @@ async fn sets_build_id_from_wft_complete() {
2855
2864
  .unwrap();
2856
2865
  worker.run_until_done().await.unwrap();
2857
2866
  }
2867
+
2868
+ #[tokio::test]
2869
+ async fn slot_provider_cant_hand_out_more_permits_than_cache_size() {
2870
+ let popped_tasks = Arc::new(AtomicUsize::new(0));
2871
+ let ptc = popped_tasks.clone();
2872
+ let mut bunch_of_first_tasks = (1..50).map(move |i| {
2873
+ ptc.fetch_add(1, Ordering::Relaxed);
2874
+ hist_to_poll_resp(
2875
+ &canned_histories::single_timer(&format!("{i}")),
2876
+ format!("wf-{i}"),
2877
+ 1.into(),
2878
+ )
2879
+ .resp
2880
+ });
2881
+ let mut mock_client = mock_workflow_client();
2882
+ mock_client
2883
+ .expect_poll_workflow_task()
2884
+ .returning(move |_| Ok(bunch_of_first_tasks.next().unwrap()));
2885
+ mock_client
2886
+ .expect_complete_workflow_task()
2887
+ .returning(|_| Ok(Default::default()));
2888
+
2889
+ struct EndlessSupplier {}
2890
+ #[async_trait::async_trait]
2891
+ impl SlotSupplier for EndlessSupplier {
2892
+ type SlotKind = WorkflowSlotKind;
2893
+ async fn reserve_slot(&self, _: &dyn SlotReservationContext) -> SlotSupplierPermit {
2894
+ SlotSupplierPermit::default()
2895
+ }
2896
+ fn try_reserve_slot(&self, _: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
2897
+ Some(SlotSupplierPermit::default())
2898
+ }
2899
+ fn mark_slot_used(&self, _: <Self::SlotKind as SlotKind>::Info<'_>) {}
2900
+ fn release_slot(&self) {}
2901
+ fn available_slots(&self) -> Option<usize> {
2902
+ None
2903
+ }
2904
+ }
2905
+
2906
+ let worker = Worker::new_test(
2907
+ test_worker_cfg()
2908
+ .max_cached_workflows(10_usize)
2909
+ .tuner(
2910
+ TunerBuilder::default()
2911
+ .workflow_slot_supplier(Arc::new(EndlessSupplier {}))
2912
+ .build(),
2913
+ )
2914
+ .max_concurrent_wft_polls(10_usize)
2915
+ .no_remote_activities(true)
2916
+ .build()
2917
+ .unwrap(),
2918
+ mock_client,
2919
+ );
2920
+
2921
+ // Should be able to get at 10 tasks
2922
+ let mut tasks = vec![];
2923
+ for _ in 0..10 {
2924
+ tasks.push(worker.poll_workflow_activation().await.unwrap());
2925
+ }
2926
+ // 11th should hang
2927
+
2928
+ assert_eq!(worker.outstanding_workflow_tasks().await, 10);
2929
+ // assert_eq!(worker.available_wft_permits(), Some(0));
2930
+ // assert_eq!(worker.unused_wft_permits(), Some(0));
2931
+
2932
+ // This one should hang until we complete some tasks since we're at the limit
2933
+ let hung_poll = async {
2934
+ // This should end up getting shut down after the other routine finishes tasks
2935
+ assert_matches!(
2936
+ worker.poll_workflow_activation().await.unwrap_err(),
2937
+ PollWfError::ShutDown
2938
+ );
2939
+ };
2940
+ // Wait for a bit concurrently with above, verify no extra tasks got taken, shutdown
2941
+ let ender = async {
2942
+ time::sleep(Duration::from_millis(300)).await;
2943
+ // initiate shutdown, then complete open tasks
2944
+ worker.initiate_shutdown();
2945
+ for t in tasks {
2946
+ worker
2947
+ .complete_workflow_activation(WorkflowActivationCompletion::empty(t.run_id))
2948
+ .await
2949
+ .unwrap();
2950
+ }
2951
+ worker.shutdown().await;
2952
+ };
2953
+ join!(hung_poll, ender);
2954
+ // We shouldn't have got more than the 10 tasks from the poller -- verifying that the concurrent
2955
+ // polling is not exceeding the task limit
2956
+ assert_eq!(popped_tasks.load(Ordering::Relaxed), 10);
2957
+ }
@@ -7,6 +7,7 @@ use futures::StreamExt;
7
7
  use serde::Deserialize;
8
8
  use std::{
9
9
  fs::OpenOptions,
10
+ io,
10
11
  path::{Path, PathBuf},
11
12
  };
12
13
  use temporal_client::ClientOptionsBuilder;
@@ -22,99 +23,6 @@ use zip::read::read_zipfile_from_stream;
22
23
  use std::os::unix::fs::OpenOptionsExt;
23
24
  use std::process::Stdio;
24
25
 
25
- /// Configuration for Temporalite.
26
- /// Will be removed eventually as its successor, Temporal CLI matures.
27
- /// We don't care for the duplication between this struct and [TemporalDevServerConfig] and prefer that over another
28
- /// abstraction since the existence of this struct is temporary.
29
- #[derive(Debug, Clone, derive_builder::Builder)]
30
- pub struct TemporaliteConfig {
31
- /// Required path to executable or download info.
32
- pub exe: EphemeralExe,
33
- /// Namespace to use.
34
- #[builder(default = "\"default\".to_owned()")]
35
- pub namespace: String,
36
- /// IP to bind to.
37
- #[builder(default = "\"127.0.0.1\".to_owned()")]
38
- pub ip: String,
39
- /// Port to use or obtains a free one if none given.
40
- #[builder(default)]
41
- pub port: Option<u16>,
42
- /// Sqlite DB filename if persisting or non-persistent if none.
43
- #[builder(default)]
44
- pub db_filename: Option<String>,
45
- /// Whether to enable the UI.
46
- #[builder(default)]
47
- pub ui: bool,
48
- /// Log format and level
49
- #[builder(default = "(\"pretty\".to_owned(), \"warn\".to_owned())")]
50
- pub log: (String, String),
51
- /// Additional arguments to Temporalite.
52
- #[builder(default)]
53
- pub extra_args: Vec<String>,
54
- }
55
-
56
- impl TemporaliteConfig {
57
- /// Start a Temporalite server.
58
- pub async fn start_server(&self) -> anyhow::Result<EphemeralServer> {
59
- self.start_server_with_output(Stdio::inherit(), Stdio::inherit())
60
- .await
61
- }
62
-
63
- /// Start a Temporalite server with configurable stdout destination.
64
- pub async fn start_server_with_output(
65
- &self,
66
- output: Stdio,
67
- err_output: Stdio,
68
- ) -> anyhow::Result<EphemeralServer> {
69
- // Get exe path
70
- let exe_path = self
71
- .exe
72
- .get_or_download("temporalite", "temporalite", None)
73
- .await?;
74
-
75
- // Get free port if not already given
76
- let port = self.port.unwrap_or_else(|| get_free_port(&self.ip));
77
-
78
- // Build arg set
79
- let mut args = vec![
80
- "start".to_owned(),
81
- "--port".to_owned(),
82
- port.to_string(),
83
- "--namespace".to_owned(),
84
- self.namespace.clone(),
85
- "--ip".to_owned(),
86
- self.ip.clone(),
87
- "--log-format".to_owned(),
88
- self.log.0.clone(),
89
- "--log-level".to_owned(),
90
- self.log.1.clone(),
91
- "--dynamic-config-value".to_owned(),
92
- "frontend.enableServerVersionCheck=false".to_owned(),
93
- ];
94
- if let Some(db_filename) = &self.db_filename {
95
- args.push("--filename".to_owned());
96
- args.push(db_filename.clone());
97
- } else {
98
- args.push("--ephemeral".to_owned());
99
- }
100
- if !self.ui {
101
- args.push("--headless".to_owned());
102
- }
103
- args.extend(self.extra_args.clone());
104
-
105
- // Start
106
- EphemeralServer::start(EphemeralServerConfig {
107
- exe_path,
108
- port,
109
- args,
110
- has_test_service: false,
111
- output,
112
- err_output,
113
- })
114
- .await
115
- }
116
- }
117
-
118
26
  /// Configuration for Temporal CLI dev server.
119
27
  #[derive(Debug, Clone, derive_builder::Builder)]
120
28
  pub struct TemporalDevServerConfig {
@@ -163,7 +71,10 @@ impl TemporalDevServerConfig {
163
71
  .await?;
164
72
 
165
73
  // Get free port if not already given
166
- let port = self.port.unwrap_or_else(|| get_free_port(&self.ip));
74
+ let port = match self.port {
75
+ Some(p) => p,
76
+ None => get_free_port(&self.ip)?,
77
+ };
167
78
 
168
79
  // Build arg set
169
80
  let mut args = vec![
@@ -241,7 +152,10 @@ impl TestServerConfig {
241
152
  .await?;
242
153
 
243
154
  // Get free port if not already given
244
- let port = self.port.unwrap_or_else(|| get_free_port("0.0.0.0"));
155
+ let port = match self.port {
156
+ Some(p) => p,
157
+ None => get_free_port("0.0.0.0")?,
158
+ };
245
159
 
246
160
  // Build arg set
247
161
  let mut args = vec![port.to_string()];
@@ -307,11 +221,7 @@ impl EphemeralServer {
307
221
  .build()?;
308
222
  for _ in 0..50 {
309
223
  sleep(Duration::from_millis(100)).await;
310
- if client_options
311
- .connect_no_namespace(None, None)
312
- .await
313
- .is_ok()
314
- {
224
+ if client_options.connect_no_namespace(None).await.is_ok() {
315
225
  return success;
316
226
  }
317
227
  }
@@ -322,7 +232,6 @@ impl EphemeralServer {
322
232
  /// a kill if the child process appears completed, but such a check is not
323
233
  /// atomic so a kill could still fail as completed if completed just before
324
234
  /// kill.
325
- #[cfg(not(target_family = "unix"))]
326
235
  pub async fn shutdown(&mut self) -> anyhow::Result<()> {
327
236
  // Only kill if there is a PID
328
237
  if self.child.id().is_some() {
@@ -332,32 +241,10 @@ impl EphemeralServer {
332
241
  }
333
242
  }
334
243
 
335
- /// Shutdown the server (i.e. kill the child process). This does not attempt
336
- /// a kill if the child process appears completed, but such a check is not
337
- /// atomic so a kill could still fail as completed if completed just before
338
- /// kill.
339
- #[cfg(target_family = "unix")]
340
- pub async fn shutdown(&mut self) -> anyhow::Result<()> {
341
- // For whatever reason, Tokio is not properly waiting on result
342
- // after sending kill in some cases which is causing defunct zombie
343
- // processes to remain and kill() to hang. Therefore, we are sending
344
- // SIGKILL and waiting on the process ourselves using a low-level call.
345
- //
346
- // WARNING: This is based on empirical evidence starting a Python test
347
- // run on Linux with Python 3.7 (does not happen on Python 3.10 nor does
348
- // it happen on Temporalite nor does it happen in Rust integration
349
- // tests). Don't alter without running that scenario. EX: SIGINT works but not SIGKILL
350
- if let Some(pid) = self.child.id() {
351
- let nix_pid = nix::unistd::Pid::from_raw(pid as i32);
352
- Ok(spawn_blocking(move || {
353
- nix::sys::signal::kill(nix_pid, nix::sys::signal::Signal::SIGINT)?;
354
- nix::sys::wait::waitpid(Some(nix_pid), None)
355
- })
356
- .await?
357
- .map(|_| ())?)
358
- } else {
359
- Ok(())
360
- }
244
+ /// Get the process ID of the child. This will be None if the process is
245
+ /// considered to be complete.
246
+ pub fn child_process_id(&self) -> Option<u32> {
247
+ self.child.id()
361
248
  }
362
249
  }
363
250
 
@@ -492,14 +379,53 @@ impl EphemeralExe {
492
379
  }
493
380
  }
494
381
 
495
- fn get_free_port(bind_ip: &str) -> u16 {
496
- // Can just ask OS to give us a port then close socket. OS's don't give that
497
- // port back to anyone else anytime soon.
498
- std::net::TcpListener::bind(format!("{bind_ip}:0"))
499
- .unwrap()
500
- .local_addr()
501
- .unwrap()
502
- .port()
382
+ /// Returns a TCP port that is available to listen on for the given local host.
383
+ ///
384
+ /// This works by binding a new TCP socket on port 0, which requests the OS to
385
+ /// allocate a free port. There is no strict guarantee that the port will remain
386
+ /// available after this function returns, but it should be safe to assume that
387
+ /// a given port will not be allocated again to any process on this machine
388
+ /// within a few seconds.
389
+ ///
390
+ /// On Unix-based systems, binding to the port returned by this function
391
+ /// requires setting the `SO_REUSEADDR` socket option (Rust already does that by
392
+ /// default, but other languages may not); otherwise, the OS may fail with a
393
+ /// message such as "address already in use". Windows default behavior is
394
+ /// already appropriate in this regard; on that platform, `SO_REUSEADDR` has a
395
+ /// different meaning and should not be set (setting it may have unpredictable
396
+ /// consequences).
397
+ fn get_free_port(bind_ip: &str) -> io::Result<u16> {
398
+ let listen = std::net::TcpListener::bind((bind_ip, 0))?;
399
+ let addr = listen.local_addr()?;
400
+
401
+ // On Linux and some BSD variants, ephemeral ports are randomized, and may
402
+ // consequently repeat within a short time frame after the listenning end
403
+ // has been closed. To avoid this, we make a connection to the port, then
404
+ // close that connection from the server's side (this is very important),
405
+ // which puts the connection in TIME_WAIT state for some time (by default,
406
+ // 60s on Linux). While it remains in that state, the OS will not reallocate
407
+ // that port number for bind(:0) syscalls, yet we are not prevented from
408
+ // explicitly binding to it (thanks to SO_REUSEADDR).
409
+ //
410
+ // On macOS and Windows, the above technique is not necessary, as the OS
411
+ // allocates ephemeral ports sequentially, meaning a port number will only
412
+ // be reused after the entire range has been exhausted. Quite the opposite,
413
+ // given that these OSes use a significantly smaller range for ephemeral
414
+ // ports, making an extra connection just to reserve a port might actually
415
+ // be harmful (by hastening ephemeral port exhaustion).
416
+ #[cfg(not(any(target_os = "windows", target_os = "macos")))]
417
+ {
418
+ // Establish a connection to the bind_ip:port
419
+ let _stream = std::net::TcpStream::connect(addr)?;
420
+
421
+ // Accept the connection from the listening side
422
+ let (socket, _addr) = listen.accept()?;
423
+
424
+ // Explicitly drop the socket to close the connection from the listening side first
425
+ std::mem::drop(socket);
426
+ }
427
+
428
+ Ok(addr.port())
503
429
  }
504
430
 
505
431
  /// Returns false if we successfully waited for another download to complete, or
@@ -625,7 +551,7 @@ async fn download_and_extract(
625
551
  // that requires Seek.
626
552
  if let Some(mut file) = read_zipfile_from_stream(&mut reader)? {
627
553
  // If this is the file we're expecting, extract it
628
- if file.enclosed_name() == Some(&file_to_extract) {
554
+ if file.enclosed_name().as_ref() == Some(&file_to_extract) {
629
555
  std::io::copy(&mut file, &mut dest)?;
630
556
  return Ok(());
631
557
  }
@@ -637,3 +563,50 @@ async fn download_and_extract(
637
563
  })
638
564
  .await?
639
565
  }
566
+
567
+ #[cfg(test)]
568
+ mod tests {
569
+ use super::get_free_port;
570
+ use std::collections::HashSet;
571
+ use std::net::{TcpListener, TcpStream};
572
+
573
+ #[test]
574
+ fn get_free_port_no_double() {
575
+ let host = "127.0.0.1";
576
+ let mut port_set = HashSet::new();
577
+
578
+ for _ in 0..2000 {
579
+ let port = get_free_port(host).unwrap();
580
+ assert!(
581
+ !port_set.contains(&port),
582
+ "Port {port} has been assigned more than once"
583
+ );
584
+
585
+ // Add port to the set
586
+ port_set.insert(port);
587
+ }
588
+ }
589
+
590
+ #[test]
591
+ fn get_free_port_can_bind_immediately() {
592
+ let host = "127.0.0.1";
593
+
594
+ for _ in 0..500 {
595
+ let port = get_free_port(host).unwrap();
596
+ try_listen_and_dial_on(host, port).expect("Failed to bind to port");
597
+ }
598
+ }
599
+
600
+ fn try_listen_and_dial_on(host: &str, port: u16) -> std::io::Result<()> {
601
+ let listener = TcpListener::bind((host, port))?;
602
+ let _stream = TcpStream::connect((host, port))?;
603
+
604
+ // Accept the connection from the listening side
605
+ let (socket, _addr) = listener.accept()?;
606
+
607
+ // Explicitly drop the socket to close the connection from the listening side first
608
+ std::mem::drop(socket);
609
+
610
+ Ok(())
611
+ }
612
+ }
@@ -43,7 +43,7 @@ pub(crate) enum InternalFlags {
43
43
  }
44
44
 
45
45
  impl InternalFlags {
46
- pub fn new(server_capabilities: &get_system_info_response::Capabilities) -> Self {
46
+ pub(crate) fn new(server_capabilities: &get_system_info_response::Capabilities) -> Self {
47
47
  match server_capabilities.sdk_metadata {
48
48
  true => Self::Enabled {
49
49
  core: Default::default(),
@@ -55,7 +55,7 @@ impl InternalFlags {
55
55
  }
56
56
  }
57
57
 
58
- pub fn add_from_complete(&mut self, e: &WorkflowTaskCompletedEventAttributes) {
58
+ pub(crate) fn add_from_complete(&mut self, e: &WorkflowTaskCompletedEventAttributes) {
59
59
  if let Self::Enabled { core, lang, .. } = self {
60
60
  if let Some(metadata) = e.sdk_metadata.as_ref() {
61
61
  core.extend(
@@ -69,7 +69,7 @@ impl InternalFlags {
69
69
  }
70
70
  }
71
71
 
72
- pub fn add_lang_used(&mut self, flags: impl IntoIterator<Item = u32>) {
72
+ pub(crate) fn add_lang_used(&mut self, flags: impl IntoIterator<Item = u32>) {
73
73
  if let Self::Enabled {
74
74
  lang_since_last_complete,
75
75
  ..
@@ -82,7 +82,7 @@ impl InternalFlags {
82
82
  /// Returns true if this flag may currently be used. If `should_record` is true, always returns
83
83
  /// true and records the flag as being used, for taking later via
84
84
  /// [Self::gather_for_wft_complete].
85
- pub fn try_use(&mut self, core_patch: CoreInternalFlags, should_record: bool) -> bool {
85
+ pub(crate) fn try_use(&mut self, core_patch: CoreInternalFlags, should_record: bool) -> bool {
86
86
  match self {
87
87
  Self::Enabled {
88
88
  core,
@@ -104,7 +104,7 @@ impl InternalFlags {
104
104
 
105
105
  /// Writes all known core flags to the set which should be recorded in the current WFT if not
106
106
  /// already known. Must only be called if not replaying.
107
- pub fn write_all_known(&mut self) {
107
+ pub(crate) fn write_all_known(&mut self) {
108
108
  if let Self::Enabled {
109
109
  core_since_last_complete,
110
110
  ..
@@ -117,7 +117,7 @@ impl InternalFlags {
117
117
  /// Wipes the recorded flags used during the current WFT and returns a partially filled
118
118
  /// sdk metadata message that can be combined with any existing data before sending the WFT
119
119
  /// complete
120
- pub fn gather_for_wft_complete(&mut self) -> WorkflowTaskCompletedMetadata {
120
+ pub(crate) fn gather_for_wft_complete(&mut self) -> WorkflowTaskCompletedMetadata {
121
121
  match self {
122
122
  Self::Enabled {
123
123
  core_since_last_complete,
@@ -148,7 +148,7 @@ impl InternalFlags {
148
148
  }
149
149
  }
150
150
 
151
- pub fn all_lang(&self) -> impl Iterator<Item = u32> + '_ {
151
+ pub(crate) fn all_lang(&self) -> impl Iterator<Item = u32> + '_ {
152
152
  match self {
153
153
  Self::Enabled { lang, .. } => Either::Left(lang.iter().copied()),
154
154
  Self::Disabled => Either::Right(iter::empty()),
@@ -165,7 +165,7 @@ impl CoreInternalFlags {
165
165
  }
166
166
  }
167
167
 
168
- pub fn all_except_too_high() -> impl Iterator<Item = CoreInternalFlags> {
168
+ pub(crate) fn all_except_too_high() -> impl Iterator<Item = CoreInternalFlags> {
169
169
  enum_iterator::all::<CoreInternalFlags>()
170
170
  .filter(|f| !matches!(f, CoreInternalFlags::TooHigh))
171
171
  }
@@ -38,9 +38,10 @@ pub use temporal_sdk_core_api as api;
38
38
  pub use temporal_sdk_core_protos as protos;
39
39
  pub use temporal_sdk_core_protos::TaskToken;
40
40
  pub use url::Url;
41
- #[cfg(feature = "save_wf_inputs")]
42
- pub use worker::replay_wf_state_inputs;
43
- pub use worker::{Worker, WorkerConfig, WorkerConfigBuilder};
41
+ pub use worker::{
42
+ FixedSizeSlotSupplier, RealSysInfo, ResourceBasedSlots, ResourceBasedTuner,
43
+ ResourceSlotOptions, TunerBuilder, TunerHolder, Worker, WorkerConfig, WorkerConfigBuilder,
44
+ };
44
45
 
45
46
  use crate::{
46
47
  replay::{HistoryForReplay, ReplayWorkerInput},
@@ -79,14 +80,7 @@ pub fn init_worker<CT>(
79
80
  where
80
81
  CT: Into<sealed::AnyClient>,
81
82
  {
82
- let client = {
83
- let ll = client.into().into_inner();
84
- let mut client = Client::new(*ll, worker_config.namespace.clone());
85
- if let Some(ref id_override) = worker_config.client_identity_override {
86
- client.options_mut().identity = id_override.clone();
87
- }
88
- RetryClient::new(client, RetryConfig::default())
89
- };
83
+ let client = init_worker_client(&worker_config, *client.into().into_inner());
90
84
  if client.namespace() != worker_config.namespace {
91
85
  panic!("Passed in client is not bound to the same namespace as the worker");
92
86
  }
@@ -125,6 +119,17 @@ where
125
119
  rwi.into_core_worker()
126
120
  }
127
121
 
122
+ pub(crate) fn init_worker_client(
123
+ config: &WorkerConfig,
124
+ client: ConfiguredClient<TemporalServiceClientWithMetrics>,
125
+ ) -> RetryClient<Client> {
126
+ let mut client = Client::new(client, config.namespace.clone());
127
+ if let Some(ref id_override) = config.client_identity_override {
128
+ client.options_mut().identity.clone_from(id_override);
129
+ }
130
+ RetryClient::new(client, RetryConfig::default())
131
+ }
132
+
128
133
  /// Creates a unique sticky queue name for a worker, iff the config allows for 1 or more cached
129
134
  /// workflows.
130
135
  pub(crate) fn sticky_q_name_for_worker(
@@ -17,15 +17,16 @@ use temporal_sdk_core_protos::temporal::api::workflowservice::v1::{
17
17
  use futures::Future;
18
18
  #[cfg(test)]
19
19
  pub(crate) use poll_buffer::MockPermittedPollBuffer;
20
+ use temporal_sdk_core_api::worker::{ActivitySlotKind, WorkflowSlotKind};
20
21
 
21
- pub type Result<T, E = tonic::Status> = std::result::Result<T, E>;
22
+ pub(crate) type Result<T, E = tonic::Status> = std::result::Result<T, E>;
22
23
 
23
24
  /// A trait for things that poll the server. Hides complexity of concurrent polling or polling
24
25
  /// on sticky/nonsticky queues simultaneously.
25
26
  #[cfg_attr(test, mockall::automock)]
26
27
  #[cfg_attr(test, allow(unused))]
27
28
  #[async_trait::async_trait]
28
- pub trait Poller<PollResult>
29
+ pub(crate) trait Poller<PollResult>
29
30
  where
30
31
  PollResult: Send + Sync + 'static,
31
32
  {
@@ -36,9 +37,14 @@ where
36
37
  async fn shutdown_box(self: Box<Self>);
37
38
  }
38
39
  pub(crate) type BoxedPoller<T> = Box<dyn Poller<T> + Send + Sync + 'static>;
39
- pub(crate) type BoxedWFPoller = BoxedPoller<(PollWorkflowTaskQueueResponse, OwnedMeteredSemPermit)>;
40
- pub(crate) type BoxedActPoller =
41
- BoxedPoller<(PollActivityTaskQueueResponse, OwnedMeteredSemPermit)>;
40
+ pub(crate) type BoxedWFPoller = BoxedPoller<(
41
+ PollWorkflowTaskQueueResponse,
42
+ OwnedMeteredSemPermit<WorkflowSlotKind>,
43
+ )>;
44
+ pub(crate) type BoxedActPoller = BoxedPoller<(
45
+ PollActivityTaskQueueResponse,
46
+ OwnedMeteredSemPermit<ActivitySlotKind>,
47
+ )>;
42
48
 
43
49
  #[async_trait::async_trait]
44
50
  impl<T> Poller<T> for Box<dyn Poller<T> + Send + Sync>