@temporalio/core-bridge 1.15.0 → 1.16.1

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 (209) hide show
  1. package/Cargo.lock +172 -70
  2. package/lib/native.d.ts +1 -1
  3. package/package.json +2 -2
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  6. package/releases/x86_64-apple-darwin/index.node +0 -0
  7. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  8. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  9. package/sdk-core/.github/workflows/per-pr.yml +6 -6
  10. package/sdk-core/AGENTS.md +41 -30
  11. package/sdk-core/Cargo.toml +3 -0
  12. package/sdk-core/README.md +15 -9
  13. package/sdk-core/crates/client/Cargo.toml +4 -0
  14. package/sdk-core/crates/client/README.md +139 -0
  15. package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
  16. package/sdk-core/crates/client/src/callback_based.rs +7 -0
  17. package/sdk-core/crates/client/src/errors.rs +294 -0
  18. package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +280 -159
  19. package/sdk-core/crates/client/src/lib.rs +920 -1326
  20. package/sdk-core/crates/client/src/metrics.rs +24 -33
  21. package/sdk-core/crates/client/src/options_structs.rs +457 -0
  22. package/sdk-core/crates/client/src/replaceable.rs +5 -4
  23. package/sdk-core/crates/client/src/request_extensions.rs +8 -9
  24. package/sdk-core/crates/client/src/retry.rs +99 -54
  25. package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +1 -1
  26. package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
  27. package/sdk-core/crates/common/Cargo.toml +61 -2
  28. package/sdk-core/crates/common/build.rs +742 -12
  29. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
  30. package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
  31. package/sdk-core/crates/common/protos/api_upstream/buf.yaml +0 -3
  32. package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
  33. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +1166 -770
  34. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +1243 -750
  35. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +2 -2
  36. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -3
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +4 -0
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +16 -1
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -6
  42. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +88 -33
  43. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +4 -2
  44. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  45. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +5 -5
  46. package/sdk-core/crates/common/src/activity_definition.rs +20 -0
  47. package/sdk-core/crates/common/src/data_converters.rs +770 -0
  48. package/sdk-core/crates/common/src/envconfig.rs +5 -0
  49. package/sdk-core/crates/common/src/lib.rs +15 -211
  50. package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
  51. package/sdk-core/crates/common/src/priority.rs +110 -0
  52. package/sdk-core/crates/common/src/protos/canned_histories.rs +3 -0
  53. package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
  54. package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
  55. package/sdk-core/crates/common/src/protos/mod.rs +122 -27
  56. package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
  57. package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
  58. package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +5 -7
  59. package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
  60. package/sdk-core/crates/common/src/telemetry/metrics.rs +268 -223
  61. package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
  62. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
  63. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
  64. package/sdk-core/crates/common/src/telemetry.rs +264 -4
  65. package/sdk-core/crates/common/src/worker.rs +68 -603
  66. package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
  67. package/sdk-core/crates/macros/Cargo.toml +5 -1
  68. package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
  69. package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
  70. package/sdk-core/crates/macros/src/lib.rs +138 -512
  71. package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
  72. package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
  73. package/sdk-core/crates/sdk/Cargo.toml +19 -6
  74. package/sdk-core/crates/sdk/README.md +415 -0
  75. package/sdk-core/crates/sdk/src/activities.rs +417 -0
  76. package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
  77. package/sdk-core/crates/sdk/src/lib.rs +757 -442
  78. package/sdk-core/crates/sdk/src/workflow_context/options.rs +45 -35
  79. package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
  80. package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
  81. package/sdk-core/crates/sdk/src/workflows.rs +711 -0
  82. package/sdk-core/crates/sdk-core/Cargo.toml +57 -64
  83. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +41 -35
  84. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
  85. package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
  86. package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
  87. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +13 -15
  88. package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
  89. package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
  90. package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
  91. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +493 -26
  92. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +4 -8
  93. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +7 -7
  94. package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
  95. package/sdk-core/crates/sdk-core/src/lib.rs +41 -111
  96. package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
  97. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +118 -19
  98. package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
  99. package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
  100. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +179 -196
  101. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -280
  102. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +6 -9
  103. package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
  104. package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
  105. package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
  106. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +9 -5
  107. package/sdk-core/crates/sdk-core/src/worker/client.rs +103 -81
  108. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +7 -11
  109. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1124 -229
  110. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +145 -23
  111. package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
  112. package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
  113. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +13 -13
  114. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +28 -8
  115. package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
  116. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
  117. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +19 -4
  118. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +14 -18
  119. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +4 -6
  120. package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
  121. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
  122. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
  123. package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
  124. package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
  125. package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
  126. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
  127. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
  128. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
  129. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
  130. package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
  131. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
  132. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
  133. package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
  134. package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
  135. package/sdk-core/crates/sdk-core/tests/common/mod.rs +241 -196
  136. package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
  137. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +3 -5
  138. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -64
  139. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +298 -252
  140. package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
  141. package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
  142. package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
  143. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +16 -12
  144. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +48 -40
  145. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +327 -255
  146. package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
  147. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +147 -126
  148. package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
  149. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -453
  150. package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
  151. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +360 -231
  152. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +248 -185
  153. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -43
  154. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
  155. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +428 -315
  156. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -56
  157. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -28
  158. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -243
  159. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
  160. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +101 -42
  161. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -147
  162. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -28
  163. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1036
  164. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -41
  165. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +397 -238
  166. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +414 -189
  167. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
  168. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
  169. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +154 -137
  170. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -105
  171. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -38
  172. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -40
  173. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -54
  174. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +363 -226
  175. package/sdk-core/crates/sdk-core/tests/main.rs +17 -15
  176. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +207 -152
  177. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +65 -34
  178. package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
  179. package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
  180. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
  181. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
  182. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
  183. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
  184. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
  185. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
  186. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
  187. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
  188. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +7 -1
  189. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +14 -14
  190. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +83 -74
  191. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +9 -14
  192. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +1 -2
  193. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +13 -13
  194. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +6 -6
  195. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +3 -4
  196. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +62 -75
  197. package/sdk-core/rustfmt.toml +2 -1
  198. package/src/client.rs +205 -318
  199. package/src/metrics.rs +22 -30
  200. package/src/runtime.rs +4 -5
  201. package/src/worker.rs +16 -19
  202. package/ts/native.ts +1 -1
  203. package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
  204. package/sdk-core/crates/common/src/errors.rs +0 -85
  205. package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
  206. package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
  207. package/sdk-core/crates/sdk/src/app_data.rs +0 -37
  208. package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
  209. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
