@temporalio/core-bridge 1.14.2-canary-release-testing.0 → 1.16.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 (233) hide show
  1. package/Cargo.lock +794 -650
  2. package/bridge-macros/src/derive_tryintojs.rs +40 -0
  3. package/lib/native.d.ts +24 -3
  4. package/package.json +4 -4
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.github/workflows/per-pr.yml +6 -6
  11. package/sdk-core/AGENTS.md +42 -31
  12. package/sdk-core/Cargo.toml +4 -1
  13. package/sdk-core/README.md +19 -13
  14. package/sdk-core/crates/client/Cargo.toml +4 -0
  15. package/sdk-core/crates/client/README.md +139 -0
  16. package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
  17. package/sdk-core/crates/client/src/callback_based.rs +7 -0
  18. package/sdk-core/crates/client/src/errors.rs +294 -0
  19. package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +370 -159
  20. package/sdk-core/crates/client/src/lib.rs +920 -1326
  21. package/sdk-core/crates/client/src/metrics.rs +24 -33
  22. package/sdk-core/crates/client/src/options_structs.rs +457 -0
  23. package/sdk-core/crates/client/src/replaceable.rs +5 -4
  24. package/sdk-core/crates/client/src/request_extensions.rs +8 -9
  25. package/sdk-core/crates/client/src/retry.rs +99 -54
  26. package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +104 -29
  27. package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
  28. package/sdk-core/crates/common/Cargo.toml +62 -3
  29. package/sdk-core/crates/common/build.rs +742 -12
  30. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
  31. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/create-release.yml +0 -5
  32. package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
  33. package/sdk-core/crates/common/protos/api_upstream/README.md +8 -0
  34. package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
  35. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +3329 -2647
  36. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +2734 -708
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/activity/v1/message.proto +155 -3
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/common/v1/message.proto +8 -1
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +27 -1
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/activity.proto +81 -0
  42. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -0
  43. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +4 -0
  44. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +15 -0
  45. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +63 -15
  46. package/sdk-core/crates/common/protos/api_upstream/temporal/api/errordetails/v1/message.proto +8 -0
  47. package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
  48. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +111 -17
  49. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +21 -0
  50. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +20 -1
  51. package/sdk-core/crates/common/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +4 -0
  52. package/sdk-core/crates/common/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
  53. package/sdk-core/crates/common/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -0
  54. package/sdk-core/crates/common/protos/api_upstream/temporal/api/worker/v1/message.proto +4 -7
  55. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto +80 -22
  56. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +347 -23
  57. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +242 -43
  58. package/sdk-core/crates/common/protos/local/temporal/sdk/core/core_interface.proto +15 -0
  59. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +9 -2
  60. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +8 -0
  61. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +22 -5
  62. package/sdk-core/crates/common/src/activity_definition.rs +20 -0
  63. package/sdk-core/crates/common/src/data_converters.rs +770 -0
  64. package/sdk-core/crates/common/src/envconfig.rs +5 -0
  65. package/sdk-core/crates/common/src/lib.rs +15 -211
  66. package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
  67. package/sdk-core/crates/common/src/priority.rs +110 -0
  68. package/sdk-core/crates/common/src/protos/canned_histories.rs +19 -0
  69. package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
  70. package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
  71. package/sdk-core/crates/common/src/protos/mod.rs +134 -27
  72. package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
  73. package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
  74. package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +11 -16
  75. package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
  76. package/sdk-core/crates/common/src/telemetry/metrics.rs +272 -225
  77. package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
  78. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
  79. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
  80. package/sdk-core/crates/common/src/telemetry.rs +278 -19
  81. package/sdk-core/crates/common/src/worker.rs +68 -636
  82. package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
  83. package/sdk-core/crates/macros/Cargo.toml +5 -1
  84. package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
  85. package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
  86. package/sdk-core/crates/macros/src/lib.rs +138 -512
  87. package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
  88. package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
  89. package/sdk-core/crates/sdk/Cargo.toml +19 -6
  90. package/sdk-core/crates/sdk/README.md +415 -0
  91. package/sdk-core/crates/sdk/src/activities.rs +417 -0
  92. package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
  93. package/sdk-core/crates/sdk/src/lib.rs +759 -442
  94. package/sdk-core/crates/sdk/src/workflow_context/options.rs +64 -35
  95. package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
  96. package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
  97. package/sdk-core/crates/sdk/src/workflows.rs +711 -0
  98. package/sdk-core/crates/sdk-core/Cargo.toml +59 -65
  99. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +45 -54
  100. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
  101. package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
  102. package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
  103. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +22 -21
  104. package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
  105. package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
  106. package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
  107. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +647 -27
  108. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +46 -41
  109. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +13 -16
  110. package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
  111. package/sdk-core/crates/sdk-core/src/lib.rs +60 -123
  112. package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
  113. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +411 -32
  114. package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
  115. package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
  116. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +183 -198
  117. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -281
  118. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +35 -16
  119. package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
  120. package/sdk-core/crates/sdk-core/src/worker/activities/activity_heartbeat_manager.rs +1 -0
  121. package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
  122. package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
  123. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +11 -5
  124. package/sdk-core/crates/sdk-core/src/worker/client.rs +104 -86
  125. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +10 -14
  126. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1175 -241
  127. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +150 -23
  128. package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
  129. package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
  130. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +25 -27
  131. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +64 -44
  132. package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
  133. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/patch_state_machine.rs +5 -8
  134. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
  135. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +28 -4
  136. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +20 -41
  137. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +50 -9
  138. package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
  139. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
  140. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
  141. package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
  142. package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
  143. package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
  144. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
  145. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
  146. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
  147. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
  148. package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
  149. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
  150. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
  151. package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
  152. package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
  153. package/sdk-core/crates/sdk-core/tests/common/mod.rs +281 -236
  154. package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
  155. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +9 -14
  156. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -66
  157. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +306 -268
  158. package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
  159. package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
  160. package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
  161. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +37 -38
  162. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +49 -40
  163. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +447 -300
  164. package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
  165. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +157 -157
  166. package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
  167. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -463
  168. package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
  169. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +389 -265
  170. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +250 -185
  171. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -49
  172. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
  173. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +437 -327
  174. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -58
  175. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -30
  176. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -251
  177. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
  178. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +110 -46
  179. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -149
  180. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -32
  181. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1040
  182. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -43
  183. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +402 -245
  184. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +343 -207
  185. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
  186. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
  187. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +155 -140
  188. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -113
  189. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -44
  190. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -48
  191. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -56
  192. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +365 -242
  193. package/sdk-core/crates/sdk-core/tests/main.rs +22 -16
  194. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +233 -187
  195. package/sdk-core/crates/sdk-core/tests/runner.rs +4 -6
  196. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +73 -27
  197. package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
  198. package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
  199. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
  200. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
  201. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
  202. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
  203. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
  204. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
  205. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
  206. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
  207. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +8 -1
  208. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +37 -26
  209. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +180 -87
  210. package/sdk-core/crates/sdk-core-c-bridge/src/lib.rs +89 -5
  211. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +10 -16
  212. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +59 -67
  213. package/sdk-core/crates/sdk-core-c-bridge/src/testing.rs +10 -10
  214. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +57 -22
  215. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +108 -12
  216. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +9 -52
  217. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +74 -91
  218. package/sdk-core/rustfmt.toml +2 -1
  219. package/src/client.rs +206 -289
  220. package/src/helpers/try_into_js.rs +88 -2
  221. package/src/metrics.rs +277 -35
  222. package/src/runtime.rs +94 -45
  223. package/src/testing.rs +9 -16
  224. package/src/worker.rs +86 -68
  225. package/ts/native.ts +39 -3
  226. package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
  227. package/sdk-core/crates/common/src/errors.rs +0 -85
  228. package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
  229. package/sdk-core/crates/macros/LICENSE.txt +0 -21
  230. package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
  231. package/sdk-core/crates/sdk/src/app_data.rs +0 -37
  232. package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
  233. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
@@ -8,38 +8,59 @@
8
8
  //!
9
9
  //! An example of running an activity worker:
10
10
  //! ```no_run
11
- //! use std::{str::FromStr, sync::Arc};
12
- //! use temporalio_sdk::{sdk_client_options, ActContext, Worker};
13
- //! use temporalio_sdk_core::{init_worker, Url, CoreRuntime, RuntimeOptionsBuilder};
11
+ //! use std::str::FromStr;
12
+ //! use temporalio_client::{Client, ClientOptions, Connection, ConnectionOptions};
14
13
  //! use temporalio_common::{
15
- //! worker::{WorkerConfigBuilder, WorkerVersioningStrategy},
16
- //! telemetry::TelemetryOptionsBuilder
14
+ //! telemetry::TelemetryOptions,
15
+ //! worker::{WorkerDeploymentOptions, WorkerDeploymentVersion, WorkerTaskTypes},
17
16
  //! };
17
+ //! use temporalio_macros::activities;
18
+ //! use temporalio_sdk::{
19
+ //! Worker, WorkerOptions,
20
+ //! activities::{ActivityContext, ActivityError},
21
+ //! };
22
+ //! use temporalio_sdk_core::{CoreRuntime, RuntimeOptions, Url};
18
23
  //!
19
- //! #[tokio::main]
20
- //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21
- //! let server_options = sdk_client_options(Url::from_str("http://localhost:7233")?).build();
24
+ //! struct MyActivities;
22
25
  //!
