@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
@@ -1,59 +1,60 @@
1
1
  use crate::common::{
2
- ActivationAssertionsInterceptor, CoreWfStarter, WorkflowHandleExt, build_fake_sdk,
3
- history_from_proto_binary, init_core_replay_preloaded, mock_sdk, mock_sdk_cfg,
4
- replay_sdk_worker, workflows::la_problem_workflow,
2
+ ActivationAssertionsInterceptor, CoreWfStarter, WorkflowHandleExt,
3
+ activity_functions::StdActivities, build_fake_sdk, history_from_proto_binary,
4
+ init_core_replay_preloaded, mock_sdk, mock_sdk_cfg, replay_sdk_worker,
5
+ workflows::LaProblemWorkflow,
5
6
  };
6
7
  use anyhow::anyhow;
7
8
  use crossbeam_queue::SegQueue;
8
- use futures_util::{FutureExt, future::join_all};
9
+ use futures_util::FutureExt;
9
10
  use rstest::Context;
10
11
  use std::{
11
12
  collections::HashMap,
12
13
  ops::Sub,
13
14
  sync::{
14
15
  Arc,
15
- atomic::{AtomicBool, AtomicI64, AtomicU8, AtomicUsize, Ordering},
16
+ atomic::{AtomicI64, AtomicU8, AtomicUsize, Ordering},
16
17
  },
17
18
  time::{Duration, Instant, SystemTime},
18
19
  };
19
- use temporalio_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions};
20
+ use temporalio_client::{
21
+ NamespacedClient, WorkflowExecuteUpdateOptions, WorkflowExecutionInfo, WorkflowStartOptions,
22
+ };
20
23
  use temporalio_common::{
21
- Worker,
22
- errors::PollError,
24
+ data_converters::RawValue,
23
25
  protos::{
24
26
  DEFAULT_ACTIVITY_TYPE, canned_histories,
25
27
  coresdk::{
26
- ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt, IntoPayloadsExt,
28
+ ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt,
27
29
  activity_result::ActivityExecutionResult,
28
30
  workflow_activation::{WorkflowActivationJob, workflow_activation_job},
29
31
  workflow_commands::{
30
32
  ActivityCancellationType, ScheduleLocalActivity, workflow_command::Variant,
31
33
  },
32
- workflow_completion,
33
- workflow_completion::{WorkflowActivationCompletion, workflow_activation_completion},
34
+ workflow_completion::{
35
+ self, WorkflowActivationCompletion, workflow_activation_completion,
36
+ },
34
37
  },
35
38
  temporal::api::{
36
39
  command::v1::{RecordMarkerCommandAttributes, command},
37
40
  common::v1::RetryPolicy,
38
- enums::v1::{
39
- CommandType, EventType, TimeoutType, UpdateWorkflowExecutionLifecycleStage,
40
- WorkflowTaskFailedCause,
41
- },
41
+ enums::v1::{CommandType, EventType, TimeoutType, WorkflowTaskFailedCause},
42
42
  failure::v1::{Failure, failure::FailureInfo},
43
43
  history::v1::history_event::Attributes::MarkerRecordedEventAttributes,
44
44
  query::v1::WorkflowQuery,
45
- update::v1::WaitPolicy,
46
45
  },
47
46
  test_utils::{query_ok, schedule_local_activity_cmd, start_timer_cmd},
48
47
  },
49
48
  };
49
+ use temporalio_macros::{activities, workflow, workflow_methods};
50
50
  use temporalio_sdk::{
51
- ActContext, ActivityError, ActivityOptions, CancellableFuture, LocalActivityOptions,
52
- UpdateContext, WfContext, WorkflowFunction, WorkflowResult,
51
+ ActivityExecutionError, ActivityOptions, CancellableFuture, LocalActivityOptions,
52
+ WorkflowContext, WorkflowContextView, WorkflowResult, WorkflowTermination,
53
+ activities::{ActivityContext, ActivityError},
53
54
  interceptors::{FailOnNondeterminismInterceptor, WorkerInterceptor},
54
55
  };