@@ -8,40 +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, RuntimeOptions};
11
+ //! use std::str::FromStr;
12
+ //! use temporalio_client::{Client, ClientOptions, Connection, ConnectionOptions};
14
13
  //! use temporalio_common::{
15
- //! worker::{WorkerConfig, WorkerTaskTypes, WorkerVersioningStrategy},
16
- //! telemetry::TelemetryOptions
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};
23
+ //!
24
+ //! struct MyActivities;
25
+ //!
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
+ //! }
18
36
  //!
19
37
  //! #[tokio::main]
20
38
  //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
21
- //! let server_options = sdk_client_options(Url::from_str("http://localhost:7233")?).build();
22
- //!
39
+ //! let connection_options =
40
+ //! ConnectionOptions::new(Url::from_str("http://localhost:7233")?).build();
23
41
  //! let telemetry_options = TelemetryOptions::builder().build();
24
- //! let runtime_options = RuntimeOptions::builder().telemetry_options(telemetry_options).build().unwrap();
42
+ //! let runtime_options = RuntimeOptions::builder()
43
+ //! .telemetry_options(telemetry_options)
44
+ //! .build()?;
25
45
  //! let runtime = CoreRuntime::new_assume_tokio(runtime_options)?;
26
46
  //!
27
- //! let client = server_options.connect("default", None).await?;
47
+ //! let connection = Connection::connect(connection_options).await?;
48
+ //! let client = Client::new(connection, ClientOptions::new("my_namespace").build())?;
28
49
  //!
29
- //! let worker_config = WorkerConfig::builder()
30
- //! .namespace("default")
31
- //! .task_queue("task_queue")
50
+ //! let worker_options = WorkerOptions::new("task_queue")
32
51
  //! .task_types(WorkerTaskTypes::activity_only())
33
- //! .versioning_strategy(WorkerVersioningStrategy::None { build_id: "rust-sdk".to_owned() })
34
- //! .build()
35
- //! .unwrap();
36
- //!
37
- //! let core_worker = init_worker(&runtime, worker_config, client)?;
38
- //!
39
- //! let mut worker = Worker::new_from_core(Arc::new(core_worker), "task_queue");
40
- //! worker.register_activity(
41
- //! "echo_activity",
42
- //! |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
43
- //! );
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();
44
62
  //!
63
+ //! let mut worker = Worker::new(&runtime, client, worker_options)?;
45
64
  //! worker.run().await?;
46
65
  //!
47
66
  //! Ok(())
@@ -50,48 +69,70 @@
50
69
 
51
70
  #[macro_use]
52
71
  extern crate tracing;
72
+ extern crate self as temporalio_sdk;
53
73
 
54
- mod activity_context;
55
- mod app_data;
74
+ pub mod activities;
56
75
  pub mod interceptors;
57
76
  mod workflow_context;
58
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;
59
97
 
60
- pub use activity_context::ActContext;
61
98
  pub use temporalio_client::Namespace;
62
- use tracing::{Instrument, Span, field};
63
99
  pub use workflow_context::{
64
- ActivityOptions, CancellableFuture, ChildWorkflow, ChildWorkflowOptions, LocalActivityOptions,
65
- NexusOperationOptions, PendingChildWorkflow, Signal, SignalData, SignalWorkflowOptions,
66
- 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,
67
104
  };
68
105
 
69
106
  use crate::{
107
+ activities::{
108
+ ActivityContext, ActivityDefinitions, ActivityError, ActivityImplementer,
109
+ ExecutableActivity,
110
+ },
70
111
  interceptors::WorkerInterceptor,
71
112
  workflow_context::{ChildWfCommon, NexusUnblockData, StartedNexusOperation},
113
+ workflows::{WorkflowDefinitions, WorkflowImplementation, WorkflowImplementer},
72
114
  };
73
115
  use anyhow::{Context, anyhow, bail};