23
- //! let telemetry_options = TelemetryOptionsBuilder::default().build()?;
24
- //! let runtime_options = RuntimeOptionsBuilder::default().telemetry_options(telemetry_options).build().unwrap();
25
- //! let runtime = CoreRuntime::new_assume_tokio(runtime_options)?;
26
- //!
27
- //! let client = server_options.connect("default", None).await?;
26
+ //! #[activities]
27
+ //! impl MyActivities {
28
+ //! #[activity]
29
+ //! pub(crate) async fn echo(
30
+ //! _ctx: ActivityContext,
31
+ //! e: String,
32
+ //! ) -> Result<String, ActivityError> {
33
+ //! Ok(e)
34
+ //! }
35
+ //! }
28
36
  //!
29
- //! let worker_config = WorkerConfigBuilder::default()
30
- //! .namespace("default")
31
- //! .task_queue("task_queue")
32
- //! .versioning_strategy(WorkerVersioningStrategy::None { build_id: "rust-sdk".to_owned() })
37
+ //! #[tokio::main]
38
+ //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39
+ //! let connection_options =
40
+ //! ConnectionOptions::new(Url::from_str("http://localhost:7233")?).build();
41
+ //! let telemetry_options = TelemetryOptions::builder().build();
42
+ //! let runtime_options = RuntimeOptions::builder()
43
+ //! .telemetry_options(telemetry_options)
33
44
  //! .build()?;
45
+ //! let runtime = CoreRuntime::new_assume_tokio(runtime_options)?;
34
46
  //!
35
- //! let core_worker = init_worker(&runtime, worker_config, client)?;
47
+ //! let connection = Connection::connect(connection_options).await?;
48
+ //! let client = Client::new(connection, ClientOptions::new("my_namespace").build())?;
36
49
  //!
37
- //! let mut worker = Worker::new_from_core(Arc::new(core_worker), "task_queue");
38
- //! worker.register_activity(
39
- //! "echo_activity",
40
- //! |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
41
- //! );
50
+ //! let worker_options = WorkerOptions::new("task_queue")
51
+ //! .task_types(WorkerTaskTypes::activity_only())
52
+ //! .deployment_options(WorkerDeploymentOptions {
53
+ //! version: WorkerDeploymentVersion {
54
+ //! deployment_name: "my_deployment".to_owned(),
55
+ //! build_id: "my_build_id".to_owned(),
56
+ //! },
57
+ //! use_worker_versioning: false,
58
+ //! default_versioning_behavior: None,
59
+ //! })
60
+ //! .register_activities(MyActivities)
61
+ //! .build();
42
62
  //!
63
+ //! let mut worker = Worker::new(&runtime, client, worker_options)?;
43
64
  //! worker.run().await?;
44
65
  //!
45
66
  //! Ok(())
@@ -48,48 +69,70 @@
48
69
 
49
70
  #[macro_use]
50
71
  extern crate tracing;
72
+ extern crate self as temporalio_sdk;
51
73
 
52
- mod activity_context;
53
- mod app_data;
74
+ pub mod activities;
54
75
  pub mod interceptors;
55
76
  mod workflow_context;
56
77
  mod workflow_future;
78
+ pub mod workflows;
79
+
80
+ #[macro_export]
81
+ #[doc(hidden)]
82
+ macro_rules! __temporal_select {
83
+ ($($tokens:tt)*) => {
84
+ ::futures_util::select_biased! { $($tokens)* }
85
+ };
86
+ }
87
+
88
+ #[macro_export]
89
+ #[doc(hidden)]
90
+ macro_rules! __temporal_join {
91
+ ($($tokens:tt)*) => {
92
+ ::futures_util::join!($($tokens)*)
93
+ };
94
+ }
95
+
96
+ use workflow_future::WorkflowFunction;
57
97
 
58
- pub use activity_context::ActContext;
59
98
  pub use temporalio_client::Namespace;
60
- use tracing::{Instrument, Span, field};
61
99
  pub use workflow_context::{
62
- ActivityOptions, CancellableFuture, ChildWorkflow, ChildWorkflowOptions, LocalActivityOptions,
63
- NexusOperationOptions, PendingChildWorkflow, Signal, SignalData, SignalWorkflowOptions,
64
- StartedChildWorkflow, TimerOptions, WfContext,
100
+ ActivityExecutionError, ActivityOptions, BaseWorkflowContext, CancellableFuture, ChildWorkflow,
101
+ ChildWorkflowOptions, LocalActivityOptions, NexusOperationOptions, ParentWorkflowInfo,
102
+ PendingChildWorkflow, RootWorkflowInfo, Signal, SignalData, SignalWorkflowOptions,
103
+ StartedChildWorkflow, SyncWorkflowContext, TimerOptions, WorkflowContext, WorkflowContextView,
65
104
  };
66
105
 
67
106
  use crate::{
107
+ activities::{
108
+ ActivityContext, ActivityDefinitions, ActivityError, ActivityImplementer,
109
+ ExecutableActivity,
110
+ },
68
111
  interceptors::WorkerInterceptor,
69
112
  workflow_context::{ChildWfCommon, NexusUnblockData, StartedNexusOperation},
113
+ workflows::{WorkflowDefinitions, WorkflowImplementation, WorkflowImplementer},
70
114
  };
71
115
  use anyhow::{Context, anyhow, bail};
72
- use app_data::AppData;
73
- use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::BoxFuture};
74
- use serde::Serialize;
116
+ use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
75
117
  use std::{
76
118
  any::{Any, TypeId},
77
119
  cell::RefCell,
78
- collections::HashMap,
120
+ collections::{HashMap, HashSet},
79
121
  fmt::{Debug, Display, Formatter},
80
122
  future::Future,
81
123
  panic::AssertUnwindSafe,
82
124
  sync::Arc,
83
125
  time::Duration,
84
126
  };