55
56
  use temporalio_sdk_core::{
56
- prost_dur,
57
+ PollError, TunerHolder, prost_dur,
57
58
  replay::{DEFAULT_WORKFLOW_TYPE, HistoryForReplay, TestHistoryBuilder, default_wes_attribs},
58
59
  test_help::{
59
60
  LEGACY_QUERY_ID, MockPollCfg, ResponseType, WorkerExt, WorkerTestHelpers,
@@ -64,28 +65,44 @@ use temporalio_sdk_core::{
64
65
  use tokio::{join, select, sync::Barrier};
65
66
  use tokio_util::sync::CancellationToken;
66
67
 
67
- pub(crate) async fn one_local_activity_wf(ctx: WfContext) -> WorkflowResult<()> {
68
- let initial_workflow_time = ctx.workflow_time().expect("Workflow time should be set");
69
- ctx.local_activity(LocalActivityOptions {
70
- activity_type: "echo_activity".to_string(),
71
- input: "hi!".as_json_payload().expect("serializes fine"),
72
- ..Default::default()
73
- })
74
- .await;
75
- // Verify LA execution advances the clock
76
- assert!(initial_workflow_time < ctx.workflow_time().unwrap());
77
- Ok(().into())
68
+ #[workflow]
69
+ #[derive(Default)]
70
+ pub(crate) struct OneLocalActivityWf;
71
+
72
+ #[workflow_methods]
73
+ impl OneLocalActivityWf {
74
+ #[run]
75
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
76
+ let initial_workflow_time = ctx.workflow_time().expect("Workflow time should be set");
77
+ ctx.start_local_activity(
78
+ StdActivities::echo,
79
+ "hi!".to_string(),
80
+ LocalActivityOptions::default(),
81
+ )
82
+ .await
83
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
84
+ assert!(initial_workflow_time < ctx.workflow_time().unwrap());
85
+ Ok(())
86
+ }
78
87
  }
79
88
 
80
89
  #[tokio::test]
81
90
  async fn one_local_activity() {
82
91
  let wf_name = "one_local_activity";
83
92
  let mut starter = CoreWfStarter::new(wf_name);
93
+ starter.sdk_config.register_activities(StdActivities);
84
94
  let mut worker = starter.worker().await;
85
- worker.register_wf(wf_name.to_owned(), one_local_activity_wf);
86
- worker.register_activity("echo_activity", echo);
87
-
88
- let handle = starter.start_with_worker(wf_name, &mut worker).await;
95
+ worker.register_workflow::<OneLocalActivityWf>();
96
+
97
+ let task_queue = starter.get_task_queue().to_owned();
98
+ let handle = worker
99
+ .submit_workflow(
100
+ OneLocalActivityWf::run,
101
+ (),
102
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
103
+ )
104
+ .await
105
+ .unwrap();
89
106
  worker.run_until_done().await.unwrap();
90
107
  handle
91
108
  .fetch_history_and_replay(worker.inner_mut())
@@ -93,142 +110,215 @@ async fn one_local_activity() {
93
110
  .unwrap();
94
111
  }
95
112
 
96
- pub(crate) async fn local_act_concurrent_with_timer_wf(ctx: WfContext) -> WorkflowResult<()> {
97
- let la = ctx.local_activity(LocalActivityOptions {
98
- activity_type: "echo_activity".to_string(),
99
- input: "hi!".as_json_payload().expect("serializes fine"),
100
- ..Default::default()
101
- });
102
- let timer = ctx.timer(Duration::from_secs(1));
103
- tokio::join!(la, timer);
104
- Ok(().into())
113
+ #[workflow]
114
+ #[derive(Default)]
115
+ pub(crate) struct LocalActConcurrentWithTimerWf;
116
+
117
+ #[workflow_methods]
118
+ impl LocalActConcurrentWithTimerWf {
119
+ #[run]
120
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
121
+ let la = ctx.start_local_activity(
122
+ StdActivities::echo,
123
+ "hi!".to_string(),
124
+ LocalActivityOptions::default(),
125
+ );
126
+ let timer = ctx.timer(Duration::from_secs(1));
127
+ let _ = temporalio_sdk::workflows::join!(la, timer);
128
+ Ok(())
129
+ }
105
130
  }
106
131
 
107
132
  #[tokio::test]
108
133
  async fn local_act_concurrent_with_timer() {
109
134
  let wf_name = "local_act_concurrent_with_timer";
110
135
  let mut starter = CoreWfStarter::new(wf_name);
136
+ starter.sdk_config.register_activities(StdActivities);
111
137
  let mut worker = starter.worker().await;
112
- worker.register_wf(wf_name.to_owned(), local_act_concurrent_with_timer_wf);
113
- worker.register_activity("echo_activity", echo);
138
+ worker.register_workflow::<LocalActConcurrentWithTimerWf>();
114
139
 
115
- starter.start_with_worker(wf_name, &mut worker).await;
140
+ let task_queue = starter.get_task_queue().to_owned();
141
+ worker
142
+ .submit_workflow(
143
+ LocalActConcurrentWithTimerWf::run,
144
+ (),
145
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
146
+ )
147
+ .await
148
+ .unwrap();
116
149
  worker.run_until_done().await.unwrap();
117
150
  }
118
151
 
119
- pub(crate) async fn local_act_then_timer_then_wait(ctx: WfContext) -> WorkflowResult<()> {
120
- let la = ctx.local_activity(LocalActivityOptions {
121
- activity_type: "echo_activity".to_string(),
122
- input: "hi!".as_json_payload().expect("serializes fine"),
123
- ..Default::default()
124
- });
125
- ctx.timer(Duration::from_secs(1)).await;
126
- let res = la.await;
127
- assert!(res.completed_ok());
128
- Ok(().into())
152
+ #[workflow]
153
+ #[derive(Default)]
154
+ struct LocalActThenTimerThenWaitResult;
155
+
156
+ #[workflow_methods]
157
+ impl LocalActThenTimerThenWaitResult {
158
+ #[run]
159
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
160
+ let la = ctx.start_local_activity(
161
+ StdActivities::echo,
162
+ "hi!".to_string(),
163
+ LocalActivityOptions::default(),
164
+ );
165
+ ctx.timer(Duration::from_secs(1)).await;
166
+ let res = la.await;
167
+ assert!(res.is_ok());
168
+ Ok(())
169
+ }
129
170
  }
130
171
 
131
172
  #[tokio::test]
132
173
  async fn local_act_then_timer_then_wait_result() {
133
174
  let wf_name = "local_act_then_timer_then_wait_result";
134
175
  let mut starter = CoreWfStarter::new(wf_name);
176
+ starter.sdk_config.register_activities(StdActivities);
135
177
  let mut worker = starter.worker().await;
136
- worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
137
- worker.register_activity("echo_activity", echo);
178
+ worker.register_workflow::<LocalActThenTimerThenWaitResult>();
138
179
 
139
- starter.start_with_worker(wf_name, &mut worker).await;
180
+ let task_queue = starter.get_task_queue().to_owned();
181
+ worker
182
+ .submit_workflow(
183
+ LocalActThenTimerThenWaitResult::run,
184
+ (),
185
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
186
+ )
187
+ .await
188
+ .unwrap();
140
189
  worker.run_until_done().await.unwrap();
141
190
  }
142
191
 
192
+ #[workflow]
193
+ #[derive(Default)]
194
+ pub(crate) struct LocalActThenTimerThenWait;
195
+
196
+ #[workflow_methods]
197
+ impl LocalActThenTimerThenWait {
198
+ #[run]
199
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
200
+ let la = ctx.start_local_activity(
201
+ StdActivities::delay,
202
+ Duration::from_secs(4),
203
+ LocalActivityOptions::default(),
204
+ );
205
+ ctx.timer(Duration::from_secs(1)).await;
206
+ let res = la.await;
207
+ assert!(res.is_ok());
208
+ Ok(())
209
+ }
210
+ }
211
+
143
212
  #[tokio::test]
144
213
  async fn long_running_local_act_with_timer() {
145
214
  let wf_name = "long_running_local_act_with_timer";
146
215
  let mut starter = CoreWfStarter::new(wf_name);
147
216
  starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
217
+ starter.sdk_config.register_activities(StdActivities);
148
218
  let mut worker = starter.worker().await;
149
- worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
150
- worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
151
- tokio::time::sleep(Duration::from_secs(4)).await;
152
- Ok(str)
153
- });
219
+ worker.register_workflow::<LocalActThenTimerThenWait>();
154
220
 
155
- starter.start_with_worker(wf_name, &mut worker).await;
221
+ let task_queue = starter.get_task_queue().to_owned();
222
+ worker
223
+ .submit_workflow(
224
+ LocalActThenTimerThenWait::run,
225
+ (),
226
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
227
+ )
228
+ .await
229
+ .unwrap();
156
230
  worker.run_until_done().await.unwrap();
157
231
  }
158
232
 
159
- pub(crate) async fn local_act_fanout_wf(ctx: WfContext) -> WorkflowResult<()> {
160
- let las: Vec<_> = (1..=50)
161
- .map(|i| {
162
- ctx.local_activity(LocalActivityOptions {
163
- activity_type: "echo_activity".to_string(),
164
- input: format!("Hi {i}")
165
- .as_json_payload()
166
- .expect("serializes fine"),
167
- ..Default::default()
233
+ #[workflow]
234
+ #[derive(Default)]
235
+ pub(crate) struct LocalActFanoutWf;
236
+
237
+ #[workflow_methods]
238
+ impl LocalActFanoutWf {
239
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
240
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
241
+ let las: Vec<_> = (1..=50)
242
+ .map(|i| {
243
+ ctx.start_local_activity(StdActivities::echo, format!("Hi {i}"), Default::default())
168
244
  })
169
- })
170
- .collect();
171
- ctx.timer(Duration::from_secs(1)).await;
172
- join_all(las).await;
173
- Ok(().into())
245
+ .collect();
246
+ ctx.timer(Duration::from_secs(1)).await;
247
+ temporalio_sdk::workflows::join_all(las).await;
248
+ Ok(())
249
+ }
174
250
  }
175
251
 
176
252
  #[tokio::test]
177
253
  async fn local_act_fanout() {
178
254
  let wf_name = "local_act_fanout";
179
255
  let mut starter = CoreWfStarter::new(wf_name);
180
- starter
181
- .worker_config
182
- .max_outstanding_local_activities(1_usize);
256
+ starter.sdk_config.tuner = Arc::new(TunerHolder::fixed_size(5, 1, 1, 1));
257
+ starter.sdk_config.register_activities(StdActivities);
183
258
  let mut worker = starter.worker().await;
184
- worker.register_wf(wf_name.to_owned(), local_act_fanout_wf);
185
- worker.register_activity("echo_activity", echo);
259
+ worker.register_workflow::<LocalActFanoutWf>();
186
260
 
187
- starter.start_with_worker(wf_name, &mut worker).await;
261
+ let task_queue = starter.get_task_queue().to_owned();
262
+ worker
263
+ .submit_workflow(
264
+ LocalActFanoutWf::run,
265
+ (),
266
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
267
+ )
268
+ .await
269
+ .unwrap();
188
270
  worker.run_until_done().await.unwrap();
189
271
  }
190
272
 
273
+ #[workflow]
274
+ #[derive(Default)]
275
+ struct LocalActRetryTimerBackoff;
276
+
277
+ #[workflow_methods]
278
+ impl LocalActRetryTimerBackoff {
279
+ #[run]
280
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
281
+ let res = ctx
282
+ .start_local_activity(
283
+ StdActivities::always_fail,
284
+ (),
285
+ LocalActivityOptions {
286
+ retry_policy: RetryPolicy {
287
+ initial_interval: Some(prost_dur!(from_micros(15))),
288
+ // We want two local backoffs that are short. Third backoff will use timer
289
+ backoff_coefficient: 1_000.,
290
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
291
+ maximum_attempts: 4,
292
+ non_retryable_error_types: vec![],
293
+ },
294
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
295
+ ..Default::default()
296
+ },
297
+ )
298
+ .await;
299
+ assert!(matches!(res, Err(ActivityExecutionError::Failed(_))));
300
+ Ok(())
301
+ }
302
+ }
303
+
191
304
  #[tokio::test]
192
305
  async fn local_act_retry_timer_backoff() {
193
306
  let wf_name = "local_act_retry_timer_backoff";
194
307
  let mut starter = CoreWfStarter::new(wf_name);
308
+ starter.sdk_config.register_activities(StdActivities);
195
309
  let mut worker = starter.worker().await;
196
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
197
- let res = ctx
198
- .local_activity(LocalActivityOptions {
199
- activity_type: "echo".to_string(),
200
- input: "hi".as_json_payload().expect("serializes fine"),
201
- retry_policy: RetryPolicy {
202
- initial_interval: Some(prost_dur!(from_micros(15))),
203
- // We want two local backoffs that are short. Third backoff will use timer
204
- backoff_coefficient: 1_000.,
205
- maximum_interval: Some(prost_dur!(from_millis(1500))),
206
- maximum_attempts: 4,
207
- non_retryable_error_types: vec![],
208
- },
209
- timer_backoff_threshold: Some(Duration::from_secs(1)),
210
- ..Default::default()
211
- })
212
- .await;
213
- assert!(res.failed());
214
- Ok(().into())
215
- });
216
- worker.register_activity("echo", |_: ActContext, _: String| async {
217
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
218
- });
219
-
220
- let run_id = worker
221
- .submit_wf(
222
- wf_name.to_owned(),
223
- wf_name.to_owned(),
224
- vec![],
225
- WorkflowOptions::default(),
310
+ worker.register_workflow::<LocalActRetryTimerBackoff>();
311
+
312
+ let task_queue = starter.get_task_queue().to_owned();
313
+ let handle = worker
314
+ .submit_workflow(
315
+ LocalActRetryTimerBackoff::run,
316
+ (),
317
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
226
318
  )
227
319
  .await
228
320
  .unwrap();
229
321
  worker.run_until_done().await.unwrap();
230
- let client = starter.get_client().await;
231
- let handle = client.get_untyped_workflow_handle(wf_name, run_id);
232
322
  handle
233
323
  .fetch_history_and_replay(worker.inner_mut())
234
324
  .await
@@ -241,41 +331,86 @@ async fn local_act_retry_timer_backoff() {
241
331
  #[case::abandon(ActivityCancellationType::Abandon)]
242
332
  #[tokio::test]
243
333
  async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
244
- let wf_name = format!("cancel_immediate_{cancel_type:?}");
245
- let mut starter = CoreWfStarter::new(&wf_name);
246
- let mut worker = starter.worker().await;
247
- worker.register_wf(&wf_name, move |ctx: WfContext| async move {
248
- let la = ctx.local_activity(LocalActivityOptions {
249
- activity_type: "echo".to_string(),
250
- input: "hi".as_json_payload().expect("serializes fine"),
251
- cancel_type,
252
- ..Default::default()
253
- });
254
- la.cancel(&ctx);
255
- let resolution = la.await;
256
- assert!(resolution.cancelled());
257
- Ok(().into())
258
- });
334
+ use temporalio_sdk::WorkflowContextView;
259
335
 
336
+ let wf_name = format!("cancel_immediate_{cancel_type:?}");
260
337
  // If we don't use this, we'd hang on shutdown for abandon cancel modes.
261
338
  let manual_cancel = CancellationToken::new();
262
- let manual_cancel_act = manual_cancel.clone();
339
+ let mut starter = CoreWfStarter::new(&wf_name);
263
340
 
264
- worker.register_activity("echo", move |ctx: ActContext, _: String| {
265
- let manual_cancel_act = manual_cancel_act.clone();
266
- async move {
341
+ struct EchoWithManualCancel {
342
+ manual_cancel: CancellationToken,
343
+ }
344
+ #[activities]
345
+ impl EchoWithManualCancel {
346
+ #[activity]
347
+ async fn echo(
348
+ self: Arc<Self>,
349
+ ctx: ActivityContext,
350
+ _: String,
351
+ ) -> Result<(), ActivityError> {
267
352
  tokio::select! {
268
- _ = tokio::time::sleep(Duration::from_secs(10)) => {},
353
+ _ = tokio::time::sleep(Duration::from_secs(10)) => {}
269
354
  _ = ctx.cancelled() => {
270
355
  return Err(ActivityError::cancelled())
271
356
  }
272
- _ = manual_cancel_act.cancelled() => {}
357
+ _ = self.manual_cancel.cancelled() => {}
273
358
  }
274
359
  Ok(())
275
360
  }
276
- });
361
+ }
362
+
363
+ starter
364
+ .sdk_config
365
+ .register_activities(EchoWithManualCancel {
366
+ manual_cancel: manual_cancel.clone(),
367
+ });
368
+
369
+ #[workflow]
370
+ struct CancelImmediate {
371
+ cancel_type: ActivityCancellationType,
372
+ }
373
+
374
+ #[workflow_methods]
375
+ impl CancelImmediate {
376
+ #[init]
377
+ fn new(_ctx: &WorkflowContextView, cancel_type: ActivityCancellationType) -> Self {
378
+ Self { cancel_type }
379
+ }
277
380
 
278
- starter.start_with_worker(wf_name, &mut worker).await;
381
+ #[run]
382
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
383
+ let cancel_type = ctx.state(|wf| wf.cancel_type);
384
+ let la = ctx.start_local_activity(
385
+ EchoWithManualCancel::echo,
386
+ "hi".to_string(),
387
+ LocalActivityOptions {
388
+ cancel_type,
389
+ ..Default::default()
390
+ },
391
+ );
392
+ la.cancel();
393
+ let resolution = la.await;
394
+ assert!(matches!(
395
+ resolution,
396
+ Err(ActivityExecutionError::Cancelled(_))
397
+ ));
398
+ Ok(())
399
+ }
400
+ }
401
+
402
+ let mut worker = starter.worker().await;
403
+ worker.register_workflow::<CancelImmediate>();
404
+
405
+ let task_queue = starter.get_task_queue().to_owned();
406
+ worker
407
+ .submit_workflow(
408
+ CancelImmediate::run,
409
+ cancel_type,
410
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
411
+ )
412
+ .await
413
+ .unwrap();
279
414
  worker
280
415
  .run_until_done_intercepted(Some(LACancellerInterceptor {
281
416
  cancel_on_workflow_completed: false,
@@ -326,47 +461,24 @@ async fn cancel_after_act_starts(
326
461
  cancel_type: ActivityCancellationType,
327
462
  ) {
328
463
  let wf_name = format!("cancel_after_act_starts_{cancel_on_backoff:?}_{cancel_type:?}");
329
- let mut starter = CoreWfStarter::new(&wf_name);
330
- starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
331
- let mut worker = starter.worker().await;
332
- let bo_dur = cancel_on_backoff.unwrap_or_else(|| Duration::from_secs(1));
333
- worker.register_wf(&wf_name, move |ctx: WfContext| async move {
334
- let la = ctx.local_activity(LocalActivityOptions {
335
- activity_type: "echo".to_string(),
336
- input: "hi".as_json_payload().expect("serializes fine"),
337
- retry_policy: RetryPolicy {
338
- initial_interval: Some(bo_dur.try_into().unwrap()),
339
- backoff_coefficient: 1.,
340
- maximum_interval: Some(bo_dur.try_into().unwrap()),
341
- // Retry forever until cancelled
342
- ..Default::default()
343
- },
344
- timer_backoff_threshold: Some(Duration::from_secs(1)),
345
- cancel_type,
346
- ..Default::default()
347
- });
348
- ctx.timer(Duration::from_secs(1)).await;
349
- // Note that this cancel can't go through for *two* WF tasks, because we do a full heartbeat
350
- // before the timer (LA hasn't resolved), and then the timer fired event won't appear in
351
- // history until *after* the next WFT because we force generated it when we sent the timer
352
- // command.
353
- la.cancel(&ctx);
354
- // This extra timer is here to ensure the presence of another WF task doesn't mess up
355
- // resolving the LA with cancel on replay
356
- ctx.timer(Duration::from_secs(1)).await;
357
- let resolution = la.await;
358
- assert!(resolution.cancelled());
359
- Ok(().into())
360
- });
361
-
362
464
  // If we don't use this, we'd hang on shutdown for abandon cancel modes.
363
465
  let manual_cancel = CancellationToken::new();
364
- let manual_cancel_act = manual_cancel.clone();
466
+ let mut starter = CoreWfStarter::new(&wf_name);
467
+ starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
365
468
 
366
- worker.register_activity("echo", move |ctx: ActContext, _: String| {
367
- let manual_cancel_act = manual_cancel_act.clone();
368
- async move {
369
- if cancel_on_backoff.is_some() {
469
+ struct EchoWithManualCancelAndBackoff {
470
+ manual_cancel: CancellationToken,
471
+ cancel_on_backoff: Option<CancellationToken>,
472
+ }
473
+ #[activities]
474
+ impl EchoWithManualCancelAndBackoff {
475
+ #[activity]
476
+ async fn echo(
477
+ self: Arc<Self>,
478
+ ctx: ActivityContext,
479
+ _: String,
480
+ ) -> Result<(), ActivityError> {
481
+ if self.cancel_on_backoff.is_some() {
370
482
  if ctx.is_cancelled() {
371
483
  return Err(ActivityError::cancelled());
372
484
  }
@@ -374,20 +486,87 @@ async fn cancel_after_act_starts(
374
486
  return Err(anyhow!("Oh no I failed!").into());
375
487
  } else {
376
488
  tokio::select! {
377
- _ = tokio::time::sleep(Duration::from_secs(100)) => {},
489
+ _ = tokio::time::sleep(Duration::from_secs(100)) => {}
378
490
  _ = ctx.cancelled() => {
379
491
  return Err(ActivityError::cancelled())
380
492
  }
381
- _ = manual_cancel_act.cancelled() => {
493
+ _ = self.manual_cancel.cancelled() => {
382
494
  return Ok(())
383
495
  }
384
496
  }
385
497
  }
386
498
  Err(anyhow!("Oh no I failed!").into())
387
499
  }
388
- });
500
+ }
389
501
 
390
- starter.start_with_worker(&wf_name, &mut worker).await;
502
+ starter
503
+ .sdk_config
504
+ .register_activities(EchoWithManualCancelAndBackoff {
505
+ manual_cancel: manual_cancel.clone(),
506
+ cancel_on_backoff: if cancel_on_backoff.is_some() {
507
+ Some(CancellationToken::new())
508
+ } else {
509
+ None
510
+ },
511
+ });
512
+ #[workflow]
513
+ #[derive(Default)]
514
+ struct CancelAfterActStartsWf;
515
+
516
+ #[workflow_methods]
517
+ impl CancelAfterActStartsWf {
518
+ #[run]
519
+ async fn run(
520
+ ctx: &mut WorkflowContext<Self>,
521
+ (bo_dur, cancel_type): (Duration, ActivityCancellationType),
522
+ ) -> WorkflowResult<()> {
523
+ let la = ctx.start_local_activity(
524
+ EchoWithManualCancelAndBackoff::echo,
525
+ "hi".to_string(),
526
+ LocalActivityOptions {
527
+ retry_policy: RetryPolicy {
528
+ initial_interval: Some(bo_dur.try_into().unwrap()),
529
+ backoff_coefficient: 1.,
530
+ maximum_interval: Some(bo_dur.try_into().unwrap()),
531
+ // Retry forever until cancelled
532
+ ..Default::default()
533
+ },
534
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
535
+ cancel_type,
536
+ ..Default::default()
537
+ },
538
+ );
539
+ ctx.timer(Duration::from_secs(1)).await;
540
+ // Note that this cancel can't go through for *two* WF tasks, because we do a full heartbeat
541
+ // before the timer (LA hasn't resolved), and then the timer fired event won't appear in
542
+ // history until *after* the next WFT because we force generated it when we sent the timer
543
+ // command.
544
+ la.cancel();
545
+ // This extra timer is here to ensure the presence of another WF task doesn't mess up
546
+ // resolving the LA with cancel on replay
547
+ ctx.timer(Duration::from_secs(1)).await;
548
+ let resolution = la.await;
549
+ assert!(matches!(
550
+ resolution,
551
+ Err(ActivityExecutionError::Cancelled(_))
552
+ ));
553
+ Ok(())
554
+ }
555
+ }
556
+
557
+ let mut worker = starter.worker().await;
558
+ let bo_dur = cancel_on_backoff.unwrap_or_else(|| Duration::from_secs(1));
559
+ worker.register_workflow::<CancelAfterActStartsWf>();
560
+
561
+ let task_queue = starter.get_task_queue().to_owned();
562
+ worker
563
+ .submit_workflow(
564
+ CancelAfterActStartsWf::run,
565
+ (bo_dur, cancel_type),
566
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
567
+ )
568
+ .await
569
+ .unwrap();
391
570
  worker
392
571
  .run_until_done_intercepted(Some(LACancellerInterceptor {
393
572
  token: manual_cancel,
@@ -410,6 +589,68 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
410
589
  if is_schedule { "schedule" } else { "start" }
411
590
  );
412
591
  let mut starter = CoreWfStarter::new(&wf_name);
592
+
593
+ struct LongRunningWithCancellation;
594
+ #[activities]
595
+ impl LongRunningWithCancellation {
596
+ #[activity]
597
+ async fn go(ctx: ActivityContext) -> Result<(), ActivityError> {
598
+ tokio::select! {
599
+ _ = tokio::time::sleep(Duration::from_secs(100)) => {}
600
+ _ = ctx.cancelled() => {
601
+ return Err(ActivityError::cancelled())
602
+ }
603
+ }
604
+ Ok(())
605
+ }
606
+ }
607
+
608
+ starter
609
+ .sdk_config
610
+ .register_activities(LongRunningWithCancellation);
611
+ #[workflow]
612
+ #[derive(Default)]
613
+ struct XToCloseTimeoutWf;
614
+
615
+ #[workflow_methods]
616
+ impl XToCloseTimeoutWf {
617
+ #[run]
618
+ async fn run(
619
+ ctx: &mut WorkflowContext<Self>,
620
+ (sched, start, timeout_type): (Option<Duration>, Option<Duration>, i32),
621
+ ) -> WorkflowResult<()> {
622
+ let res = ctx
623
+ .start_local_activity(
624
+ LongRunningWithCancellation::go,
625
+ (),
626
+ LocalActivityOptions {
627
+ retry_policy: RetryPolicy {
628
+ initial_interval: Some(prost_dur!(from_micros(15))),
629
+ backoff_coefficient: 1_000.,
630
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
631
+ maximum_attempts: 4,
632
+ non_retryable_error_types: vec![],
633
+ },
634
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
635
+ schedule_to_close_timeout: sched,
636
+ start_to_close_timeout: start,
637
+ ..Default::default()
638
+ },
639
+ )
640
+ .await;
641
+ let err = res.unwrap_err();
642
+ if let ActivityExecutionError::Failed(f) = &err {
643
+ assert_eq!(
644
+ f.is_timeout(),
645
+ Some(TimeoutType::try_from(timeout_type).unwrap())
646
+ );
647
+ } else {
648
+ return Err(anyhow!("expected Failed, got {err:?}").into());
649
+ }
650
+ Ok(())
651
+ }
652
+ }
653
+
413
654
  let mut worker = starter.worker().await;
414
655
  let (sched, start) = if is_schedule {
415
656
  (Some(Duration::from_secs(2)), None)
@@ -421,39 +662,17 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
421
662
  } else {
422
663
  TimeoutType::StartToClose
423
664
  };
665
+ worker.register_workflow::<XToCloseTimeoutWf>();
424
666
 
425
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
426
- let res = ctx
427
- .local_activity(LocalActivityOptions {
428
- activity_type: "echo".to_string(),
429
- input: "hi".as_json_payload().expect("serializes fine"),
430
- retry_policy: RetryPolicy {
431
- initial_interval: Some(prost_dur!(from_micros(15))),
432
- backoff_coefficient: 1_000.,
433
- maximum_interval: Some(prost_dur!(from_millis(1500))),
434
- maximum_attempts: 4,
435
- non_retryable_error_types: vec![],
436
- },
437
- timer_backoff_threshold: Some(Duration::from_secs(1)),
438
- schedule_to_close_timeout: sched,
439
- start_to_close_timeout: start,
440
- ..Default::default()
441
- })
442
- .await;
443
- assert_eq!(res.timed_out(), Some(timeout_type));
444
- Ok(().into())
445
- });
446
- worker.register_activity("echo", |ctx: ActContext, _: String| async move {
447
- tokio::select! {
448
- _ = tokio::time::sleep(Duration::from_secs(100)) => {},
449
- _ = ctx.cancelled() => {
450
- return Err(ActivityError::cancelled())
451
- }
452
- };
453
- Ok(())
454
- });
455
-
456
- starter.start_with_worker(wf_name, &mut worker).await;
667
+ let task_queue = starter.get_task_queue().to_owned();
668
+ worker
669
+ .submit_workflow(
670
+ XToCloseTimeoutWf::run,
671
+ (sched, start, timeout_type as i32),
672
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
673
+ )
674
+ .await
675
+ .unwrap();
457
676
  worker.run_until_done().await.unwrap();
458
677
  }
459
678
 
@@ -468,36 +687,75 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
468
687
  );
469
688
  let mut starter = CoreWfStarter::new(&wf_name);
470
689
  if !cached {
471
- starter.worker_config.max_cached_workflows(0_usize);
690
+ starter.sdk_config.max_cached_workflows = 0_usize;
472
691
  }
692
+
693
+ #[workflow]
694
+ #[derive(Default)]
695
+ struct ScheduleToCloseTimeoutAcrossTimerBackoff;
696
+
697
+ #[workflow_methods]
698
+ impl ScheduleToCloseTimeoutAcrossTimerBackoff {
699
+ #[run]
700
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
701
+ let res = ctx
702
+ .start_local_activity(
703
+ FailWithAtomicCounter::go,
704
+ "hi".to_string(),
705
+ LocalActivityOptions {
706
+ retry_policy: RetryPolicy {
707
+ initial_interval: Some(prost_dur!(from_millis(15))),
708
+ backoff_coefficient: 1_000.,
709
+ maximum_interval: Some(prost_dur!(from_millis(1000))),
710
+ maximum_attempts: 40,
711
+ non_retryable_error_types: vec![],
712
+ },
713
+ timer_backoff_threshold: Some(Duration::from_millis(500)),
714
+ schedule_to_close_timeout: Some(Duration::from_secs(2)),
715
+ ..Default::default()
716
+ },
717
+ )
718
+ .await;
719
+ let err = res.unwrap_err();
720
+ if let ActivityExecutionError::Failed(f) = &err {
721
+ assert_eq!(f.is_timeout(), Some(TimeoutType::ScheduleToClose));
722
+ } else {
723
+ panic!("expected Failed, got {err:?}");
724
+ }
725
+ Ok(())
726
+ }
727
+ }
728
+
473
729
  let mut worker = starter.worker().await;
474
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
475
- let res = ctx
476
- .local_activity(LocalActivityOptions {
477
- activity_type: "echo".to_string(),
478
- input: "hi".as_json_payload().expect("serializes fine"),
479
- retry_policy: RetryPolicy {
480
- initial_interval: Some(prost_dur!(from_millis(15))),
481
- backoff_coefficient: 1_000.,
482
- maximum_interval: Some(prost_dur!(from_millis(1000))),
483
- maximum_attempts: 40,
484
- non_retryable_error_types: vec![],
485
- },
486
- timer_backoff_threshold: Some(Duration::from_millis(500)),
487
- schedule_to_close_timeout: Some(Duration::from_secs(2)),
488
- ..Default::default()
489
- })
490
- .await;
491
- assert_eq!(res.timed_out(), Some(TimeoutType::ScheduleToClose));
492
- Ok(().into())
493
- });
494
- let num_attempts: &'static _ = Box::leak(Box::new(AtomicU8::new(0)));
495
- worker.register_activity("echo", move |_: ActContext, _: String| async {
496
- num_attempts.fetch_add(1, Ordering::Relaxed);
497
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
730
+ worker.register_workflow::<ScheduleToCloseTimeoutAcrossTimerBackoff>();
731
+
732
+ let num_attempts = Arc::new(AtomicU8::new(0));
733
+
734
+ struct FailWithAtomicCounter {
735
+ counter: Arc<AtomicU8>,
736
+ }
737
+ #[activities]
738
+ impl FailWithAtomicCounter {
739
+ #[activity]
740
+ async fn go(self: Arc<Self>, _: ActivityContext, _: String) -> Result<(), ActivityError> {
741
+ self.counter.fetch_add(1, Ordering::Relaxed);
742
+ Err(anyhow!("Oh no I failed!").into())
743
+ }
744
+ }
745
+
746
+ worker.register_activities(FailWithAtomicCounter {
747
+ counter: num_attempts.clone(),
498
748
  });
499
749
 
500
- starter.start_with_worker(wf_name, &mut worker).await;
750
+ let task_queue = starter.get_task_queue().to_owned();
751
+ worker
752
+ .submit_workflow(
753
+ ScheduleToCloseTimeoutAcrossTimerBackoff::run,
754
+ (),
755
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
756
+ )
757
+ .await
758
+ .unwrap();
501
759
  worker.run_until_done().await.unwrap();
502
760
  // 3 attempts b/c first backoff is very small, then the next 2 attempts take at least 2 seconds
503
761
  // b/c of timer backoff.
@@ -509,24 +767,21 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
509
767
  async fn eviction_wont_make_local_act_get_dropped(#[values(true, false)] short_wft_timeout: bool) {
510
768
  let wf_name = format!("eviction_wont_make_local_act_get_dropped_{short_wft_timeout}");
511
769
  let mut starter = CoreWfStarter::new(&wf_name);
512
- starter.worker_config.max_cached_workflows(0_usize);
770
+ starter.sdk_config.max_cached_workflows = 0_usize;
771
+ starter.sdk_config.register_activities(StdActivities);
513
772
  let mut worker = starter.worker().await;
514
- worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
515
- worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
516
- tokio::time::sleep(Duration::from_secs(4)).await;
517
- Ok(str)
518
- });
773
+ worker.register_workflow::<LocalActThenTimerThenWait>();
519
774
 
775
+ let task_queue = starter.get_task_queue().to_owned();
520
776
  let opts = if short_wft_timeout {
521
- WorkflowOptions {
522
- task_timeout: Some(Duration::from_secs(1)),
523
- ..Default::default()
524
- }
777
+ WorkflowStartOptions::new(task_queue, wf_name.clone())
778
+ .task_timeout(Duration::from_secs(1))
779
+ .build()
525
780
  } else {
526
- Default::default()
781
+ WorkflowStartOptions::new(task_queue, wf_name.clone()).build()
527
782
  };
528
783
  worker
529
- .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![], opts)
784
+ .submit_workflow(LocalActThenTimerThenWait::run, (), opts)
530
785
  .await
531
786
  .unwrap();
532
787
  worker.run_until_done().await.unwrap();
@@ -536,44 +791,65 @@ async fn eviction_wont_make_local_act_get_dropped(#[values(true, false)] short_w
536
791
  async fn timer_backoff_concurrent_with_non_timer_backoff() {
537
792
  let wf_name = "timer_backoff_concurrent_with_non_timer_backoff";
538
793
  let mut starter = CoreWfStarter::new(wf_name);
794
+ starter.sdk_config.register_activities(StdActivities);
795
+
796
+ #[workflow]
797
+ #[derive(Default)]
798
+ struct TimerBackoffConcurrentWf;
799
+
800
+ #[workflow_methods]
801
+ impl TimerBackoffConcurrentWf {
802
+ #[run]
803
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
804
+ let r1 = ctx.start_local_activity(
805
+ StdActivities::always_fail,
806
+ (),
807
+ LocalActivityOptions {
808
+ retry_policy: RetryPolicy {
809
+ initial_interval: Some(prost_dur!(from_micros(15))),
810
+ backoff_coefficient: 1_000.,
811
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
812
+ maximum_attempts: 4,
813
+ non_retryable_error_types: vec![],
814
+ },
815
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
816
+ ..Default::default()
817
+ },
818
+ );
819
+ let r2 = ctx.start_local_activity(
820
+ StdActivities::always_fail,
821
+ (),
822
+ LocalActivityOptions {
823
+ retry_policy: RetryPolicy {
824
+ initial_interval: Some(prost_dur!(from_millis(15))),
825
+ backoff_coefficient: 10.,
826
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
827
+ maximum_attempts: 4,
828
+ non_retryable_error_types: vec![],
829
+ },
830
+ timer_backoff_threshold: Some(Duration::from_secs(10)),
831
+ ..Default::default()
832
+ },
833
+ );
834
+ let (r1, r2) = temporalio_sdk::workflows::join!(r1, r2);
835
+ assert!(matches!(r1, Err(ActivityExecutionError::Failed(_))));
836
+ assert!(matches!(r2, Err(ActivityExecutionError::Failed(_))));
837
+ Ok(())
838
+ }
839
+ }
840
+
539
841
  let mut worker = starter.worker().await;
540
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
541
- let r1 = ctx.local_activity(LocalActivityOptions {
542
- activity_type: "echo".to_string(),
543
- input: "hi".as_json_payload().expect("serializes fine"),
544
- retry_policy: RetryPolicy {
545
- initial_interval: Some(prost_dur!(from_micros(15))),
546
- backoff_coefficient: 1_000.,
547
- maximum_interval: Some(prost_dur!(from_millis(1500))),
548
- maximum_attempts: 4,
549
- non_retryable_error_types: vec![],
550
- },
551
- timer_backoff_threshold: Some(Duration::from_secs(1)),
552
- ..Default::default()
553
- });
554
- let r2 = ctx.local_activity(LocalActivityOptions {
555
- activity_type: "echo".to_string(),
556
- input: "hi".as_json_payload().expect("serializes fine"),
557
- retry_policy: RetryPolicy {
558
- initial_interval: Some(prost_dur!(from_millis(15))),
559
- backoff_coefficient: 10.,
560
- maximum_interval: Some(prost_dur!(from_millis(1500))),
561
- maximum_attempts: 4,
562
- non_retryable_error_types: vec![],
563
- },
564
- timer_backoff_threshold: Some(Duration::from_secs(10)),
565
- ..Default::default()
566
- });
567
- let (r1, r2) = tokio::join!(r1, r2);
568
- assert!(r1.failed());
569
- assert!(r2.failed());
570
- Ok(().into())
571
- });
572
- worker.register_activity("echo", |_: ActContext, _: String| async {
573
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
574
- });
842
+ worker.register_workflow::<TimerBackoffConcurrentWf>();
575
843
 
576
- starter.start_with_worker(wf_name, &mut worker).await;
844
+ let task_queue = starter.get_task_queue().to_owned();
845
+ worker
846
+ .submit_workflow(
847
+ TimerBackoffConcurrentWf::run,
848
+ (),
849
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
850
+ )
851
+ .await
852
+ .unwrap();
577
853
  worker.run_until_done().await.unwrap();
578
854
  }
579
855
 
@@ -581,50 +857,64 @@ async fn timer_backoff_concurrent_with_non_timer_backoff() {
581
857
  async fn repro_nondeterminism_with_timer_bug() {
582
858
  let wf_name = "repro_nondeterminism_with_timer_bug";
583
859
  let mut starter = CoreWfStarter::new(wf_name);
584
- let mut worker = starter.worker().await;
585
-
586
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
587
- let t1 = ctx.timer(Duration::from_secs(30));
588
- let r1 = ctx.local_activity(LocalActivityOptions {
589
- activity_type: "delay".to_string(),
590
- input: "hi".as_json_payload().expect("serializes fine"),
591
- retry_policy: RetryPolicy {
592
- initial_interval: Some(prost_dur!(from_micros(15))),
593
- backoff_coefficient: 1_000.,
594
- maximum_interval: Some(prost_dur!(from_millis(1500))),
595
- maximum_attempts: 4,
596
- non_retryable_error_types: vec![],
597
- },
598
- timer_backoff_threshold: Some(Duration::from_secs(1)),
599
- ..Default::default()
600
- });
601
- tokio::pin!(t1);
602
- tokio::select! {
603
- _ = &mut t1 => {},
604
- _ = r1 => {
605
- t1.cancel(&ctx);
606
- },
860
+ starter.sdk_config.register_activities(StdActivities);
861
+
862
+ #[workflow]
863
+ #[derive(Default)]
864
+ struct ReproNondeterminismWithTimerBugWf;
865
+
866
+ #[workflow_methods]
867
+ impl ReproNondeterminismWithTimerBugWf {
868
+ #[run]
869
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
870
+ let mut t1 = ctx.timer(Duration::from_secs(30));
871
+ let mut r1 = ctx.start_local_activity(
872
+ StdActivities::delay,
873
+ Duration::from_secs(2),
874
+ LocalActivityOptions {
875
+ retry_policy: RetryPolicy {
876
+ initial_interval: Some(prost_dur!(from_micros(15))),
877
+ backoff_coefficient: 1_000.,
878
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
879
+ maximum_attempts: 4,
880
+ non_retryable_error_types: vec![],
881
+ },
882
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
883
+ ..Default::default()
884
+ },
885
+ );
886
+ temporalio_sdk::workflows::select! {
887
+ _ = t1 => {},
888
+ _ = r1 => {
889
+ t1.cancel();
890
+ },
891
+ }
892
+ ctx.timer(Duration::from_secs(1)).await;
893
+ Ok(())
607
894
  }
608
- ctx.timer(Duration::from_secs(1)).await;
609
- Ok(().into())
610
- });
611
- worker.register_activity("delay", |_: ActContext, _: String| async {
612
- tokio::time::sleep(Duration::from_secs(2)).await;
613
- Ok(())
614
- });
895
+ }
615
896
 
616
- let run_id = worker
617
- .submit_wf(
618
- wf_name.to_owned(),
619
- wf_name.to_owned(),
620
- vec![],
621
- WorkflowOptions::default(),
897
+ let mut worker = starter.worker().await;
898
+ worker.register_workflow::<ReproNondeterminismWithTimerBugWf>();
899
+
900
+ let task_queue = starter.get_task_queue().to_owned();
901
+ let handle = worker
902
+ .submit_workflow(
903
+ ReproNondeterminismWithTimerBugWf::run,
904
+ (),
905
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
622
906
  )
623
907
  .await
624
908
  .unwrap();
625
909
  worker.run_until_done().await.unwrap();
626
910
  let client = starter.get_client().await;
627
- let handle = client.get_untyped_workflow_handle(wf_name, run_id);
911
+ let handle = WorkflowExecutionInfo {
912
+ namespace: client.namespace(),
913
+ workflow_id: wf_name.into(),
914
+ run_id: Some(handle.run_id().unwrap().to_string()),
915
+ first_execution_run_id: None,
916
+ }
917
+ .bind_untyped(client.clone());
628
918
  handle
629
919
  .fetch_history_and_replay(worker.inner_mut())
630
920
  .await
@@ -648,14 +938,8 @@ async fn weird_la_nondeterminism_repro(#[values(true, false)] fix_hist: bool) {
648
938
  }
649
939
 
650
940
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
651
- worker.register_wf(
652
- "evict_while_la_running_no_interference",
653
- la_problem_workflow,
654
- );
655
- worker.register_activity("delay", |_: ActContext, _: String| async {
656
- tokio::time::sleep(Duration::from_secs(15)).await;
657
- Ok(())
658
- });
941
+ worker.register_workflow::<LaProblemWorkflow>();
942
+ worker.register_activities(StdActivities);
659
943
  worker.run().await.unwrap();
660
944
  }
661
945
 
@@ -672,14 +956,8 @@ async fn second_weird_la_nondeterminism_repro() {
672
956
  hist = thb.get_full_history_info().unwrap().into();
673
957
 
674
958
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
675
- worker.register_wf(
676
- "evict_while_la_running_no_interference",
677
- la_problem_workflow,
678
- );
679
- worker.register_activity("delay", |_: ActContext, _: String| async {
680
- tokio::time::sleep(Duration::from_secs(15)).await;
681
- Ok(())
682
- });
959
+ worker.register_workflow::<LaProblemWorkflow>();
960
+ worker.register_activities(StdActivities);
683
961
  worker.run().await.unwrap();
684
962
  }
685
963
 
@@ -694,14 +972,8 @@ async fn third_weird_la_nondeterminism_repro() {
694
972
  hist = thb.get_full_history_info().unwrap().into();
695
973
 
696
974
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
697
- worker.register_wf(
698
- "evict_while_la_running_no_interference",
699
- la_problem_workflow,
700
- );
701
- worker.register_activity("delay", |_: ActContext, _: String| async {
702
- tokio::time::sleep(Duration::from_secs(15)).await;
703
- Ok(())
704
- });
975
+ worker.register_workflow::<LaProblemWorkflow>();
976
+ worker.register_activities(StdActivities);
705
977
  worker.run().await.unwrap();
706
978
  }
707
979
 
@@ -721,60 +993,87 @@ async fn third_weird_la_nondeterminism_repro() {
721
993
  async fn la_resolve_same_time_as_other_cancel() {
722
994
  let wf_name = "la_resolve_same_time_as_other_cancel";
723
995
  let mut starter = CoreWfStarter::new(wf_name);
724
- // The activity won't get a chance to receive the cancel so make sure we still exit fast
725
- starter
726
- .worker_config
727
- .graceful_shutdown_period(Duration::from_millis(100));
728
- let mut worker = starter.worker().await;
729
-
730
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
731
- let normal_act = ctx.activity(ActivityOptions {
732
- activity_type: "delay".to_string(),
733
- input: 9000.as_json_payload().expect("serializes fine"),
734
- cancellation_type: ActivityCancellationType::TryCancel,
735
- start_to_close_timeout: Some(Duration::from_secs(9000)),
736
- ..Default::default()
737
- });
738
- // Make new task
739
- ctx.timer(Duration::from_millis(1)).await;
740
996
 
741
- // Start LA and cancel the activity at the same time
742
- let local_act = ctx.local_activity(LocalActivityOptions {
743
- activity_type: "delay".to_string(),
744
- input: 100.as_json_payload().expect("serializes fine"),
745
- ..Default::default()
746
- });
747
- normal_act.cancel(&ctx);
748
- // Race them, starting a timer if LA completes first
749
- tokio::select! {
750
- biased;
751
- _ = normal_act => {},
752
- _ = local_act => {
753
- ctx.timer(Duration::from_millis(1)).await;
754
- },
997
+ struct DelayWithCancellation;
998
+ #[activities]
999
+ impl DelayWithCancellation {
1000
+ #[activity]
1001
+ async fn delay(ctx: ActivityContext, dur: Duration) -> Result<(), ActivityError> {
1002
+ tokio::select! {
1003
+ _ = tokio::time::sleep(dur) => {}
1004
+ _ = ctx.cancelled() => {}
1005
+ }
1006
+ Ok(())
755
1007
  }
756
- Ok(().into())
757
- });
758
- worker.register_activity("delay", |ctx: ActContext, wait_time: u64| async move {
759
- tokio::select! {
760
- _ = tokio::time::sleep(Duration::from_millis(wait_time)) => {}
761
- _ = ctx.cancelled() => {}
1008
+ }
1009
+
1010
+ starter
1011
+ .sdk_config
1012
+ .register_activities(DelayWithCancellation);
1013
+ // The activity won't get a chance to receive the cancel so make sure we still exit fast
1014
+ starter.sdk_config.graceful_shutdown_period = Some(Duration::from_millis(100));
1015
+
1016
+ #[workflow]
1017
+ #[derive(Default)]
1018
+ struct LaResolveSameTimeAsOtherCancelWf;
1019
+
1020
+ #[workflow_methods]
1021
+ impl LaResolveSameTimeAsOtherCancelWf {
1022
+ #[run]
1023
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1024
+ let mut normal_act = ctx.start_activity(
1025
+ DelayWithCancellation::delay,
1026
+ Duration::from_secs(9),
1027
+ ActivityOptions {
1028
+ cancellation_type: ActivityCancellationType::TryCancel,
1029
+ start_to_close_timeout: Some(Duration::from_secs(9000)),
1030
+ ..Default::default()
1031
+ },
1032
+ );
1033
+ // Make new task
1034
+ ctx.timer(Duration::from_millis(1)).await;
1035
+
1036
+ // Start LA and cancel the activity at the same time
1037
+ let mut local_act = ctx.start_local_activity(
1038
+ DelayWithCancellation::delay,
1039
+ Duration::from_millis(100),
1040
+ LocalActivityOptions {
1041
+ ..Default::default()
1042
+ },
1043
+ );
1044
+ normal_act.cancel();
1045
+ // Race them, starting a timer if LA completes first
1046
+ temporalio_sdk::workflows::select! {
1047
+ _ = normal_act => {},
1048
+ _ = local_act => {
1049
+ ctx.timer(Duration::from_millis(1)).await;
1050
+ },
1051
+ }
1052
+ Ok(())
762
1053
  }
763
- Ok(())
764
- });
1054
+ }
765
1055
 
766
- let run_id = worker
767
- .submit_wf(
768
- wf_name.to_owned(),
769
- wf_name.to_owned(),
770
- vec![],
771
- WorkflowOptions::default(),
1056
+ let mut worker = starter.worker().await;
1057
+ worker.register_workflow::<LaResolveSameTimeAsOtherCancelWf>();
1058
+
1059
+ let task_queue = starter.get_task_queue().to_owned();
1060
+ let handle = worker
1061
+ .submit_workflow(
1062
+ LaResolveSameTimeAsOtherCancelWf::run,
1063
+ (),
1064
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
772
1065
  )
773
1066
  .await
774
1067
  .unwrap();
775
1068
  worker.run_until_done().await.unwrap();
776
1069
  let client = starter.get_client().await;
777
- let handle = client.get_untyped_workflow_handle(wf_name, run_id);
1070
+ let handle = WorkflowExecutionInfo {
1071
+ namespace: client.namespace(),
1072
+ workflow_id: wf_name.into(),
1073
+ run_id: Some(handle.run_id().unwrap().to_string()),
1074
+ first_execution_run_id: None,
1075
+ }
1076
+ .bind_untyped(client.clone());
778
1077
  handle
779
1078
  .fetch_history_and_replay(worker.inner_mut())
780
1079
  .await
@@ -795,59 +1094,68 @@ async fn long_local_activity_with_update(
795
1094
  let wf_name = format!("{}-{}", ctx.name, ctx.case.unwrap());
796
1095
  let mut starter = CoreWfStarter::new(&wf_name);
797
1096
  starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
798
- let mut worker = starter.worker().await;
799
- let client = starter.get_client().await;
1097
+ starter.sdk_config.register_activities(StdActivities);
800
1098
 
801
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
802
- let update_counter = Arc::new(AtomicUsize::new(1));
803
- let uc = update_counter.clone();
804
- ctx.update_handler(
805
- "update",
806
- |_: &_, _: ()| Ok(()),
807
- move |u: UpdateContext, _: ()| {
808
- let uc = uc.clone();
809
- async move {
810
- if update_inner_timer != 0 {
811
- u.wf_ctx
812
- .timer(Duration::from_millis(update_inner_timer))
813
- .await;
814
- }
815
- uc.fetch_add(1, Ordering::Relaxed);
816
- Ok(())
817
- }
818
- },
819
- );
820
- ctx.local_activity(LocalActivityOptions {
821
- activity_type: "delay".to_string(),
822
- input: "hi".as_json_payload().expect("serializes fine"),
823
- ..Default::default()
824
- })
825
- .await;
826
- update_counter.load(Ordering::Relaxed);
827
- Ok(().into())
828
- });
829
- worker.register_activity("delay", |_: ActContext, _: String| async {
830
- tokio::time::sleep(Duration::from_secs(6)).await;
831
- Ok(())
832
- });
1099
+ #[workflow]
1100
+ #[derive(Default)]
1101
+ struct LongLocalActivityWithUpdateWf {
1102
+ update_counter: usize,
1103
+ update_inner_timer: u64,
1104
+ }
833
1105
 
834
- let handle = starter
835
- .start_with_worker(wf_name.clone(), &mut worker)
836
- .await;
1106
+ #[workflow_methods]
1107
+ impl LongLocalActivityWithUpdateWf {
1108
+ #[init]
1109
+ fn init(_ctx: &WorkflowContextView, update_inner_timer: u64) -> Self {
1110
+ Self {
1111
+ update_counter: 1,
1112
+ update_inner_timer,
1113
+ }
1114
+ }
1115
+
1116
+ #[run]
1117
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<usize> {
1118
+ ctx.start_local_activity(
1119
+ StdActivities::delay,
1120
+ Duration::from_secs(6),
1121
+ LocalActivityOptions::default(),
1122
+ )
1123
+ .await
1124
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1125
+ Ok(ctx.state(|wf| wf.update_counter))
1126
+ }
1127
+
1128
+ #[update]
1129
+ async fn do_update(ctx: &mut WorkflowContext<Self>, _: ()) {
1130
+ let update_inner_timer = ctx.state(|s| s.update_inner_timer);
1131
+ if update_inner_timer != 0 {
1132
+ ctx.timer(Duration::from_millis(update_inner_timer)).await;
1133
+ }
1134
+ ctx.state_mut(|s| s.update_counter += 1);
1135
+ }
1136
+ }
1137
+
1138
+ let mut worker = starter.worker().await;
1139
+ worker.register_workflow::<LongLocalActivityWithUpdateWf>();
1140
+
1141
+ let task_queue = starter.get_task_queue().to_owned();
1142
+ let handle = worker
1143
+ .submit_workflow(
1144
+ LongLocalActivityWithUpdateWf::run,
1145
+ update_inner_timer,
1146
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
1147
+ )
1148
+ .await
1149
+ .unwrap();
837
1150
 
838
- let wf_id = starter.get_task_queue().to_string();
839
1151
  let update = async {
840
1152
  loop {
841
1153
  tokio::time::sleep(Duration::from_millis(update_interval_ms)).await;
842
- let _ = client
843
- .update_workflow_execution(
844
- wf_id.clone(),
845
- "".to_string(),
846
- "update".to_string(),
847
- WaitPolicy {
848
- lifecycle_stage: UpdateWorkflowExecutionLifecycleStage::Completed as i32,
849
- },
850
- [().as_json_payload().unwrap()].into_payloads(),
1154
+ let _ = handle
1155
+ .execute_update(
1156
+ LongLocalActivityWithUpdateWf::do_update,
1157
+ (),
1158
+ WorkflowExecuteUpdateOptions::default(),
851
1159
  )
852
1160
  .await;
853
1161
  }
@@ -856,16 +1164,12 @@ async fn long_local_activity_with_update(
856
1164
  worker.run_until_done().await.unwrap();
857
1165
  };
858
1166
  tokio::select!(_ = update => {}, _ = runner => {});
859
- let res = handle
860
- .get_workflow_result(Default::default())
861
- .await
862
- .unwrap()
863
- .unwrap_success();
1167
+ let res = handle.get_result(Default::default()).await.unwrap();
864
1168
  let replay_res = handle
865
1169
  .fetch_history_and_replay(worker.inner_mut())
866
1170
  .await
867
1171
  .unwrap();
868
- assert_eq!(res[0], replay_res.unwrap());
1172
+ assert_eq!(res, usize::from_json_payload(&replay_res.unwrap()).unwrap());
869
1173
 
870
1174
  // Load histories from pre-fix version and ensure compat
871
1175
  let replay_worker = init_core_replay_preloaded(
@@ -878,7 +1182,7 @@ async fn long_local_activity_with_update(
878
1182
  )],
879
1183
  );
880
1184
  let inner_worker = worker.inner_mut();
881
- inner_worker.with_new_core_worker(replay_worker);
1185
+ inner_worker.with_new_core_worker(Arc::new(replay_worker));
882
1186
  inner_worker.set_worker_interceptor(FailOnNondeterminismInterceptor {});
883
1187
  inner_worker.run().await.unwrap();
884
1188
  }
@@ -888,68 +1192,92 @@ async fn local_activity_with_heartbeat_only_causes_one_wakeup() {
888
1192
  let wf_name = "local_activity_with_heartbeat_only_causes_one_wakeup";
889
1193
  let mut starter = CoreWfStarter::new(wf_name);
890
1194
  starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
891
- let mut worker = starter.worker().await;
1195
+ starter.sdk_config.register_activities(StdActivities);
892
1196
 
893
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
894
- let mut wakeup_counter = 1;
895
- let la_resolved = AtomicBool::new(false);
896
- tokio::join!(
897
- async {
898
- ctx.local_activity(LocalActivityOptions {
899
- activity_type: "delay".to_string(),
900
- input: "hi".as_json_payload().expect("serializes fine"),
901
- ..Default::default()
902
- })
903
- .await;
904
- la_resolved.store(true, Ordering::Relaxed);
905
- },
906
- async {
907
- ctx.wait_condition(|| {
1197
+ #[workflow]
1198
+ #[derive(Default)]
1199
+ struct LocalActivityWithHeartbeatOnlyCausesOneWakeupWf {
1200
+ la_resolved: bool,
1201
+ }
1202
+
1203
+ #[workflow_methods]
1204
+ impl LocalActivityWithHeartbeatOnlyCausesOneWakeupWf {
1205
+ #[run]
1206
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<usize> {
1207
+ let mut wakeup_counter = 0;
1208
+ // Interestingly LA munst come first because if the condition is polled first, we won't
1209
+ // see that resolved is true.
1210
+ // TODO [rust-sdk-branch] - See if we can fix this and know that we should re-poll.
1211
+ temporalio_sdk::workflows::join!(
1212
+ async {
1213
+ ctx.start_local_activity(
1214
+ StdActivities::delay,
1215
+ Duration::from_secs(6),
1216
+ LocalActivityOptions::default(),
1217
+ )
1218
+ .await
1219
+ .ok();
1220
+ ctx.state_mut(|s| s.la_resolved = true);
1221
+ },
1222
+ ctx.wait_condition(|_| {
908
1223
  wakeup_counter += 1;
909
- la_resolved.load(Ordering::Relaxed)
1224
+ ctx.state(|s| s.la_resolved)
910
1225
  })
911
- .await;
912
- }
913
- );
914
- Ok(().into())
915
- });
916
- worker.register_activity("delay", |_: ActContext, _: String| async {
917
- tokio::time::sleep(Duration::from_secs(6)).await;
918
- Ok(())
919
- });
1226
+ );
1227
+ Ok(wakeup_counter as usize)
1228
+ }
1229
+ }
920
1230
 
921
- let handle = starter.start_with_worker(wf_name, &mut worker).await;
922
- worker.run_until_done().await.unwrap();
923
- let res = handle
924
- .get_workflow_result(Default::default())
1231
+ let mut worker = starter.worker().await;
1232
+ worker.register_workflow::<LocalActivityWithHeartbeatOnlyCausesOneWakeupWf>();
1233
+
1234
+ let task_queue = starter.get_task_queue().to_owned();
1235
+ let handle = worker
1236
+ .submit_workflow(
1237
+ LocalActivityWithHeartbeatOnlyCausesOneWakeupWf::run,
1238
+ (),
1239
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
1240
+ )
925
1241
  .await
926
- .unwrap()
927
- .unwrap_success();
928
- let replay_res = handle
1242
+ .unwrap();
1243
+ worker.run_until_done().await.unwrap();
1244
+ let r = handle.get_result(Default::default()).await.unwrap();
1245
+ assert_eq!(r, 2);
1246
+ handle
929
1247
  .fetch_history_and_replay(worker.inner_mut())
930
1248
  .await
931
1249
  .unwrap();
932
- assert_eq!(res[0], replay_res.unwrap());
933
1250
  }
934
1251
 
935
- pub(crate) async fn local_activity_with_summary_wf(ctx: WfContext) -> WorkflowResult<()> {
936
- ctx.local_activity(LocalActivityOptions {
937
- activity_type: "echo_activity".to_string(),
938
- input: "hi!".as_json_payload().expect("serializes fine"),
939
- summary: Some("Echo summary".to_string()),
940
- ..Default::default()
941
- })
942
- .await;
943
- Ok(().into())
1252
+ #[workflow]
1253
+ #[derive(Default)]
1254
+ pub(crate) struct LocalActivityWithSummaryWf;
1255
+
1256
+ #[workflow_methods]
1257
+ impl LocalActivityWithSummaryWf {
1258
+ #[run(name = "local_activity_with_summary")]
1259
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1260
+ ctx.start_local_activity(
1261
+ StdActivities::echo,
1262
+ "hi".to_string(),
1263
+ LocalActivityOptions {
1264
+ summary: Some("Echo summary".to_string()),
1265
+ ..Default::default()
1266
+ },
1267
+ )
1268
+ .await
1269
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1270
+ Ok(())
1271
+ }
944
1272
  }
945
1273
 
946
1274
  #[tokio::test]
947
1275
  async fn local_activity_with_summary() {
948
1276
  let wf_name = "local_activity_with_summary";
949
1277
  let mut starter = CoreWfStarter::new(wf_name);
1278
+ starter.sdk_config.register_activities(StdActivities);
950
1279
  let mut worker = starter.worker().await;
951
- worker.register_wf(wf_name.to_owned(), local_activity_with_summary_wf);
952
- worker.register_activity("echo_activity", echo);
1280
+ worker.register_workflow::<LocalActivityWithSummaryWf>();
953
1281
 
954
1282
  let handle = starter.start_with_worker(wf_name, &mut worker).await;
955
1283
  worker.run_until_done().await.unwrap();
@@ -982,10 +1310,6 @@ async fn local_activity_with_summary() {
982
1310
  );
983
1311
  }
984
1312
 
985
- async fn echo(_ctx: ActContext, e: String) -> Result<String, ActivityError> {
986
- Ok(e)
987
- }
988
-
989
1313
  /// This test verifies that when replaying we are able to resolve local activities whose data we
990
1314
  /// don't see until after the workflow issues the command
991
1315
  #[rstest::rstest]
@@ -1019,29 +1343,23 @@ async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached:
1019
1343
  }
1020
1344
  });
1021
1345
 
1022
- worker.register_wf(
1023
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1024
- |ctx: WfContext| async move {
1025
- let la = ctx.local_activity(LocalActivityOptions {
1026
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1027
- input: "hi".as_json_payload().expect("serializes fine"),
1028
- ..Default::default()
1029
- });
1346
+ #[workflow]
1347
+ #[derive(Default)]
1348
+ struct LocalActTwoWftsBeforeMarkerWf;
1349
+
1350
+ #[workflow_methods]
1351
+ impl LocalActTwoWftsBeforeMarkerWf {
1352
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1353
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1354
+ let la = ctx.start_local_activity(StdActivities::default, (), Default::default());
1030
1355
  ctx.timer(Duration::from_secs(1)).await;
1031
- la.await;
1032
- Ok(().into())
1033
- },
1034
- );
1035
- worker.register_activity(DEFAULT_ACTIVITY_TYPE, echo);
1036
- worker
1037
- .submit_wf(
1038
- wf_id.to_owned(),
1039
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1040
- vec![],
1041
- WorkflowOptions::default(),
1042
- )
1043
- .await
1044
- .unwrap();
1356
+ let _ = la.await;
1357
+ Ok(())
1358
+ }
1359
+ }
1360
+
1361
+ worker.register_workflow::<LocalActTwoWftsBeforeMarkerWf>();
1362
+ worker.register_activities(StdActivities);
1045
1363
  worker.run_until_done().await.unwrap();
1046
1364
  }