74
- use app_data::AppData;
75
- use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::BoxFuture};
76
- use serde::Serialize;
116
+ use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
77
117
  use std::{
78
118
  any::{Any, TypeId},
79
119
  cell::RefCell,
80
- collections::HashMap,
120
+ collections::{HashMap, HashSet},
81
121
  fmt::{Debug, Display, Formatter},
82
122
  future::Future,
83
123
  panic::AssertUnwindSafe,
84
124
  sync::Arc,
85
125
  time::Duration,
86
126
  };
87
- use temporalio_client::{ClientOptions, ClientOptionsBuilder, client_options_builder};
127
+ use temporalio_client::{Client, NamespacedClient};
88
128
  use temporalio_common::{
89
- Worker as CoreWorker,
90
- errors::PollError,
129
+ ActivityDefinition, WorkflowDefinition,
130
+ data_converters::{DataConverter, SerializationContextData},
131
+ payload_visitor::{decode_payloads, encode_payloads},
91
132
  protos::{
92
133
  TaskToken,
93
134
  coresdk::{
94
- ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt,
135
+ ActivityTaskCompletion, AsJsonPayloadExt,
95
136
  activity_result::{ActivityExecutionResult, ActivityResolution},
96
137
  activity_task::{ActivityTask, activity_task},
97
138
  child_workflow::ChildWorkflowResult,
@@ -113,8 +154,12 @@ use temporalio_common::{
113
154
  failure::v1::{Failure, failure},
114
155
  },
115
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,
116
162
  };
117
- use temporalio_sdk_core::Url;
118
163
  use tokio::{
119
164
  sync::{
120
165
  Notify,
@@ -125,40 +170,271 @@ use tokio::{
125
170
  };
126
171
  use tokio_stream::wrappers::UnboundedReceiverStream;
127
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
+ }
128
326
 
129
- const VERSION: &str = env!("CARGO_PKG_VERSION");
130
-
131
- /// Returns a [ClientOptionsBuilder] with required fields set to appropriate values
132
- /// for the Rust SDK.
133
- pub fn sdk_client_options(
134
- url: impl Into<Url>,
135
- ) -> ClientOptionsBuilder<impl client_options_builder::IsComplete> {
136
- ClientOptions::builder()
137
- .target_url(url)
138
- .client_name("temporal-rust".to_string())
139
- .client_version(VERSION.to_string())
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())
140
330
  }
141
331
 
142
- /// A worker that can poll for and respond to workflow tasks by using [WorkflowFunction]s,
143
- /// and activity tasks by using [ActivityFunction]s
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
+ }
377
+
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
+ }
415
+ }
416
+
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].
144
420
  pub struct Worker {
145
421
  common: CommonWorker,
146
422
  workflow_half: WorkflowHalf,
147
423
  activity_half: ActivityHalf,
148
- app_data: Option<AppData>,
149
424
  }
150
425
 
151
426
  struct CommonWorker {
152
- worker: Arc<dyn CoreWorker>,
427
+ worker: Arc<CoreWorker>,
153
428
  task_queue: String,
154
429
  worker_interceptor: Option<Box<dyn WorkerInterceptor>>,
430
+ data_converter: DataConverter,
155
431
  }
156
432
 
433
+ #[derive(Default)]
157
434
  struct WorkflowHalf {
158
435
  /// Maps run id to cached workflow state
159
436
  workflows: RefCell<HashMap<String, WorkflowData>>,
160
- /// Maps workflow type to the function for executing workflow runs with that ID
161
- workflow_fns: RefCell<HashMap<String, WorkflowFunction>>,
437
+ workflow_definitions: WorkflowDefinitions,
162
438
  workflow_removed_from_map: Notify,
163
439
  }
164
440
  struct WorkflowData {
@@ -171,31 +447,71 @@ struct WorkflowFutureHandle<F: Future<Output = Result<WorkflowResult<Payload>, J
171
447
  run_id: String,
172
448
  }
173
449
 
450
+ #[derive(Default)]
174
451
  struct ActivityHalf {
175
452
  /// Maps activity type to the function for executing activities of that type
176
- activity_fns: HashMap<String, ActivityFunction>,
453
+ activities: ActivityDefinitions,
177
454
  task_tokens_to_cancels: HashMap<TaskToken, CancellationToken>,
178
455
  }
179
456
 
