@temporalio/core-bridge 1.13.2 → 1.14.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 (388) hide show
  1. package/Cargo.lock +135 -100
  2. package/Cargo.toml +3 -2
  3. package/{sdk-core/fsm/rustfsm_trait/LICENSE.txt → LICENSE} +5 -5
  4. package/README.md +0 -1
  5. package/bridge-macros/Cargo.toml +16 -0
  6. package/bridge-macros/src/derive_js_function.rs +126 -0
  7. package/bridge-macros/src/derive_tryfromjs.rs +138 -0
  8. package/bridge-macros/src/derive_tryintojs.rs +151 -0
  9. package/bridge-macros/src/lib.rs +42 -0
  10. package/lib/native.d.ts +13 -6
  11. package/package.json +6 -5
  12. package/releases/aarch64-apple-darwin/index.node +0 -0
  13. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  14. package/releases/x86_64-apple-darwin/index.node +0 -0
  15. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  16. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  17. package/sdk-core/.cargo/config.toml +9 -3
  18. package/sdk-core/.github/workflows/per-pr.yml +42 -5
  19. package/sdk-core/AGENTS.md +17 -19
  20. package/sdk-core/Cargo.toml +6 -7
  21. package/sdk-core/README.md +12 -15
  22. package/sdk-core/arch_docs/diagrams/deps.svg +102 -0
  23. package/sdk-core/{client → crates/client}/Cargo.toml +7 -9
  24. package/sdk-core/{client → crates/client}/src/lib.rs +110 -159
  25. package/sdk-core/{client → crates/client}/src/metrics.rs +1 -1
  26. package/sdk-core/{client → crates/client}/src/raw.rs +73 -44
  27. package/sdk-core/crates/client/src/request_extensions.rs +40 -0
  28. package/sdk-core/{client → crates/client}/src/retry.rs +24 -17
  29. package/sdk-core/crates/client/src/worker/mod.rs +1468 -0
  30. package/sdk-core/{client → crates/client}/src/workflow_handle/mod.rs +4 -4
  31. package/sdk-core/{core-api → crates/common}/Cargo.toml +17 -8
  32. package/sdk-core/crates/common/protos/api_cloud_upstream/VERSION +1 -0
  33. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/account/v1/message.proto +18 -0
  34. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +38 -11
  35. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +21 -4
  36. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +6 -6
  37. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/sink/v1/message.proto +22 -0
  38. package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/create-release.yml +5 -0
  39. package/sdk-core/{core-api → crates/common}/src/envconfig.rs +2 -2
  40. package/sdk-core/{core-api → crates/common}/src/errors.rs +8 -1
  41. package/sdk-core/{fsm/rustfsm_trait/src/lib.rs → crates/common/src/fsm_trait.rs} +1 -27
  42. package/sdk-core/{core-api → crates/common}/src/lib.rs +24 -9
  43. package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/canned_histories.rs +1 -1
  44. package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/history_builder.rs +1 -1
  45. package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/history_info.rs +2 -2
  46. package/sdk-core/{sdk-core-protos/src/lib.rs → crates/common/src/protos/mod.rs} +18 -17
  47. package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/test_utils.rs +1 -1
  48. package/sdk-core/{core-api → crates/common}/src/telemetry/metrics.rs +447 -50
  49. package/sdk-core/{core-api → crates/common}/src/telemetry.rs +3 -1
  50. package/sdk-core/{core-api → crates/common}/src/worker.rs +112 -18
  51. package/sdk-core/crates/common/tests/worker_task_types_test.rs +129 -0
  52. package/sdk-core/crates/macros/Cargo.toml +23 -0
  53. package/sdk-core/{fsm/rustfsm_procmacro → crates/macros}/src/lib.rs +6 -11
  54. package/sdk-core/{sdk → crates/sdk}/Cargo.toml +10 -10
  55. package/sdk-core/{sdk → crates/sdk}/src/activity_context.rs +8 -6
  56. package/sdk-core/{sdk → crates/sdk}/src/interceptors.rs +1 -1
  57. package/sdk-core/{sdk → crates/sdk}/src/lib.rs +42 -37
  58. package/sdk-core/{sdk → crates/sdk}/src/workflow_context/options.rs +2 -2
  59. package/sdk-core/{sdk → crates/sdk}/src/workflow_context.rs +21 -19
  60. package/sdk-core/{sdk → crates/sdk}/src/workflow_future.rs +1 -1
  61. package/sdk-core/{core → crates/sdk-core}/Cargo.toml +32 -25
  62. package/sdk-core/{tests → crates/sdk-core/benches}/workflow_replay_bench.rs +10 -10
  63. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +32 -0
  64. package/sdk-core/crates/sdk-core/machine_coverage/CancelExternalMachine_Coverage.puml +9 -0
  65. package/sdk-core/crates/sdk-core/machine_coverage/CancelWorkflowMachine_Coverage.puml +6 -0
  66. package/sdk-core/crates/sdk-core/machine_coverage/ChildWorkflowMachine_Coverage.puml +27 -0
  67. package/sdk-core/crates/sdk-core/machine_coverage/CompleteWorkflowMachine_Coverage.puml +6 -0
  68. package/sdk-core/crates/sdk-core/machine_coverage/ContinueAsNewWorkflowMachine_Coverage.puml +6 -0
  69. package/sdk-core/crates/sdk-core/machine_coverage/FailWorkflowMachine_Coverage.puml +6 -0
  70. package/sdk-core/crates/sdk-core/machine_coverage/LocalActivityMachine_Coverage.puml +23 -0
  71. package/sdk-core/crates/sdk-core/machine_coverage/ModifyWorkflowPropertiesMachine_Coverage.puml +5 -0
  72. package/sdk-core/crates/sdk-core/machine_coverage/PatchMachine_Coverage.puml +8 -0
  73. package/sdk-core/crates/sdk-core/machine_coverage/SignalExternalMachine_Coverage.puml +12 -0
  74. package/sdk-core/crates/sdk-core/machine_coverage/TimerMachine_Coverage.puml +13 -0
  75. package/sdk-core/crates/sdk-core/machine_coverage/UpdateMachine_Coverage.puml +19 -0
  76. package/sdk-core/crates/sdk-core/machine_coverage/UpsertSearchAttributesMachine_Coverage.puml +5 -0
  77. package/sdk-core/crates/sdk-core/machine_coverage/WorkflowTaskMachine_Coverage.puml +11 -0
  78. package/sdk-core/{core → crates/sdk-core}/src/abstractions.rs +62 -6
  79. package/sdk-core/crates/sdk-core/src/antithesis.rs +60 -0
  80. package/sdk-core/{core → crates/sdk-core}/src/core_tests/activity_tasks.rs +36 -31
  81. package/sdk-core/{core → crates/sdk-core}/src/core_tests/mod.rs +12 -9
  82. package/sdk-core/{core → crates/sdk-core}/src/core_tests/queries.rs +24 -21
  83. package/sdk-core/{core → crates/sdk-core}/src/core_tests/replay_flag.rs +10 -8
  84. package/sdk-core/{core → crates/sdk-core}/src/core_tests/updates.rs +17 -15
  85. package/sdk-core/{core → crates/sdk-core}/src/core_tests/workers.rs +242 -17
  86. package/sdk-core/{core → crates/sdk-core}/src/core_tests/workflow_cancels.rs +1 -1
  87. package/sdk-core/{core → crates/sdk-core}/src/core_tests/workflow_tasks.rs +126 -39
  88. package/sdk-core/{core → crates/sdk-core}/src/debug_client.rs +1 -1
  89. package/sdk-core/{core → crates/sdk-core}/src/ephemeral_server/mod.rs +3 -3
  90. package/sdk-core/crates/sdk-core/src/histfetch.rs +33 -0
  91. package/sdk-core/{core → crates/sdk-core}/src/internal_flags.rs +4 -3
  92. package/sdk-core/{core → crates/sdk-core}/src/lib.rs +85 -43
  93. package/sdk-core/{core → crates/sdk-core}/src/pollers/mod.rs +8 -6
  94. package/sdk-core/{core → crates/sdk-core}/src/pollers/poll_buffer.rs +51 -16
  95. package/sdk-core/{core → crates/sdk-core}/src/protosext/mod.rs +1 -1
  96. package/sdk-core/{core → crates/sdk-core}/src/protosext/protocol_messages.rs +1 -1
  97. package/sdk-core/{core → crates/sdk-core}/src/replay/mod.rs +14 -11
  98. package/sdk-core/{core → crates/sdk-core}/src/retry_logic.rs +19 -1
  99. package/sdk-core/{core → crates/sdk-core}/src/telemetry/log_export.rs +2 -2
  100. package/sdk-core/{core → crates/sdk-core}/src/telemetry/metrics.rs +80 -34
  101. package/sdk-core/{core → crates/sdk-core}/src/telemetry/mod.rs +4 -4
  102. package/sdk-core/{core → crates/sdk-core}/src/telemetry/otel.rs +1 -1
  103. package/sdk-core/{core → crates/sdk-core}/src/telemetry/prometheus_meter.rs +13 -13
  104. package/sdk-core/{core → crates/sdk-core}/src/telemetry/prometheus_server.rs +2 -2
  105. package/sdk-core/{core → crates/sdk-core}/src/test_help/integ_helpers.rs +127 -40
  106. package/sdk-core/{core → crates/sdk-core}/src/test_help/unit_helpers.rs +13 -11
  107. package/sdk-core/{core → crates/sdk-core}/src/worker/activities/activity_heartbeat_manager.rs +2 -2
  108. package/sdk-core/{core → crates/sdk-core}/src/worker/activities/local_activities.rs +14 -12
  109. package/sdk-core/{core → crates/sdk-core}/src/worker/activities.rs +21 -12
  110. package/sdk-core/{core → crates/sdk-core}/src/worker/client/mocks.rs +25 -12
  111. package/sdk-core/{core → crates/sdk-core}/src/worker/client.rs +164 -71
  112. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +246 -0
  113. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1462 -0
  114. package/sdk-core/{core → crates/sdk-core}/src/worker/nexus.rs +15 -14
  115. package/sdk-core/{core → crates/sdk-core}/src/worker/slot_provider.rs +12 -13
  116. package/sdk-core/{core → crates/sdk-core}/src/worker/tuner/fixed_size.rs +5 -1
  117. package/sdk-core/{core → crates/sdk-core}/src/worker/tuner/resource_based.rs +453 -57
  118. package/sdk-core/{core → crates/sdk-core}/src/worker/tuner.rs +21 -4
  119. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/driven_workflow.rs +1 -1
  120. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/history_update.rs +2 -2
  121. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/activity_state_machine.rs +147 -37
  122. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/cancel_external_state_machine.rs +8 -9
  123. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/cancel_nexus_op_state_machine.rs +14 -12
  124. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -7
  125. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/child_workflow_state_machine.rs +31 -37
  126. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/complete_workflow_state_machine.rs +13 -8
  127. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -7
  128. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/fail_workflow_state_machine.rs +6 -7
  129. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/local_activity_state_machine.rs +33 -30
  130. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/mod.rs +22 -17
  131. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +4 -3
  132. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/nexus_operation_state_machine.rs +20 -22
  133. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/patch_state_machine.rs +12 -11
  134. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/signal_external_state_machine.rs +9 -12
  135. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/timer_state_machine.rs +26 -13
  136. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/transition_coverage.rs +1 -2
  137. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/update_state_machine.rs +19 -13
  138. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +20 -18
  139. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/workflow_machines/local_acts.rs +1 -1
  140. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/workflow_machines.rs +61 -70
  141. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/machines/workflow_task_state_machine.rs +13 -15
  142. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/managed_run.rs +55 -37
  143. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/mod.rs +166 -60
  144. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/run_cache.rs +10 -7
  145. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/wft_extraction.rs +4 -2
  146. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/wft_poller.rs +15 -4
  147. package/sdk-core/{core → crates/sdk-core}/src/worker/workflow/workflow_stream.rs +30 -16
  148. package/sdk-core/{tests → crates/sdk-core/tests}/c_bridge_smoke_test.c +1 -1
  149. package/sdk-core/{tests → crates/sdk-core/tests}/cloud_tests.rs +1 -1
  150. package/sdk-core/crates/sdk-core/tests/common/fake_grpc_server.rs +106 -0
  151. package/sdk-core/{tests → crates/sdk-core/tests}/common/http_proxy.rs +1 -1
  152. package/sdk-core/{tests → crates/sdk-core/tests}/common/mod.rs +93 -74
  153. package/sdk-core/{tests → crates/sdk-core/tests}/common/workflows.rs +4 -3
  154. package/sdk-core/crates/sdk-core/tests/fsm_procmacro.rs +6 -0
  155. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/dupe_transitions_fail.rs +1 -3
  156. package/sdk-core/crates/sdk-core/tests/fsm_trybuild/dupe_transitions_fail.stderr +12 -0
  157. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/dynamic_dest_pass.rs +2 -4
  158. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/forgot_name_fail.rs +1 -3
  159. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/forgot_name_fail.stderr +4 -4
  160. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/handler_arg_pass.rs +2 -4
  161. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/handler_pass.rs +2 -4
  162. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/medium_complex_pass.rs +2 -4
  163. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/no_handle_conversions_require_into_fail.rs +2 -4
  164. package/sdk-core/crates/sdk-core/tests/fsm_trybuild/no_handle_conversions_require_into_fail.stderr +15 -0
  165. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/simple_pass.rs +2 -4
  166. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/struct_event_variant_fail.rs +1 -3
  167. package/sdk-core/crates/sdk-core/tests/fsm_trybuild/struct_event_variant_fail.stderr +5 -0
  168. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/tuple_more_item_event_variant_fail.rs +1 -3
  169. package/sdk-core/crates/sdk-core/tests/fsm_trybuild/tuple_more_item_event_variant_fail.stderr +5 -0
  170. package/sdk-core/{fsm/rustfsm_procmacro/tests/trybuild → crates/sdk-core/tests/fsm_trybuild}/tuple_zero_item_event_variant_fail.rs +1 -3
  171. package/sdk-core/crates/sdk-core/tests/fsm_trybuild/tuple_zero_item_event_variant_fail.stderr +5 -0
  172. package/sdk-core/{tests → crates/sdk-core/tests}/global_metric_tests.rs +14 -15
  173. package/sdk-core/{tests → crates/sdk-core/tests/heavy_tests}/fuzzy_workflow.rs +3 -3
  174. package/sdk-core/{tests → crates/sdk-core/tests}/heavy_tests.rs +19 -12
  175. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/activity_functions.rs +1 -1
  176. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/client_tests.rs +16 -111
  177. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/ephemeral_server_tests.rs +5 -6
  178. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/heartbeat_tests.rs +23 -19
  179. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/metrics_tests.rs +134 -60
  180. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/pagination_tests.rs +4 -4
  181. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/polling_tests.rs +37 -36
  182. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/queries_tests.rs +12 -10
  183. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/update_tests.rs +41 -29
  184. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/visibility_tests.rs +24 -19
  185. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +1061 -0
  186. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/worker_tests.rs +113 -51
  187. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/worker_versioning_tests.rs +19 -17
  188. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/activities.rs +35 -30
  189. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/appdata_propagation.rs +3 -3
  190. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/cancel_external.rs +14 -9
  191. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/cancel_wf.rs +13 -8
  192. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/child_workflows.rs +48 -35
  193. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/continue_as_new.rs +14 -9
  194. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/determinism.rs +24 -15
  195. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/eager.rs +9 -4
  196. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/local_activities.rs +47 -47
  197. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/modify_wf_properties.rs +16 -11
  198. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/nexus.rs +51 -23
  199. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/patches.rs +22 -10
  200. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/replay.rs +19 -17
  201. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/resets.rs +14 -5
  202. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/signals.rs +24 -15
  203. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/stickyness.rs +8 -6
  204. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/timers.rs +28 -18
  205. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/upsert_search_attrs.rs +18 -13
  206. package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests.rs +46 -41
  207. package/sdk-core/{tests → crates/sdk-core/tests}/main.rs +15 -9
  208. package/sdk-core/{tests → crates/sdk-core/tests}/manual_tests.rs +20 -14
  209. package/sdk-core/{tests → crates/sdk-core/tests}/runner.rs +2 -2
  210. package/sdk-core/{tests → crates/sdk-core/tests}/shared_tests/mod.rs +10 -5
  211. package/sdk-core/{tests → crates/sdk-core/tests}/shared_tests/priority.rs +5 -5
  212. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/Cargo.toml +13 -10
  213. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/include/temporal-sdk-core-c-bridge.h +32 -23
  214. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/client.rs +55 -32
  215. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/envconfig.rs +1 -1
  216. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/lib.rs +1 -1
  217. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/metric.rs +1 -1
  218. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/runtime.rs +24 -9
  219. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/testing.rs +1 -1
  220. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/tests/context.rs +11 -10
  221. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/tests/mod.rs +7 -7
  222. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/tests/utils.rs +3 -4
  223. package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/worker.rs +111 -58
  224. package/sdk-core/docker-cgroup-tests.sh +24 -0
  225. package/sdk-core/{docker → etc/docker}/docker-compose-ci.yaml +9 -9
  226. package/sdk-core/{docker → etc/docker}/docker-compose-telem.yaml +11 -11
  227. package/sdk-core/{docker → etc/docker}/docker-compose.yaml +8 -8
  228. package/sdk-core/{integ-with-otel.sh → etc/integ-with-otel.sh} +1 -1
  229. package/sdk-core/etc/regen-depgraph.sh +2 -2
  230. package/src/client.rs +24 -33
  231. package/src/helpers/try_from_js.rs +1 -1
  232. package/src/logs.rs +1 -1
  233. package/src/metrics.rs +3 -3
  234. package/src/runtime.rs +42 -28
  235. package/src/testing.rs +3 -3
  236. package/src/worker.rs +70 -36
  237. package/ts/native.ts +13 -6
  238. package/LICENSE.md +0 -23
  239. package/sdk-core/client/src/worker_registry/mod.rs +0 -282
  240. package/sdk-core/core/src/worker/heartbeat.rs +0 -230
  241. package/sdk-core/core/src/worker/mod.rs +0 -990
  242. package/sdk-core/etc/deps.svg +0 -162
  243. package/sdk-core/fsm/Cargo.toml +0 -21
  244. package/sdk-core/fsm/README.md +0 -3
  245. package/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +0 -27
  246. package/sdk-core/fsm/rustfsm_procmacro/LICENSE.txt +0 -21
  247. package/sdk-core/fsm/rustfsm_procmacro/tests/progress.rs +0 -8
  248. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +0 -12
  249. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +0 -15
  250. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/struct_event_variant_fail.stderr +0 -5
  251. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_more_item_event_variant_fail.stderr +0 -5
  252. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_zero_item_event_variant_fail.stderr +0 -5
  253. package/sdk-core/fsm/rustfsm_trait/Cargo.toml +0 -14
  254. package/sdk-core/fsm/src/lib.rs +0 -2
  255. package/sdk-core/sdk-core-protos/Cargo.toml +0 -37
  256. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +0 -1
  257. /package/sdk-core/{client → crates/client}/src/callback_based.rs +0 -0
  258. /package/sdk-core/{client → crates/client}/src/proxy.rs +0 -0
  259. /package/sdk-core/{client → crates/client}/src/replaceable.rs +0 -0
  260. /package/sdk-core/{sdk-core-protos → crates/common}/build.rs +0 -0
  261. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/.github/workflows/build.yaml +0 -0
  262. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/.github/workflows/push-to-buf.yml +0 -0
  263. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/CODEOWNERS +0 -0
  264. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/LICENSE +0 -0
  265. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/Makefile +0 -0
  266. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/README.md +0 -0
  267. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/buf.gen.yaml +0 -0
  268. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/buf.lock +0 -0
  269. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/buf.yaml +0 -0
  270. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/connectivityrule/v1/message.proto +0 -0
  271. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +0 -0
  272. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +0 -0
  273. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/nexus/v1/message.proto +0 -0
  274. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +0 -0
  275. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/resource/v1/message.proto +0 -0
  276. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_cloud_upstream/temporal/api/cloud/usage/v1/message.proto +0 -0
  277. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/CODEOWNERS +0 -0
  278. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  279. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/ci.yml +0 -0
  280. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/publish-docs.yml +0 -0
  281. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/push-to-buf.yml +0 -0
  282. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/trigger-api-go-delete-release.yml +0 -0
  283. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/trigger-api-go-publish-release.yml +0 -0
  284. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +0 -0
  285. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/LICENSE +0 -0
  286. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/Makefile +0 -0
  287. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/README.md +0 -0
  288. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/api-linter.yaml +0 -0
  289. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/buf.gen.yaml +0 -0
  290. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/buf.lock +0 -0
  291. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/buf.yaml +0 -0
  292. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/api/annotations.proto +0 -0
  293. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/api/http.proto +0 -0
  294. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/any.proto +0 -0
  295. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/descriptor.proto +0 -0
  296. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/duration.proto +0 -0
  297. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/empty.proto +0 -0
  298. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/struct.proto +0 -0
  299. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/timestamp.proto +0 -0
  300. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/google/protobuf/wrappers.proto +0 -0
  301. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/openapi/openapiv2.json +0 -0
  302. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/openapi/openapiv3.yaml +0 -0
  303. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/openapi/payload_description.txt +0 -0
  304. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/activity/v1/message.proto +0 -0
  305. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/batch/v1/message.proto +0 -0
  306. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/command/v1/message.proto +0 -0
  307. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/common/v1/message.proto +0 -0
  308. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/deployment/v1/message.proto +0 -0
  309. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +0 -0
  310. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/command_type.proto +0 -0
  311. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/common.proto +0 -0
  312. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/deployment.proto +0 -0
  313. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/event_type.proto +0 -0
  314. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +0 -0
  315. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/namespace.proto +0 -0
  316. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/nexus.proto +0 -0
  317. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/query.proto +0 -0
  318. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/reset.proto +0 -0
  319. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/schedule.proto +0 -0
  320. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +0 -0
  321. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/update.proto +0 -0
  322. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/enums/v1/workflow.proto +0 -0
  323. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/errordetails/v1/message.proto +0 -0
  324. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/export/v1/message.proto +0 -0
  325. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/failure/v1/message.proto +0 -0
  326. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/filter/v1/message.proto +0 -0
  327. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/history/v1/message.proto +0 -0
  328. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/namespace/v1/message.proto +0 -0
  329. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/nexus/v1/message.proto +0 -0
  330. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +0 -0
  331. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -0
  332. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/protocol/v1/message.proto +0 -0
  333. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/query/v1/message.proto +0 -0
  334. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/replication/v1/message.proto +0 -0
  335. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/rules/v1/message.proto +0 -0
  336. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/schedule/v1/message.proto +0 -0
  337. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +0 -0
  338. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +0 -0
  339. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +0 -0
  340. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/sdk/v1/worker_config.proto +0 -0
  341. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +0 -0
  342. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +0 -0
  343. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/update/v1/message.proto +0 -0
  344. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/version/v1/message.proto +0 -0
  345. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/worker/v1/message.proto +0 -0
  346. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/workflow/v1/message.proto +0 -0
  347. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +0 -0
  348. /package/sdk-core/{sdk-core-protos → crates/common}/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +0 -0
  349. /package/sdk-core/{sdk-core-protos → crates/common}/protos/google/rpc/status.proto +0 -0
  350. /package/sdk-core/{sdk-core-protos → crates/common}/protos/grpc/health/v1/health.proto +0 -0
  351. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/activity_result/activity_result.proto +0 -0
  352. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/activity_task/activity_task.proto +0 -0
  353. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +0 -0
  354. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/common/common.proto +0 -0
  355. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/core_interface.proto +0 -0
  356. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/external_data/external_data.proto +0 -0
  357. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/nexus/nexus.proto +0 -0
  358. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +0 -0
  359. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +0 -0
  360. /package/sdk-core/{sdk-core-protos → crates/common}/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +0 -0
  361. /package/sdk-core/{sdk-core-protos → crates/common}/protos/testsrv_upstream/Makefile +0 -0
  362. /package/sdk-core/{sdk-core-protos → crates/common}/protos/testsrv_upstream/api-linter.yaml +0 -0
  363. /package/sdk-core/{sdk-core-protos → crates/common}/protos/testsrv_upstream/buf.yaml +0 -0
  364. /package/sdk-core/{sdk-core-protos → crates/common}/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +0 -0
  365. /package/sdk-core/{sdk-core-protos → crates/common}/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +0 -0
  366. /package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/constants.rs +0 -0
  367. /package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/task_token.rs +0 -0
  368. /package/sdk-core/{sdk-core-protos/src → crates/common/src/protos}/utilities.rs +0 -0
  369. /package/sdk-core/{fsm → crates/macros}/LICENSE.txt +0 -0
  370. /package/sdk-core/{sdk → crates/sdk}/src/app_data.rs +0 -0
  371. /package/sdk-core/{core → crates/sdk-core}/src/abstractions/take_cell.rs +0 -0
  372. /package/sdk-core/{core → crates/sdk-core}/src/test_help/mod.rs +0 -0
  373. /package/sdk-core/{core → crates/sdk-core}/src/worker/slot_supplier.rs +0 -0
  374. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/ends_empty_wft_complete.bin +0 -0
  375. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/evict_while_la_running_no_interference-16_history.bin +0 -0
  376. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/evict_while_la_running_no_interference-23_history.bin +0 -0
  377. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/evict_while_la_running_no_interference-85_history.bin +0 -0
  378. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/fail_wf_task.bin +0 -0
  379. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/long_local_activity_with_update-0_history.bin +0 -0
  380. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/long_local_activity_with_update-1_history.bin +0 -0
  381. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/long_local_activity_with_update-2_history.bin +0 -0
  382. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/long_local_activity_with_update-3_history.bin +0 -0
  383. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/old_change_marker_format.bin +0 -0
  384. /package/sdk-core/{histories → crates/sdk-core/tests/histories}/timer_workflow_history.bin +0 -0
  385. /package/sdk-core/{tests → crates/sdk-core/tests}/integ_tests/workflow_tests/priority.rs +0 -0
  386. /package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/build.rs +0 -0
  387. /package/sdk-core/{core-c-bridge → crates/sdk-core-c-bridge}/src/random.rs +0 -0
  388. /package/sdk-core/{cargo-tokio-console.sh → etc/cargo-tokio-console.sh} +0 -0