1047
1365
 
@@ -1064,17 +1382,8 @@ async fn local_act_many_concurrent() {
1064
1382
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
1065
1383
  let mut worker = mock_sdk(mh);
1066
1384
 
1067
- worker.register_wf(DEFAULT_WORKFLOW_TYPE.to_owned(), local_act_fanout_wf);
1068
- worker.register_activity("echo_activity", echo);
1069
- worker
1070
- .submit_wf(
1071
- wf_id.to_owned(),
1072
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1073
- vec![],
1074
- WorkflowOptions::default(),
1075
- )
1076
- .await
1077
- .unwrap();
1385
+ worker.register_workflow::<LocalActFanoutWf>();
1386
+ worker.register_activities(StdActivities);
1078
1387
  worker.run_until_done().await.unwrap();
1079
1388
  }
1080
1389
 
@@ -1105,43 +1414,67 @@ async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
1105
1414
  wc.max_cached_workflows = 1;
1106
1415
  wc.max_outstanding_workflow_tasks = Some(1);
1107
1416
  });
1108
- let core = worker.core_worker.clone();
1417
+ let core = worker.core_worker();
1418
+
1419
+ let shutdown_barr = Arc::new(Barrier::new(2));
1420
+
1421
+ #[workflow]
1422
+ #[derive(Default)]
1423
+ struct LocalActHeartbeatWf;
1424
+
1425
+ #[workflow_methods]
1426
+ impl LocalActHeartbeatWf {
1427
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1428
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1429
+ dbg!("dafuq");
1430
+ ctx.start_local_activity(
1431
+ EchoWithConditionalBarrier::echo,
1432
+ "hi".to_string(),
1433
+ LocalActivityOptions::default(),
1434
+ )
1435
+ .await
1436
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1437
+ Ok(())
1438
+ }
1439
+ }
1109
1440
 