85
- use temporalio_client::{ClientOptions, ClientOptionsBuilder, client_options_builder};
127
+ use temporalio_client::{Client, NamespacedClient};
86
128
  use temporalio_common::{
87
- Worker as CoreWorker,
88
- errors::PollError,
129
+ ActivityDefinition, WorkflowDefinition,
130
+ data_converters::{DataConverter, SerializationContextData},
131
+ payload_visitor::{decode_payloads, encode_payloads},
89
132
  protos::{
90
133
  TaskToken,
91
134
  coresdk::{
92
- ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt,
135
+ ActivityTaskCompletion, AsJsonPayloadExt,
93
136
  activity_result::{ActivityExecutionResult, ActivityResolution},
94
137
  activity_task::{ActivityTask, activity_task},
95
138
  child_workflow::ChildWorkflowResult,
@@ -111,8 +154,12 @@ use temporalio_common::{
111
154
  failure::v1::{Failure, failure},
112
155
  },
113
156
  },
157
+ worker::{WorkerDeploymentOptions, WorkerTaskTypes, build_id_from_current_exe},
158
+ };
159
+ use temporalio_sdk_core::{
160
+ CoreRuntime, PollError, PollerBehavior, TunerBuilder, Worker as CoreWorker, WorkerConfig,
161
+ WorkerTuner, WorkerVersioningStrategy, WorkflowErrorType, init_worker,
114
162
  };
115
- use temporalio_sdk_core::Url;
116
163
  use tokio::{
117
164
  sync::{
118
165
  Notify,
@@ -123,40 +170,271 @@ use tokio::{
123
170
  };
124
171
  use tokio_stream::wrappers::UnboundedReceiverStream;
125
172
  use tokio_util::sync::CancellationToken;
173
+ use tracing::{Instrument, Span, field};
174
+ use uuid::Uuid;
175
+
176
+ /// Contains options for configuring a worker.
177
+ #[derive(bon::Builder, Clone)]
178
+ #[builder(start_fn = new, on(String, into), state_mod(vis = "pub"))]
179
+ #[non_exhaustive]
180
+ pub struct WorkerOptions {
181
+ /// What task queue will this worker poll from? This task queue name will be used for both
182
+ /// workflow and activity polling.
183
+ #[builder(start_fn)]
184
+ pub task_queue: String,
185
+
186
+ #[builder(field)]
187
+ activities: ActivityDefinitions,
188
+
189
+ #[builder(field)]
190
+ workflows: WorkflowDefinitions,
191
+
192
+ /// Set the deployment options for this worker. Defaults to a hash of the currently running
193
+ /// executable.
194
+ #[builder(default = def_build_id())]
195
+ pub deployment_options: WorkerDeploymentOptions,
196
+ /// A human-readable string that can identify this worker. If set, overrides the identity on
197
+ /// the client used by this worker. If unset and the client has no identity, defaults to
198
+ /// `{pid}@{hostname}`.
199
+ pub client_identity_override: Option<String>,
200
+ /// If set nonzero, workflows will be cached and sticky task queues will be used, meaning that
201
+ /// history updates are applied incrementally to suspended instances of workflow execution.
202
+ /// Workflows are evicted according to a least-recently-used policy once the cache maximum is
203
+ /// reached. Workflows may also be explicitly evicted at any time, or as a result of errors
204
+ /// or failures.
205
+ #[builder(default = 1000)]
206
+ pub max_cached_workflows: usize,
207
+ /// Set a [crate::WorkerTuner] for this worker, which controls how many slots are available for
208
+ /// the different kinds of tasks.
209
+ #[builder(default = Arc::new(TunerBuilder::default().build()))]
210
+ pub tuner: Arc<dyn WorkerTuner + Send + Sync>,
211
+ /// Controls how polling for Workflow tasks will happen on this worker's task queue. See also
212
+ /// [WorkerConfig::nonsticky_to_sticky_poll_ratio]. If using SimpleMaximum, Must be at least 2
213
+ /// when `max_cached_workflows` > 0, or is an error.
214
+ #[builder(default = PollerBehavior::SimpleMaximum(5))]
215
+ pub workflow_task_poller_behavior: PollerBehavior,
216
+ /// Only applies when using [PollerBehavior::SimpleMaximum]
217
+ ///
218
+ /// (max workflow task polls * this number) = the number of max pollers that will be allowed for
219
+ /// the nonsticky queue when sticky tasks are enabled. If both defaults are used, the sticky
220
+ /// queue will allow 4 max pollers while the nonsticky queue will allow one. The minimum for
221
+ /// either poller is 1, so if the maximum allowed is 1 and sticky queues are enabled, there will
222
+ /// be 2 concurrent polls.
223
+ #[builder(default = 0.2)]
224
+ pub nonsticky_to_sticky_poll_ratio: f32,
225
+ /// Controls how polling for Activity tasks will happen on this worker's task queue.
226
+ #[builder(default = PollerBehavior::SimpleMaximum(5))]
227
+ pub activity_task_poller_behavior: PollerBehavior,
228
+ /// Controls how polling for Nexus tasks will happen on this worker's task queue.
229
+ #[builder(default = PollerBehavior::SimpleMaximum(5))]
230
+ pub nexus_task_poller_behavior: PollerBehavior,
231
+ // TODO [rust-sdk-branch]: Will go away once workflow registration can only happen in here.
232
+ // Then it can be auto-determined.
233
+ /// Specifies which task types this worker will poll for.
234
+ ///
235
+ /// Note: At least one task type must be specified or the worker will fail validation.
236
+ #[builder(default = WorkerTaskTypes::all())]
237
+ pub task_types: WorkerTaskTypes,
238
+ /// How long a workflow task is allowed to sit on the sticky queue before it is timed out
239
+ /// and moved to the non-sticky queue where it may be picked up by any worker.
240
+ #[builder(default = Duration::from_secs(10))]
241
+ pub sticky_queue_schedule_to_start_timeout: Duration,
242
+ /// Longest interval for throttling activity heartbeats
243
+ #[builder(default = Duration::from_secs(60))]
244
+ pub max_heartbeat_throttle_interval: Duration,
245
+ /// Default interval for throttling activity heartbeats in case
246
+ /// `ActivityOptions.heartbeat_timeout` is unset.
247
+ /// When the timeout *is* set in the `ActivityOptions`, throttling is set to
248
+ /// `heartbeat_timeout * 0.8`.
249
+ #[builder(default = Duration::from_secs(30))]
250
+ pub default_heartbeat_throttle_interval: Duration,
251
+ /// Sets the maximum number of activities per second the task queue will dispatch, controlled
252
+ /// server-side. Note that this only takes effect upon an activity poll request. If multiple
253
+ /// workers on the same queue have different values set, they will thrash with the last poller
254
+ /// winning.
255
+ ///
256
+ /// Setting this to a nonzero value will also disable eager activity execution.
257
+ pub max_task_queue_activities_per_second: Option<f64>,
258
+ /// Limits the number of activities per second that this worker will process. The worker will
259
+ /// not poll for new activities if by doing so it might receive and execute an activity which
260
+ /// would cause it to exceed this limit. Negative, zero, or NaN values will cause building
261
+ /// the options to fail.
262
+ pub max_worker_activities_per_second: Option<f64>,
263
+ /// Any error types listed here will cause any workflow being processed by this worker to fail,
264
+ /// rather than simply failing the workflow task.
265
+ #[builder(default)]
266
+ pub workflow_failure_errors: HashSet<WorkflowErrorType>,
267
+ /// Like [WorkerConfig::workflow_failure_errors], but specific to certain workflow types (the
268
+ /// map key).
269
+ #[builder(default)]
270
+ pub workflow_types_to_failure_errors: HashMap<String, HashSet<WorkflowErrorType>>,
271
+ /// If set, the worker will issue cancels for all outstanding activities and nexus operations after
272
+ /// shutdown has been initiated and this amount of time has elapsed.
273
+ pub graceful_shutdown_period: Option<Duration>,
274
+ }
275
+
276
+ impl<S: worker_options_builder::State> WorkerOptionsBuilder<S> {
277
+ /// Registers all activities on an activity implementer.
278
+ pub fn register_activities<AI: ActivityImplementer>(mut self, instance: AI) -> Self {
279
+ self.activities.register_activities::<AI>(instance);
280
+ self
281
+ }
282
+ /// Registers a specific activitiy.
283
+ pub fn register_activity<AD>(mut self, instance: Arc<AD::Implementer>) -> Self
284
+ where
285
+ AD: ActivityDefinition + ExecutableActivity,
286
+ AD::Output: Send + Sync,
287
+ {
288
+ self.activities.register_activity::<AD>(instance);
289
+ self
290
+ }
291
+
292
+ /// Registers all workflows on a workflow implementer.
293
+ pub fn register_workflow<WI: WorkflowImplementer>(mut self) -> Self {
294
+ self.workflows.register_workflow::<WI>();
295
+ self
296
+ }
297
+
298
+ /// Register a workflow with a custom factory for instance creation.
299
+ ///
300
+ /// # Warning: Advanced Usage
301
+ ///
302
+ /// This method is intended for scenarios requiring injection of un-serializable
303
+ /// state into workflows.
304
+ ///
305
+ /// **This can easily cause nondeterminism**
306
+ ///
307
+ /// Only use when you understand the implications and have a specific need that cannot be met
308
+ /// otherwise.
309
+ ///
310
+ /// # Panics
311
+ ///
312
+ /// Panics if the workflow type defines an `#[init]` method. Workflows using
313
+ /// factory registration must not have `#[init]` to avoid ambiguity about
314
+ /// instance creation.
315
+ pub fn register_workflow_with_factory<W, F>(mut self, factory: F) -> Self
316
+ where
317
+ W: WorkflowImplementation,
318
+ <W::Run as WorkflowDefinition>::Input: Send,
319
+ F: Fn() -> W + Send + Sync + 'static,
320
+ {
321
+ self.workflows
322
+ .register_workflow_run_with_factory::<W, F>(factory);
323
+ self
324
+ }
325
+ }
326
+
327
+ // Needs to exist to avoid https://github.com/elastio/bon/issues/359
328
+ fn def_build_id() -> WorkerDeploymentOptions {
329
+ WorkerDeploymentOptions::from_build_id(build_id_from_current_exe().to_owned())
330
+ }
331
+
332
+ impl WorkerOptions {
333
+ /// Registers all activities on an activity implementer.
334
+ pub fn register_activities<AI: ActivityImplementer>(&mut self, instance: AI) -> &mut Self {
335
+ self.activities.register_activities::<AI>(instance);
336
+ self
337
+ }
338
+ /// Registers a specific activitiy.
339
+ pub fn register_activity<AD>(&mut self, instance: Arc<AD::Implementer>) -> &mut Self
340
+ where
341
+ AD: ActivityDefinition + ExecutableActivity,
342
+ AD::Output: Send + Sync,
343
+ {
344
+ self.activities.register_activity::<AD>(instance);
345
+ self
346
+ }
347
+ /// Returns all the registered activities by cloning the current set.
348
+ pub fn activities(&self) -> ActivityDefinitions {
349
+ self.activities.clone()
350
+ }
351
+
352
+ /// Registers all workflows on a workflow implementer.
353
+ pub fn register_workflow<WI: WorkflowImplementer>(&mut self) -> &mut Self {
354
+ self.workflows.register_workflow::<WI>();
355
+ self
356
+ }
357
+
358
+ /// Register a workflow with a custom factory for instance creation.
359
+ ///
360
+ /// # Warning: Advanced Usage
361
+ /// See [WorkerOptionsBuilder::register_workflow_with_factory] for more.
362
+ pub fn register_workflow_with_factory<W, F>(&mut self, factory: F) -> &mut Self
363
+ where
364
+ W: WorkflowImplementation,
365
+ <W::Run as WorkflowDefinition>::Input: Send,
366
+ F: Fn() -> W + Send + Sync + 'static,
367
+ {
368
+ self.workflows
369
+ .register_workflow_run_with_factory::<W, F>(factory);
370
+ self
371
+ }
372
+
373
+ /// Returns all the registered workflows by cloning the current set.
374
+ pub fn workflows(&self) -> WorkflowDefinitions {
375
+ self.workflows.clone()
376
+ }
126
377
 
127
- const VERSION: &str = env!("CARGO_PKG_VERSION");
128
-
129
- /// Returns a [ClientOptionsBuilder] with required fields set to appropriate values
130
- /// for the Rust SDK.
131
- pub fn sdk_client_options(
132
- url: impl Into<Url>,
133
- ) -> ClientOptionsBuilder<impl client_options_builder::IsComplete> {
134
- ClientOptions::builder()
135
- .target_url(url)
136
- .client_name("temporal-rust".to_string())
137
- .client_version(VERSION.to_string())
378
+ #[doc(hidden)]
379
+ pub fn to_core_options(
380
+ &self,
381
+ namespace: String,
382
+ connection_identity: String,
383
+ ) -> Result<WorkerConfig, String> {
384
+ WorkerConfig::builder()
385
+ .namespace(namespace)
386
+ .task_queue(self.task_queue.clone())
387
+ .maybe_client_identity_override(self.client_identity_override.clone().or_else(|| {
388
+ connection_identity.is_empty().then(|| {
389
+ format!(
390
+ "{}@{}",
391
+ std::process::id(),
392
+ gethostname::gethostname().to_string_lossy()
393
+ )
394
+ })
395
+ }))
396
+ .max_cached_workflows(self.max_cached_workflows)
397
+ .tuner(self.tuner.clone())
398
+ .workflow_task_poller_behavior(self.workflow_task_poller_behavior)
399
+ .activity_task_poller_behavior(self.activity_task_poller_behavior)
400
+ .nexus_task_poller_behavior(self.nexus_task_poller_behavior)
401
+ .task_types(self.task_types)
402
+ .sticky_queue_schedule_to_start_timeout(self.sticky_queue_schedule_to_start_timeout)
403
+ .max_heartbeat_throttle_interval(self.max_heartbeat_throttle_interval)
404
+ .default_heartbeat_throttle_interval(self.default_heartbeat_throttle_interval)
405
+ .maybe_max_task_queue_activities_per_second(self.max_task_queue_activities_per_second)
406
+ .maybe_max_worker_activities_per_second(self.max_worker_activities_per_second)
407
+ .maybe_graceful_shutdown_period(self.graceful_shutdown_period)
408
+ .versioning_strategy(WorkerVersioningStrategy::WorkerDeploymentBased(
409
+ self.deployment_options.clone(),
410
+ ))
411
+ .workflow_failure_errors(self.workflow_failure_errors.clone())
412
+ .workflow_types_to_failure_errors(self.workflow_types_to_failure_errors.clone())
413
+ .build()
414
+ }
138
415
  }
139
416
 
140
- /// A worker that can poll for and respond to workflow tasks by using [WorkflowFunction]s,
141
- /// and activity tasks by using [ActivityFunction]s
417
+ /// A worker that can poll for and respond to workflow tasks by using
418
+ /// [temporalio_macros::workflow], and activity tasks by using activities defined with
419
+ /// [temporalio_macros::activities].
142
420
  pub struct Worker {
143
421
  common: CommonWorker,
144
422
  workflow_half: WorkflowHalf,
145
423
  activity_half: ActivityHalf,
146
- app_data: Option<AppData>,
147
424
  }
148
425
 
149
426
  struct CommonWorker {
150
- worker: Arc<dyn CoreWorker>,
427
+ worker: Arc<CoreWorker>,
151
428
  task_queue: String,
152
429
  worker_interceptor: Option<Box<dyn WorkerInterceptor>>,
430
+ data_converter: DataConverter,
153
431
  }
154
432
 
433
+ #[derive(Default)]
155
434
  struct WorkflowHalf {
156
435
  /// Maps run id to cached workflow state
157
436
  workflows: RefCell<HashMap<String, WorkflowData>>,
158
- /// Maps workflow type to the function for executing workflow runs with that ID
159
- workflow_fns: RefCell<HashMap<String, WorkflowFunction>>,
437
+ workflow_definitions: WorkflowDefinitions,
160
438
  workflow_removed_from_map: Notify,
161
439
  }
162
440
  struct WorkflowData {
@@ -169,31 +447,71 @@ struct WorkflowFutureHandle<F: Future<Output = Result<WorkflowResult<Payload>, J
169
447
  run_id: String,
170
448
  }
171
449
 
450
+ #[derive(Default)]
172
451
  struct ActivityHalf {
173
452
  /// Maps activity type to the function for executing activities of that type
174
- activity_fns: HashMap<String, ActivityFunction>,
453
+ activities: ActivityDefinitions,
175
454
  task_tokens_to_cancels: HashMap<TaskToken, CancellationToken>,
176
455
  }
177
456
 
178
457
  impl Worker {
179
- /// Create a new Rust SDK worker from a core worker
180
- pub fn new_from_core(worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
458
+ /// Create a new worker from an existing connection, and options.
459
+ pub fn new(
460
+ runtime: &CoreRuntime,
461
+ client: Client,
462
+ mut options: WorkerOptions,
463
+ ) -> Result<Self, Box<dyn std::error::Error>> {
464
+ let acts = std::mem::take(&mut options.activities);
465
+ let wfs = std::mem::take(&mut options.workflows);
466
+ let wc = options
467
+ .to_core_options(client.namespace(), client.identity())
468
+ .map_err(|s| anyhow::anyhow!("{s}"))?;
469
+ let core = init_worker(runtime, wc, client.connection().clone())?;
470
+ let mut me = Self::new_from_core_definitions(
471
+ Arc::new(core),
472
+ client.data_converter().clone(),
473
+ Default::default(),
474
+ Default::default(),
475
+ );
476
+ me.activity_half.activities = acts;
477
+ me.workflow_half.workflow_definitions = wfs;
478
+ Ok(me)
479
+ }
480
+
481
+ // TODO [rust-sdk-branch]: Eliminate this constructor in favor of passing in fake connection
482
+ #[doc(hidden)]
483
+ pub fn new_from_core(worker: Arc<CoreWorker>, data_converter: DataConverter) -> Self {
484
+ Self::new_from_core_definitions(
485
+ worker,
486
+ data_converter,
487
+ Default::default(),
488
+ Default::default(),
489
+ )
490
+ }
491
+
492
+ // TODO [rust-sdk-branch]: Eliminate this constructor in favor of passing in fake connection
493
+ #[doc(hidden)]
494
+ pub fn new_from_core_definitions(
495
+ worker: Arc<CoreWorker>,
496
+ data_converter: DataConverter,
497
+ activities: ActivityDefinitions,
498
+ workflows: WorkflowDefinitions,
499
+ ) -> Self {
181
500
  Self {
182
501
  common: CommonWorker {
502
+ task_queue: worker.get_config().task_queue.clone(),
183
503
  worker,
184
- task_queue: task_queue.into(),
185
504
  worker_interceptor: None,
505
+ data_converter,
186
506
  },
187
507
  workflow_half: WorkflowHalf {
188
- workflows: Default::default(),
189
- workflow_fns: Default::default(),
190
- workflow_removed_from_map: Default::default(),
508
+ workflow_definitions: workflows,
509
+ ..Default::default()
191
510
  },
192
511
  activity_half: ActivityHalf {
193
- activity_fns: Default::default(),
194
- task_tokens_to_cancels: Default::default(),
512
+ activities,
513
+ ..Default::default()
195
514
  },
196
- app_data: Some(Default::default()),
197
515
  }
198
516
  }
199
517
 
@@ -202,58 +520,67 @@ impl Worker {
202
520
  &self.common.task_queue
203
521
  }
204
522
 
205
- /// Return a handle that can be used to initiate shutdown.
206
- /// TODO: Doc better after shutdown changes
523
+ /// Return a handle that can be used to initiate shutdown. This is useful because [Worker::run]
524
+ /// takes self mutably, so you may want to obtain a handle for shutting down before running.
207
525
  pub fn shutdown_handle(&self) -> impl Fn() + use<> {
208
526
  let w = self.common.worker.clone();
209
527
  move || w.initiate_shutdown()
210
528
  }
211
529
 
212
- /// Register a Workflow function to invoke when the Worker is asked to run a workflow of
213
- /// `workflow_type`
214
- pub fn register_wf(
215
- &mut self,
216
- workflow_type: impl Into<String>,
217
- wf_function: impl Into<WorkflowFunction>,
218
- ) {
219
- self.workflow_half
220
- .workflow_fns
221
- .get_mut()
222
- .insert(workflow_type.into(), wf_function.into());
530
+ /// Registers all activities on an activity implementer.
531
+ pub fn register_activities<AI: ActivityImplementer>(&mut self, instance: AI) -> &mut Self {
532
+ self.activity_half
533
+ .activities
534
+ .register_activities::<AI>(instance);
535
+ self
536
+ }
537
+ /// Registers a specific activitiy.
538
+ pub fn register_activity<AD>(&mut self, instance: Arc<AD::Implementer>) -> &mut Self
539
+ where
540
+ AD: ActivityDefinition + ExecutableActivity,
541
+ AD::Output: Send + Sync,
542
+ {
543
+ self.activity_half
544
+ .activities
545
+ .register_activity::<AD>(instance);
546
+ self
223
547
  }
224
548
 
225
- /// Register an Activity function to invoke when the Worker is asked to run an activity of
226
- /// `activity_type`
227
- pub fn register_activity<A, R, O>(
228
- &mut self,
229
- activity_type: impl Into<String>,
230
- act_function: impl IntoActivityFunc<A, R, O>,
231
- ) {
232
- self.activity_half.activity_fns.insert(
233
- activity_type.into(),
234
- ActivityFunction {
235
- act_func: act_function.into_activity_fn(),
236
- },
237
- );
549
+ /// Registers all workflows on a workflow implementer.
550
+ pub fn register_workflow<WI: WorkflowImplementer>(&mut self) -> &mut Self {
551
+ self.workflow_half
552
+ .workflow_definitions
553
+ .register_workflow::<WI>();
554
+ self
238
555
  }
239
556
 
240
- /// Insert Custom App Context for Workflows and Activities
241
- pub fn insert_app_data<T: Send + Sync + 'static>(&mut self, data: T) {
242
- self.app_data.as_mut().map(|a| a.insert(data));
557
+ /// Register a workflow with a custom factory for instance creation.
558
+ ///
559
+ /// See [WorkerOptionsBuilder::register_workflow_with_factory] for more.
560
+ pub fn register_workflow_with_factory<W, F>(&mut self, factory: F) -> &mut Self
561
+ where
562
+ W: WorkflowImplementation,
563
+ <W::Run as WorkflowDefinition>::Input: Send,
564
+ F: Fn() -> W + Send + Sync + 'static,
565
+ {
566
+ self.workflow_half
567
+ .workflow_definitions
568
+ .register_workflow_run_with_factory::<W, F>(factory);
569
+ self
243
570
  }
244
571
 
245
572
  /// Runs the worker. Eventually resolves after the worker has been explicitly shut down,
246
573
  /// or may return early with an error in the event of some unresolvable problem.
247
574
  pub async fn run(&mut self) -> Result<(), anyhow::Error> {
248
575
  let shutdown_token = CancellationToken::new();
249
- let (common, wf_half, act_half, app_data) = self.split_apart();
250
- let safe_app_data = Arc::new(
251
- app_data
252
- .take()
253
- .ok_or_else(|| anyhow!("app_data should exist on run"))?,
254
- );
576
+ let (common, wf_half, act_half) = self.split_apart();
255
577
  let (wf_future_tx, wf_future_rx) = unbounded_channel();
256
578
  let (completions_tx, completions_rx) = unbounded_channel();
579
+
580
+ // Workflows run in a LocalSet because they use Rc<RefCell> for state management.
581
+ // This allows them to not require Send/Sync bounds.
582
+ let workflow_local_set = tokio::task::LocalSet::new();
583
+
257
584
  let wf_future_joiner = async {
258
585
  UnboundedReceiverStream::new(wf_future_rx)
259
586
  .map(Result::<_, anyhow::Error>::Ok)
@@ -265,7 +592,14 @@ impl Worker {
265
592
  }| {
266
593
  let wf_half = &*wf_half;
267
594
  async move {
268
- join_handle.await??;
595
+ let result = join_handle.await?;
596
+ // Eviction is normal workflow lifecycle - workflows loop waiting for
597
+ // eviction after completion to manage cache cleanup
598
+ if let Err(e) = result
599
+ && !matches!(e, WorkflowTermination::Evicted)
600
+ {
601
+ return Err(e.into());
602
+ }
269
603
  debug!(run_id=%run_id, "Removing workflow from cache");
270
604
  wf_half.workflows.borrow_mut().remove(&run_id);
271
605
  wf_half.workflow_removed_from_map.notify_one();
@@ -279,7 +613,13 @@ impl Worker {
279
613
  let wf_completion_processor = async {
280
614
  UnboundedReceiverStream::new(completions_rx)
281
615
  .map(Ok)
282
- .try_for_each_concurrent(None, |completion| async {
616
+ .try_for_each_concurrent(None, |mut completion| async {
617
+ encode_payloads(
618
+ &mut completion,
619
+ common.data_converter.codec(),
620
+ &SerializationContextData::Workflow,
621
+ )
622
+ .await;
283
623
  if let Some(ref i) = common.worker_interceptor {
284
624
  i.on_workflow_activation_completion(&completion).await;
285
625
  }
@@ -290,72 +630,89 @@ impl Worker {
290
630
  .context("Workflow completions processor encountered an error")
291
631
  };
292
632
  tokio::try_join!(
293
- // Workflow polling loop
633
+ // Workflow-related tasks run inside LocalSet (allows !Send futures)
294
634
  async {
295
- loop {
296
- let activation = match common.worker.poll_workflow_activation().await {
297
- Err(PollError::ShutDown) => {
298
- break;
635
+ workflow_local_set.run_until(async {
636
+ tokio::try_join!(
637
+ // Workflow polling loop
638
+ async {
639
+ loop {
640
+ let mut activation =
641
+ match common.worker.poll_workflow_activation().await {
642
+ Err(PollError::ShutDown) => {
643
+ break;
644
+ }
645
+ o => o?,
646
+ };
647
+ decode_payloads(
648
+ &mut activation,
649
+ common.data_converter.codec(),
650
+ &SerializationContextData::Workflow,
651
+ )
652
+ .await;
653
+ if let Some(ref i) = common.worker_interceptor {
654
+ i.on_workflow_activation(&activation).await?;
655
+ }
656
+ if let Some(wf_fut) = wf_half
657
+ .workflow_activation_handler(
658
+ common,
659
+ shutdown_token.clone(),
660
+ activation,
661
+ &completions_tx,
662
+ )
663
+ .await?
664
+ && wf_future_tx.send(wf_fut).is_err()
665
+ {
666
+ panic!(
667
+ "Receive half of completion processor channel cannot be dropped"
668
+ );
669
+ }
299
670
  }
300
- o => o?,
301
- };
302
- if let Some(ref i) = common.worker_interceptor {
303
- i.on_workflow_activation(&activation).await?;
304
- }
305
- if let Some(wf_fut) = wf_half
306
- .workflow_activation_handler(
307
- common,
308
- shutdown_token.clone(),
309
- activation,
310
- &completions_tx,
311
- )
312
- .await?
313
- && wf_future_tx.send(wf_fut).is_err()
314
- {
315
- panic!("Receive half of completion processor channel cannot be dropped");
316
- }
317
- }
318
- // Tell still-alive workflows to evict themselves
319
- shutdown_token.cancel();
320
- // It's important to drop these so the future and completion processors will
321
- // terminate.
322
- drop(wf_future_tx);
323
- drop(completions_tx);
324
- Result::<_, anyhow::Error>::Ok(())
671
+ // Tell still-alive workflows to evict themselves
672
+ shutdown_token.cancel();
673
+ // It's important to drop these so the future and completion processors will
674
+ // terminate.
675
+ drop(wf_future_tx);
676
+ drop(completions_tx);
677
+ Result::<_, anyhow::Error>::Ok(())
678
+ },
679
+ wf_future_joiner,
680
+ )
681
+ }).await
325
682
  },
326
683
  // Only poll on the activity queue if activity functions have been registered. This
327
684
  // makes tests which use mocks dramatically more manageable.
328
685
  async {
329
- if !act_half.activity_fns.is_empty() {
686
+ if !act_half.activities.is_empty() {
330
687
  loop {
331
688
  let activity = common.worker.poll_activity_task().await;
332
689
  if matches!(activity, Err(PollError::ShutDown)) {
333
690
  break;
334
691
  }
692
+ let mut activity = activity?;
693
+ decode_payloads(
694
+ &mut activity,
695
+ common.data_converter.codec(),
696
+ &SerializationContextData::Activity,
697
+ )
698
+ .await;
335
699
  act_half.activity_task_handler(
336
700
  common.worker.clone(),
337
- safe_app_data.clone(),
338
701
  common.task_queue.clone(),
339
- activity?,
702
+ common.data_converter.clone(),
703
+ activity,
340
704
  )?;
341
705
  }
342
706
  };
343
707
  Result::<_, anyhow::Error>::Ok(())
344
708
  },
345
- wf_future_joiner,
346
709
  wf_completion_processor,
347
710
  )?;
348
711
 
349
- info!("Polling loops exited");
350
712
  if let Some(i) = self.common.worker_interceptor.as_ref() {
351
713
  i.on_shutdown(self);
352
714
  }
353
715
  self.common.worker.shutdown().await;
354
- debug!("Worker shutdown complete");
355
- self.app_data = Some(
356
- Arc::try_unwrap(safe_app_data)
357
- .map_err(|_| anyhow!("some references of AppData exist on worker shutdown"))?,
358
- );
359
716
  Ok(())
360
717
  }
361
718
 
@@ -367,7 +724,7 @@ impl Worker {
367
724
  /// Turns this rust worker into a new worker with all the same workflows and activities
368
725
  /// registered, but with a new underlying core worker. Can be used to swap the worker for
369
726
  /// a replay worker, change task queues, etc.
370
- pub fn with_new_core_worker(&mut self, new_core_worker: Arc<dyn CoreWorker>) {
727
+ pub fn with_new_core_worker(&mut self, new_core_worker: Arc<CoreWorker>) {
371
728
  self.common.worker = new_core_worker;
372
729
  }
373
730
 
@@ -377,19 +734,21 @@ impl Worker {
377
734
  self.workflow_half.workflows.borrow().len()
378
735
  }
379
736
 
380
- fn split_apart(
381
- &mut self,
382
- ) -> (
383
- &mut CommonWorker,
384
- &mut WorkflowHalf,
385
- &mut ActivityHalf,
386
- &mut Option<AppData>,
387
- ) {
737
+ /// Returns the instance key for this worker, used for worker heartbeating.
738
+ pub fn worker_instance_key(&self) -> Uuid {
739
+ self.common.worker.worker_instance_key()
740
+ }
741
+
742
+ #[doc(hidden)]
743
+ pub fn core_worker(&self) -> Arc<CoreWorker> {
744
+ self.common.worker.clone()
745
+ }
746
+
747
+ fn split_apart(&mut self) -> (&mut CommonWorker, &mut WorkflowHalf, &mut ActivityHalf) {
388
748
  (
389
749
  &mut self.common,
390
750
  &mut self.workflow_half,
391
751
  &mut self.activity_half,
392
- &mut self.app_data,
393
752
  )
394
753
  }
395
754
  }
@@ -419,13 +778,33 @@ impl WorkflowHalf {
419
778
  Some(Variant::InitializeWorkflow(ref mut sw)) => Some(sw),
420
779
  _ => None,
421
780
  }) {
422
- let workflow_type = &sw.workflow_type;
781
+ let workflow_type = sw.workflow_type.clone();
782
+ let payload_converter = common.data_converter.payload_converter().clone();
423
783
  let (wff, activations) = {
424
- let wf_fns_borrow = self.workflow_fns.borrow();
425
-
426
- let Some(wf_function) = wf_fns_borrow.get(workflow_type) else {
784
+ if let Some(factory) = self.workflow_definitions.get_workflow(&workflow_type) {
785
+ match WorkflowFunction::from_invocation(factory).start_workflow(
786
+ common.worker.get_config().namespace.clone(),
787
+ common.task_queue.clone(),
788
+ run_id.clone(),
789
+ std::mem::take(sw),
790
+ completions_tx.clone(),
791
+ payload_converter,
792
+ ) {
793
+ Ok(result) => result,
794
+ Err(e) => {
795
+ warn!("Failed to create workflow {workflow_type}: {e}");
796
+ completions_tx
797
+ .send(WorkflowActivationCompletion::fail(
798
+ run_id,
799
+ format!("Failed to create workflow: {e}").into(),
800
+ Some(WorkflowTaskFailedCause::WorkflowWorkerUnhandledFailure),
801
+ ))
802
+ .expect("Completion channel intact");
803
+ return Ok(None);
804
+ }
805
+ }
806
+ } else {
427
807
  warn!("Workflow type {workflow_type} not found");
428
-
429
808
  completions_tx
430
809
  .send(WorkflowActivationCompletion::fail(
431
810
  run_id,
@@ -434,22 +813,19 @@ impl WorkflowHalf {
434
813
  ))
435
814
  .expect("Completion channel intact");
436
815
  return Ok(None);
437
- };
438
-
439
- wf_function.start_workflow(
440
- common.worker.get_config().namespace.clone(),
441
- common.task_queue.clone(),
442
- std::mem::take(sw),
443
- completions_tx.clone(),
444
- )
816
+ }
445
817
  };
446
- let jh = tokio::spawn(async move {
818
+ // Wrap in unconstrained to prevent Tokio from imposing limits on commands per poll
819
+ // TODO [rust-sdk-branch]: Deadlock detection
820
+ let wff = tokio::task::unconstrained(wff);
821
+ // The LocalSet is created in Worker::run().
822
+ let jh = tokio::task::spawn_local(async move {
447
823
  tokio::select! {
448
824
  r = wff.fuse() => r,
449
825
  // TODO: This probably shouldn't abort early, as it could cause an in-progress
450
826
  // complete to abort. Send synthetic remove activation
451
827
  _ = shutdown_token.cancelled() => {
452
- Ok(WfExitValue::Evicted)
828
+ Err(WorkflowTermination::Evicted)
453
829
  }
454
830
  }
455
831
  });
@@ -513,23 +889,19 @@ impl ActivityHalf {
513
889
  /// Spawns off a task to handle the provided activity task
514
890
  fn activity_task_handler(
515
891
  &mut self,
516
- worker: Arc<dyn CoreWorker>,
517
- app_data: Arc<AppData>,
892
+ worker: Arc<CoreWorker>,
518
893
  task_queue: String,
894
+ data_converter: DataConverter,
519
895
  activity: ActivityTask,
520
896
  ) -> Result<(), anyhow::Error> {
521
897
  match activity.variant {
522
898
  Some(activity_task::Variant::Start(start)) => {
523
- let act_fn = self
524
- .activity_fns
525
- .get(&start.activity_type)
526
- .ok_or_else(|| {
527
- anyhow!(
528
- "No function registered for activity type {}",
529
- start.activity_type
530
- )
531
- })?
532
- .clone();
899
+ let act_fn = self.activities.get(&start.activity_type).ok_or_else(|| {
900
+ anyhow!(
901
+ "No function registered for activity type {}",
902
+ start.activity_type
903
+ )
904
+ })?;
533
905
  let span = info_span!(
534
906
  "RunActivity",
535
907
  "otel.name" = format!("RunActivity:{}", start.activity_type),
@@ -543,23 +915,18 @@ impl ActivityHalf {
543
915
  self.task_tokens_to_cancels
544
916
  .insert(task_token.clone().into(), ct.clone());
545
917
 
546
- let (ctx, arg) = ActContext::new(
547
- worker.clone(),
548
- app_data,
549
- ct,
550
- task_queue,
551
- task_token.clone(),
552
- start,
553
- );
918
+ let (ctx, args) =
919
+ ActivityContext::new(worker.clone(), ct, task_queue, task_token.clone(), start);
920
+ let codec_data_converter = data_converter.clone();
554
921
 
555
922
  tokio::spawn(async move {
556
923
  let act_fut = async move {
557
- if let Some(info) = &ctx.get_info().workflow_execution {
924
+ if let Some(info) = &ctx.info().workflow_execution {
558
925
  Span::current()
559
926
  .record("temporalWorkflowID", &info.workflow_id)
560
927
  .record("temporalRunID", &info.run_id);
561
928
  }
562
- (act_fn.act_func)(ctx, arg).await
929
+ (act_fn)(args, data_converter, ctx).await
563
930
  }
564
931
  .instrument(span);
565
932
  let output = AssertUnwindSafe(act_fut).catch_unwind().await;
@@ -568,16 +935,16 @@ impl ActivityHalf {
568
935
  format!("Activity function panicked: {}", panic_formatter(e)),
569
936
  true,
570
937
  )),
571
- Ok(Ok(ActExitValue::Normal(p))) => ActivityExecutionResult::ok(p),
572
- Ok(Ok(ActExitValue::WillCompleteAsync)) => {
573
- ActivityExecutionResult::will_complete_async()
574
- }
938
+ Ok(Ok(p)) => ActivityExecutionResult::ok(p),
575
939
  Ok(Err(err)) => match err {
576
940
  ActivityError::Retryable {
577
941
  source,
578
942
  explicit_delay,
579
943
  } => ActivityExecutionResult::fail({
580
- let mut f = Failure::application_failure_from_error(source, false);
944
+ let mut f = Failure::application_failure_from_error(
945
+ anyhow::Error::from_boxed(source),
946
+ false,
947
+ );
581
948
  if let Some(d) = explicit_delay
582
949
  && let Some(failure::FailureInfo::ApplicationFailureInfo(fi)) =
583
950
  f.failure_info.as_mut()
@@ -590,16 +957,27 @@ impl ActivityHalf {
590
957
  ActivityExecutionResult::cancel_from_details(details)
591
958
  }
592
959
  ActivityError::NonRetryable(nre) => ActivityExecutionResult::fail(
593
- Failure::application_failure_from_error(nre, true),
960
+ Failure::application_failure_from_error(
961
+ anyhow::Error::from_boxed(nre),
962
+ true,
963
+ ),
594
964
  ),
965
+ ActivityError::WillCompleteAsync => {
966
+ ActivityExecutionResult::will_complete_async()
967
+ }
595
968
  },
596
969
  };
597
- worker
598
- .complete_activity_task(ActivityTaskCompletion {
599
- task_token,
600
- result: Some(result),
601
- })
602
- .await?;
970
+ let mut completion = ActivityTaskCompletion {
971
+ task_token,
972
+ result: Some(result),
973
+ };
974
+ encode_payloads(
975
+ &mut completion,
976
+ codec_data_converter.codec(),
977
+ &SerializationContextData::Activity,
978
+ )
979
+ .await;
980
+ worker.complete_activity_task(completion).await?;
603
981
  Ok::<_, anyhow::Error>(())
604
982
  });
605
983
  }
@@ -630,7 +1008,7 @@ enum UnblockEvent {
630
1008
  }
631
1009
 
632
1010
  /// Result of awaiting on a timer
633
- #[derive(Debug, Copy, Clone)]
1011
+ #[derive(Debug, Copy, Clone, PartialEq, Eq)]
634
1012
  pub enum TimerResult {
635
1013
  /// The timer was cancelled
636
1014
  Cancelled,
@@ -639,13 +1017,13 @@ pub enum TimerResult {
639
1017
  }
640
1018
 
641
1019
  /// Successful result of sending a signal to an external workflow
642
- #[derive(Debug)]
1020
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
643
1021
  pub struct SignalExternalOk;
644
1022
  /// Result of awaiting on sending a signal to an external workflow
645
1023
  pub type SignalExternalWfResult = Result<SignalExternalOk, Failure>;
646
1024
 
647
1025
  /// Successful result of sending a cancel request to an external workflow
648
- #[derive(Debug)]
1026
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
649
1027
  pub struct CancelExternalOk;
650
1028
  /// Result of awaiting on sending a cancel request to an external workflow
651
1029
  pub type CancelExternalWfResult = Result<CancelExternalOk, Failure>;
@@ -834,8 +1212,6 @@ enum RustWfCmd {
834
1212
  NewCmd(CommandCreateRequest),
835
1213
  NewNonblockingCmd(workflow_command::Variant),
836
1214
  SubscribeChildWorkflowCompletion(CommandSubscribeChildWorkflowCompletion),
837
- SubscribeSignal(String, UnboundedSender<SignalData>),
838
- RegisterUpdate(String, UpdateFunctions),
839
1215
  SubscribeNexusOperationCompletion {
840
1216
  seq: u32,
841
1217
  unblocker: oneshot::Sender<UnblockEvent>,
@@ -852,81 +1228,61 @@ struct CommandSubscribeChildWorkflowCompletion {
852
1228
  unblocker: oneshot::Sender<UnblockEvent>,
853
1229
  }
854
1230
 
855
- type WfFunc = dyn Fn(WfContext) -> BoxFuture<'static, Result<WfExitValue<Payload>, anyhow::Error>>
856
- + Send
857
- + Sync
858
- + 'static;
1231
+ /// The result of running a workflow.
1232
+ ///
1233
+ /// Successful completion returns `Ok(T)` where `T` is the workflow's return type.
1234
+ /// Non-error terminations (cancel, eviction, continue-as-new) return `Err(WorkflowTermination)`.
1235
+ pub type WorkflowResult<T> = Result<T, WorkflowTermination>;
1236
+
1237
+ /// Represents ways a workflow can terminate without producing a normal result.
1238
+ ///
1239
+ /// This is used as the error type in [`WorkflowResult<T>`] for non-error termination conditions
1240
+ /// like cancellation, eviction, continue-as-new, or actual failures.
1241
+ #[derive(Debug, thiserror::Error)]
1242
+ pub enum WorkflowTermination {
1243
+ /// The workflow was cancelled.
1244
+ #[error("Workflow cancelled")]
1245
+ Cancelled,
859
1246
 
860
- /// The user's async function / workflow code
861
- pub struct WorkflowFunction {
862
- wf_func: Box<WfFunc>,
1247
+ /// The workflow was evicted from the cache.
1248
+ #[error("Workflow evicted from cache")]
1249
+ Evicted,
1250
+
1251
+ /// The workflow should continue as a new execution.
1252
+ #[error("Continue as new")]
1253
+ ContinueAsNew(Box<ContinueAsNewWorkflowExecution>),
1254
+
1255
+ /// The workflow failed with an error.
1256
+ #[error("Workflow failed: {0}")]
1257
+ Failed(#[source] anyhow::Error),
863
1258
  }
864
1259
 
865
- impl<F, Fut, O> From<F> for WorkflowFunction
866
- where
867
- F: Fn(WfContext) -> Fut + Send + Sync + 'static,
868
- Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
869
- O: Serialize,
870
- {
871
- fn from(wf_func: F) -> Self {
872
- Self::new(wf_func)
1260
+ impl WorkflowTermination {
1261
+ /// Construct a [WorkflowTermination::ContinueAsNew]
1262
+ pub fn continue_as_new(can: ContinueAsNewWorkflowExecution) -> Self {
1263
+ Self::ContinueAsNew(Box::new(can))
873
1264
  }
874
- }
875
1265
 
876
- impl WorkflowFunction {
877
- /// Build a workflow function from a closure or function pointer which accepts a [WfContext]
878
- pub fn new<F, Fut, O>(f: F) -> Self
879
- where
880
- F: Fn(WfContext) -> Fut + Send + Sync + 'static,
881
- Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
882
- O: Serialize,
883
- {
884
- Self {
885
- wf_func: Box::new(move |ctx: WfContext| {
886
- (f)(ctx)
887
- .map(|r| {
888
- r.and_then(|r| {
889
- Ok(match r {
890
- WfExitValue::ContinueAsNew(b) => WfExitValue::ContinueAsNew(b),
891
- WfExitValue::Cancelled => WfExitValue::Cancelled,
892
- WfExitValue::Evicted => WfExitValue::Evicted,
893
- WfExitValue::Normal(o) => WfExitValue::Normal(o.as_json_payload()?),
894
- })
895
- })
896
- })
897
- .boxed()
898
- }),
899
- }
1266
+ /// Construct a [WorkflowTermination::Failed] variant from any error.
1267
+ pub fn failed(err: impl Into<anyhow::Error>) -> Self {
1268
+ Self::Failed(err.into())
900
1269
  }
901
1270
  }
902
1271
 
903
- /// The result of running a workflow
904
- pub type WorkflowResult<T> = Result<WfExitValue<T>, anyhow::Error>;
905
-
906
- /// Workflow functions may return these values when exiting
907
- #[derive(Debug, derive_more::From)]
908
- pub enum WfExitValue<T> {
909
- /// Continue the workflow as a new execution
910
- #[from(ignore)]
911
- ContinueAsNew(Box<ContinueAsNewWorkflowExecution>),
912
- /// Confirm the workflow was cancelled (can be automatic in a more advanced iteration)
913
- #[from(ignore)]
914
- Cancelled,
915
- /// The run was evicted
916
- #[from(ignore)]
917
- Evicted,
918
- /// Finish with a result
919
- Normal(T),
1272
+ impl From<anyhow::Error> for WorkflowTermination {
1273
+ fn from(err: anyhow::Error) -> Self {
1274
+ Self::Failed(err)
1275
+ }
920
1276
  }
921
1277
 
922
- impl<T> WfExitValue<T> {
923
- /// Construct a [WfExitValue::ContinueAsNew] variant (handles boxing)
924
- pub fn continue_as_new(can: ContinueAsNewWorkflowExecution) -> Self {
925
- Self::ContinueAsNew(Box::new(can))
1278
+ impl From<ActivityExecutionError> for WorkflowTermination {
1279
+ fn from(value: ActivityExecutionError) -> Self {
1280
+ Self::failed(value)
926
1281
  }
927
1282
  }
928
1283
 
929
1284
  /// Activity functions may return these values when exiting
1285
+ #[derive(Debug)]
930
1286
  pub enum ActExitValue<T> {
931
1287
  /// Completion requires an asynchronous callback
932
1288
  WillCompleteAsync,
@@ -940,180 +1296,6 @@ impl<T: AsJsonPayloadExt> From<T> for ActExitValue<T> {
940
1296
  }
941
1297
  }
942
1298
 
943
- type BoxActFn = Arc<
944
- dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<ActExitValue<Payload>, ActivityError>>
945
- + Send
946
- + Sync,
947
- >;
948
-
949
- /// Container for user-defined activity functions
950
- #[derive(Clone)]
951
- pub struct ActivityFunction {
952
- act_func: BoxActFn,
953
- }
954
-
955
- /// Returned as errors from activity functions
956
- #[derive(Debug)]
957
- pub enum ActivityError {
958
- /// This error can be returned from activities to allow the explicit configuration of certain
959
- /// error properties. It's also the default error type that arbitrary errors will be converted
960
- /// into.
961
- Retryable {
962
- /// The underlying error
963
- source: anyhow::Error,
964
- /// If specified, the next retry (if there is one) will occur after this delay
965
- explicit_delay: Option<Duration>,
966
- },
967
- /// Return this error to indicate your activity is cancelling
968
- Cancelled {
969
- /// Some data to save as the cancellation reason
970
- details: Option<Payload>,
971
- },
972
- /// Return this error to indicate that your activity non-retryable
973
- /// this is a transparent wrapper around anyhow Error so essentially any type of error
974
- /// could be used here.
975
- NonRetryable(anyhow::Error),
976
- }
977
-
978
- impl<E> From<E> for ActivityError
979
- where
980
- E: Into<anyhow::Error>,
981
- {
982
- fn from(source: E) -> Self {
983
- Self::Retryable {
984
- source: source.into(),
985
- explicit_delay: None,
986
- }
987
- }
988
- }
989
-
990
- impl ActivityError {
991
- /// Construct a cancelled error without details
992
- pub fn cancelled() -> Self {
993
- Self::Cancelled { details: None }
994
- }
995
- }
996
-
997
- /// Closures / functions which can be turned into activity functions implement this trait
998
- pub trait IntoActivityFunc<Args, Res, Out> {
999
- /// Consume the closure or fn pointer and turned it into a boxed activity function
1000
- fn into_activity_fn(self) -> BoxActFn;
1001
- }
1002
-
1003
- impl<A, Rf, R, O, F> IntoActivityFunc<A, Rf, O> for F
1004
- where
1005
- F: (Fn(ActContext, A) -> Rf) + Sync + Send + 'static,
1006
- A: FromJsonPayloadExt + Send,
1007
- Rf: Future<Output = Result<R, ActivityError>> + Send + 'static,
1008
- R: Into<ActExitValue<O>>,
1009
- O: AsJsonPayloadExt,
1010
- {
1011
- fn into_activity_fn(self) -> BoxActFn {
1012
- let wrapper = move |ctx: ActContext, input: Payload| {
1013
- // Some minor gymnastics are required to avoid needing to clone the function
1014
- match A::from_json_payload(&input) {
1015
- Ok(deser) => self(ctx, deser)
1016
- .map(|r| {
1017
- r.and_then(|r| {
1018
- let exit_val: ActExitValue<O> = r.into();
1019
- match exit_val {
1020
- ActExitValue::WillCompleteAsync => {
1021
- Ok(ActExitValue::WillCompleteAsync)
1022
- }
1023
- ActExitValue::Normal(x) => match x.as_json_payload() {
1024
- Ok(v) => Ok(ActExitValue::Normal(v)),
1025
- Err(e) => Err(ActivityError::NonRetryable(e)),
1026
- },
1027
- }
1028
- })
1029
- })
1030
- .boxed(),
1031
- Err(e) => async move { Err(ActivityError::NonRetryable(e.into())) }.boxed(),
1032
- }
1033
- };
1034
- Arc::new(wrapper)
1035
- }
1036
- }
1037
-
1038
- /// Extra information attached to workflow updates
1039
- #[derive(Clone)]
1040
- pub struct UpdateInfo {
1041
- /// The update's id, unique within the workflow
1042
- pub update_id: String,
1043
- /// Headers attached to the update
1044
- pub headers: HashMap<String, Payload>,
1045
- }
1046
-
1047
- /// Context for a workflow update
1048
- pub struct UpdateContext {
1049
- /// The workflow context, can be used to do normal workflow things inside the update handler
1050
- pub wf_ctx: WfContext,
1051
- /// Additional update info
1052
- pub info: UpdateInfo,
1053
- }
1054
-
1055
- struct UpdateFunctions {
1056
- validator: BoxUpdateValidatorFn,
1057
- handler: BoxUpdateHandlerFn,
1058
- }
1059
-
1060
- impl UpdateFunctions {
1061
- pub(crate) fn new<Arg, Res>(
1062
- v: impl IntoUpdateValidatorFunc<Arg> + Sized,
1063
- h: impl IntoUpdateHandlerFunc<Arg, Res> + Sized,
1064
- ) -> Self {
1065
- Self {
1066
- validator: v.into_update_validator_fn(),
1067
- handler: h.into_update_handler_fn(),
1068
- }
1069
- }
1070
- }
1071
-
1072
- type BoxUpdateValidatorFn = Box<dyn Fn(&UpdateInfo, &Payload) -> Result<(), anyhow::Error> + Send>;
1073
- /// Closures / functions which can be turned into update validation functions implement this trait
1074
- pub trait IntoUpdateValidatorFunc<Arg> {
1075
- /// Consume the closure/fn pointer and turn it into an update validator
1076
- fn into_update_validator_fn(self) -> BoxUpdateValidatorFn;
1077
- }
1078
- impl<A, F> IntoUpdateValidatorFunc<A> for F
1079
- where
1080
- A: FromJsonPayloadExt + Send,
1081
- F: (for<'a> Fn(&'a UpdateInfo, A) -> Result<(), anyhow::Error>) + Send + 'static,
1082
- {
1083
- fn into_update_validator_fn(self) -> BoxUpdateValidatorFn {
1084
- let wrapper = move |ctx: &UpdateInfo, input: &Payload| match A::from_json_payload(input) {
1085
- Ok(deser) => (self)(ctx, deser),
1086
- Err(e) => Err(e.into()),
1087
- };
1088
- Box::new(wrapper)
1089
- }
1090
- }
1091
- type BoxUpdateHandlerFn = Box<
1092
- dyn FnMut(UpdateContext, &Payload) -> BoxFuture<'static, Result<Payload, anyhow::Error>> + Send,
1093
- >;
1094
- /// Closures / functions which can be turned into update handler functions implement this trait
1095
- pub trait IntoUpdateHandlerFunc<Arg, Res> {
1096
- /// Consume the closure/fn pointer and turn it into an update handler
1097
- fn into_update_handler_fn(self) -> BoxUpdateHandlerFn;
1098
- }
1099
- impl<A, F, Rf, R> IntoUpdateHandlerFunc<A, R> for F
1100
- where
1101
- A: FromJsonPayloadExt + Send,
1102
- F: (FnMut(UpdateContext, A) -> Rf) + Send + 'static,
1103
- Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
1104
- R: AsJsonPayloadExt,
1105
- {
1106
- fn into_update_handler_fn(mut self) -> BoxUpdateHandlerFn {
1107
- let wrapper = move |ctx: UpdateContext, input: &Payload| match A::from_json_payload(input) {
1108
- Ok(deser) => (self)(ctx, deser)
1109
- .map(|r| r.and_then(|r| r.as_json_payload()))
1110
- .boxed(),
1111
- Err(e) => async move { Err(e.into()) }.boxed(),
1112
- };
1113
- Box::new(wrapper)
1114
- }
1115
- }
1116
-
1117
1299
  /// Attempts to turn caught panics into something printable
1118
1300
  fn panic_formatter(panic: Box<dyn Any>) -> Box<dyn Display> {
1119
1301
  _panic_formatter::<&str>(panic)
@@ -1149,3 +1331,138 @@ impl Display for EndPrintingAttempts {
1149
1331
  impl PrintablePanicType for EndPrintingAttempts {
1150
1332
  type NextType = EndPrintingAttempts;
1151
1333
  }
1334
+
1335
+ #[cfg(test)]
1336
+ mod tests {
1337
+ use super::*;
1338
+ use temporalio_macros::{activities, workflow, workflow_methods};
1339
+
1340
+ struct MyActivities {}
1341
+
1342
+ #[activities]
1343
+ impl MyActivities {
1344
+ #[activity]
1345
+ async fn my_activity(_ctx: ActivityContext) -> Result<(), ActivityError> {
1346
+ Ok(())
1347
+ }
1348
+
1349
+ #[activity]
1350
+ async fn takes_self(
1351
+ self: Arc<Self>,
1352
+ _ctx: ActivityContext,
1353
+ _: String,
1354
+ ) -> Result<(), ActivityError> {
1355
+ Ok(())
1356
+ }
1357
+ }
1358
+
1359
+ #[test]
1360
+ fn test_activity_registration() {
1361
+ let act_instance = MyActivities {};
1362
+ let _ = WorkerOptions::new("task_q").register_activities(act_instance);
1363
+ }
1364
+
1365
+ // Compile-only test for workflow context invocation
1366
+ #[allow(unused, clippy::diverging_sub_expression)]
1367
+ fn test_activity_via_workflow_context() {
1368
+ let wf_ctx: WorkflowContext<MyWorkflow> = unimplemented!();
1369
+ wf_ctx.start_activity(MyActivities::my_activity, (), ActivityOptions::default());
1370
+ wf_ctx.start_activity(
1371
+ MyActivities::takes_self,
1372
+ "Hi".to_owned(),
1373
+ ActivityOptions::default(),
1374
+ );
1375
+ }
1376
+
1377
+ // Compile-only test for direct invocation via .run()
1378
+ #[allow(dead_code, unreachable_code, unused, clippy::diverging_sub_expression)]
1379
+ async fn test_activity_direct_invocation() {
1380
+ let ctx: ActivityContext = unimplemented!();
1381
+ let _result = MyActivities::my_activity.run(ctx).await;
1382
+ }
1383
+
1384
+ #[workflow]
1385
+ struct MyWorkflow {
1386
+ counter: u32,
1387
+ }
1388
+
1389
+ #[allow(dead_code)]
1390
+ #[workflow_methods]
1391
+ impl MyWorkflow {
1392
+ #[init]
1393
+ fn new(_ctx: &WorkflowContextView, _input: String) -> Self {
1394
+ Self { counter: 0 }
1395
+ }
1396
+
1397
+ #[run]
1398
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<String> {
1399
+ Ok(format!("Counter: {}", ctx.state(|s| s.counter)))
1400
+ }
1401
+
1402
+ #[signal(name = "increment")]
1403
+ fn increment_counter(&mut self, _ctx: &mut SyncWorkflowContext<Self>, amount: u32) {
1404
+ self.counter += amount;
1405
+ }
1406
+
1407
+ #[signal]
1408
+ async fn async_signal(_ctx: &mut WorkflowContext<Self>) {}
1409
+
1410
+ #[query]
1411
+ fn get_counter(&self, _ctx: &WorkflowContextView) -> u32 {
1412
+ self.counter
1413
+ }
1414
+
1415
+ #[update(name = "double")]
1416
+ fn double_counter(&mut self, _ctx: &mut SyncWorkflowContext<Self>) -> u32 {
1417
+ self.counter *= 2;
1418
+ self.counter
1419
+ }
1420
+
1421
+ #[update]
1422
+ async fn async_update(_ctx: &mut WorkflowContext<Self>, val: i32) -> i32 {
1423
+ val * 2
1424
+ }
1425
+ }
1426
+
1427
+ #[test]
1428
+ fn test_workflow_registration() {
1429
+ let _ = WorkerOptions::new("task_q").register_workflow::<MyWorkflow>();
1430
+ }
1431
+
1432
+ fn default_identity() -> String {
1433
+ format!(
1434
+ "{}@{}",
1435
+ std::process::id(),
1436
+ gethostname::gethostname().to_string_lossy()
1437
+ )
1438
+ }
1439
+
1440
+ #[rstest::rstest]
1441
+ #[case::default_when_none_provided(None, "", Some(default_identity()))]
1442
+ #[case::connection_identity_preserved(None, "conn-identity", None)]
1443
+ #[case::worker_override_takes_precedence(
1444
+ Some("worker-identity"),
1445
+ "conn-identity",
1446
+ Some("worker-identity".into())
1447
+ )]
1448
+ #[case::worker_override_with_empty_connection(
1449
+ Some("worker-identity"),
1450
+ "",
1451
+ Some("worker-identity".into())
1452
+ )]
1453
+ #[test]
1454
+ fn client_identity_resolution(
1455
+ #[case] worker_override: Option<&str>,
1456
+ #[case] connection_identity: &str,
1457
+ #[case] expected: Option<String>,
1458
+ ) {
1459
+ let opts = WorkerOptions::new("task_q")
1460
+ .task_types(WorkerTaskTypes::activity_only())
1461
+ .maybe_client_identity_override(worker_override.map(|s| s.to_owned()))
1462
+ .build();
1463
+ let config = opts
1464
+ .to_core_options("ns".into(), connection_identity.into())
1465
+ .unwrap();
1466
+ assert_eq!(config.client_identity_override, expected);
1467
+ }
1468
+ }