@@ -0,0 +1,1468 @@
1
+ //! Contains types and logic for interactions between clients and Core/SDK workers
2
+
3
+ use anyhow::bail;
4
+ use parking_lot::RwLock;
5
+ use rand::seq::SliceRandom;
6
+ use std::{
7
+ collections::{
8
+ HashMap,
9
+ hash_map::Entry::{Occupied, Vacant},
10
+ },
11
+ sync::Arc,
12
+ };
13
+ use temporalio_common::{
14
+ protos::temporal::api::{
15
+ worker::v1::WorkerHeartbeat, workflowservice::v1::PollWorkflowTaskQueueResponse,
16
+ },
17
+ worker::{WorkerDeploymentOptions, WorkerTaskTypes},
18
+ };
19
+ use uuid::Uuid;
20
+
21
+ /// This trait represents a slot reserved for processing a WFT by a worker.
22
+ #[cfg_attr(test, mockall::automock)]
23
+ pub trait Slot {
24
+ /// Consumes this slot by dispatching a WFT to its worker. This can only be called once.
25
+ fn schedule_wft(
26
+ self: Box<Self>,
27
+ task: PollWorkflowTaskQueueResponse,
28
+ ) -> Result<(), anyhow::Error>;
29
+ }
30
+
31
+ /// Result of reserving a workflow task slot, including deployment options if applicable.
32
+ pub(crate) struct SlotReservation {
33
+ /// The reserved slot for processing the workflow task
34
+ pub slot: Box<dyn Slot + Send>,
35
+ /// Worker deployment options, if the worker is using deployment-based versioning
36
+ pub deployment_options: Option<WorkerDeploymentOptions>,
37
+ }
38
+
39
+ #[derive(PartialEq, Eq, Hash, Debug, Clone)]
40
+ struct SlotKey {
41
+ namespace: String,
42
+ task_queue: String,
43
+ }
44
+
45
+ impl SlotKey {
46
+ fn new(namespace: String, task_queue: String) -> SlotKey {
47
+ SlotKey {
48
+ namespace,
49
+ task_queue,
50
+ }
51
+ }
52
+ }
53
+
54
+ /// Information about a registered worker in the slot provider registry
55
+ #[derive(Debug, Clone)]
56
+ struct RegisteredWorkerInfo {
57
+ /// Unique identifier for this worker instance
58
+ worker_id: Uuid,
59
+ /// Optional deployment build ID for versioning
60
+ build_id: Option<String>,
61
+ /// Task types this worker can handle
62
+ task_types: WorkerTaskTypes,
63
+ }
64
+
65
+ impl RegisteredWorkerInfo {
66
+ fn new(worker_id: Uuid, build_id: Option<String>, task_types: WorkerTaskTypes) -> Self {
67
+ Self {
68
+ worker_id,
69
+ build_id,
70
+ task_types,
71
+ }
72
+ }
73
+ }
74
+
75
+ /// This is an inner class for [ClientWorkerSet] needed to hide the mutex.
76
+ struct ClientWorkerSetImpl {
77
+ /// Maps slot keys to registered worker information
78
+ slot_providers: HashMap<SlotKey, Vec<RegisteredWorkerInfo>>,
79
+ /// Maps worker_instance_key to registered workers
80
+ all_workers: HashMap<Uuid, Arc<dyn ClientWorker + Send + Sync>>,
81
+ /// Maps namespace to shared worker for worker heartbeating
82
+ shared_worker: HashMap<String, Box<dyn SharedNamespaceWorkerTrait + Send + Sync>>,
83
+ }
84
+
85
+ impl ClientWorkerSetImpl {
86
+ /// Factory method.
87
+ fn new() -> Self {
88
+ Self {
89
+ slot_providers: Default::default(),
90
+ all_workers: Default::default(),
91
+ shared_worker: Default::default(),
92
+ }
93
+ }
94
+
95
+ fn try_reserve_wft_slot(
96
+ &self,
97
+ namespace: String,
98
+ task_queue: String,
99
+ ) -> Option<SlotReservation> {
100
+ let key = SlotKey::new(namespace, task_queue);
101
+ if let Some(worker_list) = self.slot_providers.get(&key) {
102
+ let workflow_workers: Vec<&RegisteredWorkerInfo> = worker_list
103
+ .iter()
104
+ .filter(|info| info.task_types.enable_workflows)
105
+ .collect();
106
+
107
+ for worker_id in Self::worker_ids_in_selection_order(&workflow_workers) {
108
+ if let Some(worker) = self.all_workers.get(&worker_id)
109
+ && let Some(slot) = worker.try_reserve_wft_slot()
110
+ {
111
+ let deployment_options = worker.deployment_options();
112
+ return Some(SlotReservation {
113
+ slot,
114
+ deployment_options,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ None
120
+ }
121
+
122
+ fn worker_ids_in_selection_order(worker_list: &[&RegisteredWorkerInfo]) -> Vec<Uuid> {
123
+ // For tests we return workers in the order they're registered, so we can test
124
+ // the retry mechanism deterministically
125
+ if cfg!(test) {
126
+ worker_list.iter().map(|info| info.worker_id).collect()
127
+ } else {
128
+ let mut rng = rand::rng();
129
+ let mut shuffled: Vec<_> = worker_list.to_vec();
130
+ shuffled.shuffle(&mut rng);
131
+ shuffled.iter().map(|info| info.worker_id).collect()
132
+ }
133
+ }
134
+
135
+ fn register(
136
+ &mut self,
137
+ worker: Arc<dyn ClientWorker + Send + Sync>,
138
+ skip_client_worker_set_check: bool,
139
+ ) -> Result<(), anyhow::Error> {
140
+ let slot_key = SlotKey::new(
141
+ worker.namespace().to_string(),
142
+ worker.task_queue().to_string(),
143
+ );
144
+ let build_id = worker
145
+ .deployment_options()
146
+ .map(|opts| opts.version.build_id);
147
+ let task_types = worker.worker_task_types();
148
+
149
+ if !task_types.enable_workflows
150
+ && !task_types.enable_local_activities
151
+ && !task_types.enable_remote_activities
152
+ && !task_types.enable_nexus
153
+ {
154
+ bail!(
155
+ "Worker must have at least one capability enabled (workflows, activities, or nexus)"
156
+ );
157
+ }
158
+
159
+ if !task_types.enable_workflows && task_types.enable_local_activities {
160
+ bail!("Local activities cannot be enabled without workflows")
161
+ }
162
+
163
+ if !skip_client_worker_set_check
164
+ && let Some(existing_workers) = self.slot_providers.get(&slot_key)
165
+ {
166
+ for existing_worker_info in existing_workers {
167
+ if existing_worker_info.build_id.as_ref() == build_id.as_ref()
168
+ && task_types.overlaps_with(&existing_worker_info.task_types)
169
+ {
170
+ bail!(
171
+ "Registration of multiple workers with overlapping worker task types \
172
+ on the same namespace, task queue, and deployment build ID not allowed: \
173
+ {slot_key:?}, worker_instance_key: {:?} \
174
+ build_id: {build_id:?}, \
175
+ new task types: {task_types:?}, \
176
+ existing task types: {:?}.",
177
+ existing_worker_info.task_types,
178
+ worker.worker_instance_key()
179
+ );
180
+ }
181
+ }
182
+ }
183
+
184
+ if worker.heartbeat_enabled()
185
+ && let Some(heartbeat_callback) = worker.heartbeat_callback()
186
+ {
187
+ let worker_instance_key = worker.worker_instance_key();
188
+ let namespace = worker.namespace().to_string();
189
+
190
+ let shared_worker = match self.shared_worker.entry(namespace.clone()) {
191
+ Occupied(o) => o.into_mut(),
192
+ Vacant(v) => {
193
+ let shared_worker = worker.new_shared_namespace_worker()?;
194
+ v.insert(shared_worker)
195
+ }
196
+ };
197
+ shared_worker.register_callback(worker_instance_key, heartbeat_callback);
198
+ }
199
+
200
+ let worker_info =
201
+ RegisteredWorkerInfo::new(worker.worker_instance_key(), build_id, task_types);
202
+
203
+ match self.slot_providers.entry(slot_key.clone()) {
204
+ Occupied(o) => o.into_mut().push(worker_info),
205
+ Vacant(v) => {
206
+ v.insert(vec![worker_info]);
207
+ }
208
+ };
209
+
210
+ self.all_workers
211
+ .insert(worker.worker_instance_key(), worker);
212
+
213
+ Ok(())
214
+ }
215
+
216
+ fn unregister(
217
+ &mut self,
218
+ worker_instance_key: Uuid,
219
+ ) -> Result<Arc<dyn ClientWorker + Send + Sync>, anyhow::Error> {
220
+ let worker = self
221
+ .all_workers
222
+ .remove(&worker_instance_key)
223
+ .ok_or_else(|| {
224
+ anyhow::anyhow!("Worker with worker_instance_key {worker_instance_key} not found")
225
+ })?;
226
+
227
+ let slot_key = SlotKey::new(
228
+ worker.namespace().to_string(),
229
+ worker.task_queue().to_string(),
230
+ );
231
+
232
+ if let Some(slot_vec) = self.slot_providers.get_mut(&slot_key) {
233
+ slot_vec.retain(|info| info.worker_id != worker_instance_key);
234
+ if slot_vec.is_empty() {
235
+ self.slot_providers.remove(&slot_key);
236
+ }
237
+ }
238
+
239
+ if let Some(w) = self.shared_worker.get_mut(worker.namespace()) {
240
+ let (callback, is_empty) = w.unregister_callback(worker.worker_instance_key());
241
+ if callback.is_some() && is_empty {
242
+ self.shared_worker.remove(worker.namespace());
243
+ }
244
+ }
245
+
246
+ Ok(worker)
247
+ }
248
+
249
+ #[cfg(test)]
250
+ fn num_providers(&self) -> usize {
251
+ self.slot_providers.values().map(|v| v.len()).sum()
252
+ }
253
+
254
+ #[cfg(test)]
255
+ fn num_heartbeat_workers(&self) -> usize {
256
+ self.shared_worker.values().map(|v| v.num_workers()).sum()
257
+ }
258
+ }
259
+
260
+ /// This trait represents a shared namespace worker that sends worker heartbeats and
261
+ /// receives worker commands.
262
+ pub trait SharedNamespaceWorkerTrait {
263
+ /// Namespace that the shared namespace worker is connected to.
264
+ fn namespace(&self) -> String;
265
+
266
+ /// Registers a heartbeat callback.
267
+ fn register_callback(&self, worker_instance_key: Uuid, heartbeat_callback: HeartbeatCallback);
268
+
269
+ /// Unregisters a heartbeat callback. Returns the callback removed, as well as a bool that
270
+ /// indicates if there are no remaining callbacks in the SharedNamespaceWorker, indicating
271
+ /// the shared worker itself can be shut down.
272
+ fn unregister_callback(&self, worker_instance_key: Uuid) -> (Option<HeartbeatCallback>, bool);
273
+
274
+ /// Returns the number of workers registered to this shared worker.
275
+ fn num_workers(&self) -> usize;
276
+ }
277
+
278
+ /// Enables local workers to make themselves visible to a shared client instance.
279
+ ///
280
+ /// For slot managing, there can only be one worker registered per
281
+ /// namespace+queue_name+client, others will return an error.
282
+ /// It also provides a convenient method to find compatible slots within the collection.
283
+ pub struct ClientWorkerSet {
284
+ worker_grouping_key: Uuid,
285
+ worker_manager: RwLock<ClientWorkerSetImpl>,
286
+ }
287
+
288
+ impl Default for ClientWorkerSet {
289
+ fn default() -> Self {
290
+ Self::new()
291
+ }
292
+ }
293
+
294
+ impl ClientWorkerSet {
295
+ /// Factory method.
296
+ pub fn new() -> Self {
297
+ Self {
298
+ worker_grouping_key: Uuid::new_v4(),
299
+ worker_manager: RwLock::new(ClientWorkerSetImpl::new()),
300
+ }
301
+ }
302
+
303
+ /// Try to reserve a compatible processing slot in any of the registered workers.
304
+ /// Returns the slot and the worker's deployment options (if using deployment-based versioning).
305
+ pub(crate) fn try_reserve_wft_slot(
306
+ &self,
307
+ namespace: String,
308
+ task_queue: String,
309
+ ) -> Option<SlotReservation> {
310
+ self.worker_manager
311
+ .read()
312
+ .try_reserve_wft_slot(namespace, task_queue)
313
+ }
314
+
315
+ /// Register a local worker that can provide WFT processing slots and potentially worker heartbeating.
316
+ pub fn register_worker(
317
+ &self,
318
+ worker: Arc<dyn ClientWorker + Send + Sync>,
319
+ skip_client_worker_set_check: bool,
320
+ ) -> Result<(), anyhow::Error> {
321
+ self.worker_manager
322
+ .write()
323
+ .register(worker, skip_client_worker_set_check)
324
+ }
325
+
326
+ /// Unregisters a local worker, typically when that worker starts shutdown.
327
+ pub fn unregister_worker(
328
+ &self,
329
+ worker_instance_key: Uuid,
330
+ ) -> Result<Arc<dyn ClientWorker + Send + Sync>, anyhow::Error> {
331
+ self.worker_manager.write().unregister(worker_instance_key)
332
+ }
333
+
334
+ /// Returns the worker grouping key, which is unique for each worker.
335
+ pub fn worker_grouping_key(&self) -> Uuid {
336
+ self.worker_grouping_key
337
+ }
338
+
339
+ #[cfg(test)]
340
+ /// Returns (num_providers, num_buckets), where a bucket key is namespace+task_queue.
341
+ /// There is only one provider per bucket so `num_providers` should be equal to `num_buckets`.
342
+ pub fn num_providers(&self) -> usize {
343
+ self.worker_manager.read().num_providers()
344
+ }
345
+
346
+ #[cfg(test)]
347
+ /// Returns the total number of heartbeat workers registered across all namespaces.
348
+ pub fn num_heartbeat_workers(&self) -> usize {
349
+ self.worker_manager.read().num_heartbeat_workers()
350
+ }
351
+ }
352
+
353
+ impl std::fmt::Debug for ClientWorkerSet {
354
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355
+ f.debug_struct("ClientWorkerSet")
356
+ .field("worker_grouping_key", &self.worker_grouping_key)
357
+ .finish()
358
+ }
359
+ }
360
+
361
+ /// Contains a worker heartbeat callback, wrapped for mocking
362
+ pub type HeartbeatCallback = Arc<dyn Fn() -> WorkerHeartbeat + Send + Sync>;
363
+
364
+ /// Represents a complete worker that can handle both slot management
365
+ /// and worker heartbeat functionality.
366
+ #[cfg_attr(test, mockall::automock)]
367
+ pub trait ClientWorker: Send + Sync {
368
+ /// The namespace this worker operates in
369
+ fn namespace(&self) -> &str;
370
+
371
+ /// The task queue this worker listens to
372
+ fn task_queue(&self) -> &str;
373
+
374
+ /// Try to reserve a slot for workflow task processing.
375
+ ///
376
+ /// This method should return `Some(slot)` if a workflow task slot is available,
377
+ /// or `None` if all slots are currently in use. The returned slot will be used
378
+ /// to process exactly one workflow task.
379
+ fn try_reserve_wft_slot(&self) -> Option<Box<dyn Slot + Send>>;
380
+
381
+ /// Get the worker deployment options for this worker, if using deployment-based versioning.
382
+ fn deployment_options(&self) -> Option<WorkerDeploymentOptions>;
383
+
384
+ /// Unique identifier for this worker instance.
385
+ /// This must be stable across the worker's lifetime and unique per instance.
386
+ fn worker_instance_key(&self) -> Uuid;
387
+
388
+ /// Indicates if worker heartbeating is enabled for this client worker.
389
+ fn heartbeat_enabled(&self) -> bool;
390
+
391
+ /// Returns the heartbeat callback that can be used to get WorkerHeartbeat data.
392
+ fn heartbeat_callback(&self) -> Option<HeartbeatCallback>;
393
+
394
+ /// Creates a new worker that implements the [SharedNamespaceWorkerTrait]
395
+ fn new_shared_namespace_worker(
396
+ &self,
397
+ ) -> Result<Box<dyn SharedNamespaceWorkerTrait + Send + Sync>, anyhow::Error>;
398
+
399
+ /// Returns the task types this worker can handle
400
+ fn worker_task_types(&self) -> WorkerTaskTypes;
401
+ }
402
+
403
+ #[cfg(test)]
404
+ mod tests {
405
+ use super::*;
406
+
407
+ fn new_mock_slot(with_error: bool) -> Box<MockSlot> {
408
+ let mut mock_slot = MockSlot::new();
409
+ if with_error {
410
+ mock_slot
411
+ .expect_schedule_wft()
412
+ .returning(|_| Err(anyhow::anyhow!("Changed my mind")));
413
+ } else {
414
+ mock_slot.expect_schedule_wft().returning(|_| Ok(()));
415
+ }
416
+ Box::new(mock_slot)
417
+ }
418
+
419
+ fn new_mock_provider(
420
+ namespace: String,
421
+ task_queue: String,
422
+ with_error: bool,
423
+ no_slots: bool,
424
+ heartbeat_enabled: bool,
425
+ ) -> MockClientWorker {
426
+ let mut mock_provider = MockClientWorker::new();
427
+ mock_provider
428
+ .expect_try_reserve_wft_slot()
429
+ .returning(move || {
430
+ if no_slots {
431
+ None
432
+ } else {
433
+ Some(new_mock_slot(with_error))
434
+ }
435
+ });
436
+ mock_provider.expect_namespace().return_const(namespace);
437
+ mock_provider.expect_task_queue().return_const(task_queue);
438
+ mock_provider.expect_deployment_options().return_const(None);
439
+ mock_provider
440
+ .expect_heartbeat_enabled()
441
+ .return_const(heartbeat_enabled);
442
+ mock_provider
443
+ .expect_worker_instance_key()
444
+ .return_const(Uuid::new_v4());
445
+ mock_provider
446
+ .expect_worker_task_types()
447
+ .return_const(WorkerTaskTypes {
448
+ enable_workflows: true,
449
+ enable_local_activities: true,
450
+ enable_remote_activities: true,
451
+ enable_nexus: true,
452
+ });
453
+ mock_provider
454
+ }
455
+
456
+ #[test]
457
+ fn reserve_wft_slot_retries_another_worker_when_first_has_no_slot() {
458
+ let mut manager = ClientWorkerSetImpl::new();
459
+ let namespace = "retry_namespace".to_string();
460
+ let task_queue = "retry_queue".to_string();
461
+
462
+ let failing_worker_id = Uuid::new_v4();
463
+ let mut failing_worker = MockClientWorker::new();
464
+ failing_worker
465
+ .expect_try_reserve_wft_slot()
466
+ .times(1)
467
+ .returning(|| None);
468
+ failing_worker
469
+ .expect_namespace()
470
+ .return_const(namespace.clone());
471
+ failing_worker
472
+ .expect_task_queue()
473
+ .return_const(task_queue.clone());
474
+ failing_worker
475
+ .expect_deployment_options()
476
+ .return_const(WorkerDeploymentOptions {
477
+ version: temporalio_common::worker::WorkerDeploymentVersion {
478
+ deployment_name: "test-deployment".to_string(),
479
+ build_id: "build-fail".to_string(),
480
+ },
481
+ use_worker_versioning: true,
482
+ default_versioning_behavior: None,
483
+ });
484
+ failing_worker
485
+ .expect_worker_instance_key()
486
+ .return_const(failing_worker_id);
487
+ failing_worker
488
+ .expect_heartbeat_enabled()
489
+ .return_const(false);
490
+ failing_worker
491
+ .expect_worker_task_types()
492
+ .return_const(WorkerTaskTypes {
493
+ enable_workflows: true,
494
+ enable_local_activities: true,
495
+ enable_remote_activities: true,
496
+ enable_nexus: true,
497
+ });
498
+
499
+ let succeeding_worker_id = Uuid::new_v4();
500
+ let mut succeeding_worker = MockClientWorker::new();
501
+ succeeding_worker
502
+ .expect_try_reserve_wft_slot()
503
+ .times(1)
504
+ .returning(|| Some(new_mock_slot(false)));
505
+ succeeding_worker
506
+ .expect_namespace()
507
+ .return_const(namespace.clone());
508
+ succeeding_worker
509
+ .expect_task_queue()
510
+ .return_const(task_queue.clone());
511
+ let success_deployment_options = WorkerDeploymentOptions {
512
+ version: temporalio_common::worker::WorkerDeploymentVersion {
513
+ deployment_name: "test-deployment".to_string(),
514
+ build_id: "build-success".to_string(),
515
+ },
516
+ use_worker_versioning: true,
517
+ default_versioning_behavior: None,
518
+ };
519
+ succeeding_worker
520
+ .expect_deployment_options()
521
+ .return_const(success_deployment_options.clone());
522
+ succeeding_worker
523
+ .expect_worker_instance_key()
524
+ .return_const(succeeding_worker_id);
525
+ succeeding_worker
526
+ .expect_heartbeat_enabled()
527
+ .return_const(false);
528
+ succeeding_worker
529
+ .expect_worker_task_types()
530
+ .return_const(WorkerTaskTypes {
531
+ enable_workflows: true,
532
+ enable_local_activities: true,
533
+ enable_remote_activities: true,
534
+ enable_nexus: true,
535
+ });
536
+
537
+ manager
538
+ .register(Arc::new(failing_worker), false)
539
+ .expect("failing worker registration succeeds");
540
+ manager
541
+ .register(Arc::new(succeeding_worker), false)
542
+ .expect("succeeding worker registration succeeds");
543
+
544
+ let reservation = manager.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
545
+
546
+ let reservation_deployment_options = reservation
547
+ .expect("succeeding worker was used after failing worker failed")
548
+ .deployment_options
549
+ .unwrap();
550
+ assert_eq!(
551
+ reservation_deployment_options, success_deployment_options,
552
+ "deployment options bubble through from succeeding worker"
553
+ );
554
+ }
555
+
556
+ #[test]
557
+ fn reserve_wft_slot_retries_respects_slot_boundary() {
558
+ let mut manager = ClientWorkerSetImpl::new();
559
+ let namespace = "retry_namespace".to_string();
560
+ let task_queue = "retry_queue".to_string();
561
+
562
+ let failing_worker_id = Uuid::new_v4();
563
+ let mut failing_worker = MockClientWorker::new();
564
+ failing_worker
565
+ .expect_try_reserve_wft_slot()
566
+ .times(1)
567
+ .returning(|| None);
568
+ failing_worker
569
+ .expect_namespace()
570
+ .return_const(namespace.clone());
571
+ failing_worker
572
+ .expect_task_queue()
573
+ .return_const(task_queue.clone());
574
+ failing_worker
575
+ .expect_deployment_options()
576
+ .return_const(WorkerDeploymentOptions {
577
+ version: temporalio_common::worker::WorkerDeploymentVersion {
578
+ deployment_name: "test-deployment".to_string(),
579
+ build_id: "build-fail".to_string(),
580
+ },
581
+ use_worker_versioning: true,
582
+ default_versioning_behavior: None,
583
+ });
584
+ failing_worker
585
+ .expect_worker_instance_key()
586
+ .return_const(failing_worker_id);
587
+ failing_worker
588
+ .expect_heartbeat_enabled()
589
+ .return_const(false);
590
+ failing_worker
591
+ .expect_worker_task_types()
592
+ .return_const(WorkerTaskTypes {
593
+ enable_workflows: true,
594
+ enable_local_activities: true,
595
+ enable_remote_activities: true,
596
+ enable_nexus: true,
597
+ });
598
+
599
+ // On a separate task queue
600
+ let succeeding_worker_id = Uuid::new_v4();
601
+ let mut succeeding_worker = MockClientWorker::new();
602
+ succeeding_worker.expect_try_reserve_wft_slot().times(0);
603
+ succeeding_worker
604
+ .expect_namespace()
605
+ .return_const(namespace.clone());
606
+ succeeding_worker
607
+ .expect_task_queue()
608
+ .return_const("other_task_queue".to_string());
609
+ succeeding_worker
610
+ .expect_deployment_options()
611
+ .return_const(None);
612
+ succeeding_worker
613
+ .expect_worker_instance_key()
614
+ .return_const(succeeding_worker_id);
615
+ succeeding_worker
616
+ .expect_heartbeat_enabled()
617
+ .return_const(false);
618
+ succeeding_worker
619
+ .expect_worker_task_types()
620
+ .return_const(WorkerTaskTypes {
621
+ enable_workflows: true,
622
+ enable_local_activities: true,
623
+ enable_remote_activities: true,
624
+ enable_nexus: true,
625
+ });
626
+
627
+ manager
628
+ .register(Arc::new(failing_worker), false)
629
+ .expect("failing worker registration succeeds");
630
+ manager
631
+ .register(Arc::new(succeeding_worker), false)
632
+ .expect("succeeding worker registration succeeds");
633
+
634
+ let reservation = manager.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
635
+ assert!(
636
+ reservation.is_none(),
637
+ "succeeding_worker should not be picked due to it being on a separate task queue"
638
+ );
639
+ }
640
+
641
+ #[test]
642
+ fn registry_keeps_one_provider_per_namespace() {
643
+ let manager = ClientWorkerSet::new();
644
+ let mut worker_keys = vec![];
645
+ let mut successful_registrations = 0;
646
+
647
+ for i in 0..10 {
648
+ let namespace = format!("myId{}", i % 3);
649
+ let mock_provider =
650
+ new_mock_provider(namespace, "bar_q".to_string(), false, false, false);
651
+ let worker_instance_key = mock_provider.worker_instance_key();
652
+
653
+ let result = manager.register_worker(Arc::new(mock_provider), false);
654
+ if result.is_ok() {
655
+ successful_registrations += 1;
656
+ worker_keys.push(worker_instance_key);
657
+ } else {
658
+ // Should get error for overlapping worker task types
659
+ assert!(result.unwrap_err().to_string().contains(
660
+ "Registration of multiple workers with overlapping worker task types"
661
+ ));
662
+ }
663
+ }
664
+
665
+ assert_eq!(successful_registrations, 3);
666
+ assert_eq!(3, manager.num_providers());
667
+
668
+ let count = worker_keys.iter().fold(0, |count, key| {
669
+ manager.unregister_worker(*key).unwrap();
670
+ // expect error since worker is already unregistered
671
+ let result = manager.unregister_worker(*key);
672
+ assert!(result.is_err());
673
+ count + 1
674
+ });
675
+ assert_eq!(3, count);
676
+ assert_eq!(0, manager.num_providers());
677
+ }
678
+
679
+ struct MockSharedNamespaceWorker {
680
+ namespace: String,
681
+ callbacks: Arc<RwLock<HashMap<Uuid, HeartbeatCallback>>>,
682
+ }
683
+
684
+ impl std::fmt::Debug for MockSharedNamespaceWorker {
685
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686
+ f.debug_struct("MockSharedNamespaceWorker")
687
+ .field("namespace", &self.namespace)
688
+ .field("callbacks_count", &self.callbacks.read().len())
689
+ .finish()
690
+ }
691
+ }
692
+
693
+ impl MockSharedNamespaceWorker {
694
+ fn new(namespace: String) -> Self {
695
+ Self {
696
+ namespace,
697
+ callbacks: Arc::new(RwLock::new(HashMap::new())),
698
+ }
699
+ }
700
+ }
701
+
702
+ impl SharedNamespaceWorkerTrait for MockSharedNamespaceWorker {
703
+ fn namespace(&self) -> String {
704
+ self.namespace.clone()
705
+ }
706
+
707
+ fn register_callback(
708
+ &self,
709
+ worker_instance_key: Uuid,
710
+ heartbeat_callback: HeartbeatCallback,
711
+ ) {
712
+ self.callbacks
713
+ .write()
714
+ .insert(worker_instance_key, heartbeat_callback);
715
+ }
716
+
717
+ fn unregister_callback(
718
+ &self,
719
+ worker_instance_key: Uuid,
720
+ ) -> (Option<HeartbeatCallback>, bool) {
721
+ let mut callbacks = self.callbacks.write();
722
+ let callback = callbacks.remove(&worker_instance_key);
723
+ let is_empty = callbacks.is_empty();
724
+ (callback, is_empty)
725
+ }
726
+
727
+ fn num_workers(&self) -> usize {
728
+ self.callbacks.read().len()
729
+ }
730
+ }
731
+
732
+ fn new_mock_provider_with_heartbeat(
733
+ namespace: String,
734
+ task_queue: String,
735
+ heartbeat_enabled: bool,
736
+ build_id: Option<String>,
737
+ ) -> MockClientWorker {
738
+ let mut mock_provider = MockClientWorker::new();
739
+ mock_provider
740
+ .expect_try_reserve_wft_slot()
741
+ .returning(|| Some(new_mock_slot(false)));
742
+ mock_provider
743
+ .expect_namespace()
744
+ .return_const(namespace.clone());
745
+ mock_provider.expect_task_queue().return_const(task_queue);
746
+ mock_provider
747
+ .expect_heartbeat_enabled()
748
+ .return_const(heartbeat_enabled);
749
+ mock_provider
750
+ .expect_worker_instance_key()
751
+ .return_const(Uuid::new_v4());
752
+ let deployment_name = "test-deployment".to_string();
753
+ let build_id_for_closure = build_id.clone();
754
+ mock_provider
755
+ .expect_deployment_options()
756
+ .returning(move || {
757
+ build_id_for_closure
758
+ .as_ref()
759
+ .map(|build_id| WorkerDeploymentOptions {
760
+ version: temporalio_common::worker::WorkerDeploymentVersion {
761
+ deployment_name: deployment_name.clone(),
762
+ build_id: build_id.clone(),
763
+ },
764
+ use_worker_versioning: true,
765
+ default_versioning_behavior: None,
766
+ })
767
+ });
768
+
769
+ if heartbeat_enabled {
770
+ mock_provider
771
+ .expect_heartbeat_callback()
772
+ .returning(|| Some(Arc::new(WorkerHeartbeat::default)));
773
+
774
+ let namespace_clone = namespace.clone();
775
+ mock_provider
776
+ .expect_new_shared_namespace_worker()
777
+ .returning(move || {
778
+ Ok(Box::new(MockSharedNamespaceWorker::new(
779
+ namespace_clone.clone(),
780
+ )))
781
+ });
782
+ }
783
+
784
+ mock_provider
785
+ .expect_worker_task_types()
786
+ .return_const(WorkerTaskTypes {
787
+ enable_workflows: true,
788
+ enable_local_activities: true,
789
+ enable_remote_activities: true,
790
+ enable_nexus: true,
791
+ });
792
+
793
+ mock_provider
794
+ }
795
+
796
+ #[test]
797
+ fn duplicate_namespace_task_queue_registration_fails() {
798
+ let manager = ClientWorkerSet::new();
799
+
800
+ let worker1 = new_mock_provider_with_heartbeat(
801
+ "test_namespace".to_string(),
802
+ "test_queue".to_string(),
803
+ true,
804
+ None,
805
+ );
806
+
807
+ // Same namespace+task_queue but different worker instance
808
+ let worker2 = new_mock_provider_with_heartbeat(
809
+ "test_namespace".to_string(),
810
+ "test_queue".to_string(),
811
+ true,
812
+ None,
813
+ );
814
+
815
+ manager.register_worker(Arc::new(worker1), false).unwrap();
816
+
817
+ // second worker register should fail due to overlapping worker task types
818
+ let result = manager.register_worker(Arc::new(worker2), false);
819
+ assert!(result.is_err());
820
+ assert!(
821
+ result
822
+ .unwrap_err()
823
+ .to_string()
824
+ .contains("Registration of multiple workers with overlapping worker task types")
825
+ );
826
+
827
+ assert_eq!(1, manager.num_providers());
828
+ assert_eq!(manager.num_heartbeat_workers(), 1);
829
+
830
+ let impl_ref = manager.worker_manager.read();
831
+ assert_eq!(impl_ref.shared_worker.len(), 1);
832
+ assert!(impl_ref.shared_worker.contains_key("test_namespace"));
833
+ }
834
+
835
+ #[test]
836
+ fn duplicate_namespace_with_different_build_ids_succeeds() {
837
+ let manager = ClientWorkerSet::new();
838
+ let namespace = "test_namespace".to_string();
839
+ let task_queue = "test_queue".to_string();
840
+
841
+ let worker1 =
842
+ new_mock_provider_with_heartbeat(namespace.clone(), task_queue.clone(), false, None);
843
+ let worker1_instance_key = worker1.worker_instance_key();
844
+ let worker2 = new_mock_provider_with_heartbeat(
845
+ namespace.clone(),
846
+ task_queue.clone(),
847
+ false,
848
+ Some("build-1".to_string()),
849
+ );
850
+ let worker2_instance_key = worker2.worker_instance_key();
851
+ let worker3 =
852
+ new_mock_provider_with_heartbeat(namespace.clone(), task_queue.clone(), false, None);
853
+ let worker4 = new_mock_provider_with_heartbeat(
854
+ namespace.clone(),
855
+ task_queue.clone(),
856
+ false,
857
+ Some("build-1".to_string()),
858
+ );
859
+
860
+ manager.register_worker(Arc::new(worker1), false).unwrap();
861
+
862
+ manager
863
+ .register_worker(Arc::new(worker2), false)
864
+ .expect("worker with new build ID should register");
865
+ assert_eq!(2, manager.num_providers());
866
+
867
+ assert!(
868
+ manager
869
+ .register_worker(Arc::new(worker3), false)
870
+ .unwrap_err()
871
+ .to_string()
872
+ .contains("Registration of multiple workers with overlapping worker task types")
873
+ );
874
+
875
+ assert!(
876
+ manager
877
+ .register_worker(Arc::new(worker4), false)
878
+ .unwrap_err()
879
+ .to_string()
880
+ .contains("Registration of multiple workers with overlapping worker task types")
881
+ );
882
+ assert_eq!(2, manager.num_providers());
883
+
884
+ {
885
+ let impl_ref = manager.worker_manager.read();
886
+ let slot_key = SlotKey::new(namespace.clone(), task_queue.clone());
887
+ let providers = impl_ref
888
+ .slot_providers
889
+ .get(&slot_key)
890
+ .expect("slot providers should exist for namespace/task queue");
891
+ assert_eq!(2, providers.len());
892
+
893
+ assert_eq!(providers[0].worker_id, worker1_instance_key);
894
+ assert_eq!(providers[0].build_id, None);
895
+ assert_eq!(providers[1].worker_id, worker2_instance_key);
896
+ assert_eq!(providers[1].build_id, Some("build-1".to_string()));
897
+ }
898
+
899
+ manager.unregister_worker(worker2_instance_key).unwrap();
900
+
901
+ {
902
+ let impl_ref = manager.worker_manager.read();
903
+ let slot_key = SlotKey::new(namespace.clone(), task_queue.clone());
904
+ let providers = impl_ref
905
+ .slot_providers
906
+ .get(&slot_key)
907
+ .expect("slot providers should exist for namespace/task queue");
908
+
909
+ assert_eq!(1, providers.len());
910
+ assert_eq!(providers[0].worker_id, worker1_instance_key);
911
+ assert_eq!(providers[0].build_id, None);
912
+ }
913
+ }
914
+
915
+ #[test]
916
+ fn multiple_workers_same_namespace_share_heartbeat_manager() {
917
+ let manager = ClientWorkerSet::new();
918
+
919
+ let worker1 = new_mock_provider_with_heartbeat(
920
+ "shared_namespace".to_string(),
921
+ "queue1".to_string(),
922
+ true,
923
+ None,
924
+ );
925
+
926
+ // Same namespace but different task queue
927
+ let worker2 = new_mock_provider_with_heartbeat(
928
+ "shared_namespace".to_string(),
929
+ "queue2".to_string(),
930
+ true,
931
+ None,
932
+ );
933
+
934
+ manager.register_worker(Arc::new(worker1), false).unwrap();
935
+ manager.register_worker(Arc::new(worker2), false).unwrap();
936
+
937
+ assert_eq!(2, manager.num_providers());
938
+ assert_eq!(manager.num_heartbeat_workers(), 2);
939
+
940
+ let impl_ref = manager.worker_manager.read();
941
+ assert_eq!(impl_ref.shared_worker.len(), 1);
942
+ assert!(impl_ref.shared_worker.contains_key("shared_namespace"));
943
+
944
+ let shared_worker = impl_ref.shared_worker.get("shared_namespace").unwrap();
945
+ assert_eq!(shared_worker.namespace(), "shared_namespace");
946
+ }
947
+
948
+ #[test]
949
+ fn different_namespaces_get_separate_heartbeat_managers() {
950
+ let manager = ClientWorkerSet::new();
951
+ let worker1 = new_mock_provider_with_heartbeat(
952
+ "namespace1".to_string(),
953
+ "queue1".to_string(),
954
+ true,
955
+ None,
956
+ );
957
+ let worker2 = new_mock_provider_with_heartbeat(
958
+ "namespace2".to_string(),
959
+ "queue1".to_string(),
960
+ true,
961
+ None,
962
+ );
963
+
964
+ manager.register_worker(Arc::new(worker1), false).unwrap();
965
+ manager.register_worker(Arc::new(worker2), false).unwrap();
966
+
967
+ assert_eq!(2, manager.num_providers());
968
+ assert_eq!(manager.num_heartbeat_workers(), 2);
969
+
970
+ let impl_ref = manager.worker_manager.read();
971
+ assert_eq!(impl_ref.num_heartbeat_workers(), 2);
972
+ assert!(impl_ref.shared_worker.contains_key("namespace1"));
973
+ assert!(impl_ref.shared_worker.contains_key("namespace2"));
974
+ }
975
+
976
+ #[test]
977
+ fn unregister_heartbeat_workers_cleans_up_shared_worker_when_last_removed() {
978
+ let manager = ClientWorkerSet::new();
979
+
980
+ // Create two workers with same namespace but different task queues
981
+ let worker1 = new_mock_provider_with_heartbeat(
982
+ "test_namespace".to_string(),
983
+ "queue1".to_string(),
984
+ true,
985
+ None,
986
+ );
987
+ let worker2 = new_mock_provider_with_heartbeat(
988
+ "test_namespace".to_string(),
989
+ "queue2".to_string(),
990
+ true,
991
+ None,
992
+ );
993
+ let worker_instance_key1 = worker1.worker_instance_key();
994
+ let worker_instance_key2 = worker2.worker_instance_key();
995
+
996
+ assert_ne!(worker_instance_key1, worker_instance_key2);
997
+
998
+ manager.register_worker(Arc::new(worker1), false).unwrap();
999
+ manager.register_worker(Arc::new(worker2), false).unwrap();
1000
+
1001
+ // Verify initial state: 2 slot providers, 2 heartbeat workers, 1 shared worker
1002
+ assert_eq!(2, manager.num_providers());
1003
+ assert_eq!(manager.num_heartbeat_workers(), 2);
1004
+
1005
+ let impl_ref = manager.worker_manager.read();
1006
+ assert_eq!(impl_ref.shared_worker.len(), 1);
1007
+ assert!(impl_ref.shared_worker.contains_key("test_namespace"));
1008
+ assert_eq!(
1009
+ impl_ref
1010
+ .shared_worker
1011
+ .get("test_namespace")
1012
+ .unwrap()
1013
+ .num_workers(),
1014
+ 2
1015
+ );
1016
+ drop(impl_ref);
1017
+
1018
+ // Unregister first worker
1019
+ manager.unregister_worker(worker_instance_key1).unwrap();
1020
+
1021
+ // After unregistering first worker: 1 slot provider, 1 heartbeat worker, shared worker still exists
1022
+ assert_eq!(1, manager.num_providers());
1023
+ assert_eq!(manager.num_heartbeat_workers(), 1);
1024
+
1025
+ let impl_ref = manager.worker_manager.read();
1026
+ assert_eq!(impl_ref.num_heartbeat_workers(), 1); // SharedNamespaceWorker still exists
1027
+ assert!(impl_ref.shared_worker.contains_key("test_namespace"));
1028
+ assert_eq!(
1029
+ impl_ref
1030
+ .shared_worker
1031
+ .get("test_namespace")
1032
+ .unwrap()
1033
+ .num_workers(),
1034
+ 1
1035
+ );
1036
+ drop(impl_ref);
1037
+
1038
+ // Unregister second worker
1039
+ manager.unregister_worker(worker_instance_key2).unwrap();
1040
+
1041
+ // After unregistering last worker: 0 slot providers, 0 heartbeat workers, shared worker is removed
1042
+ assert_eq!(0, manager.num_providers());
1043
+ assert_eq!(manager.num_heartbeat_workers(), 0);
1044
+
1045
+ let impl_ref = manager.worker_manager.read();
1046
+ assert_eq!(impl_ref.shared_worker.len(), 0); // SharedNamespaceWorker is cleaned up
1047
+ assert!(!impl_ref.shared_worker.contains_key("test_namespace"));
1048
+ }
1049
+
1050
+ #[test]
1051
+ fn workflow_and_activity_only_workers_coexist() {
1052
+ let manager = ClientWorkerSet::new();
1053
+ let namespace = "test_namespace".to_string();
1054
+ let task_queue = "test_queue".to_string();
1055
+
1056
+ let mut workflow_nexus_worker = MockClientWorker::new();
1057
+ workflow_nexus_worker
1058
+ .expect_namespace()
1059
+ .return_const(namespace.clone());
1060
+ workflow_nexus_worker
1061
+ .expect_task_queue()
1062
+ .return_const(task_queue.clone());
1063
+ workflow_nexus_worker
1064
+ .expect_deployment_options()
1065
+ .return_const(None);
1066
+ workflow_nexus_worker
1067
+ .expect_worker_instance_key()
1068
+ .return_const(Uuid::new_v4());
1069
+ workflow_nexus_worker
1070
+ .expect_heartbeat_enabled()
1071
+ .return_const(false);
1072
+ workflow_nexus_worker
1073
+ .expect_worker_task_types()
1074
+ .return_const(WorkerTaskTypes {
1075
+ enable_workflows: true,
1076
+ enable_local_activities: false,
1077
+ enable_remote_activities: false,
1078
+ enable_nexus: true,
1079
+ });
1080
+
1081
+ let mut activity_worker = MockClientWorker::new();
1082
+ activity_worker
1083
+ .expect_namespace()
1084
+ .return_const(namespace.clone());
1085
+ activity_worker
1086
+ .expect_task_queue()
1087
+ .return_const(task_queue.clone());
1088
+ activity_worker
1089
+ .expect_deployment_options()
1090
+ .return_const(None);
1091
+ activity_worker
1092
+ .expect_worker_instance_key()
1093
+ .return_const(Uuid::new_v4());
1094
+ activity_worker
1095
+ .expect_heartbeat_enabled()
1096
+ .return_const(false);
1097
+ activity_worker
1098
+ .expect_worker_task_types()
1099
+ .return_const(WorkerTaskTypes {
1100
+ enable_workflows: false,
1101
+ enable_local_activities: false,
1102
+ enable_remote_activities: true,
1103
+ enable_nexus: false,
1104
+ });
1105
+ activity_worker.expect_try_reserve_wft_slot().times(0); // Should not be called for activity-only worker
1106
+
1107
+ manager
1108
+ .register_worker(Arc::new(workflow_nexus_worker), false)
1109
+ .expect("workflow-nexus worker should register");
1110
+ manager
1111
+ .register_worker(Arc::new(activity_worker), false)
1112
+ .expect("activity-only worker should register");
1113
+
1114
+ assert_eq!(2, manager.num_providers());
1115
+ }
1116
+
1117
+ #[test]
1118
+ fn overlapping_capabilities_rejected() {
1119
+ let manager = ClientWorkerSet::new();
1120
+ let namespace = "test_namespace".to_string();
1121
+ let task_queue = "test_queue".to_string();
1122
+
1123
+ // workflow+activity worker
1124
+ let mut worker1 = MockClientWorker::new();
1125
+ worker1.expect_namespace().return_const(namespace.clone());
1126
+ worker1.expect_task_queue().return_const(task_queue.clone());
1127
+ worker1.expect_deployment_options().return_const(None);
1128
+ worker1
1129
+ .expect_worker_instance_key()
1130
+ .return_const(Uuid::new_v4());
1131
+ worker1.expect_heartbeat_enabled().return_const(false);
1132
+ worker1
1133
+ .expect_worker_task_types()
1134
+ .return_const(WorkerTaskTypes {
1135
+ enable_workflows: true,
1136
+ enable_local_activities: true,
1137
+ enable_remote_activities: true,
1138
+ enable_nexus: false,
1139
+ });
1140
+
1141
+ // workflow+activity worker
1142
+ let mut worker2 = MockClientWorker::new();
1143
+ worker2.expect_namespace().return_const(namespace.clone());
1144
+ worker2.expect_task_queue().return_const(task_queue.clone());
1145
+ worker2.expect_deployment_options().return_const(None);
1146
+ worker2
1147
+ .expect_worker_instance_key()
1148
+ .return_const(Uuid::new_v4());
1149
+ worker2.expect_heartbeat_enabled().return_const(false);
1150
+ worker2
1151
+ .expect_worker_task_types()
1152
+ .return_const(WorkerTaskTypes {
1153
+ enable_workflows: true,
1154
+ enable_local_activities: true,
1155
+ enable_remote_activities: true,
1156
+ enable_nexus: false,
1157
+ });
1158
+
1159
+ manager
1160
+ .register_worker(Arc::new(worker1), false)
1161
+ .expect("first worker should register");
1162
+
1163
+ let result = manager.register_worker(Arc::new(worker2), false);
1164
+ assert!(result.is_err());
1165
+ assert!(
1166
+ result
1167
+ .unwrap_err()
1168
+ .to_string()
1169
+ .contains("overlapping worker task types")
1170
+ );
1171
+
1172
+ // activity-only worker
1173
+ let mut worker3 = MockClientWorker::new();
1174
+ worker3.expect_namespace().return_const(namespace.clone());
1175
+ worker3.expect_task_queue().return_const(task_queue.clone());
1176
+ worker3.expect_deployment_options().return_const(None);
1177
+ worker3
1178
+ .expect_worker_instance_key()
1179
+ .return_const(Uuid::new_v4());
1180
+ worker3.expect_heartbeat_enabled().return_const(false);
1181
+ worker3
1182
+ .expect_worker_task_types()
1183
+ .return_const(WorkerTaskTypes {
1184
+ enable_workflows: false,
1185
+ enable_local_activities: false,
1186
+ enable_remote_activities: true,
1187
+ enable_nexus: false,
1188
+ });
1189
+
1190
+ let result = manager.register_worker(Arc::new(worker3), false);
1191
+ assert!(result.is_err());
1192
+ assert!(
1193
+ result
1194
+ .unwrap_err()
1195
+ .to_string()
1196
+ .contains("overlapping worker task types")
1197
+ );
1198
+ }
1199
+
1200
+ #[test]
1201
+ fn wft_slot_reservation_ignores_non_workflow_workers() {
1202
+ let mut manager_impl = ClientWorkerSetImpl::new();
1203
+ let namespace = "test_namespace".to_string();
1204
+ let task_queue = "test_queue".to_string();
1205
+
1206
+ let mut activity_worker = MockClientWorker::new();
1207
+ activity_worker
1208
+ .expect_namespace()
1209
+ .return_const(namespace.clone());
1210
+ activity_worker
1211
+ .expect_task_queue()
1212
+ .return_const(task_queue.clone());
1213
+ activity_worker
1214
+ .expect_deployment_options()
1215
+ .return_const(None);
1216
+ activity_worker
1217
+ .expect_worker_instance_key()
1218
+ .return_const(Uuid::new_v4());
1219
+ activity_worker
1220
+ .expect_heartbeat_enabled()
1221
+ .return_const(false);
1222
+ activity_worker
1223
+ .expect_worker_task_types()
1224
+ .return_const(WorkerTaskTypes {
1225
+ enable_workflows: false,
1226
+ enable_local_activities: false,
1227
+ enable_remote_activities: true,
1228
+ enable_nexus: false,
1229
+ });
1230
+
1231
+ let mut nexus_worker = MockClientWorker::new();
1232
+ nexus_worker
1233
+ .expect_namespace()
1234
+ .return_const(namespace.clone());
1235
+ nexus_worker
1236
+ .expect_task_queue()
1237
+ .return_const(task_queue.clone());
1238
+ nexus_worker.expect_deployment_options().return_const(None);
1239
+ nexus_worker
1240
+ .expect_worker_instance_key()
1241
+ .return_const(Uuid::new_v4());
1242
+ nexus_worker.expect_heartbeat_enabled().return_const(false);
1243
+ nexus_worker
1244
+ .expect_worker_task_types()
1245
+ .return_const(WorkerTaskTypes {
1246
+ enable_workflows: false,
1247
+ enable_local_activities: false,
1248
+ enable_remote_activities: false,
1249
+ enable_nexus: true,
1250
+ });
1251
+
1252
+ manager_impl
1253
+ .register(Arc::new(activity_worker), false)
1254
+ .expect("activity worker should register");
1255
+ manager_impl
1256
+ .register(Arc::new(nexus_worker), false)
1257
+ .expect("nexus worker should register");
1258
+
1259
+ let reservation = manager_impl.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
1260
+ assert!(
1261
+ reservation.is_none(),
1262
+ "should not find workflow workers when only activity/nexus workers registered"
1263
+ );
1264
+
1265
+ // Now register a workflow worker
1266
+ let mut workflow_worker = MockClientWorker::new();
1267
+ workflow_worker
1268
+ .expect_namespace()
1269
+ .return_const(namespace.clone());
1270
+ workflow_worker
1271
+ .expect_task_queue()
1272
+ .return_const(task_queue.clone());
1273
+ workflow_worker
1274
+ .expect_deployment_options()
1275
+ .return_const(None);
1276
+ workflow_worker
1277
+ .expect_worker_instance_key()
1278
+ .return_const(Uuid::new_v4());
1279
+ workflow_worker
1280
+ .expect_heartbeat_enabled()
1281
+ .return_const(false);
1282
+ workflow_worker
1283
+ .expect_worker_task_types()
1284
+ .return_const(WorkerTaskTypes {
1285
+ enable_workflows: true,
1286
+ enable_local_activities: true,
1287
+ enable_remote_activities: false,
1288
+ enable_nexus: false,
1289
+ });
1290
+ workflow_worker
1291
+ .expect_try_reserve_wft_slot()
1292
+ .times(1)
1293
+ .returning(|| Some(new_mock_slot(false)));
1294
+
1295
+ manager_impl
1296
+ .register(Arc::new(workflow_worker), false)
1297
+ .expect("workflow worker should register");
1298
+
1299
+ let reservation = manager_impl.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
1300
+ assert!(
1301
+ reservation.is_some(),
1302
+ "should find workflow worker after it's registered"
1303
+ );
1304
+ }
1305
+
1306
+ #[test]
1307
+ fn worker_invalid_type_config_rejected() {
1308
+ let manager = ClientWorkerSet::new();
1309
+
1310
+ // no types enabled
1311
+ let mut worker = MockClientWorker::new();
1312
+ worker
1313
+ .expect_namespace()
1314
+ .return_const("test_namespace".to_string());
1315
+ worker
1316
+ .expect_task_queue()
1317
+ .return_const("test_queue".to_string());
1318
+ worker.expect_deployment_options().return_const(None);
1319
+ worker
1320
+ .expect_worker_instance_key()
1321
+ .return_const(Uuid::new_v4());
1322
+ worker.expect_heartbeat_enabled().return_const(false);
1323
+ worker
1324
+ .expect_worker_task_types()
1325
+ .return_const(WorkerTaskTypes {
1326
+ enable_workflows: false,
1327
+ enable_local_activities: false,
1328
+ enable_remote_activities: false,
1329
+ enable_nexus: false,
1330
+ });
1331
+
1332
+ let result = manager.register_worker(Arc::new(worker), false);
1333
+ assert!(result.is_err());
1334
+ assert!(
1335
+ result
1336
+ .unwrap_err()
1337
+ .to_string()
1338
+ .contains("must have at least one capability enabled")
1339
+ );
1340
+
1341
+ // local activities enabled without workflows
1342
+ let mut worker = MockClientWorker::new();
1343
+ worker
1344
+ .expect_namespace()
1345
+ .return_const("test_namespace".to_string());
1346
+ worker
1347
+ .expect_task_queue()
1348
+ .return_const("test_queue".to_string());
1349
+ worker.expect_deployment_options().return_const(None);
1350
+ worker
1351
+ .expect_worker_instance_key()
1352
+ .return_const(Uuid::new_v4());
1353
+ worker.expect_heartbeat_enabled().return_const(false);
1354
+ worker
1355
+ .expect_worker_task_types()
1356
+ .return_const(WorkerTaskTypes {
1357
+ enable_workflows: false,
1358
+ enable_local_activities: true,
1359
+ enable_remote_activities: true,
1360
+ enable_nexus: false,
1361
+ });
1362
+
1363
+ let result = manager.register_worker(Arc::new(worker), false);
1364
+ assert!(result.is_err());
1365
+ assert_eq!(
1366
+ result.unwrap_err().to_string(),
1367
+ "Local activities cannot be enabled without workflows".to_string()
1368
+ );
1369
+ }
1370
+
1371
+ #[test]
1372
+ fn unregister_with_multiple_workers() {
1373
+ let manager = ClientWorkerSet::new();
1374
+ let namespace = "test_namespace".to_string();
1375
+ let task_queue = "test_queue".to_string();
1376
+
1377
+ // workflow-only worker
1378
+ let mut workflow_worker = MockClientWorker::new();
1379
+ workflow_worker
1380
+ .expect_namespace()
1381
+ .return_const(namespace.clone());
1382
+ workflow_worker
1383
+ .expect_task_queue()
1384
+ .return_const(task_queue.clone());
1385
+ workflow_worker
1386
+ .expect_deployment_options()
1387
+ .return_const(None);
1388
+ let wf_worker_key = Uuid::new_v4();
1389
+ workflow_worker
1390
+ .expect_worker_instance_key()
1391
+ .return_const(wf_worker_key);
1392
+ workflow_worker
1393
+ .expect_heartbeat_enabled()
1394
+ .return_const(false);
1395
+ workflow_worker
1396
+ .expect_worker_task_types()
1397
+ .return_const(WorkerTaskTypes {
1398
+ enable_workflows: true,
1399
+ enable_local_activities: true,
1400
+ enable_remote_activities: false,
1401
+ enable_nexus: false,
1402
+ });
1403
+ workflow_worker
1404
+ .expect_try_reserve_wft_slot()
1405
+ .returning(|| Some(new_mock_slot(false)));
1406
+
1407
+ // activity-only worker
1408
+ let mut activity_worker = MockClientWorker::new();
1409
+ activity_worker
1410
+ .expect_namespace()
1411
+ .return_const(namespace.clone());
1412
+ activity_worker
1413
+ .expect_task_queue()
1414
+ .return_const(task_queue.clone());
1415
+ activity_worker
1416
+ .expect_deployment_options()
1417
+ .return_const(None);
1418
+ let act_worker_key = Uuid::new_v4();
1419
+ activity_worker
1420
+ .expect_worker_instance_key()
1421
+ .return_const(act_worker_key);
1422
+ activity_worker
1423
+ .expect_heartbeat_enabled()
1424
+ .return_const(false);
1425
+ activity_worker
1426
+ .expect_worker_task_types()
1427
+ .return_const(WorkerTaskTypes {
1428
+ enable_workflows: false,
1429
+ enable_local_activities: false,
1430
+ enable_remote_activities: true,
1431
+ enable_nexus: false,
1432
+ });
1433
+
1434
+ manager
1435
+ .register_worker(Arc::new(workflow_worker), false)
1436
+ .expect("workflow worker should register");
1437
+ manager
1438
+ .register_worker(Arc::new(activity_worker), false)
1439
+ .expect("activity worker should register");
1440
+
1441
+ assert_eq!(2, manager.num_providers());
1442
+
1443
+ let reservation = manager.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
1444
+ assert!(
1445
+ reservation.is_some(),
1446
+ "should be able to reserve slot from workflow worker"
1447
+ );
1448
+
1449
+ manager
1450
+ .unregister_worker(wf_worker_key)
1451
+ .expect("should unregister workflow worker");
1452
+
1453
+ // Activity worker should still be registered
1454
+ assert_eq!(1, manager.num_providers());
1455
+
1456
+ let reservation = manager.try_reserve_wft_slot(namespace.clone(), task_queue.clone());
1457
+ assert!(
1458
+ reservation.is_none(),
1459
+ "should not find workflow worker after unregistration"
1460
+ );
1461
+
1462
+ manager
1463
+ .unregister_worker(act_worker_key)
1464
+ .expect("should unregister activity worker");
1465
+
1466
+ assert_eq!(0, manager.num_providers());
1467
+ }
1468
+ }