1110
- let shutdown_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
1441
+ worker.register_workflow::<LocalActHeartbeatWf>();
1111
1442
 
1112
- worker.register_wf(
1113
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1114
- |ctx: WfContext| async move {
1115
- ctx.local_activity(LocalActivityOptions {
1116
- activity_type: "echo".to_string(),
1117
- input: "hi".as_json_payload().expect("serializes fine"),
1118
- ..Default::default()
1119
- })
1120
- .await;
1121
- Ok(().into())
1122
- },
1123
- );
1124
- worker.register_activity("echo", move |_ctx: ActContext, str: String| async move {
1125
- if shutdown_middle {
1126
- shutdown_barr.wait().await;
1443
+ struct EchoWithConditionalBarrier {
1444
+ shutdown_barr: Option<Arc<Barrier>>,
1445
+ wft_timeout: Duration,
1446
+ }
1447
+ #[activities]
1448
+ impl EchoWithConditionalBarrier {
1449
+ #[activity]
1450
+ async fn echo(
1451
+ self: Arc<Self>,
1452
+ _: ActivityContext,
1453
+ str: String,
1454
+ ) -> Result<String, ActivityError> {
1455
+ dbg!("Running activity");
1456
+ if let Some(barr) = &self.shutdown_barr {
1457
+ barr.wait().await;
1458
+ }
1459
+ // Take slightly more than two workflow tasks
1460
+ tokio::time::sleep(self.wft_timeout.mul_f32(2.2)).await;
1461
+ Ok(str)
1127
1462
  }
1128
- // Take slightly more than two workflow tasks
1129
- tokio::time::sleep(wft_timeout.mul_f32(2.2)).await;
1130
- Ok(str)
1463
+ }
1464
+
1465
+ worker.register_activities(EchoWithConditionalBarrier {
1466
+ shutdown_barr: if shutdown_middle {
1467
+ Some(shutdown_barr.clone())
1468
+ } else {
1469
+ None
1470
+ },
1471
+ wft_timeout,
1131
1472
  });
1132
- worker
1133
- .submit_wf(
1134
- wf_id.to_owned(),
1135
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1136
- vec![],
1137
- WorkflowOptions::default(),
1138
- )
1139
- .await
1140
- .unwrap();
1141
1473
  let (_, runres) = tokio::join!(
1142
1474
  async {
1143
1475
  if shutdown_middle {
1144
1476
  shutdown_barr.wait().await;
1477
+ dbg!("Past barrier");
1145
1478
  core.shutdown().await;
1146
1479
  }
1147
1480
  },
@@ -1157,6 +1490,7 @@ async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
1157
1490
  async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
1158
1491
  let mut t = TestHistoryBuilder::default();
1159
1492
  t.add_by_type(EventType::WorkflowExecutionStarted);
1493
+ t.set_wf_input(eventually_pass.as_json_payload().unwrap());
1160
1494
  t.add_workflow_task_scheduled_and_started();
1161
1495
 
1162
1496
  let wf_id = "fakeid";
@@ -1164,49 +1498,63 @@ async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
1164
1498
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
1165
1499
  let mut worker = mock_sdk(mh);
1166
1500
 
1167
- worker.register_wf(
1168
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1169
- move |ctx: WfContext| async move {
1501
+ #[workflow]
1502
+ #[derive(Default)]
1503
+ struct LocalActFailAndRetryWf;
1504
+
1505
+ #[workflow_methods]
1506
+ impl LocalActFailAndRetryWf {
1507
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1508
+ async fn run(ctx: &mut WorkflowContext<Self>, eventually_pass: bool) -> WorkflowResult<()> {
1170
1509
  let la_res = ctx
1171
- .local_activity(LocalActivityOptions {
1172
- activity_type: "echo".to_string(),
1173
- input: "hi".as_json_payload().expect("serializes fine"),
1174
- retry_policy: RetryPolicy {
1175
- initial_interval: Some(prost_dur!(from_millis(50))),
1176
- backoff_coefficient: 1.2,
1177
- maximum_interval: None,
1178
- maximum_attempts: 5,
1179
- non_retryable_error_types: vec![],
1510
+ .start_local_activity(
1511
+ EventuallyPassingActivity::echo,
1512
+ "hi".to_string(),
1513
+ LocalActivityOptions {
1514
+ retry_policy: RetryPolicy {
1515
+ initial_interval: Some(prost_dur!(from_millis(50))),
1516
+ backoff_coefficient: 1.2,
1517
+ maximum_interval: None,
1518
+ maximum_attempts: 5,
1519
+ non_retryable_error_types: vec![],
1520
+ },
1521
+ ..Default::default()
1180
1522
  },
1181
- ..Default::default()
1182
- })
1523
+ )
1183
1524
  .await;
1184
1525
  if eventually_pass {
1185
- assert!(la_res.completed_ok())
1526
+ assert!(la_res.is_ok())
1186
1527
  } else {
1187
- assert!(la_res.failed())
1528
+ assert!(matches!(la_res, Err(ActivityExecutionError::Failed(_))))
1188
1529
  }
1189
- Ok(().into())
1190
- },
1191
- );
1192
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1193
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1194
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
1195
- if 2 == attempts.fetch_add(1, Ordering::Relaxed) && eventually_pass {
1196
1530
  Ok(())
1197
- } else {
1198
- Err(anyhow!("Oh no I failed!").into())
1199
1531
  }
1532
+ }
1533
+
1534
+ worker.register_workflow::<LocalActFailAndRetryWf>();
1535
+ let attempts = Arc::new(AtomicUsize::new(0));
1536
+
1537
+ struct EventuallyPassingActivity {
1538
+ attempts: Arc<AtomicUsize>,
1539
+ eventually_pass: bool,
1540
+ }
1541
+ #[activities]
1542
+ impl EventuallyPassingActivity {
1543
+ #[activity]
1544
+ async fn echo(self: Arc<Self>, _: ActivityContext, _: String) -> Result<(), ActivityError> {
1545
+ // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
1546
+ if 2 == self.attempts.fetch_add(1, Ordering::Relaxed) && self.eventually_pass {
1547
+ Ok(())
1548
+ } else {
1549
+ Err(anyhow!("Oh no I failed!").into())
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ worker.register_activities(EventuallyPassingActivity {
1555
+ attempts: attempts.clone(),
1556
+ eventually_pass,
1200
1557
  });
1201
- worker
1202
- .submit_wf(
1203
- wf_id.to_owned(),
1204
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1205
- vec![],
1206
- WorkflowOptions::default(),
1207
- )
1208
- .await
1209
- .unwrap();
1210
1558
  worker.run_until_done().await.unwrap();
1211
1559
  let expected_attempts = if eventually_pass { 3 } else { 5 };
1212
1560
  assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
@@ -1217,18 +1565,22 @@ async fn local_act_retry_long_backoff_uses_timer() {
1217
1565
  let mut t = TestHistoryBuilder::default();
1218
1566
  t.add_by_type(EventType::WorkflowExecutionStarted);
1219
1567
  t.add_full_wf_task();
1220
- t.add_local_activity_fail_marker(
1568
+ t.add_local_activity_marker(
1221
1569
  1,
1222
1570
  "1",
1223
- Failure::application_failure("la failed".to_string(), false),
1571
+ None,
1572
+ Some(Failure::application_failure("la failed".to_string(), false)),
1573
+ |m| m.activity_type = StdActivities::always_fail.name().to_owned(),
1224
1574
  );
1225
1575
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1226
1576
  t.add_timer_fired(timer_started_event_id, "1".to_string());
1227
1577
  t.add_full_wf_task();
1228
- t.add_local_activity_fail_marker(
1578
+ t.add_local_activity_marker(
1229
1579
  2,
1230
1580
  "2",
1231
- Failure::application_failure("la failed".to_string(), false),
1581
+ None,
1582
+ Some(Failure::application_failure("la failed".to_string(), false)),
1583
+ |m| m.activity_type = StdActivities::always_fail.name().to_owned(),
1232
1584
  );
1233
1585
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1234
1586
  t.add_timer_fired(timer_started_event_id, "2".to_string());
@@ -1245,45 +1597,38 @@ async fn local_act_retry_long_backoff_uses_timer() {
1245
1597
  );
1246
1598
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1247
1599
 
1248
- worker.register_wf(
1249
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1250
- |ctx: WfContext| async move {
1600
+ #[workflow]
1601
+ #[derive(Default)]
1602
+ struct LocalActRetryLongBackoffUsesTimerWf;
1603
+
1604
+ #[workflow_methods]
1605
+ impl LocalActRetryLongBackoffUsesTimerWf {
1606
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1607
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1251
1608
  let la_res = ctx
1252
- .local_activity(LocalActivityOptions {
1253
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1254
- input: "hi".as_json_payload().expect("serializes fine"),
1255
- retry_policy: RetryPolicy {
1256
- initial_interval: Some(prost_dur!(from_millis(65))),
1257
- // This will make the second backoff 65 seconds, plenty to use timer
1258
- backoff_coefficient: 1_000.,
1259
- maximum_interval: Some(prost_dur!(from_secs(600))),
1260
- maximum_attempts: 3,
1261
- non_retryable_error_types: vec![],
1609
+ .start_local_activity(
1610
+ StdActivities::always_fail,
1611
+ (),
1612
+ LocalActivityOptions {
1613
+ retry_policy: RetryPolicy {
1614
+ initial_interval: Some(prost_dur!(from_millis(65))),
1615
+ backoff_coefficient: 1_000.,
1616
+ maximum_interval: Some(prost_dur!(from_secs(600))),
1617
+ maximum_attempts: 3,
1618
+ non_retryable_error_types: vec![],
1619
+ },
1620
+ ..Default::default()
1262
1621
  },
1263
- ..Default::default()
1264
- })
1622
+ )
1265
1623
  .await;
1266
- assert!(la_res.failed());
1267
- // Extra timer just to have an extra workflow task which we can return full history for
1624
+ assert!(matches!(la_res, Err(ActivityExecutionError::Failed(_))));
1268
1625
  ctx.timer(Duration::from_secs(1)).await;
1269
- Ok(().into())
1270
- },
1271
- );
1272
- worker.register_activity(
1273
- DEFAULT_ACTIVITY_TYPE,
1274
- move |_ctx: ActContext, _: String| async move {
1275
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
1276
- },
1277
- );
1278
- worker
1279
- .submit_wf(
1280
- wf_id.to_owned(),
1281
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1282
- vec![],
1283
- WorkflowOptions::default(),
1284
- )
1285
- .await
1286
- .unwrap();
1626
+ Ok(())
1627
+ }
1628
+ }
1629
+
1630
+ worker.register_workflow::<LocalActRetryLongBackoffUsesTimerWf>();
1631
+ worker.register_activities(StdActivities);
1287
1632
  worker.run_until_done().await.unwrap();
1288
1633
  }
1289
1634
 
@@ -1300,28 +1645,23 @@ async fn local_act_null_result() {
1300
1645
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
1301
1646
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1302
1647
 
1303
- worker.register_wf(
1304
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1305
- |ctx: WfContext| async move {
1306
- ctx.local_activity(LocalActivityOptions {
1307
- activity_type: "nullres".to_string(),
1308
- input: "hi".as_json_payload().expect("serializes fine"),
1309
- ..Default::default()
1310
- })
1311
- .await;
1312
- Ok(().into())
1313
- },
1314
- );
1315
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
1316
- worker
1317
- .submit_wf(
1318
- wf_id.to_owned(),
1319
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1320
- vec![],
1321
- WorkflowOptions::default(),
1322
- )
1323
- .await
1324
- .unwrap();
1648
+ #[workflow]
1649
+ #[derive(Default)]
1650
+ struct LocalActNullResultWf;
1651
+
1652
+ #[workflow_methods]
1653
+ impl LocalActNullResultWf {
1654
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1655
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1656
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
1657
+ .await
1658
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1659
+ Ok(())
1660
+ }
1661
+ }
1662
+
1663
+ worker.register_workflow::<LocalActNullResultWf>();
1664
+ worker.register_activities(StdActivities);
1325
1665
  worker.run_until_done().await.unwrap();
1326
1666
  }
1327
1667
 
@@ -1339,33 +1679,27 @@ async fn local_act_command_immediately_follows_la_marker() {
1339
1679
 
1340
1680
  let wf_id = "fakeid";
1341
1681
  let mock = mock_worker_client();
1342
- // Bug only repros when seeing history up to third wft
1343
1682
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [3], mock);
1344
1683
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 0);
1345
1684
 
1346
- worker.register_wf(
1347
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1348
- |ctx: WfContext| async move {
1349
- ctx.local_activity(LocalActivityOptions {
1350
- activity_type: "nullres".to_string(),
1351
- input: "hi".as_json_payload().expect("serializes fine"),
1352
- ..Default::default()
1353
- })
1354
- .await;
1685
+ #[workflow]
1686
+ #[derive(Default)]
1687
+ struct LocalActCommandImmediatelyFollowsLaMarkerWf;
1688
+
1689
+ #[workflow_methods]
1690
+ impl LocalActCommandImmediatelyFollowsLaMarkerWf {
1691
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1692
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1693
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
1694
+ .await
1695
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1355
1696
  ctx.timer(Duration::from_secs(1)).await;
1356
- Ok(().into())
1357
- },
1358
- );
1359
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
1360
- worker
1361
- .submit_wf(
1362
- wf_id.to_owned(),
1363
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1364
- vec![],
1365
- WorkflowOptions::default(),
1366
- )
1367
- .await
1368
- .unwrap();
1697
+ Ok(())
1698
+ }
1699
+ }
1700
+
1701
+ worker.register_workflow::<LocalActCommandImmediatelyFollowsLaMarkerWf>();
1702
+ worker.register_activities(StdActivities);
1369
1703
  worker.run_until_done().await.unwrap();
1370
1704
  }
1371
1705
 
@@ -1636,44 +1970,39 @@ async fn test_schedule_to_start_timeout() {
1636
1970
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::ToTaskNum(1)], mock);
1637
1971
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1638
1972
 
1639
- worker.register_wf(
1640
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1641
- |ctx: WfContext| async move {
1973
+ #[workflow]
1974
+ #[derive(Default)]
1975
+ struct TestScheduleToStartTimeoutWf;
1976
+
1977
+ #[workflow_methods]
1978
+ impl TestScheduleToStartTimeoutWf {
1979
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
1980
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
1642
1981
  let la_res = ctx
1643
- .local_activity(LocalActivityOptions {
1644
- activity_type: "echo".to_string(),
1645
- input: "hi".as_json_payload().expect("serializes fine"),
1646
- // Impossibly small timeout so we timeout in the queue
1647
- schedule_to_start_timeout: prost_dur!(from_nanos(1)),
1648
- ..Default::default()
1649
- })
1982
+ .start_local_activity(
1983
+ StdActivities::echo,
1984
+ "hi".to_string(),
1985
+ LocalActivityOptions {
1986
+ schedule_to_start_timeout: prost_dur!(from_nanos(1)),
1987
+ ..Default::default()
1988
+ },
1989
+ )
1650
1990
  .await;
1651
- assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
1652
- let rfail = la_res.unwrap_failure();
1653
- assert_matches!(
1654
- rfail.failure_info,
1655
- Some(FailureInfo::ActivityFailureInfo(_))
1656
- );
1657
- assert_matches!(
1658
- rfail.cause.unwrap().failure_info,
1659
- Some(FailureInfo::TimeoutFailureInfo(_))
1660
- );
1661
- Ok(().into())
1662
- },
1663
- );
1664
- worker.register_activity(
1665
- "echo",
1666
- move |_ctx: ActContext, _: String| async move { Ok(()) },
1667
- );
1668
- worker
1669
- .submit_wf(
1670
- wf_id.to_owned(),
1671
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1672
- vec![],
1673
- WorkflowOptions::default(),
1674
- )
1675
- .await
1676
- .unwrap();
1991
+ assert!(la_res.is_err());
1992
+ if let Err(ActivityExecutionError::Failed(ref fail)) = la_res {
1993
+ assert_eq!(fail.is_timeout(), Some(TimeoutType::ScheduleToStart));
1994
+ assert_matches!(fail.failure_info, Some(FailureInfo::ActivityFailureInfo(_)));
1995
+ assert_matches!(
1996
+ fail.cause.as_ref().unwrap().failure_info,
1997
+ Some(FailureInfo::TimeoutFailureInfo(_))
1998
+ );
1999
+ }
2000
+ Ok(())
2001
+ }
2002
+ }
2003
+
2004
+ worker.register_workflow::<TestScheduleToStartTimeoutWf>();
2005
+ worker.register_activities(StdActivities);
1677
2006
  worker.run_until_done().await.unwrap();
1678
2007
  }
1679
2008
 
@@ -1690,8 +2019,19 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1690
2019
  // * we don't time out on s-t-s timeouts because of that, when the param is true.
1691
2020
  // * we do properly time out on s-t-c timeouts when the param is false
1692
2021
 
2022
+ let schedule_to_close_timeout = Some(if is_sched_to_start {
2023
+ Duration::from_secs(60 * 60)
2024
+ } else {
2025
+ Duration::from_secs(10 * 60)
2026
+ });
2027
+
1693
2028
  let mut t = TestHistoryBuilder::default();
1694
2029
  t.add_by_type(EventType::WorkflowExecutionStarted);
2030
+ t.set_wf_input(
2031
+ (is_sched_to_start, schedule_to_close_timeout)
2032
+ .as_json_payload()
2033
+ .unwrap(),
2034
+ );
1695
2035
  t.add_full_wf_task();
1696
2036
  let orig_sched = SystemTime::now().sub(Duration::from_secs(60 * 20));
1697
2037
  t.add_local_activity_marker(
@@ -1715,55 +2055,47 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1715
2055
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
1716
2056
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1717
2057
 
1718
- let schedule_to_close_timeout = Some(if is_sched_to_start {
1719
- // This 60 minute timeout will not have elapsed according to the original
1720
- // schedule time in the history.
1721
- Duration::from_secs(60 * 60)
1722
- } else {
1723
- // This 10 minute timeout will have already elapsed
1724
- Duration::from_secs(10 * 60)
1725
- });
1726
-
1727
- worker.register_wf(
1728
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1729
- move |ctx: WfContext| async move {
2058
+ #[workflow]
2059
+ #[derive(Default)]
2060
+ struct TestScheduleToStartTimeoutNotBasedOnOriginalTimeWf;
2061
+
2062
+ #[workflow_methods]
2063
+ impl TestScheduleToStartTimeoutNotBasedOnOriginalTimeWf {
2064
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2065
+ async fn run(
2066
+ ctx: &mut WorkflowContext<Self>,
2067
+ input: (bool, Option<Duration>),
2068
+ ) -> WorkflowResult<()> {
2069
+ let (is_sched_to_start, schedule_to_close_timeout) = input;
1730
2070
  let la_res = ctx
1731
- .local_activity(LocalActivityOptions {
1732
- activity_type: "echo".to_string(),
1733
- input: "hi".as_json_payload().expect("serializes fine"),
1734
- retry_policy: RetryPolicy {
1735
- initial_interval: Some(prost_dur!(from_millis(50))),
1736
- backoff_coefficient: 1.2,
1737
- maximum_interval: None,
1738
- maximum_attempts: 5,
1739
- non_retryable_error_types: vec![],
2071
+ .start_local_activity(
2072
+ StdActivities::echo,
2073
+ "hi".to_string(),
2074
+ LocalActivityOptions {
2075
+ retry_policy: RetryPolicy {
2076
+ initial_interval: Some(prost_dur!(from_millis(50))),
2077
+ backoff_coefficient: 1.2,
2078
+ maximum_interval: None,
2079
+ maximum_attempts: 5,
2080
+ non_retryable_error_types: vec![],
2081
+ },
2082
+ schedule_to_start_timeout: Some(Duration::from_secs(60)),
2083
+ schedule_to_close_timeout,
2084
+ ..Default::default()
1740
2085
  },
1741
- schedule_to_start_timeout: Some(Duration::from_secs(60)),
1742
- schedule_to_close_timeout,
1743
- ..Default::default()
1744
- })
2086
+ )
1745
2087
  .await;
1746
2088
  if is_sched_to_start {
1747
- assert!(la_res.completed_ok());
1748
- } else {
1749
- assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToClose));
2089
+ assert!(la_res.is_ok());
2090
+ } else if let Err(ActivityExecutionError::Failed(ref fail)) = la_res {
2091
+ assert_eq!(fail.is_timeout(), Some(TimeoutType::ScheduleToClose));
1750
2092
  }
1751
- Ok(().into())
1752
- },
1753
- );
1754
- worker.register_activity(
1755
- "echo",
1756
- move |_ctx: ActContext, _: String| async move { Ok(()) },
1757
- );
1758
- worker
1759
- .submit_wf(
1760
- wf_id.to_owned(),
1761
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1762
- vec![],
1763
- WorkflowOptions::default(),
1764
- )
1765
- .await
1766
- .unwrap();
2093
+ Ok(())
2094
+ }
2095
+ }
2096
+
2097
+ worker.register_workflow::<TestScheduleToStartTimeoutNotBasedOnOriginalTimeWf>();
2098
+ worker.register_activities(StdActivities);
1767
2099
  worker.run_until_done().await.unwrap();
1768
2100
  }
1769
2101
 
@@ -1772,6 +2104,7 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1772
2104
  async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_completes: bool) {
1773
2105
  let mut t = TestHistoryBuilder::default();
1774
2106
  t.add_by_type(EventType::WorkflowExecutionStarted);
2107
+ t.set_wf_input(la_completes.as_json_payload().unwrap());
1775
2108
  t.add_full_wf_task();
1776
2109
  if la_completes {
1777
2110
  t.add_local_activity_marker(1, "1", Some("hi".into()), None, |_| {});
@@ -1797,61 +2130,73 @@ async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_complet
1797
2130
  );
1798
2131
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1799
2132
 
1800
- worker.register_wf(
1801
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1802
- move |ctx: WfContext| async move {
2133
+ #[workflow]
2134
+ #[derive(Default)]
2135
+ struct StartToCloseTimeoutAllowsRetriesWf;
2136
+
2137
+ #[workflow_methods]
2138
+ impl StartToCloseTimeoutAllowsRetriesWf {
2139
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2140
+ async fn run(ctx: &mut WorkflowContext<Self>, la_completes: bool) -> WorkflowResult<()> {
1803
2141
  let la_res = ctx
1804
- .local_activity(LocalActivityOptions {
1805
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1806
- input: "hi".as_json_payload().expect("serializes fine"),
1807
- retry_policy: RetryPolicy {
1808
- initial_interval: Some(prost_dur!(from_millis(20))),
1809
- backoff_coefficient: 1.0,
1810
- maximum_interval: None,
1811
- maximum_attempts: 5,
1812
- non_retryable_error_types: vec![],
2142
+ .start_local_activity(
2143
+ ActivityWithRetriesAndCancellation::go,
2144
+ (),
2145
+ LocalActivityOptions {
2146
+ retry_policy: RetryPolicy {
2147
+ initial_interval: Some(prost_dur!(from_millis(20))),
2148
+ backoff_coefficient: 1.0,
2149
+ maximum_interval: None,
2150
+ maximum_attempts: 5,
2151
+ non_retryable_error_types: vec![],
2152
+ },
2153
+ start_to_close_timeout: Some(prost_dur!(from_millis(25))),
2154
+ ..Default::default()
1813
2155
  },
1814
- start_to_close_timeout: Some(prost_dur!(from_millis(25))),
1815
- ..Default::default()
1816
- })
2156
+ )
1817
2157
  .await;
1818
2158
  if la_completes {
1819
- assert!(la_res.completed_ok());
1820
- } else {
1821
- assert_eq!(la_res.timed_out(), Some(TimeoutType::StartToClose));
2159
+ assert!(la_res.is_ok(), "Result should be ok was {la_res:?}");
2160
+ } else if let Err(ActivityExecutionError::Failed(ref fail)) = la_res {
2161
+ assert_eq!(fail.is_timeout(), Some(TimeoutType::StartToClose));
1822
2162
  }
1823
- Ok(().into())
1824
- },
1825
- );
1826
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1827
- let cancels: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1828
- worker.register_activity(
1829
- DEFAULT_ACTIVITY_TYPE,
1830
- move |ctx: ActContext, _: String| async move {
2163
+ Ok(())
2164
+ }
2165
+ }
2166
+
2167
+ worker.register_workflow::<StartToCloseTimeoutAllowsRetriesWf>();
2168
+ let attempts = Arc::new(AtomicUsize::new(0));
2169
+ let cancels = Arc::new(AtomicUsize::new(0));
2170
+
2171
+ struct ActivityWithRetriesAndCancellation {
2172
+ attempts: Arc<AtomicUsize>,
2173
+ cancels: Arc<AtomicUsize>,
2174
+ la_completes: bool,
2175
+ }
2176
+ #[activities]
2177
+ impl ActivityWithRetriesAndCancellation {
2178
+ #[activity(name = DEFAULT_ACTIVITY_TYPE)]
2179
+ async fn go(self: Arc<Self>, ctx: ActivityContext) -> Result<RawValue, ActivityError> {
1831
2180
  // Timeout the first 4 attempts, or all of them if we intend to fail
1832
- if attempts.fetch_add(1, Ordering::AcqRel) < 4 || !la_completes {
2181
+ if self.attempts.fetch_add(1, Ordering::AcqRel) < 4 || !self.la_completes {
1833
2182
  select! {
1834
2183
  _ = tokio::time::sleep(Duration::from_millis(100)) => (),
1835
2184
  _ = ctx.cancelled() => {
1836
- cancels.fetch_add(1, Ordering::AcqRel);
2185
+ self.cancels.fetch_add(1, Ordering::AcqRel);
1837
2186
  return Err(ActivityError::cancelled());
1838
2187
  }
1839
2188
  }
1840
2189
  }
1841
- Ok(())
1842
- },
1843
- );
1844
- worker
1845
- .submit_wf(
1846
- wf_id.to_owned(),
1847
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1848
- vec![],
1849
- WorkflowOptions::default(),
1850
- )
1851
- .await
1852
- .unwrap();
2190
+ Ok(RawValue::empty())
2191
+ }
2192
+ }
2193
+
2194
+ worker.register_activities(ActivityWithRetriesAndCancellation {
2195
+ attempts: attempts.clone(),
2196
+ cancels: cancels.clone(),
2197
+ la_completes,
2198
+ });
1853
2199
  worker.run_until_done().await.unwrap();
1854
- // Activity should have been attempted all 5 times
1855
2200
  assert_eq!(attempts.load(Ordering::Acquire), 5);
1856
2201
  let num_cancels = if la_completes { 4 } else { 5 };
1857
2202
  assert_eq!(cancels.load(Ordering::Acquire), num_cancels);
@@ -1872,43 +2217,46 @@ async fn wft_failure_cancels_running_las() {
1872
2217
  mh.num_expected_fails = 1;
1873
2218
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1874
2219
 
1875
- worker.register_wf(
1876
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1877
- |ctx: WfContext| async move {
1878
- let la_handle = ctx.local_activity(LocalActivityOptions {
1879
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1880
- input: "hi".as_json_payload().expect("serializes fine"),
1881
- ..Default::default()
1882
- });
1883
- tokio::join!(
2220
+ #[workflow]
2221
+ #[derive(Default)]
2222
+ struct WftFailureCancelsRunningLasWf;
2223
+
2224
+ #[workflow_methods]
2225
+ impl WftFailureCancelsRunningLasWf {
2226
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2227
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2228
+ let la_handle = ctx.start_local_activity(
2229
+ ActivityThatExpectsCancellation::go,
2230
+ (),
2231
+ Default::default(),
2232
+ );
2233
+ temporalio_sdk::workflows::join!(
1884
2234
  async {
1885
2235
  ctx.timer(Duration::from_secs(1)).await;
1886
2236
  panic!("ahhh I'm failing wft")
1887
2237
  },
1888
2238
  la_handle
1889
2239
  );
1890
- Ok(().into())
1891
- },
1892
- );
1893
- worker.register_activity(
1894
- DEFAULT_ACTIVITY_TYPE,
1895
- move |ctx: ActContext, _: String| async move {
2240
+ Ok(())
2241
+ }
2242
+ }
2243
+
2244
+ worker.register_workflow::<WftFailureCancelsRunningLasWf>();
2245
+
2246
+ struct ActivityThatExpectsCancellation;
2247
+ #[activities]
2248
+ impl ActivityThatExpectsCancellation {
2249
+ #[activity]
2250
+ async fn go(ctx: ActivityContext) -> Result<(), ActivityError> {
1896
2251
  let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
1897
2252
  if res.is_err() {
1898
2253
  panic!("Activity must be cancelled!!!!");
1899
2254
  }
1900
- Result::<(), _>::Err(ActivityError::cancelled())
1901
- },
1902
- );
1903
- worker
1904
- .submit_wf(
1905
- wf_id.to_owned(),
1906
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1907
- vec![],
1908
- WorkflowOptions::default(),
1909
- )
1910
- .await
1911
- .unwrap();
2255
+ Err(ActivityError::cancelled())
2256
+ }
2257
+ }
2258
+
2259
+ worker.register_activities(ActivityThatExpectsCancellation);
1912
2260
  worker.run_until_done().await.unwrap();
1913
2261
  }
1914
2262
 
@@ -1938,32 +2286,30 @@ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
1938
2286
  mh.num_expected_completions = Some(0.into());
1939
2287
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1940
2288
 
1941
- #[allow(unreachable_code)]
1942
- worker.register_wf(
1943
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1944
- WorkflowFunction::new::<_, _, ()>(|ctx: WfContext| async move {
1945
- ctx.local_activity(LocalActivityOptions {
1946
- activity_type: "echo".to_string(),
1947
- input: "hi".as_json_payload().expect("serializes fine"),
1948
- ..Default::default()
1949
- })
1950
- .await;
2289
+ #[workflow]
2290
+ #[derive(Default)]
2291
+ struct ResolvedLasNotRecordedIfWftFailsManyTimesWf;
2292
+
2293
+ #[workflow_methods]
2294
+ impl ResolvedLasNotRecordedIfWftFailsManyTimesWf {
2295
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2296
+ #[allow(unreachable_code)]
2297
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2298
+ ctx.start_local_activity(
2299
+ StdActivities::echo,
2300
+ "hi".to_string(),
2301
+ LocalActivityOptions {
2302
+ ..Default::default()
2303
+ },
2304
+ )
2305
+ .await
2306
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
1951
2307
  panic!()
1952
- }),
1953
- );
1954
- worker.register_activity(
1955
- "echo",
1956
- move |_: ActContext, _: String| async move { Ok(()) },
1957
- );
1958
- worker
1959
- .submit_wf(
1960
- wf_id.to_owned(),
1961
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1962
- vec![],
1963
- WorkflowOptions::default(),
1964
- )
1965
- .await
1966
- .unwrap();
2308
+ }
2309
+ }
2310
+
2311
+ worker.register_workflow::<ResolvedLasNotRecordedIfWftFailsManyTimesWf>();
2312
+ worker.register_activities(StdActivities);
1967
2313
  worker.run_until_done().await.unwrap();
1968
2314
  }
1969
2315
 
@@ -1994,39 +2340,37 @@ async fn local_act_records_nonfirst_attempts_ok() {
1994
2340
  wc.max_outstanding_workflow_tasks = Some(1);
1995
2341
  });
1996
2342
 
1997
- worker.register_wf(
1998
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1999
- |ctx: WfContext| async move {
2000
- ctx.local_activity(LocalActivityOptions {
2001
- activity_type: "echo".to_string(),
2002
- input: "hi".as_json_payload().expect("serializes fine"),
2003
- retry_policy: RetryPolicy {
2004
- initial_interval: Some(prost_dur!(from_millis(10))),
2005
- backoff_coefficient: 1.0,
2006
- maximum_interval: None,
2007
- maximum_attempts: 0,
2008
- non_retryable_error_types: vec![],
2343
+ #[workflow]
2344
+ #[derive(Default)]
2345
+ struct LocalActRecordsNonfirstAttemptsOkWf;
2346
+
2347
+ #[workflow_methods]
2348
+ impl LocalActRecordsNonfirstAttemptsOkWf {
2349
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2350
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2351
+ ctx.start_local_activity(
2352
+ StdActivities::always_fail,
2353
+ (),
2354
+ LocalActivityOptions {
2355
+ retry_policy: RetryPolicy {
2356
+ initial_interval: Some(prost_dur!(from_millis(10))),
2357
+ backoff_coefficient: 1.0,
2358
+ maximum_interval: None,
2359
+ maximum_attempts: 0,
2360
+ non_retryable_error_types: vec![],
2361
+ },
2362
+ ..Default::default()
2009
2363
  },
2010
- ..Default::default()
2011
- })
2012
- .await;
2013
- Ok(().into())
2014
- },
2015
- );
2016
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
2017
- Result::<(), _>::Err(anyhow!("I fail").into())
2018
- });
2019
- worker
2020
- .submit_wf(
2021
- wf_id.to_owned(),
2022
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2023
- vec![],
2024
- WorkflowOptions::default(),
2025
- )
2026
- .await
2027
- .unwrap();
2364
+ )
2365
+ .await
2366
+ .ok();
2367
+ Ok(())
2368
+ }
2369
+ }
2370
+
2371
+ worker.register_workflow::<LocalActRecordsNonfirstAttemptsOkWf>();
2372
+ worker.register_activities(StdActivities);
2028
2373
  worker.run_until_done().await.unwrap();