180
457
  impl Worker {
181
- /// Create a new Rust SDK worker from a core worker
182
- 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 {
183
500
  Self {
184
501
  common: CommonWorker {
502
+ task_queue: worker.get_config().task_queue.clone(),
185
503
  worker,
186
- task_queue: task_queue.into(),
187
504
  worker_interceptor: None,
505
+ data_converter,
188
506
  },
189
507
  workflow_half: WorkflowHalf {
190
- workflows: Default::default(),
191
- workflow_fns: Default::default(),
192
- workflow_removed_from_map: Default::default(),
508
+ workflow_definitions: workflows,
509
+ ..Default::default()
193
510
  },
194
511
  activity_half: ActivityHalf {
195
- activity_fns: Default::default(),
196
- task_tokens_to_cancels: Default::default(),
512
+ activities,
513
+ ..Default::default()
197
514
  },
198
- app_data: Some(Default::default()),
199
515
  }
200
516
  }
201
517
 
@@ -204,58 +520,67 @@ impl Worker {
204
520
  &self.common.task_queue
205
521
  }
206
522
 
207
- /// Return a handle that can be used to initiate shutdown.
208
- /// 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.
209
525
  pub fn shutdown_handle(&self) -> impl Fn() + use<> {
210
526
  let w = self.common.worker.clone();
211
527
  move || w.initiate_shutdown()
212
528
  }
213
529
 
214
- /// Register a Workflow function to invoke when the Worker is asked to run a workflow of
215
- /// `workflow_type`
216
- pub fn register_wf(
217
- &mut self,
218
- workflow_type: impl Into<String>,
219
- wf_function: impl Into<WorkflowFunction>,
220
- ) {
221
- self.workflow_half
222
- .workflow_fns
223
- .get_mut()
224
- .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
225
547
  }
226
548
 
227
- /// Register an Activity function to invoke when the Worker is asked to run an activity of
228
- /// `activity_type`
229
- pub fn register_activity<A, R, O>(
230
- &mut self,
231
- activity_type: impl Into<String>,
232
- act_function: impl IntoActivityFunc<A, R, O>,
233
- ) {
234
- self.activity_half.activity_fns.insert(
235
- activity_type.into(),
236
- ActivityFunction {
237
- act_func: act_function.into_activity_fn(),
238
- },
239
- );
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
240
555
  }
241
556
 
242
- /// Insert Custom App Context for Workflows and Activities
243
- pub fn insert_app_data<T: Send + Sync + 'static>(&mut self, data: T) {
244
- 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
245
570
  }
246
571
 
247
572
  /// Runs the worker. Eventually resolves after the worker has been explicitly shut down,
248
573
  /// or may return early with an error in the event of some unresolvable problem.