2029
- // 3 workflow tasks
2030
2374
  assert_eq!(nonfirst_counts.len(), 3);
2031
2375
  // First task's non-first count should, of course, be 0
2032
2376
  assert_eq!(nonfirst_counts.pop().unwrap(), 0);
@@ -2312,70 +2656,92 @@ async fn local_act_retry_explicit_delay() {
2312
2656
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
2313
2657
  let mut worker = mock_sdk(mh);
2314
2658
 
2315
- worker.register_wf(
2316
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2317
- move |ctx: WfContext| async move {
2659
+ #[workflow]
2660
+ #[derive(Default)]
2661
+ struct LocalActRetryExplicitDelayWf;
2662
+
2663
+ #[workflow_methods]
2664
+ impl LocalActRetryExplicitDelayWf {
2665
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2666
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2318
2667
  let la_res = ctx
2319
- .local_activity(LocalActivityOptions {
2320
- activity_type: "echo".to_string(),
2321
- input: "hi".as_json_payload().expect("serializes fine"),
2322
- retry_policy: RetryPolicy {
2323
- initial_interval: Some(prost_dur!(from_millis(50))),
2324
- backoff_coefficient: 1.0,
2325
- maximum_attempts: 5,
2668
+ .start_local_activity(
2669
+ ActivityWithExplicitBackoff::go,
2670
+ (),
2671
+ LocalActivityOptions {
2672
+ retry_policy: RetryPolicy {
2673
+ initial_interval: Some(prost_dur!(from_millis(50))),
2674
+ backoff_coefficient: 1.0,
2675
+ maximum_attempts: 5,
2676
+ ..Default::default()
2677
+ },
2326
2678
  ..Default::default()
2327
2679
  },
2328
- ..Default::default()
2329
- })
2680
+ )
2330
2681
  .await;
2331
- assert!(la_res.completed_ok());
2332
- Ok(().into())
2333
- },
2334
- );
2335
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
2336
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
2337
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
2338
- let last_attempt = attempts.fetch_add(1, Ordering::Relaxed);
2339
- if 0 == last_attempt {
2340
- Err(ActivityError::Retryable {
2341
- source: anyhow!("Explicit backoff error"),
2342
- explicit_delay: Some(Duration::from_millis(300)),
2343
- })
2344
- } else if 2 == last_attempt {
2682
+ assert!(la_res.is_ok());
2345
2683
  Ok(())
2346
- } else {
2347
- Err(anyhow!("Oh no I failed!").into())
2348
2684
  }
2685
+ }
2686
+
2687
+ worker.register_workflow::<LocalActRetryExplicitDelayWf>();
2688
+ let attempts = Arc::new(AtomicUsize::new(0));
2689
+
2690
+ struct ActivityWithExplicitBackoff {
2691
+ attempts: Arc<AtomicUsize>,
2692
+ }
2693
+ #[activities]
2694
+ impl ActivityWithExplicitBackoff {
2695
+ #[activity]
2696
+ async fn go(self: Arc<Self>, _: ActivityContext) -> Result<(), ActivityError> {
2697
+ // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
2698
+ let last_attempt = self.attempts.fetch_add(1, Ordering::Relaxed);
2699
+ if 0 == last_attempt {
2700
+ Err(ActivityError::Retryable {
2701
+ source: anyhow!("Explicit backoff error").into_boxed_dyn_error(),
2702
+ explicit_delay: Some(Duration::from_millis(300)),
2703
+ })
2704
+ } else if 2 == last_attempt {
2705
+ Ok(())
2706
+ } else {
2707
+ Err(anyhow!("Oh no I failed!").into())
2708
+ }
2709
+ }
2710
+ }
2711
+
2712
+ worker.register_activities(ActivityWithExplicitBackoff {
2713
+ attempts: attempts.clone(),
2349
2714
  });
2350
- worker
2351
- .submit_wf(
2352
- wf_id.to_owned(),
2353
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2354
- vec![],
2355
- WorkflowOptions::default(),
2356
- )
2357
- .await
2358
- .unwrap();
2359
2715
  let start = Instant::now();
2360
2716
  worker.run_until_done().await.unwrap();
2361
2717
  let expected_attempts = 3;
2362
2718
  assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
2363
- // There will be one 300ms backoff and one 50s backoff, so things should take at least that long
2364
2719
  assert!(start.elapsed() > Duration::from_millis(350));
2365
2720
  }
2366
2721
 
2367
- async fn la_wf(ctx: WfContext) -> WorkflowResult<()> {
2368
- ctx.local_activity(LocalActivityOptions {
2369
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2370
- input: ().as_json_payload().unwrap(),
2371
- retry_policy: RetryPolicy {
2372
- maximum_attempts: 1,
2373
- ..Default::default()
2374
- },
2375
- ..Default::default()
2376
- })
2377
- .await;
2378
- Ok(().into())
2722
+ #[workflow]
2723
+ #[derive(Default)]
2724
+ struct LaWf;
2725
+
2726
+ #[workflow_methods]
2727
+ impl LaWf {
2728
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2729
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2730
+ let _ = ctx
2731
+ .start_local_activity(
2732
+ StdActivities::default,
2733
+ (),
2734
+ LocalActivityOptions {
2735
+ retry_policy: RetryPolicy {
2736
+ maximum_attempts: 1,
2737
+ ..Default::default()
2738
+ },
2739
+ ..Default::default()
2740
+ },
2741
+ )
2742
+ .await;
2743
+ Ok(())
2744
+ }
2379
2745
  }
2380
2746
 
2381
2747
  #[rstest]
@@ -2444,53 +2810,81 @@ async fn one_la_success(#[case] replay: bool, #[case] completes_ok: bool) {
2444
2810
  });
2445
2811
 
2446
2812
  let mut worker = build_fake_sdk(mock_cfg);
2447
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
2448
- worker.register_activity(
2449
- DEFAULT_ACTIVITY_TYPE,
2450
- move |_ctx: ActContext, _: ()| async move {
2451
- if replay {
2813
+ worker.register_workflow::<LaWf>();
2814
+
2815
+ struct ActivityWithReplayCheck {
2816
+ replay: bool,
2817
+ completes_ok: bool,
2818
+ }
2819
+ #[activities]
2820
+ impl ActivityWithReplayCheck {
2821
+ #[activity(name = DEFAULT_ACTIVITY_TYPE)]
2822
+ #[allow(unused)]
2823
+ async fn echo(
2824
+ self: Arc<Self>,
2825
+ _: ActivityContext,
2826
+ _: RawValue,
2827
+ ) -> Result<String, ActivityError> {
2828
+ if self.replay {
2452
2829
  panic!("Should not be invoked on replay");
2453
2830
  }
2454
- if completes_ok {
2455
- Ok("hi")
2831
+ if self.completes_ok {
2832
+ Ok("hi".to_string())
2456
2833
  } else {
2457
2834
  Err(anyhow!("Oh no I failed!").into())
2458
2835
  }
2459
- },
2460
- );
2836
+ }
2837
+ }
2838
+
2839
+ worker.register_activities(ActivityWithReplayCheck {
2840
+ replay,
2841
+ completes_ok,
2842
+ });
2461
2843
  worker.run().await.unwrap();
2462
2844
  }
2463
2845
 
2464
- async fn two_la_wf(ctx: WfContext) -> WorkflowResult<()> {
2465
- ctx.local_activity(LocalActivityOptions {
2466
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2467
- input: ().as_json_payload().unwrap(),
2468
- ..Default::default()
2469
- })
2470
- .await;
2471
- ctx.local_activity(LocalActivityOptions {
2472
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2473
- input: ().as_json_payload().unwrap(),
2474
- ..Default::default()
2475
- })
2476
- .await;
2477
- Ok(().into())
2846
+ #[workflow]
2847
+ #[derive(Default)]
2848
+ struct TwoLaWf;
2849
+
2850
+ #[workflow_methods]
2851
+ impl TwoLaWf {
2852
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2853
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2854
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
2855
+ .await
2856
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
2857
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
2858
+ .await
2859
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
2860
+ Ok(())
2861
+ }
2478
2862
  }
2479
2863
 
2480
- async fn two_la_wf_parallel(ctx: WfContext) -> WorkflowResult<()> {
2481
- tokio::join!(
2482
- ctx.local_activity(LocalActivityOptions {
2483
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2484
- input: ().as_json_payload().unwrap(),
2485
- ..Default::default()
2486
- }),
2487
- ctx.local_activity(LocalActivityOptions {
2488
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2489
- input: ().as_json_payload().unwrap(),
2490
- ..Default::default()
2491
- })
2492
- );
2493
- Ok(().into())
2864
+ #[workflow]
2865
+ #[derive(Default)]
2866
+ struct TwoLaWfParallel;
2867
+
2868
+ #[workflow_methods]
2869
+ impl TwoLaWfParallel {
2870
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2871
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2872
+ let _ = temporalio_sdk::workflows::join!(
2873
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default()),
2874
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
2875
+ );
2876
+ Ok(())
2877
+ }
2878
+ }
2879
+
2880
+ struct ResolvedActivity;
2881
+ #[activities]
2882
+ impl ResolvedActivity {
2883
+ #[allow(unused)]
2884
+ #[activity(name = DEFAULT_ACTIVITY_TYPE)]
2885
+ async fn echo(_: ActivityContext, _: ()) -> Result<String, ActivityError> {
2886
+ Ok("Resolved".to_string())
2887
+ }
2494
2888
  }