249
574
  pub async fn run(&mut self) -> Result<(), anyhow::Error> {
250
575
  let shutdown_token = CancellationToken::new();
251
- let (common, wf_half, act_half, app_data) = self.split_apart();
252
- let safe_app_data = Arc::new(
253
- app_data
254
- .take()
255
- .ok_or_else(|| anyhow!("app_data should exist on run"))?,
256
- );
576
+ let (common, wf_half, act_half) = self.split_apart();
257
577
  let (wf_future_tx, wf_future_rx) = unbounded_channel();
258
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
+
259
584
  let wf_future_joiner = async {
260
585
  UnboundedReceiverStream::new(wf_future_rx)
261
586
  .map(Result::<_, anyhow::Error>::Ok)
@@ -267,7 +592,14 @@ impl Worker {
267
592
  }| {
268
593
  let wf_half = &*wf_half;
269
594
  async move {
270
- 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
+ }
271
603
  debug!(run_id=%run_id, "Removing workflow from cache");
272
604
  wf_half.workflows.borrow_mut().remove(&run_id);
273
605
  wf_half.workflow_removed_from_map.notify_one();
@@ -281,7 +613,13 @@ impl Worker {
281
613
  let wf_completion_processor = async {
282
614
  UnboundedReceiverStream::new(completions_rx)
283
615
  .map(Ok)
284
- .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;
285
623
  if let Some(ref i) = common.worker_interceptor {
286
624
  i.on_workflow_activation_completion(&completion).await;
287
625
  }
@@ -292,72 +630,89 @@ impl Worker {
292
630
  .context("Workflow completions processor encountered an error")
293
631
  };
294
632
  tokio::try_join!(
295
- // Workflow polling loop
633
+ // Workflow-related tasks run inside LocalSet (allows !Send futures)
296
634
  async {
297
- loop {
298
- let activation = match common.worker.poll_workflow_activation().await {
299
- Err(PollError::ShutDown) => {
300
- 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
+ }
301
670
  }
302
- o => o?,
303
- };
304
- if let Some(ref i) = common.worker_interceptor {
305
- i.on_workflow_activation(&activation).await?;
306
- }
307
- if let Some(wf_fut) = wf_half
308
- .workflow_activation_handler(
309
- common,
310
- shutdown_token.clone(),
311
- activation,
312
- &completions_tx,
313
- )
314
- .await?
315
- && wf_future_tx.send(wf_fut).is_err()
316
- {
317
- panic!("Receive half of completion processor channel cannot be dropped");
318
- }
319
- }
320
- // Tell still-alive workflows to evict themselves
321
- shutdown_token.cancel();
322
- // It's important to drop these so the future and completion processors will
323
- // terminate.
324
- drop(wf_future_tx);
325
- drop(completions_tx);
326
- 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
327
682
  },
328
683
  // Only poll on the activity queue if activity functions have been registered. This
329
684
  // makes tests which use mocks dramatically more manageable.
330
685
  async {
331
- if !act_half.activity_fns.is_empty() {
686
+ if !act_half.activities.is_empty() {
332
687
  loop {
333
688
  let activity = common.worker.poll_activity_task().await;
334
689
  if matches!(activity, Err(PollError::ShutDown)) {
335
690
  break;
336
691
  }
692
+ let mut activity = activity?;
693
+ decode_payloads(
694
+ &mut activity,
695
+ common.data_converter.codec(),
696
+ &SerializationContextData::Activity,
697
+ )
698
+ .await;
337
699
  act_half.activity_task_handler(
338
700
  common.worker.clone(),
339
- safe_app_data.clone(),
340
701
  common.task_queue.clone(),
341
- activity?,
702
+ common.data_converter.clone(),
703
+ activity,
342
704
  )?;
343
705
  }
344
706
  };
345
707
  Result::<_, anyhow::Error>::Ok(())
346
708
  },
347
- wf_future_joiner,
348
709
  wf_completion_processor,
349
710
  )?;
350
711
 
351
- debug!("Polling loops exited");
352
712
  if let Some(i) = self.common.worker_interceptor.as_ref() {
353
713
  i.on_shutdown(self);
354
714
  }
355
715
  self.common.worker.shutdown().await;
356
- debug!("Worker shutdown complete");
357
- self.app_data = Some(
358
- Arc::try_unwrap(safe_app_data)
359
- .map_err(|_| anyhow!("some references of AppData exist on worker shutdown"))?,
360
- );
361
716
  Ok(())
362
717
  }
363
718
 
@@ -369,7 +724,7 @@ impl Worker {
369
724
  /// Turns this rust worker into a new worker with all the same workflows and activities
370
725
  /// registered, but with a new underlying core worker. Can be used to swap the worker for
371
726
  /// a replay worker, change task queues, etc.
372
- 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>) {
373
728
  self.common.worker = new_core_worker;
374
729
  }
375
730
 
@@ -379,19 +734,21 @@ impl Worker {
379
734
  self.workflow_half.workflows.borrow().len()
380
735
  }
381
736
 
382
- fn split_apart(
383
- &mut self,
384
- ) -> (
385
- &mut CommonWorker,
386
- &mut WorkflowHalf,
387
- &mut ActivityHalf,
388
- &mut Option<AppData>,
389
- ) {
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) {
390
748
  (
391
749
  &mut self.common,
392
750
  &mut self.workflow_half,
393
751
  &mut self.activity_half,
394
- &mut self.app_data,
395
752
  )
396
753
  }
397
754
  }
@@ -421,13 +778,33 @@ impl WorkflowHalf {
421
778
  Some(Variant::InitializeWorkflow(ref mut sw)) => Some(sw),
422
779
  _ => None,
423
780
  }) {
424
- let workflow_type = &sw.workflow_type;
781
+ let workflow_type = sw.workflow_type.clone();
782
+ let payload_converter = common.data_converter.payload_converter().clone();
425
783
  let (wff, activations) = {
426
- let wf_fns_borrow = self.workflow_fns.borrow();
427
-
428
- 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 {
429
807
  warn!("Workflow type {workflow_type} not found");
430
-
431
808
  completions_tx
432
809
  .send(WorkflowActivationCompletion::fail(
433
810
  run_id,
@@ -436,22 +813,19 @@ impl WorkflowHalf {
436
813
  ))
437
814
  .expect("Completion channel intact");
438
815
  return Ok(None);
439
- };
440
-
441
- wf_function.start_workflow(
442
- common.worker.get_config().namespace.clone(),
443
- common.task_queue.clone(),
444
- std::mem::take(sw),
445
- completions_tx.clone(),
446
- )
816
+ }
447
817
  };
448
- 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 {
449
823
  tokio::select! {
450
824
  r = wff.fuse() => r,
451
825
  // TODO: This probably shouldn't abort early, as it could cause an in-progress
452
826
  // complete to abort. Send synthetic remove activation
453
827
  _ = shutdown_token.cancelled() => {
454
- Ok(WfExitValue::Evicted)
828
+ Err(WorkflowTermination::Evicted)
455
829
  }
456
830
  }
457
831
  });
@@ -515,23 +889,19 @@ impl ActivityHalf {
515
889
  /// Spawns off a task to handle the provided activity task
516
890
  fn activity_task_handler(
517
891
  &mut self,
518
- worker: Arc<dyn CoreWorker>,
519
- app_data: Arc<AppData>,
892
+ worker: Arc<CoreWorker>,
520
893
  task_queue: String,
894
+ data_converter: DataConverter,
521
895
  activity: ActivityTask,
522
896
  ) -> Result<(), anyhow::Error> {
523
897
  match activity.variant {
524
898
  Some(activity_task::Variant::Start(start)) => {
525
- let act_fn = self
526
- .activity_fns
527
- .get(&start.activity_type)
528
- .ok_or_else(|| {
529
- anyhow!(
530
- "No function registered for activity type {}",
531
- start.activity_type
532
- )
533
- })?
534
- .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
+ })?;
535
905
  let span = info_span!(
536
906
  "RunActivity",
537
907
  "otel.name" = format!("RunActivity:{}", start.activity_type),
@@ -545,23 +915,18 @@ impl ActivityHalf {
545
915
  self.task_tokens_to_cancels
546
916
  .insert(task_token.clone().into(), ct.clone());
547
917
 
548
- let (ctx, arg) = ActContext::new(
549
- worker.clone(),
550
- app_data,
551
- ct,
552
- task_queue,
553
- task_token.clone(),
554
- start,
555
- );
918
+ let (ctx, args) =
919
+ ActivityContext::new(worker.clone(), ct, task_queue, task_token.clone(), start);
920
+ let codec_data_converter = data_converter.clone();
556
921
 
557
922
  tokio::spawn(async move {
558
923
  let act_fut = async move {
559
- if let Some(info) = &ctx.get_info().workflow_execution {
924
+ if let Some(info) = &ctx.info().workflow_execution {
560
925
  Span::current()
561
926
  .record("temporalWorkflowID", &info.workflow_id)
562
927
  .record("temporalRunID", &info.run_id);
563
928
  }
564
- (act_fn.act_func)(ctx, arg).await
929
+ (act_fn)(args, data_converter, ctx).await
565
930
  }
566
931
  .instrument(span);
567
932
  let output = AssertUnwindSafe(act_fut).catch_unwind().await;
@@ -570,16 +935,16 @@ impl ActivityHalf {
570
935
  format!("Activity function panicked: {}", panic_formatter(e)),
571
936
  true,
572
937
  )),
573
- Ok(Ok(ActExitValue::Normal(p))) => ActivityExecutionResult::ok(p),
574
- Ok(Ok(ActExitValue::WillCompleteAsync)) => {
575
- ActivityExecutionResult::will_complete_async()
576
- }
938
+ Ok(Ok(p)) => ActivityExecutionResult::ok(p),
577
939
  Ok(Err(err)) => match err {
578
940
  ActivityError::Retryable {
579
941
  source,
580
942
  explicit_delay,
581
943
  } => ActivityExecutionResult::fail({
582
- 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
+ );
583
948
  if let Some(d) = explicit_delay
584
949
  && let Some(failure::FailureInfo::ApplicationFailureInfo(fi)) =
585
950
  f.failure_info.as_mut()
@@ -592,16 +957,27 @@ impl ActivityHalf {
592
957
  ActivityExecutionResult::cancel_from_details(details)
593
958
  }
594
959
  ActivityError::NonRetryable(nre) => ActivityExecutionResult::fail(
595
- Failure::application_failure_from_error(nre, true),
960
+ Failure::application_failure_from_error(
961
+ anyhow::Error::from_boxed(nre),
962
+ true,
963
+ ),
596
964
  ),
965
+ ActivityError::WillCompleteAsync => {
966
+ ActivityExecutionResult::will_complete_async()
967
+ }
597
968
  },
598
969
  };
599
- worker
600
- .complete_activity_task(ActivityTaskCompletion {
601
- task_token,
602
- result: Some(result),
603
- })
604
- .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?;
605
981
  Ok::<_, anyhow::Error>(())
606
982
  });
607
983
  }
@@ -632,7 +1008,7 @@ enum UnblockEvent {
632
1008
  }
633
1009
 
634
1010
  /// Result of awaiting on a timer
635
- #[derive(Debug, Copy, Clone)]
1011
+ #[derive(Debug, Copy, Clone, PartialEq, Eq)]
636
1012
  pub enum TimerResult {
637
1013
  /// The timer was cancelled
638
1014
  Cancelled,
@@ -641,13 +1017,13 @@ pub enum TimerResult {
641
1017
  }
642
1018
 
643
1019
  /// Successful result of sending a signal to an external workflow
644
- #[derive(Debug)]
1020
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
645
1021
  pub struct SignalExternalOk;
646
1022
  /// Result of awaiting on sending a signal to an external workflow
647
1023
  pub type SignalExternalWfResult = Result<SignalExternalOk, Failure>;
648
1024
 
649
1025
  /// Successful result of sending a cancel request to an external workflow
650
- #[derive(Debug)]
1026
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
651
1027
  pub struct CancelExternalOk;
652
1028
  /// Result of awaiting on sending a cancel request to an external workflow
653
1029
  pub type CancelExternalWfResult = Result<CancelExternalOk, Failure>;
@@ -836,8 +1212,6 @@ enum RustWfCmd {
836
1212
  NewCmd(CommandCreateRequest),
837
1213
  NewNonblockingCmd(workflow_command::Variant),
838
1214
  SubscribeChildWorkflowCompletion(CommandSubscribeChildWorkflowCompletion),
839
- SubscribeSignal(String, UnboundedSender<SignalData>),
840
- RegisterUpdate(String, UpdateFunctions),
841
1215
  SubscribeNexusOperationCompletion {
842
1216
  seq: u32,
843
1217
  unblocker: oneshot::Sender<UnblockEvent>,
@@ -854,81 +1228,61 @@ struct CommandSubscribeChildWorkflowCompletion {
854
1228
  unblocker: oneshot::Sender<UnblockEvent>,
855
1229
  }
856
1230
 
857
- type WfFunc = dyn Fn(WfContext) -> BoxFuture<'static, Result<WfExitValue<Payload>, anyhow::Error>>
858
- + Send
859
- + Sync
860
- + '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,
861
1246
 
862
- /// The user's async function / workflow code
863
- pub struct WorkflowFunction {
864
- 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),
865
1258
  }
866
1259
 
867
- impl<F, Fut, O> From<F> for WorkflowFunction
868
- where
869
- F: Fn(WfContext) -> Fut + Send + Sync + 'static,
870
- Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
871
- O: Serialize,
872
- {
873
- fn from(wf_func: F) -> Self {
874
- 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))
875
1264
  }
876
- }
877
1265
 
878
- impl WorkflowFunction {
879
- /// Build a workflow function from a closure or function pointer which accepts a [WfContext]
880
- pub fn new<F, Fut, O>(f: F) -> Self
881
- where
882
- F: Fn(WfContext) -> Fut + Send + Sync + 'static,
883
- Fut: Future<Output = Result<WfExitValue<O>, anyhow::Error>> + Send + 'static,
884
- O: Serialize,
885
- {
886
- Self {
887
- wf_func: Box::new(move |ctx: WfContext| {
888
- (f)(ctx)
889
- .map(|r| {
890
- r.and_then(|r| {
891
- Ok(match r {
892
- WfExitValue::ContinueAsNew(b) => WfExitValue::ContinueAsNew(b),
893
- WfExitValue::Cancelled => WfExitValue::Cancelled,
894
- WfExitValue::Evicted => WfExitValue::Evicted,
895
- WfExitValue::Normal(o) => WfExitValue::Normal(o.as_json_payload()?),
896
- })
897
- })
898
- })
899
- .boxed()
900
- }),
901
- }
1266
+ /// Construct a [WorkflowTermination::Failed] variant from any error.
1267
+ pub fn failed(err: impl Into<anyhow::Error>) -> Self {
1268
+ Self::Failed(err.into())
902
1269
  }
903
1270
  }
904
1271
 
905
- /// The result of running a workflow
906
- pub type WorkflowResult<T> = Result<WfExitValue<T>, anyhow::Error>;
907
-
908
- /// Workflow functions may return these values when exiting
909
- #[derive(Debug, derive_more::From)]
910
- pub enum WfExitValue<T> {
911
- /// Continue the workflow as a new execution
912
- #[from(ignore)]
913
- ContinueAsNew(Box<ContinueAsNewWorkflowExecution>),
914
- /// Confirm the workflow was cancelled (can be automatic in a more advanced iteration)
915
- #[from(ignore)]
916
- Cancelled,
917
- /// The run was evicted
918
- #[from(ignore)]
919
- Evicted,
920
- /// Finish with a result
921
- Normal(T),
1272
+ impl From<anyhow::Error> for WorkflowTermination {
1273
+ fn from(err: anyhow::Error) -> Self {
1274
+ Self::Failed(err)
1275
+ }
922
1276
  }
923
1277
 
924
- impl<T> WfExitValue<T> {
925
- /// Construct a [WfExitValue::ContinueAsNew] variant (handles boxing)
926
- pub fn continue_as_new(can: ContinueAsNewWorkflowExecution) -> Self {
927
- Self::ContinueAsNew(Box::new(can))
1278
+ impl From<ActivityExecutionError> for WorkflowTermination {
1279
+ fn from(value: ActivityExecutionError) -> Self {
1280
+ Self::failed(value)
928
1281
  }
929
1282
  }
930
1283
 
931
1284
  /// Activity functions may return these values when exiting
1285
+ #[derive(Debug)]
932
1286
  pub enum ActExitValue<T> {
933
1287
  /// Completion requires an asynchronous callback
934
1288
  WillCompleteAsync,
@@ -942,180 +1296,6 @@ impl<T: AsJsonPayloadExt> From<T> for ActExitValue<T> {
942
1296
  }
943
1297
  }
944
1298
 
945
- type BoxActFn = Arc<
946
- dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<ActExitValue<Payload>, ActivityError>>
947
- + Send
948
- + Sync,
949
- >;
950
-
951
- /// Container for user-defined activity functions
952
- #[derive(Clone)]
953
- pub struct ActivityFunction {
954
- act_func: BoxActFn,
955
- }
956
-
957
- /// Returned as errors from activity functions
958
- #[derive(Debug)]
959
- pub enum ActivityError {
960
- /// This error can be returned from activities to allow the explicit configuration of certain
961
- /// error properties. It's also the default error type that arbitrary errors will be converted
962
- /// into.
963
- Retryable {
964
- /// The underlying error
965
- source: anyhow::Error,
966
- /// If specified, the next retry (if there is one) will occur after this delay
967
- explicit_delay: Option<Duration>,
968
- },
969
- /// Return this error to indicate your activity is cancelling
970
- Cancelled {
971
- /// Some data to save as the cancellation reason
972
- details: Option<Payload>,
973
- },
974
- /// Return this error to indicate that your activity non-retryable
975
- /// this is a transparent wrapper around anyhow Error so essentially any type of error
976
- /// could be used here.
977
- NonRetryable(anyhow::Error),
978
- }
979
-
980
- impl<E> From<E> for ActivityError
981
- where
982
- E: Into<anyhow::Error>,
983
- {
984
- fn from(source: E) -> Self {
985
- Self::Retryable {
986
- source: source.into(),
987
- explicit_delay: None,
988
- }
989
- }
990
- }
991
-
992
- impl ActivityError {
993
- /// Construct a cancelled error without details
994
- pub fn cancelled() -> Self {
995
- Self::Cancelled { details: None }
996
- }
997
- }
998
-
999
- /// Closures / functions which can be turned into activity functions implement this trait
1000
- pub trait IntoActivityFunc<Args, Res, Out> {
1001
- /// Consume the closure or fn pointer and turned it into a boxed activity function
1002
- fn into_activity_fn(self) -> BoxActFn;
1003
- }
1004
-
1005
- impl<A, Rf, R, O, F> IntoActivityFunc<A, Rf, O> for F
1006
- where
1007
- F: (Fn(ActContext, A) -> Rf) + Sync + Send + 'static,
1008
- A: FromJsonPayloadExt + Send,
1009
- Rf: Future<Output = Result<R, ActivityError>> + Send + 'static,
1010
- R: Into<ActExitValue<O>>,
1011
- O: AsJsonPayloadExt,
1012
- {
1013
- fn into_activity_fn(self) -> BoxActFn {
1014
- let wrapper = move |ctx: ActContext, input: Payload| {
1015
- // Some minor gymnastics are required to avoid needing to clone the function
1016
- match A::from_json_payload(&input) {
1017
- Ok(deser) => self(ctx, deser)
1018
- .map(|r| {
1019
- r.and_then(|r| {
1020
- let exit_val: ActExitValue<O> = r.into();
1021
- match exit_val {
1022
- ActExitValue::WillCompleteAsync => {
1023
- Ok(ActExitValue::WillCompleteAsync)
1024
- }
1025
- ActExitValue::Normal(x) => match x.as_json_payload() {
1026
- Ok(v) => Ok(ActExitValue::Normal(v)),
1027
- Err(e) => Err(ActivityError::NonRetryable(e)),
1028
- },
1029
- }
1030
- })
1031
- })
1032
- .boxed(),
1033
- Err(e) => async move { Err(ActivityError::NonRetryable(e.into())) }.boxed(),
1034
- }
1035
- };
1036
- Arc::new(wrapper)
1037
- }
1038
- }
1039
-
1040
- /// Extra information attached to workflow updates
1041
- #[derive(Clone)]
1042
- pub struct UpdateInfo {
1043
- /// The update's id, unique within the workflow
1044
- pub update_id: String,
1045
- /// Headers attached to the update
1046
- pub headers: HashMap<String, Payload>,
1047
- }
1048
-
1049
- /// Context for a workflow update
1050
- pub struct UpdateContext {
1051
- /// The workflow context, can be used to do normal workflow things inside the update handler
1052
- pub wf_ctx: WfContext,
1053
- /// Additional update info
1054
- pub info: UpdateInfo,
1055
- }
1056
-
1057
- struct UpdateFunctions {
1058
- validator: BoxUpdateValidatorFn,
1059
- handler: BoxUpdateHandlerFn,
1060
- }
1061
-
1062
- impl UpdateFunctions {
1063
- pub(crate) fn new<Arg, Res>(
1064
- v: impl IntoUpdateValidatorFunc<Arg> + Sized,
1065
- h: impl IntoUpdateHandlerFunc<Arg, Res> + Sized,
1066
- ) -> Self {
1067
- Self {
1068
- validator: v.into_update_validator_fn(),
1069
- handler: h.into_update_handler_fn(),
1070
- }
1071
- }
1072
- }
1073
-
1074
- type BoxUpdateValidatorFn = Box<dyn Fn(&UpdateInfo, &Payload) -> Result<(), anyhow::Error> + Send>;
1075
- /// Closures / functions which can be turned into update validation functions implement this trait
1076
- pub trait IntoUpdateValidatorFunc<Arg> {
1077
- /// Consume the closure/fn pointer and turn it into an update validator
1078
- fn into_update_validator_fn(self) -> BoxUpdateValidatorFn;
1079
- }
1080
- impl<A, F> IntoUpdateValidatorFunc<A> for F
1081
- where
1082
- A: FromJsonPayloadExt + Send,
1083
- F: (for<'a> Fn(&'a UpdateInfo, A) -> Result<(), anyhow::Error>) + Send + 'static,
1084
- {
1085
- fn into_update_validator_fn(self) -> BoxUpdateValidatorFn {
1086
- let wrapper = move |ctx: &UpdateInfo, input: &Payload| match A::from_json_payload(input) {
1087
- Ok(deser) => (self)(ctx, deser),
1088
- Err(e) => Err(e.into()),
1089
- };
1090
- Box::new(wrapper)
1091
- }
1092
- }
1093
- type BoxUpdateHandlerFn = Box<
1094
- dyn FnMut(UpdateContext, &Payload) -> BoxFuture<'static, Result<Payload, anyhow::Error>> + Send,
1095
- >;
1096
- /// Closures / functions which can be turned into update handler functions implement this trait
1097
- pub trait IntoUpdateHandlerFunc<Arg, Res> {
1098
- /// Consume the closure/fn pointer and turn it into an update handler
1099
- fn into_update_handler_fn(self) -> BoxUpdateHandlerFn;
1100
- }
1101
- impl<A, F, Rf, R> IntoUpdateHandlerFunc<A, R> for F
1102
- where
1103
- A: FromJsonPayloadExt + Send,
1104
- F: (FnMut(UpdateContext, A) -> Rf) + Send + 'static,
1105
- Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
1106
- R: AsJsonPayloadExt,
1107
- {
1108
- fn into_update_handler_fn(mut self) -> BoxUpdateHandlerFn {
1109
- let wrapper = move |ctx: UpdateContext, input: &Payload| match A::from_json_payload(input) {
1110
- Ok(deser) => (self)(ctx, deser)
1111
- .map(|r| r.and_then(|r| r.as_json_payload()))
1112
- .boxed(),
1113
- Err(e) => async move { Err(e.into()) }.boxed(),
1114
- };
1115
- Box::new(wrapper)
1116
- }
1117
- }
1118
-
1119
1299
  /// Attempts to turn caught panics into something printable
1120
1300
  fn panic_formatter(panic: Box<dyn Any>) -> Box<dyn Display> {
1121
1301
  _panic_formatter::<&str>(panic)
@@ -1151,3 +1331,138 @@ impl Display for EndPrintingAttempts {
1151
1331
  impl PrintablePanicType for EndPrintingAttempts {
1152
1332
  type NextType = EndPrintingAttempts;
1153
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
+ }