2495
2889
 
2496
2890
  #[rstest]
@@ -2578,32 +2972,31 @@ async fn two_sequential_las(
2578
2972
  let mut worker = build_fake_sdk(mock_cfg);
2579
2973
  worker.set_worker_interceptor(aai);
2580
2974
  if parallel {
2581
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf_parallel);
2975
+ worker.register_workflow::<TwoLaWfParallel>();
2582
2976
  } else {
2583
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf);
2977
+ worker.register_workflow::<TwoLaWf>();
2584
2978
  }
2585
- worker.register_activity(
2586
- DEFAULT_ACTIVITY_TYPE,
2587
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2588
- );
2979
+ worker.register_activities(ResolvedActivity);
2589
2980
  worker.run().await.unwrap();
2590
2981
  }
2591
2982
 
2592
- async fn la_timer_la(ctx: WfContext) -> WorkflowResult<()> {
2593
- ctx.local_activity(LocalActivityOptions {
2594
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2595
- input: ().as_json_payload().unwrap(),
2596
- ..Default::default()
2597
- })
2598
- .await;
2599
- ctx.timer(Duration::from_secs(5)).await;
2600
- ctx.local_activity(LocalActivityOptions {
2601
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2602
- input: ().as_json_payload().unwrap(),
2603
- ..Default::default()
2604
- })
2605
- .await;
2606
- Ok(().into())
2983
+ #[workflow]
2984
+ #[derive(Default)]
2985
+ struct LaTimerLaWf;
2986
+
2987
+ #[workflow_methods]
2988
+ impl LaTimerLaWf {
2989
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
2990
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
2991
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
2992
+ .await
2993
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
2994
+ ctx.timer(Duration::from_secs(5)).await;
2995
+ ctx.start_local_activity(StdActivities::default, (), LocalActivityOptions::default())
2996
+ .await
2997
+ .map_err(|e| WorkflowTermination::from(anyhow::Error::from(e)))?;
2998
+ Ok(())
2999
+ }
2607
3000
  }
2608
3001
 
2609
3002
  #[rstest]
@@ -2676,11 +3069,8 @@ async fn las_separated_by_timer(#[case] replay: bool) {
2676
3069
 
2677
3070
  let mut worker = build_fake_sdk(mock_cfg);
2678
3071
  worker.set_worker_interceptor(aai);
2679
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_timer_la);
2680
- worker.register_activity(
2681
- DEFAULT_ACTIVITY_TYPE,
2682
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2683
- );
3072
+ worker.register_workflow::<LaTimerLaWf>();
3073
+ worker.register_activities(ResolvedActivity);
2684
3074
  worker.run().await.unwrap();
2685
3075
  }
2686
3076
 
@@ -2711,11 +3101,8 @@ async fn one_la_heartbeating_wft_failure_still_executes() {
2711
3101
  });
2712
3102
 
2713
3103
  let mut worker = build_fake_sdk(mock_cfg);
2714
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
2715
- worker.register_activity(
2716
- DEFAULT_ACTIVITY_TYPE,
2717
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2718
- );
3104
+ worker.register_workflow::<LaWf>();
3105
+ worker.register_activities(ResolvedActivity);
2719
3106
  worker.run().await.unwrap();
2720
3107
  }
2721
3108
 
@@ -2731,6 +3118,7 @@ async fn immediate_cancel(
2731
3118
  ) {
2732
3119
  let mut t = TestHistoryBuilder::default();
2733
3120
  t.add_by_type(EventType::WorkflowExecutionStarted);
3121
+ t.set_wf_input(cancel_type.as_json_payload().unwrap());
2734
3122
  t.add_full_wf_task();
2735
3123
  t.add_workflow_execution_completed();
2736
3124
 
@@ -2748,16 +3136,33 @@ async fn immediate_cancel(
2748
3136
  });
2749
3137
 
2750
3138
  let mut worker = build_fake_sdk(mock_cfg);
2751
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
2752
- let la = ctx.local_activity(LocalActivityOptions {
2753
- cancel_type,
2754
- ..Default::default()
2755
- });
2756
- la.cancel(&ctx);
2757
- la.await;
2758
- Ok(().into())
2759
- });
2760
- // Explicitly don't register an activity, since we shouldn't need to run one.
3139
+
3140
+ #[workflow]
3141
+ #[derive(Default)]
3142
+ struct CancelBeforeActStartsWf;
3143
+
3144
+ #[workflow_methods]
3145
+ impl CancelBeforeActStartsWf {
3146
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
3147
+ async fn run(
3148
+ ctx: &mut WorkflowContext<Self>,
3149
+ cancel_type: ActivityCancellationType,
3150
+ ) -> WorkflowResult<()> {
3151
+ let la = ctx.start_local_activity(
3152
+ StdActivities::default,
3153
+ (),
3154
+ LocalActivityOptions {
3155
+ cancel_type,
3156
+ ..Default::default()
3157
+ },
3158
+ );
3159
+ la.cancel();
3160
+ la.await.ok();
3161
+ Ok(())
3162
+ }
3163
+ }
3164
+
3165
+ worker.register_workflow::<CancelBeforeActStartsWf>();
2761
3166
  worker.run().await.unwrap();
2762
3167
  }
2763
3168
 
@@ -2776,6 +3181,7 @@ async fn cancel_after_act_starts_canned(
2776
3181
  ) {
2777
3182
  let mut t = TestHistoryBuilder::default();
2778
3183
  t.add_wfe_started_with_wft_timeout(Duration::from_millis(100));
3184
+ t.set_wf_input(cancel_type.as_json_payload().unwrap());
2779
3185
  t.add_full_wf_task();
2780
3186
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
2781
3187
  t.add_timer_fired(timer_started_event_id, "1".to_string());
@@ -2845,40 +3251,69 @@ async fn cancel_after_act_starts_canned(
2845
3251
  }
2846
3252
 
2847
3253
  let mut worker = build_fake_sdk(mock_cfg);
2848
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
2849
- let la = ctx.local_activity(LocalActivityOptions {
2850
- cancel_type,
2851
- input: ().as_json_payload().unwrap(),
2852
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2853
- ..Default::default()
2854
- });
2855
- ctx.timer(Duration::from_secs(1)).await;
2856
- la.cancel(&ctx);
2857
- // This extra timer is here to ensure the presence of another WF task doesn't mess up
2858
- // resolving the LA with cancel on replay
2859
- ctx.timer(Duration::from_secs(1)).await;
2860
- let resolution = la.await;
2861
- assert!(resolution.cancelled());
2862
- let rfail = resolution.unwrap_failure();
2863
- assert_matches!(
2864
- rfail.failure_info,
2865
- Some(FailureInfo::ActivityFailureInfo(_))
2866
- );
2867
- assert_matches!(
2868
- rfail.cause.unwrap().failure_info,
2869
- Some(FailureInfo::CanceledFailureInfo(_))
2870
- );
2871
- Ok(().into())
2872
- });
2873
- worker.register_activity(DEFAULT_ACTIVITY_TYPE, move |ctx: ActContext, _: ()| {
2874
- let allow_cancel_barr_clone = allow_cancel_barr_clone.clone();
2875
- async move {
2876
- if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
3254
+
3255
+ #[workflow]
3256
+ #[derive(Default)]
3257
+ struct CancelAfterActStartsCannedWf;
3258
+
3259
+ #[workflow_methods]
3260
+ impl CancelAfterActStartsCannedWf {
3261
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
3262
+ async fn run(
3263
+ ctx: &mut WorkflowContext<Self>,
3264
+ cancel_type: ActivityCancellationType,
3265
+ ) -> WorkflowResult<()> {
3266
+ let la = ctx.start_local_activity(
3267
+ ActivityWithConditionalCancelWait::echo,
3268
+ (),
3269
+ LocalActivityOptions {
3270
+ cancel_type,
3271
+ ..Default::default()
3272
+ },
3273
+ );
3274
+ ctx.timer(Duration::from_secs(1)).await;
3275
+ la.cancel();
3276
+ ctx.timer(Duration::from_secs(1)).await;
3277
+ let resolution = la.await;
3278
+ assert!(matches!(
3279
+ resolution,
3280
+ Err(ActivityExecutionError::Cancelled(_))
3281
+ ));
3282
+ if let Err(ActivityExecutionError::Cancelled(rfail)) = resolution {
3283
+ assert_matches!(
3284
+ rfail.failure_info,
3285
+ Some(FailureInfo::ActivityFailureInfo(_))
3286
+ );
3287
+ assert_matches!(
3288
+ rfail.cause.unwrap().failure_info,
3289
+ Some(FailureInfo::CanceledFailureInfo(_))
3290
+ );
3291
+ }
3292
+ Ok(())
3293
+ }
3294
+ }
3295
+
3296
+ worker.register_workflow::<CancelAfterActStartsCannedWf>();
3297
+
3298
+ struct ActivityWithConditionalCancelWait {
3299
+ cancel_type: ActivityCancellationType,
3300
+ allow_cancel_barr: CancellationToken,
3301
+ }
3302
+ #[activities]
3303
+ impl ActivityWithConditionalCancelWait {
3304
+ #[activity(name = DEFAULT_ACTIVITY_TYPE)]
3305
+ async fn echo(self: Arc<Self>, ctx: ActivityContext, _: ()) -> Result<(), ActivityError> {
3306
+ if self.cancel_type == ActivityCancellationType::WaitCancellationCompleted {
2877
3307
  ctx.cancelled().await;
2878
3308
  }
2879
- allow_cancel_barr_clone.cancelled().await;
2880
- Result::<(), _>::Err(ActivityError::cancelled())
3309
+ self.allow_cancel_barr.cancelled().await;
3310
+ Err(ActivityError::cancelled())
2881
3311
  }
3312
+ }
3313
+
3314
+ worker.register_activities(ActivityWithConditionalCancelWait {
3315
+ cancel_type,
3316
+ allow_cancel_barr: allow_cancel_barr_clone,
2882
3317
  });
2883
3318
  worker.run().await.unwrap();
2884
3319
  }