@temporalio/core-bridge 1.15.0 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/Cargo.lock +172 -70
  2. package/lib/native.d.ts +1 -1
  3. package/package.json +2 -2
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  6. package/releases/x86_64-apple-darwin/index.node +0 -0
  7. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  8. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  9. package/sdk-core/.github/workflows/per-pr.yml +6 -6
  10. package/sdk-core/AGENTS.md +41 -30
  11. package/sdk-core/Cargo.toml +3 -0
  12. package/sdk-core/README.md +15 -9
  13. package/sdk-core/crates/client/Cargo.toml +4 -0
  14. package/sdk-core/crates/client/README.md +139 -0
  15. package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
  16. package/sdk-core/crates/client/src/callback_based.rs +7 -0
  17. package/sdk-core/crates/client/src/errors.rs +294 -0
  18. package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +280 -159
  19. package/sdk-core/crates/client/src/lib.rs +920 -1326
  20. package/sdk-core/crates/client/src/metrics.rs +24 -33
  21. package/sdk-core/crates/client/src/options_structs.rs +457 -0
  22. package/sdk-core/crates/client/src/replaceable.rs +5 -4
  23. package/sdk-core/crates/client/src/request_extensions.rs +8 -9
  24. package/sdk-core/crates/client/src/retry.rs +99 -54
  25. package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +1 -1
  26. package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
  27. package/sdk-core/crates/common/Cargo.toml +61 -2
  28. package/sdk-core/crates/common/build.rs +742 -12
  29. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
  30. package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
  31. package/sdk-core/crates/common/protos/api_upstream/buf.yaml +0 -3
  32. package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
  33. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +1166 -770
  34. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +1243 -750
  35. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +2 -2
  36. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -3
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +4 -0
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +16 -1
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -6
  42. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +88 -33
  43. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +4 -2
  44. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  45. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +5 -5
  46. package/sdk-core/crates/common/src/activity_definition.rs +20 -0
  47. package/sdk-core/crates/common/src/data_converters.rs +770 -0
  48. package/sdk-core/crates/common/src/envconfig.rs +5 -0
  49. package/sdk-core/crates/common/src/lib.rs +15 -211
  50. package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
  51. package/sdk-core/crates/common/src/priority.rs +110 -0
  52. package/sdk-core/crates/common/src/protos/canned_histories.rs +3 -0
  53. package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
  54. package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
  55. package/sdk-core/crates/common/src/protos/mod.rs +122 -27
  56. package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
  57. package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
  58. package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +5 -7
  59. package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
  60. package/sdk-core/crates/common/src/telemetry/metrics.rs +268 -223
  61. package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
  62. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
  63. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
  64. package/sdk-core/crates/common/src/telemetry.rs +264 -4
  65. package/sdk-core/crates/common/src/worker.rs +68 -603
  66. package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
  67. package/sdk-core/crates/macros/Cargo.toml +5 -1
  68. package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
  69. package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
  70. package/sdk-core/crates/macros/src/lib.rs +138 -512
  71. package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
  72. package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
  73. package/sdk-core/crates/sdk/Cargo.toml +19 -6
  74. package/sdk-core/crates/sdk/README.md +415 -0
  75. package/sdk-core/crates/sdk/src/activities.rs +417 -0
  76. package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
  77. package/sdk-core/crates/sdk/src/lib.rs +757 -442
  78. package/sdk-core/crates/sdk/src/workflow_context/options.rs +45 -35
  79. package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
  80. package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
  81. package/sdk-core/crates/sdk/src/workflows.rs +711 -0
  82. package/sdk-core/crates/sdk-core/Cargo.toml +57 -64
  83. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +41 -35
  84. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
  85. package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
  86. package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
  87. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +13 -15
  88. package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
  89. package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
  90. package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
  91. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +493 -26
  92. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +4 -8
  93. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +7 -7
  94. package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
  95. package/sdk-core/crates/sdk-core/src/lib.rs +41 -111
  96. package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
  97. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +118 -19
  98. package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
  99. package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
  100. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +179 -196
  101. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -280
  102. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +6 -9
  103. package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
  104. package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
  105. package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
  106. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +9 -5
  107. package/sdk-core/crates/sdk-core/src/worker/client.rs +103 -81
  108. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +7 -11
  109. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1124 -229
  110. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +145 -23
  111. package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
  112. package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
  113. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +13 -13
  114. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +28 -8
  115. package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
  116. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
  117. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +19 -4
  118. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +14 -18
  119. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +4 -6
  120. package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
  121. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
  122. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
  123. package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
  124. package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
  125. package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
  126. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
  127. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
  128. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
  129. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
  130. package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
  131. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
  132. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
  133. package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
  134. package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
  135. package/sdk-core/crates/sdk-core/tests/common/mod.rs +241 -196
  136. package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
  137. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +3 -5
  138. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -64
  139. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +298 -252
  140. package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
  141. package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
  142. package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
  143. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +16 -12
  144. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +48 -40
  145. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +327 -255
  146. package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
  147. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +147 -126
  148. package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
  149. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -453
  150. package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
  151. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +360 -231
  152. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +248 -185
  153. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -43
  154. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
  155. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +428 -315
  156. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -56
  157. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -28
  158. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -243
  159. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
  160. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +101 -42
  161. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -147
  162. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -28
  163. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1036
  164. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -41
  165. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +397 -238
  166. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +414 -189
  167. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
  168. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
  169. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +154 -137
  170. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -105
  171. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -38
  172. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -40
  173. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -54
  174. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +363 -226
  175. package/sdk-core/crates/sdk-core/tests/main.rs +17 -15
  176. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +207 -152
  177. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +65 -34
  178. package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
  179. package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
  180. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
  181. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
  182. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
  183. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
  184. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
  185. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
  186. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
  187. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
  188. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +7 -1
  189. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +14 -14
  190. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +83 -74
  191. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +9 -14
  192. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +1 -2
  193. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +13 -13
  194. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +6 -6
  195. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +3 -4
  196. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +62 -75
  197. package/sdk-core/rustfmt.toml +2 -1
  198. package/src/client.rs +205 -318
  199. package/src/metrics.rs +22 -30
  200. package/src/runtime.rs +4 -5
  201. package/src/worker.rs +16 -19
  202. package/ts/native.ts +1 -1
  203. package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
  204. package/sdk-core/crates/common/src/errors.rs +0 -85
  205. package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
  206. package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
  207. package/sdk-core/crates/sdk/src/app_data.rs +0 -37
  208. package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
  209. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
@@ -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,140 +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.worker_config.max_outstanding_local_activities = Some(1_usize);
256
+ starter.sdk_config.tuner = Arc::new(TunerHolder::fixed_size(5, 1, 1, 1));
257
+ starter.sdk_config.register_activities(StdActivities);
181
258
  let mut worker = starter.worker().await;
182
- worker.register_wf(wf_name.to_owned(), local_act_fanout_wf);
183
- worker.register_activity("echo_activity", echo);
259
+ worker.register_workflow::<LocalActFanoutWf>();
184
260
 
185
- 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();
186
270
  worker.run_until_done().await.unwrap();
187
271
  }
188
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
+
189
304
  #[tokio::test]
190
305
  async fn local_act_retry_timer_backoff() {
191
306
  let wf_name = "local_act_retry_timer_backoff";
192
307
  let mut starter = CoreWfStarter::new(wf_name);
308
+ starter.sdk_config.register_activities(StdActivities);
193
309
  let mut worker = starter.worker().await;
194
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
195
- let res = ctx
196
- .local_activity(LocalActivityOptions {
197
- activity_type: "echo".to_string(),
198
- input: "hi".as_json_payload().expect("serializes fine"),
199
- retry_policy: RetryPolicy {
200
- initial_interval: Some(prost_dur!(from_micros(15))),
201
- // We want two local backoffs that are short. Third backoff will use timer
202
- backoff_coefficient: 1_000.,
203
- maximum_interval: Some(prost_dur!(from_millis(1500))),
204
- maximum_attempts: 4,
205
- non_retryable_error_types: vec![],
206
- },
207
- timer_backoff_threshold: Some(Duration::from_secs(1)),
208
- ..Default::default()
209
- })
210
- .await;
211
- assert!(res.failed());
212
- Ok(().into())
213
- });
214
- worker.register_activity("echo", |_: ActContext, _: String| async {
215
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
216
- });
217
-
218
- let run_id = worker
219
- .submit_wf(
220
- wf_name.to_owned(),
221
- wf_name.to_owned(),
222
- vec![],
223
- 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(),
224
318
  )
225
319
  .await
226
320
  .unwrap();
227
321
  worker.run_until_done().await.unwrap();
228
- let client = starter.get_client().await;
229
- let handle = client.get_untyped_workflow_handle(wf_name, run_id);
230
322
  handle
231
323
  .fetch_history_and_replay(worker.inner_mut())
232
324
  .await
@@ -239,41 +331,86 @@ async fn local_act_retry_timer_backoff() {
239
331
  #[case::abandon(ActivityCancellationType::Abandon)]
240
332
  #[tokio::test]
241
333
  async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
242
- let wf_name = format!("cancel_immediate_{cancel_type:?}");
243
- let mut starter = CoreWfStarter::new(&wf_name);
244
- let mut worker = starter.worker().await;
245
- worker.register_wf(&wf_name, move |ctx: WfContext| async move {
246
- let la = ctx.local_activity(LocalActivityOptions {
247
- activity_type: "echo".to_string(),
248
- input: "hi".as_json_payload().expect("serializes fine"),
249
- cancel_type,
250
- ..Default::default()
251
- });
252
- la.cancel(&ctx);
253
- let resolution = la.await;
254
- assert!(resolution.cancelled());
255
- Ok(().into())
256
- });
334
+ use temporalio_sdk::WorkflowContextView;
257
335
 
336
+ let wf_name = format!("cancel_immediate_{cancel_type:?}");
258
337
  // If we don't use this, we'd hang on shutdown for abandon cancel modes.
259
338
  let manual_cancel = CancellationToken::new();
260
- let manual_cancel_act = manual_cancel.clone();
339
+ let mut starter = CoreWfStarter::new(&wf_name);
261
340
 
262
- worker.register_activity("echo", move |ctx: ActContext, _: String| {
263
- let manual_cancel_act = manual_cancel_act.clone();
264
- 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> {
265
352
  tokio::select! {
266
- _ = tokio::time::sleep(Duration::from_secs(10)) => {},
353
+ _ = tokio::time::sleep(Duration::from_secs(10)) => {}
267
354
  _ = ctx.cancelled() => {
268
355
  return Err(ActivityError::cancelled())
269
356
  }
270
- _ = manual_cancel_act.cancelled() => {}
357
+ _ = self.manual_cancel.cancelled() => {}
271
358
  }
272
359
  Ok(())
273
360
  }
274
- });
361
+ }
275
362
 
276
- starter.start_with_worker(wf_name, &mut worker).await;
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
+ }
380
+
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();
277
414
  worker
278
415
  .run_until_done_intercepted(Some(LACancellerInterceptor {
279
416
  cancel_on_workflow_completed: false,
@@ -324,47 +461,24 @@ async fn cancel_after_act_starts(
324
461
  cancel_type: ActivityCancellationType,
325
462
  ) {
326
463
  let wf_name = format!("cancel_after_act_starts_{cancel_on_backoff:?}_{cancel_type:?}");
327
- let mut starter = CoreWfStarter::new(&wf_name);
328
- starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
329
- let mut worker = starter.worker().await;
330
- let bo_dur = cancel_on_backoff.unwrap_or_else(|| Duration::from_secs(1));
331
- worker.register_wf(&wf_name, move |ctx: WfContext| async move {
332
- let la = ctx.local_activity(LocalActivityOptions {
333
- activity_type: "echo".to_string(),
334
- input: "hi".as_json_payload().expect("serializes fine"),
335
- retry_policy: RetryPolicy {
336
- initial_interval: Some(bo_dur.try_into().unwrap()),
337
- backoff_coefficient: 1.,
338
- maximum_interval: Some(bo_dur.try_into().unwrap()),
339
- // Retry forever until cancelled
340
- ..Default::default()
341
- },
342
- timer_backoff_threshold: Some(Duration::from_secs(1)),
343
- cancel_type,
344
- ..Default::default()
345
- });
346
- ctx.timer(Duration::from_secs(1)).await;
347
- // Note that this cancel can't go through for *two* WF tasks, because we do a full heartbeat
348
- // before the timer (LA hasn't resolved), and then the timer fired event won't appear in
349
- // history until *after* the next WFT because we force generated it when we sent the timer
350
- // command.
351
- la.cancel(&ctx);
352
- // This extra timer is here to ensure the presence of another WF task doesn't mess up
353
- // resolving the LA with cancel on replay
354
- ctx.timer(Duration::from_secs(1)).await;
355
- let resolution = la.await;
356
- assert!(resolution.cancelled());
357
- Ok(().into())
358
- });
359
-
360
464
  // If we don't use this, we'd hang on shutdown for abandon cancel modes.
361
465
  let manual_cancel = CancellationToken::new();
362
- 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));
363
468
 
364
- worker.register_activity("echo", move |ctx: ActContext, _: String| {
365
- let manual_cancel_act = manual_cancel_act.clone();
366
- async move {
367
- 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() {
368
482
  if ctx.is_cancelled() {
369
483
  return Err(ActivityError::cancelled());
370
484
  }
@@ -372,20 +486,87 @@ async fn cancel_after_act_starts(
372
486
  return Err(anyhow!("Oh no I failed!").into());
373
487
  } else {
374
488
  tokio::select! {
375
- _ = tokio::time::sleep(Duration::from_secs(100)) => {},
489
+ _ = tokio::time::sleep(Duration::from_secs(100)) => {}
376
490
  _ = ctx.cancelled() => {
377
491
  return Err(ActivityError::cancelled())
378
492
  }
379
- _ = manual_cancel_act.cancelled() => {
493
+ _ = self.manual_cancel.cancelled() => {
380
494
  return Ok(())
381
495
  }
382
496
  }
383
497
  }
384
498
  Err(anyhow!("Oh no I failed!").into())
385
499
  }
386
- });
500
+ }
501
+
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
+ }
387
556
 
388
- starter.start_with_worker(&wf_name, &mut worker).await;
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();
389
570
  worker
390
571
  .run_until_done_intercepted(Some(LACancellerInterceptor {
391
572
  token: manual_cancel,
@@ -408,6 +589,68 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
408
589
  if is_schedule { "schedule" } else { "start" }
409
590
  );
410
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
+
411
654
  let mut worker = starter.worker().await;
412
655
  let (sched, start) = if is_schedule {
413
656
  (Some(Duration::from_secs(2)), None)
@@ -419,39 +662,17 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
419
662
  } else {
420
663
  TimeoutType::StartToClose
421
664
  };
665
+ worker.register_workflow::<XToCloseTimeoutWf>();
422
666
 
423
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
424
- let res = ctx
425
- .local_activity(LocalActivityOptions {
426
- activity_type: "echo".to_string(),
427
- input: "hi".as_json_payload().expect("serializes fine"),
428
- retry_policy: RetryPolicy {
429
- initial_interval: Some(prost_dur!(from_micros(15))),
430
- backoff_coefficient: 1_000.,
431
- maximum_interval: Some(prost_dur!(from_millis(1500))),
432
- maximum_attempts: 4,
433
- non_retryable_error_types: vec![],
434
- },
435
- timer_backoff_threshold: Some(Duration::from_secs(1)),
436
- schedule_to_close_timeout: sched,
437
- start_to_close_timeout: start,
438
- ..Default::default()
439
- })
440
- .await;
441
- assert_eq!(res.timed_out(), Some(timeout_type));
442
- Ok(().into())
443
- });
444
- worker.register_activity("echo", |ctx: ActContext, _: String| async move {
445
- tokio::select! {
446
- _ = tokio::time::sleep(Duration::from_secs(100)) => {},
447
- _ = ctx.cancelled() => {
448
- return Err(ActivityError::cancelled())
449
- }
450
- };
451
- Ok(())
452
- });
453
-
454
- 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();
455
676
  worker.run_until_done().await.unwrap();
456
677
  }
457
678
 
@@ -466,36 +687,75 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
466
687
  );
467
688
  let mut starter = CoreWfStarter::new(&wf_name);
468
689
  if !cached {
469
- starter.worker_config.max_cached_workflows = 0_usize;
690
+ starter.sdk_config.max_cached_workflows = 0_usize;
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
+ }
470
727
  }
728
+
471
729
  let mut worker = starter.worker().await;
472
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
473
- let res = ctx
474
- .local_activity(LocalActivityOptions {
475
- activity_type: "echo".to_string(),
476
- input: "hi".as_json_payload().expect("serializes fine"),
477
- retry_policy: RetryPolicy {
478
- initial_interval: Some(prost_dur!(from_millis(15))),
479
- backoff_coefficient: 1_000.,
480
- maximum_interval: Some(prost_dur!(from_millis(1000))),
481
- maximum_attempts: 40,
482
- non_retryable_error_types: vec![],
483
- },
484
- timer_backoff_threshold: Some(Duration::from_millis(500)),
485
- schedule_to_close_timeout: Some(Duration::from_secs(2)),
486
- ..Default::default()
487
- })
488
- .await;
489
- assert_eq!(res.timed_out(), Some(TimeoutType::ScheduleToClose));
490
- Ok(().into())
491
- });
492
- let num_attempts: &'static _ = Box::leak(Box::new(AtomicU8::new(0)));
493
- worker.register_activity("echo", move |_: ActContext, _: String| async {
494
- num_attempts.fetch_add(1, Ordering::Relaxed);
495
- 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(),
496
748
  });
497
749
 
498
- 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();
499
759
  worker.run_until_done().await.unwrap();
500
760
  // 3 attempts b/c first backoff is very small, then the next 2 attempts take at least 2 seconds
501
761
  // b/c of timer backoff.
@@ -507,24 +767,21 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
507
767
  async fn eviction_wont_make_local_act_get_dropped(#[values(true, false)] short_wft_timeout: bool) {
508
768
  let wf_name = format!("eviction_wont_make_local_act_get_dropped_{short_wft_timeout}");
509
769
  let mut starter = CoreWfStarter::new(&wf_name);
510
- starter.worker_config.max_cached_workflows = 0_usize;
770
+ starter.sdk_config.max_cached_workflows = 0_usize;
771
+ starter.sdk_config.register_activities(StdActivities);
511
772
  let mut worker = starter.worker().await;
512
- worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
513
- worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
514
- tokio::time::sleep(Duration::from_secs(4)).await;
515
- Ok(str)
516
- });
773
+ worker.register_workflow::<LocalActThenTimerThenWait>();
517
774
 
775
+ let task_queue = starter.get_task_queue().to_owned();
518
776
  let opts = if short_wft_timeout {
519
- WorkflowOptions {
520
- task_timeout: Some(Duration::from_secs(1)),
521
- ..Default::default()
522
- }
777
+ WorkflowStartOptions::new(task_queue, wf_name.clone())
778
+ .task_timeout(Duration::from_secs(1))
779
+ .build()
523
780
  } else {
524
- Default::default()
781
+ WorkflowStartOptions::new(task_queue, wf_name.clone()).build()
525
782
  };
526
783
  worker
527
- .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![], opts)
784
+ .submit_workflow(LocalActThenTimerThenWait::run, (), opts)
528
785
  .await
529
786
  .unwrap();
530
787
  worker.run_until_done().await.unwrap();
@@ -534,44 +791,65 @@ async fn eviction_wont_make_local_act_get_dropped(#[values(true, false)] short_w
534
791
  async fn timer_backoff_concurrent_with_non_timer_backoff() {
535
792
  let wf_name = "timer_backoff_concurrent_with_non_timer_backoff";
536
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
+
537
841
  let mut worker = starter.worker().await;
538
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
539
- let r1 = ctx.local_activity(LocalActivityOptions {
540
- activity_type: "echo".to_string(),
541
- input: "hi".as_json_payload().expect("serializes fine"),
542
- retry_policy: RetryPolicy {
543
- initial_interval: Some(prost_dur!(from_micros(15))),
544
- backoff_coefficient: 1_000.,
545
- maximum_interval: Some(prost_dur!(from_millis(1500))),
546
- maximum_attempts: 4,
547
- non_retryable_error_types: vec![],
548
- },
549
- timer_backoff_threshold: Some(Duration::from_secs(1)),
550
- ..Default::default()
551
- });
552
- let r2 = ctx.local_activity(LocalActivityOptions {
553
- activity_type: "echo".to_string(),
554
- input: "hi".as_json_payload().expect("serializes fine"),
555
- retry_policy: RetryPolicy {
556
- initial_interval: Some(prost_dur!(from_millis(15))),
557
- backoff_coefficient: 10.,
558
- maximum_interval: Some(prost_dur!(from_millis(1500))),
559
- maximum_attempts: 4,
560
- non_retryable_error_types: vec![],
561
- },
562
- timer_backoff_threshold: Some(Duration::from_secs(10)),
563
- ..Default::default()
564
- });
565
- let (r1, r2) = tokio::join!(r1, r2);
566
- assert!(r1.failed());
567
- assert!(r2.failed());
568
- Ok(().into())
569
- });
570
- worker.register_activity("echo", |_: ActContext, _: String| async {
571
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
572
- });
842
+ worker.register_workflow::<TimerBackoffConcurrentWf>();
573
843
 
574
- 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();
575
853
  worker.run_until_done().await.unwrap();
576
854
  }
577
855
 
@@ -579,50 +857,64 @@ async fn timer_backoff_concurrent_with_non_timer_backoff() {
579
857
  async fn repro_nondeterminism_with_timer_bug() {
580
858
  let wf_name = "repro_nondeterminism_with_timer_bug";
581
859
  let mut starter = CoreWfStarter::new(wf_name);
582
- let mut worker = starter.worker().await;
583
-
584
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
585
- let t1 = ctx.timer(Duration::from_secs(30));
586
- let r1 = ctx.local_activity(LocalActivityOptions {
587
- activity_type: "delay".to_string(),
588
- input: "hi".as_json_payload().expect("serializes fine"),
589
- retry_policy: RetryPolicy {
590
- initial_interval: Some(prost_dur!(from_micros(15))),
591
- backoff_coefficient: 1_000.,
592
- maximum_interval: Some(prost_dur!(from_millis(1500))),
593
- maximum_attempts: 4,
594
- non_retryable_error_types: vec![],
595
- },
596
- timer_backoff_threshold: Some(Duration::from_secs(1)),
597
- ..Default::default()
598
- });
599
- tokio::pin!(t1);
600
- tokio::select! {
601
- _ = &mut t1 => {},
602
- _ = r1 => {
603
- t1.cancel(&ctx);
604
- },
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(())
605
894
  }
606
- ctx.timer(Duration::from_secs(1)).await;
607
- Ok(().into())
608
- });
609
- worker.register_activity("delay", |_: ActContext, _: String| async {
610
- tokio::time::sleep(Duration::from_secs(2)).await;
611
- Ok(())
612
- });
895
+ }
613
896
 
614
- let run_id = worker
615
- .submit_wf(
616
- wf_name.to_owned(),
617
- wf_name.to_owned(),
618
- vec![],
619
- 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(),
620
906
  )
621
907
  .await
622
908
  .unwrap();
623
909
  worker.run_until_done().await.unwrap();
624
910
  let client = starter.get_client().await;
625
- 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());
626
918
  handle
627
919
  .fetch_history_and_replay(worker.inner_mut())
628
920
  .await
@@ -646,14 +938,8 @@ async fn weird_la_nondeterminism_repro(#[values(true, false)] fix_hist: bool) {
646
938
  }
647
939
 
648
940
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
649
- worker.register_wf(
650
- "evict_while_la_running_no_interference",
651
- la_problem_workflow,
652
- );
653
- worker.register_activity("delay", |_: ActContext, _: String| async {
654
- tokio::time::sleep(Duration::from_secs(15)).await;
655
- Ok(())
656
- });
941
+ worker.register_workflow::<LaProblemWorkflow>();
942
+ worker.register_activities(StdActivities);
657
943
  worker.run().await.unwrap();
658
944
  }
659
945
 
@@ -670,14 +956,8 @@ async fn second_weird_la_nondeterminism_repro() {
670
956
  hist = thb.get_full_history_info().unwrap().into();
671
957
 
672
958
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
673
- worker.register_wf(
674
- "evict_while_la_running_no_interference",
675
- la_problem_workflow,
676
- );
677
- worker.register_activity("delay", |_: ActContext, _: String| async {
678
- tokio::time::sleep(Duration::from_secs(15)).await;
679
- Ok(())
680
- });
959
+ worker.register_workflow::<LaProblemWorkflow>();
960
+ worker.register_activities(StdActivities);
681
961
  worker.run().await.unwrap();
682
962
  }
683
963
 
@@ -692,14 +972,8 @@ async fn third_weird_la_nondeterminism_repro() {
692
972
  hist = thb.get_full_history_info().unwrap().into();
693
973
 
694
974
  let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
695
- worker.register_wf(
696
- "evict_while_la_running_no_interference",
697
- la_problem_workflow,
698
- );
699
- worker.register_activity("delay", |_: ActContext, _: String| async {
700
- tokio::time::sleep(Duration::from_secs(15)).await;
701
- Ok(())
702
- });
975
+ worker.register_workflow::<LaProblemWorkflow>();
976
+ worker.register_activities(StdActivities);
703
977
  worker.run().await.unwrap();
704
978
  }
705
979
 
@@ -719,58 +993,87 @@ async fn third_weird_la_nondeterminism_repro() {
719
993
  async fn la_resolve_same_time_as_other_cancel() {
720
994
  let wf_name = "la_resolve_same_time_as_other_cancel";
721
995
  let mut starter = CoreWfStarter::new(wf_name);
722
- // The activity won't get a chance to receive the cancel so make sure we still exit fast
723
- starter.worker_config.graceful_shutdown_period = Some(Duration::from_millis(100));
724
- let mut worker = starter.worker().await;
725
-
726
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
727
- let normal_act = ctx.activity(ActivityOptions {
728
- activity_type: "delay".to_string(),
729
- input: 9000.as_json_payload().expect("serializes fine"),
730
- cancellation_type: ActivityCancellationType::TryCancel,
731
- start_to_close_timeout: Some(Duration::from_secs(9000)),
732
- ..Default::default()
733
- });
734
- // Make new task
735
- ctx.timer(Duration::from_millis(1)).await;
736
996
 
737
- // Start LA and cancel the activity at the same time
738
- let local_act = ctx.local_activity(LocalActivityOptions {
739
- activity_type: "delay".to_string(),
740
- input: 100.as_json_payload().expect("serializes fine"),
741
- ..Default::default()
742
- });
743
- normal_act.cancel(&ctx);
744
- // Race them, starting a timer if LA completes first
745
- tokio::select! {
746
- biased;
747
- _ = normal_act => {},
748
- _ = local_act => {
749
- ctx.timer(Duration::from_millis(1)).await;
750
- },
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(())
751
1007
  }
752
- Ok(().into())
753
- });
754
- worker.register_activity("delay", |ctx: ActContext, wait_time: u64| async move {
755
- tokio::select! {
756
- _ = tokio::time::sleep(Duration::from_millis(wait_time)) => {}
757
- _ = 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(())
758
1053
  }
759
- Ok(())
760
- });
1054
+ }
761
1055
 
762
- let run_id = worker
763
- .submit_wf(
764
- wf_name.to_owned(),
765
- wf_name.to_owned(),
766
- vec![],
767
- 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(),
768
1065
  )
769
1066
  .await
770
1067
  .unwrap();
771
1068
  worker.run_until_done().await.unwrap();
772
1069
  let client = starter.get_client().await;
773
- 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());
774
1077
  handle
775
1078
  .fetch_history_and_replay(worker.inner_mut())
776
1079
  .await
@@ -791,59 +1094,68 @@ async fn long_local_activity_with_update(
791
1094
  let wf_name = format!("{}-{}", ctx.name, ctx.case.unwrap());
792
1095
  let mut starter = CoreWfStarter::new(&wf_name);
793
1096
  starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
794
- let mut worker = starter.worker().await;
795
- let client = starter.get_client().await;
1097
+ starter.sdk_config.register_activities(StdActivities);
796
1098
 
797
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
798
- let update_counter = Arc::new(AtomicUsize::new(1));
799
- let uc = update_counter.clone();
800
- ctx.update_handler(
801
- "update",
802
- |_: &_, _: ()| Ok(()),
803
- move |u: UpdateContext, _: ()| {
804
- let uc = uc.clone();
805
- async move {
806
- if update_inner_timer != 0 {
807
- u.wf_ctx
808
- .timer(Duration::from_millis(update_inner_timer))
809
- .await;
810
- }
811
- uc.fetch_add(1, Ordering::Relaxed);
812
- Ok(())
813
- }
814
- },
815
- );
816
- ctx.local_activity(LocalActivityOptions {
817
- activity_type: "delay".to_string(),
818
- input: "hi".as_json_payload().expect("serializes fine"),
819
- ..Default::default()
820
- })
821
- .await;
822
- update_counter.load(Ordering::Relaxed);
823
- Ok(().into())
824
- });
825
- worker.register_activity("delay", |_: ActContext, _: String| async {
826
- tokio::time::sleep(Duration::from_secs(6)).await;
827
- Ok(())
828
- });
1099
+ #[workflow]
1100
+ #[derive(Default)]
1101
+ struct LongLocalActivityWithUpdateWf {
1102
+ update_counter: usize,
1103
+ update_inner_timer: u64,
1104
+ }
829
1105
 
830
- let handle = starter
831
- .start_with_worker(wf_name.clone(), &mut worker)
832
- .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();
833
1150
 
834
- let wf_id = starter.get_task_queue().to_string();
835
1151
  let update = async {
836
1152
  loop {
837
1153
  tokio::time::sleep(Duration::from_millis(update_interval_ms)).await;
838
- let _ = client
839
- .update_workflow_execution(
840
- wf_id.clone(),
841
- "".to_string(),
842
- "update".to_string(),
843
- WaitPolicy {
844
- lifecycle_stage: UpdateWorkflowExecutionLifecycleStage::Completed as i32,
845
- },
846
- [().as_json_payload().unwrap()].into_payloads(),
1154
+ let _ = handle
1155
+ .execute_update(
1156
+ LongLocalActivityWithUpdateWf::do_update,
1157
+ (),
1158
+ WorkflowExecuteUpdateOptions::default(),
847
1159
  )
848
1160
  .await;
849
1161
  }
@@ -852,16 +1164,12 @@ async fn long_local_activity_with_update(
852
1164
  worker.run_until_done().await.unwrap();
853
1165
  };
854
1166
  tokio::select!(_ = update => {}, _ = runner => {});
855
- let res = handle
856
- .get_workflow_result(Default::default())
857
- .await
858
- .unwrap()
859
- .unwrap_success();
1167
+ let res = handle.get_result(Default::default()).await.unwrap();
860
1168
  let replay_res = handle
861
1169
  .fetch_history_and_replay(worker.inner_mut())
862
1170
  .await
863
1171
  .unwrap();
864
- assert_eq!(res[0], replay_res.unwrap());
1172
+ assert_eq!(res, usize::from_json_payload(&replay_res.unwrap()).unwrap());
865
1173
 
866
1174
  // Load histories from pre-fix version and ensure compat
867
1175
  let replay_worker = init_core_replay_preloaded(
@@ -874,7 +1182,7 @@ async fn long_local_activity_with_update(
874
1182
  )],
875
1183
  );
876
1184
  let inner_worker = worker.inner_mut();
877
- inner_worker.with_new_core_worker(replay_worker);
1185
+ inner_worker.with_new_core_worker(Arc::new(replay_worker));
878
1186
  inner_worker.set_worker_interceptor(FailOnNondeterminismInterceptor {});
879
1187
  inner_worker.run().await.unwrap();
880
1188
  }
@@ -884,68 +1192,92 @@ async fn local_activity_with_heartbeat_only_causes_one_wakeup() {
884
1192
  let wf_name = "local_activity_with_heartbeat_only_causes_one_wakeup";
885
1193
  let mut starter = CoreWfStarter::new(wf_name);
886
1194
  starter.workflow_options.task_timeout = Some(Duration::from_secs(1));
887
- let mut worker = starter.worker().await;
1195
+ starter.sdk_config.register_activities(StdActivities);
888
1196
 
889
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| async move {
890
- let mut wakeup_counter = 1;
891
- let la_resolved = AtomicBool::new(false);
892
- tokio::join!(
893
- async {
894
- ctx.local_activity(LocalActivityOptions {
895
- activity_type: "delay".to_string(),
896
- input: "hi".as_json_payload().expect("serializes fine"),
897
- ..Default::default()
898
- })
899
- .await;
900
- la_resolved.store(true, Ordering::Relaxed);
901
- },
902
- async {
903
- 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(|_| {
904
1223
  wakeup_counter += 1;
905
- la_resolved.load(Ordering::Relaxed)
1224
+ ctx.state(|s| s.la_resolved)
906
1225
  })
907
- .await;
908
- }
909
- );
910
- Ok(().into())
911
- });
912
- worker.register_activity("delay", |_: ActContext, _: String| async {
913
- tokio::time::sleep(Duration::from_secs(6)).await;
914
- Ok(())
915
- });
1226
+ );
1227
+ Ok(wakeup_counter as usize)
1228
+ }
1229
+ }
916
1230
 
917
- let handle = starter.start_with_worker(wf_name, &mut worker).await;
918
- worker.run_until_done().await.unwrap();
919
- let res = handle
920
- .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
+ )
921
1241
  .await
922
- .unwrap()
923
- .unwrap_success();
924
- 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
925
1247
  .fetch_history_and_replay(worker.inner_mut())
926
1248
  .await
927
1249
  .unwrap();
928
- assert_eq!(res[0], replay_res.unwrap());
929
1250
  }
930
1251
 
931
- pub(crate) async fn local_activity_with_summary_wf(ctx: WfContext) -> WorkflowResult<()> {
932
- ctx.local_activity(LocalActivityOptions {
933
- activity_type: "echo_activity".to_string(),
934
- input: "hi!".as_json_payload().expect("serializes fine"),
935
- summary: Some("Echo summary".to_string()),
936
- ..Default::default()
937
- })
938
- .await;
939
- 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
+ }
940
1272
  }
941
1273
 
942
1274
  #[tokio::test]
943
1275
  async fn local_activity_with_summary() {
944
1276
  let wf_name = "local_activity_with_summary";
945
1277
  let mut starter = CoreWfStarter::new(wf_name);
1278
+ starter.sdk_config.register_activities(StdActivities);
946
1279
  let mut worker = starter.worker().await;
947
- worker.register_wf(wf_name.to_owned(), local_activity_with_summary_wf);
948
- worker.register_activity("echo_activity", echo);
1280
+ worker.register_workflow::<LocalActivityWithSummaryWf>();
949
1281
 
950
1282
  let handle = starter.start_with_worker(wf_name, &mut worker).await;
951
1283
  worker.run_until_done().await.unwrap();
@@ -978,10 +1310,6 @@ async fn local_activity_with_summary() {
978
1310
  );
979
1311
  }
980
1312
 
981
- async fn echo(_ctx: ActContext, e: String) -> Result<String, ActivityError> {
982
- Ok(e)
983
- }
984
-
985
1313
  /// This test verifies that when replaying we are able to resolve local activities whose data we
986
1314
  /// don't see until after the workflow issues the command
987
1315
  #[rstest::rstest]
@@ -1015,29 +1343,23 @@ async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached:
1015
1343
  }
1016
1344
  });
1017
1345
 
1018
- worker.register_wf(
1019
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1020
- |ctx: WfContext| async move {
1021
- let la = ctx.local_activity(LocalActivityOptions {
1022
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1023
- input: "hi".as_json_payload().expect("serializes fine"),
1024
- ..Default::default()
1025
- });
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());
1026
1355
  ctx.timer(Duration::from_secs(1)).await;
1027
- la.await;
1028
- Ok(().into())
1029
- },
1030
- );
1031
- worker.register_activity(DEFAULT_ACTIVITY_TYPE, echo);
1032
- worker
1033
- .submit_wf(
1034
- wf_id.to_owned(),
1035
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1036
- vec![],
1037
- WorkflowOptions::default(),
1038
- )
1039
- .await
1040
- .unwrap();
1356
+ let _ = la.await;
1357
+ Ok(())
1358
+ }
1359
+ }
1360
+
1361
+ worker.register_workflow::<LocalActTwoWftsBeforeMarkerWf>();
1362
+ worker.register_activities(StdActivities);
1041
1363
  worker.run_until_done().await.unwrap();
1042
1364
  }
1043
1365
 
@@ -1060,17 +1382,8 @@ async fn local_act_many_concurrent() {
1060
1382
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
1061
1383
  let mut worker = mock_sdk(mh);
1062
1384
 
1063
- worker.register_wf(DEFAULT_WORKFLOW_TYPE.to_owned(), local_act_fanout_wf);
1064
- worker.register_activity("echo_activity", echo);
1065
- worker
1066
- .submit_wf(
1067
- wf_id.to_owned(),
1068
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1069
- vec![],
1070
- WorkflowOptions::default(),
1071
- )
1072
- .await
1073
- .unwrap();
1385
+ worker.register_workflow::<LocalActFanoutWf>();
1386
+ worker.register_activities(StdActivities);
1074
1387
  worker.run_until_done().await.unwrap();
1075
1388
  }
1076
1389
 
@@ -1101,43 +1414,67 @@ async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
1101
1414
  wc.max_cached_workflows = 1;
1102
1415
  wc.max_outstanding_workflow_tasks = Some(1);
1103
1416
  });
1104
- 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
+ }
1105
1440
 
1106
- let shutdown_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
1441
+ worker.register_workflow::<LocalActHeartbeatWf>();
1107
1442
 
1108
- worker.register_wf(
1109
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1110
- |ctx: WfContext| async move {
1111
- ctx.local_activity(LocalActivityOptions {
1112
- activity_type: "echo".to_string(),
1113
- input: "hi".as_json_payload().expect("serializes fine"),
1114
- ..Default::default()
1115
- })
1116
- .await;
1117
- Ok(().into())
1118
- },
1119
- );
1120
- worker.register_activity("echo", move |_ctx: ActContext, str: String| async move {
1121
- if shutdown_middle {
1122
- 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)
1123
1462
  }
1124
- // Take slightly more than two workflow tasks
1125
- tokio::time::sleep(wft_timeout.mul_f32(2.2)).await;
1126
- 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,
1127
1472
  });
1128
- worker
1129
- .submit_wf(
1130
- wf_id.to_owned(),
1131
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1132
- vec![],
1133
- WorkflowOptions::default(),
1134
- )
1135
- .await
1136
- .unwrap();
1137
1473
  let (_, runres) = tokio::join!(
1138
1474
  async {
1139
1475
  if shutdown_middle {
1140
1476
  shutdown_barr.wait().await;
1477
+ dbg!("Past barrier");
1141
1478
  core.shutdown().await;
1142
1479
  }
1143
1480
  },
@@ -1153,6 +1490,7 @@ async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
1153
1490
  async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
1154
1491
  let mut t = TestHistoryBuilder::default();
1155
1492
  t.add_by_type(EventType::WorkflowExecutionStarted);
1493
+ t.set_wf_input(eventually_pass.as_json_payload().unwrap());
1156
1494
  t.add_workflow_task_scheduled_and_started();
1157
1495
 
1158
1496
  let wf_id = "fakeid";
@@ -1160,49 +1498,63 @@ async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
1160
1498
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
1161
1499
  let mut worker = mock_sdk(mh);
1162
1500
 
1163
- worker.register_wf(
1164
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1165
- 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<()> {
1166
1509
  let la_res = ctx
1167
- .local_activity(LocalActivityOptions {
1168
- activity_type: "echo".to_string(),
1169
- input: "hi".as_json_payload().expect("serializes fine"),
1170
- retry_policy: RetryPolicy {
1171
- initial_interval: Some(prost_dur!(from_millis(50))),
1172
- backoff_coefficient: 1.2,
1173
- maximum_interval: None,
1174
- maximum_attempts: 5,
1175
- 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()
1176
1522
  },
1177
- ..Default::default()
1178
- })
1523
+ )
1179
1524
  .await;
1180
1525
  if eventually_pass {
1181
- assert!(la_res.completed_ok())
1526
+ assert!(la_res.is_ok())
1182
1527
  } else {
1183
- assert!(la_res.failed())
1528
+ assert!(matches!(la_res, Err(ActivityExecutionError::Failed(_))))
1184
1529
  }
1185
- Ok(().into())
1186
- },
1187
- );
1188
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1189
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
1190
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
1191
- if 2 == attempts.fetch_add(1, Ordering::Relaxed) && eventually_pass {
1192
1530
  Ok(())
1193
- } else {
1194
- Err(anyhow!("Oh no I failed!").into())
1195
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,
1196
1557
  });
1197
- worker
1198
- .submit_wf(
1199
- wf_id.to_owned(),
1200
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1201
- vec![],
1202
- WorkflowOptions::default(),
1203
- )
1204
- .await
1205
- .unwrap();
1206
1558
  worker.run_until_done().await.unwrap();
1207
1559
  let expected_attempts = if eventually_pass { 3 } else { 5 };
1208
1560
  assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
@@ -1213,18 +1565,22 @@ async fn local_act_retry_long_backoff_uses_timer() {
1213
1565
  let mut t = TestHistoryBuilder::default();
1214
1566
  t.add_by_type(EventType::WorkflowExecutionStarted);
1215
1567
  t.add_full_wf_task();
1216
- t.add_local_activity_fail_marker(
1568
+ t.add_local_activity_marker(
1217
1569
  1,
1218
1570
  "1",
1219
- 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(),
1220
1574
  );
1221
1575
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1222
1576
  t.add_timer_fired(timer_started_event_id, "1".to_string());
1223
1577
  t.add_full_wf_task();
1224
- t.add_local_activity_fail_marker(
1578
+ t.add_local_activity_marker(
1225
1579
  2,
1226
1580
  "2",
1227
- 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(),
1228
1584
  );
1229
1585
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
1230
1586
  t.add_timer_fired(timer_started_event_id, "2".to_string());
@@ -1241,45 +1597,38 @@ async fn local_act_retry_long_backoff_uses_timer() {
1241
1597
  );
1242
1598
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1243
1599
 
1244
- worker.register_wf(
1245
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1246
- |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<()> {
1247
1608
  let la_res = ctx
1248
- .local_activity(LocalActivityOptions {
1249
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1250
- input: "hi".as_json_payload().expect("serializes fine"),
1251
- retry_policy: RetryPolicy {
1252
- initial_interval: Some(prost_dur!(from_millis(65))),
1253
- // This will make the second backoff 65 seconds, plenty to use timer
1254
- backoff_coefficient: 1_000.,
1255
- maximum_interval: Some(prost_dur!(from_secs(600))),
1256
- maximum_attempts: 3,
1257
- 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()
1258
1621
  },
1259
- ..Default::default()
1260
- })
1622
+ )
1261
1623
  .await;
1262
- assert!(la_res.failed());
1263
- // Extra timer just to have an extra workflow task which we can return full history for
1624
+ assert!(matches!(la_res, Err(ActivityExecutionError::Failed(_))));
1264
1625
  ctx.timer(Duration::from_secs(1)).await;
1265
- Ok(().into())
1266
- },
1267
- );
1268
- worker.register_activity(
1269
- DEFAULT_ACTIVITY_TYPE,
1270
- move |_ctx: ActContext, _: String| async move {
1271
- Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
1272
- },
1273
- );
1274
- worker
1275
- .submit_wf(
1276
- wf_id.to_owned(),
1277
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1278
- vec![],
1279
- WorkflowOptions::default(),
1280
- )
1281
- .await
1282
- .unwrap();
1626
+ Ok(())
1627
+ }
1628
+ }
1629
+
1630
+ worker.register_workflow::<LocalActRetryLongBackoffUsesTimerWf>();
1631
+ worker.register_activities(StdActivities);
1283
1632
  worker.run_until_done().await.unwrap();
1284
1633
  }
1285
1634
 
@@ -1296,28 +1645,23 @@ async fn local_act_null_result() {
1296
1645
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
1297
1646
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1298
1647
 
1299
- worker.register_wf(
1300
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1301
- |ctx: WfContext| async move {
1302
- ctx.local_activity(LocalActivityOptions {
1303
- activity_type: "nullres".to_string(),
1304
- input: "hi".as_json_payload().expect("serializes fine"),
1305
- ..Default::default()
1306
- })
1307
- .await;
1308
- Ok(().into())
1309
- },
1310
- );
1311
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
1312
- worker
1313
- .submit_wf(
1314
- wf_id.to_owned(),
1315
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1316
- vec![],
1317
- WorkflowOptions::default(),
1318
- )
1319
- .await
1320
- .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);
1321
1665
  worker.run_until_done().await.unwrap();
1322
1666
  }
1323
1667
 
@@ -1335,33 +1679,27 @@ async fn local_act_command_immediately_follows_la_marker() {
1335
1679
 
1336
1680
  let wf_id = "fakeid";
1337
1681
  let mock = mock_worker_client();
1338
- // Bug only repros when seeing history up to third wft
1339
1682
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [3], mock);
1340
1683
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 0);
1341
1684
 
1342
- worker.register_wf(
1343
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1344
- |ctx: WfContext| async move {
1345
- ctx.local_activity(LocalActivityOptions {
1346
- activity_type: "nullres".to_string(),
1347
- input: "hi".as_json_payload().expect("serializes fine"),
1348
- ..Default::default()
1349
- })
1350
- .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)))?;
1351
1696
  ctx.timer(Duration::from_secs(1)).await;
1352
- Ok(().into())
1353
- },
1354
- );
1355
- worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
1356
- worker
1357
- .submit_wf(
1358
- wf_id.to_owned(),
1359
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1360
- vec![],
1361
- WorkflowOptions::default(),
1362
- )
1363
- .await
1364
- .unwrap();
1697
+ Ok(())
1698
+ }
1699
+ }
1700
+
1701
+ worker.register_workflow::<LocalActCommandImmediatelyFollowsLaMarkerWf>();
1702
+ worker.register_activities(StdActivities);
1365
1703
  worker.run_until_done().await.unwrap();
1366
1704
  }
1367
1705
 
@@ -1632,44 +1970,39 @@ async fn test_schedule_to_start_timeout() {
1632
1970
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::ToTaskNum(1)], mock);
1633
1971
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1634
1972
 
1635
- worker.register_wf(
1636
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1637
- |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<()> {
1638
1981
  let la_res = ctx
1639
- .local_activity(LocalActivityOptions {
1640
- activity_type: "echo".to_string(),
1641
- input: "hi".as_json_payload().expect("serializes fine"),
1642
- // Impossibly small timeout so we timeout in the queue
1643
- schedule_to_start_timeout: prost_dur!(from_nanos(1)),
1644
- ..Default::default()
1645
- })
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
+ )
1646
1990
  .await;
1647
- assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
1648
- let rfail = la_res.unwrap_failure();
1649
- assert_matches!(
1650
- rfail.failure_info,
1651
- Some(FailureInfo::ActivityFailureInfo(_))
1652
- );
1653
- assert_matches!(
1654
- rfail.cause.unwrap().failure_info,
1655
- Some(FailureInfo::TimeoutFailureInfo(_))
1656
- );
1657
- Ok(().into())
1658
- },
1659
- );
1660
- worker.register_activity(
1661
- "echo",
1662
- move |_ctx: ActContext, _: String| async move { Ok(()) },
1663
- );
1664
- worker
1665
- .submit_wf(
1666
- wf_id.to_owned(),
1667
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1668
- vec![],
1669
- WorkflowOptions::default(),
1670
- )
1671
- .await
1672
- .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);
1673
2006
  worker.run_until_done().await.unwrap();
1674
2007
  }
1675
2008
 
@@ -1686,8 +2019,19 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1686
2019
  // * we don't time out on s-t-s timeouts because of that, when the param is true.
1687
2020
  // * we do properly time out on s-t-c timeouts when the param is false
1688
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
+
1689
2028
  let mut t = TestHistoryBuilder::default();
1690
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
+ );
1691
2035
  t.add_full_wf_task();
1692
2036
  let orig_sched = SystemTime::now().sub(Duration::from_secs(60 * 20));
1693
2037
  t.add_local_activity_marker(
@@ -1711,55 +2055,47 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1711
2055
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
1712
2056
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1713
2057
 
1714
- let schedule_to_close_timeout = Some(if is_sched_to_start {
1715
- // This 60 minute timeout will not have elapsed according to the original
1716
- // schedule time in the history.
1717
- Duration::from_secs(60 * 60)
1718
- } else {
1719
- // This 10 minute timeout will have already elapsed
1720
- Duration::from_secs(10 * 60)
1721
- });
1722
-
1723
- worker.register_wf(
1724
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1725
- 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;
1726
2070
  let la_res = ctx
1727
- .local_activity(LocalActivityOptions {
1728
- activity_type: "echo".to_string(),
1729
- input: "hi".as_json_payload().expect("serializes fine"),
1730
- retry_policy: RetryPolicy {
1731
- initial_interval: Some(prost_dur!(from_millis(50))),
1732
- backoff_coefficient: 1.2,
1733
- maximum_interval: None,
1734
- maximum_attempts: 5,
1735
- 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()
1736
2085
  },
1737
- schedule_to_start_timeout: Some(Duration::from_secs(60)),
1738
- schedule_to_close_timeout,
1739
- ..Default::default()
1740
- })
2086
+ )
1741
2087
  .await;
1742
2088
  if is_sched_to_start {
1743
- assert!(la_res.completed_ok());
1744
- } else {
1745
- 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));
1746
2092
  }
1747
- Ok(().into())
1748
- },
1749
- );
1750
- worker.register_activity(
1751
- "echo",
1752
- move |_ctx: ActContext, _: String| async move { Ok(()) },
1753
- );
1754
- worker
1755
- .submit_wf(
1756
- wf_id.to_owned(),
1757
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1758
- vec![],
1759
- WorkflowOptions::default(),
1760
- )
1761
- .await
1762
- .unwrap();
2093
+ Ok(())
2094
+ }
2095
+ }
2096
+
2097
+ worker.register_workflow::<TestScheduleToStartTimeoutNotBasedOnOriginalTimeWf>();
2098
+ worker.register_activities(StdActivities);
1763
2099
  worker.run_until_done().await.unwrap();
1764
2100
  }
1765
2101
 
@@ -1768,6 +2104,7 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
1768
2104
  async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_completes: bool) {
1769
2105
  let mut t = TestHistoryBuilder::default();
1770
2106
  t.add_by_type(EventType::WorkflowExecutionStarted);
2107
+ t.set_wf_input(la_completes.as_json_payload().unwrap());
1771
2108
  t.add_full_wf_task();
1772
2109
  if la_completes {
1773
2110
  t.add_local_activity_marker(1, "1", Some("hi".into()), None, |_| {});
@@ -1793,61 +2130,73 @@ async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_complet
1793
2130
  );
1794
2131
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1795
2132
 
1796
- worker.register_wf(
1797
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1798
- 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<()> {
1799
2141
  let la_res = ctx
1800
- .local_activity(LocalActivityOptions {
1801
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1802
- input: "hi".as_json_payload().expect("serializes fine"),
1803
- retry_policy: RetryPolicy {
1804
- initial_interval: Some(prost_dur!(from_millis(20))),
1805
- backoff_coefficient: 1.0,
1806
- maximum_interval: None,
1807
- maximum_attempts: 5,
1808
- 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()
1809
2155
  },
1810
- start_to_close_timeout: Some(prost_dur!(from_millis(25))),
1811
- ..Default::default()
1812
- })
2156
+ )
1813
2157
  .await;
1814
2158
  if la_completes {
1815
- assert!(la_res.completed_ok());
1816
- } else {
1817
- 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));
1818
2162
  }
1819
- Ok(().into())
1820
- },
1821
- );
1822
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1823
- let cancels: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
1824
- worker.register_activity(
1825
- DEFAULT_ACTIVITY_TYPE,
1826
- 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> {
1827
2180
  // Timeout the first 4 attempts, or all of them if we intend to fail
1828
- if attempts.fetch_add(1, Ordering::AcqRel) < 4 || !la_completes {
2181
+ if self.attempts.fetch_add(1, Ordering::AcqRel) < 4 || !self.la_completes {
1829
2182
  select! {
1830
2183
  _ = tokio::time::sleep(Duration::from_millis(100)) => (),
1831
2184
  _ = ctx.cancelled() => {
1832
- cancels.fetch_add(1, Ordering::AcqRel);
2185
+ self.cancels.fetch_add(1, Ordering::AcqRel);
1833
2186
  return Err(ActivityError::cancelled());
1834
2187
  }
1835
2188
  }
1836
2189
  }
1837
- Ok(())
1838
- },
1839
- );
1840
- worker
1841
- .submit_wf(
1842
- wf_id.to_owned(),
1843
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1844
- vec![],
1845
- WorkflowOptions::default(),
1846
- )
1847
- .await
1848
- .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
+ });
1849
2199
  worker.run_until_done().await.unwrap();
1850
- // Activity should have been attempted all 5 times
1851
2200
  assert_eq!(attempts.load(Ordering::Acquire), 5);
1852
2201
  let num_cancels = if la_completes { 4 } else { 5 };
1853
2202
  assert_eq!(cancels.load(Ordering::Acquire), num_cancels);
@@ -1868,43 +2217,46 @@ async fn wft_failure_cancels_running_las() {
1868
2217
  mh.num_expected_fails = 1;
1869
2218
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1870
2219
 
1871
- worker.register_wf(
1872
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1873
- |ctx: WfContext| async move {
1874
- let la_handle = ctx.local_activity(LocalActivityOptions {
1875
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
1876
- input: "hi".as_json_payload().expect("serializes fine"),
1877
- ..Default::default()
1878
- });
1879
- 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!(
1880
2234
  async {
1881
2235
  ctx.timer(Duration::from_secs(1)).await;
1882
2236
  panic!("ahhh I'm failing wft")
1883
2237
  },
1884
2238
  la_handle
1885
2239
  );
1886
- Ok(().into())
1887
- },
1888
- );
1889
- worker.register_activity(
1890
- DEFAULT_ACTIVITY_TYPE,
1891
- 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> {
1892
2251
  let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
1893
2252
  if res.is_err() {
1894
2253
  panic!("Activity must be cancelled!!!!");
1895
2254
  }
1896
- Result::<(), _>::Err(ActivityError::cancelled())
1897
- },
1898
- );
1899
- worker
1900
- .submit_wf(
1901
- wf_id.to_owned(),
1902
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1903
- vec![],
1904
- WorkflowOptions::default(),
1905
- )
1906
- .await
1907
- .unwrap();
2255
+ Err(ActivityError::cancelled())
2256
+ }
2257
+ }
2258
+
2259
+ worker.register_activities(ActivityThatExpectsCancellation);
1908
2260
  worker.run_until_done().await.unwrap();
1909
2261
  }
1910
2262
 
@@ -1934,32 +2286,30 @@ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
1934
2286
  mh.num_expected_completions = Some(0.into());
1935
2287
  let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
1936
2288
 
1937
- #[allow(unreachable_code)]
1938
- worker.register_wf(
1939
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1940
- WorkflowFunction::new::<_, _, ()>(|ctx: WfContext| async move {
1941
- ctx.local_activity(LocalActivityOptions {
1942
- activity_type: "echo".to_string(),
1943
- input: "hi".as_json_payload().expect("serializes fine"),
1944
- ..Default::default()
1945
- })
1946
- .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)))?;
1947
2307
  panic!()
1948
- }),
1949
- );
1950
- worker.register_activity(
1951
- "echo",
1952
- move |_: ActContext, _: String| async move { Ok(()) },
1953
- );
1954
- worker
1955
- .submit_wf(
1956
- wf_id.to_owned(),
1957
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1958
- vec![],
1959
- WorkflowOptions::default(),
1960
- )
1961
- .await
1962
- .unwrap();
2308
+ }
2309
+ }
2310
+
2311
+ worker.register_workflow::<ResolvedLasNotRecordedIfWftFailsManyTimesWf>();
2312
+ worker.register_activities(StdActivities);
1963
2313
  worker.run_until_done().await.unwrap();
1964
2314
  }
1965
2315
 
@@ -1990,39 +2340,37 @@ async fn local_act_records_nonfirst_attempts_ok() {
1990
2340
  wc.max_outstanding_workflow_tasks = Some(1);
1991
2341
  });
1992
2342
 
1993
- worker.register_wf(
1994
- DEFAULT_WORKFLOW_TYPE.to_owned(),
1995
- |ctx: WfContext| async move {
1996
- ctx.local_activity(LocalActivityOptions {
1997
- activity_type: "echo".to_string(),
1998
- input: "hi".as_json_payload().expect("serializes fine"),
1999
- retry_policy: RetryPolicy {
2000
- initial_interval: Some(prost_dur!(from_millis(10))),
2001
- backoff_coefficient: 1.0,
2002
- maximum_interval: None,
2003
- maximum_attempts: 0,
2004
- 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()
2005
2363
  },
2006
- ..Default::default()
2007
- })
2008
- .await;
2009
- Ok(().into())
2010
- },
2011
- );
2012
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
2013
- Result::<(), _>::Err(anyhow!("I fail").into())
2014
- });
2015
- worker
2016
- .submit_wf(
2017
- wf_id.to_owned(),
2018
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2019
- vec![],
2020
- WorkflowOptions::default(),
2021
- )
2022
- .await
2023
- .unwrap();
2364
+ )
2365
+ .await
2366
+ .ok();
2367
+ Ok(())
2368
+ }
2369
+ }
2370
+
2371
+ worker.register_workflow::<LocalActRecordsNonfirstAttemptsOkWf>();
2372
+ worker.register_activities(StdActivities);
2024
2373
  worker.run_until_done().await.unwrap();
2025
- // 3 workflow tasks
2026
2374
  assert_eq!(nonfirst_counts.len(), 3);
2027
2375
  // First task's non-first count should, of course, be 0
2028
2376
  assert_eq!(nonfirst_counts.pop().unwrap(), 0);
@@ -2308,70 +2656,92 @@ async fn local_act_retry_explicit_delay() {
2308
2656
  let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
2309
2657
  let mut worker = mock_sdk(mh);
2310
2658
 
2311
- worker.register_wf(
2312
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2313
- 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<()> {
2314
2667
  let la_res = ctx
2315
- .local_activity(LocalActivityOptions {
2316
- activity_type: "echo".to_string(),
2317
- input: "hi".as_json_payload().expect("serializes fine"),
2318
- retry_policy: RetryPolicy {
2319
- initial_interval: Some(prost_dur!(from_millis(50))),
2320
- backoff_coefficient: 1.0,
2321
- 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
+ },
2322
2678
  ..Default::default()
2323
2679
  },
2324
- ..Default::default()
2325
- })
2680
+ )
2326
2681
  .await;
2327
- assert!(la_res.completed_ok());
2328
- Ok(().into())
2329
- },
2330
- );
2331
- let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
2332
- worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
2333
- // Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
2334
- let last_attempt = attempts.fetch_add(1, Ordering::Relaxed);
2335
- if 0 == last_attempt {
2336
- Err(ActivityError::Retryable {
2337
- source: anyhow!("Explicit backoff error"),
2338
- explicit_delay: Some(Duration::from_millis(300)),
2339
- })
2340
- } else if 2 == last_attempt {
2682
+ assert!(la_res.is_ok());
2341
2683
  Ok(())
2342
- } else {
2343
- Err(anyhow!("Oh no I failed!").into())
2344
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(),
2345
2714
  });
2346
- worker
2347
- .submit_wf(
2348
- wf_id.to_owned(),
2349
- DEFAULT_WORKFLOW_TYPE.to_owned(),
2350
- vec![],
2351
- WorkflowOptions::default(),
2352
- )
2353
- .await
2354
- .unwrap();
2355
2715
  let start = Instant::now();
2356
2716
  worker.run_until_done().await.unwrap();
2357
2717
  let expected_attempts = 3;
2358
2718
  assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
2359
- // There will be one 300ms backoff and one 50s backoff, so things should take at least that long
2360
2719
  assert!(start.elapsed() > Duration::from_millis(350));
2361
2720
  }
2362
2721
 
2363
- async fn la_wf(ctx: WfContext) -> WorkflowResult<()> {
2364
- ctx.local_activity(LocalActivityOptions {
2365
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2366
- input: ().as_json_payload().unwrap(),
2367
- retry_policy: RetryPolicy {
2368
- maximum_attempts: 1,
2369
- ..Default::default()
2370
- },
2371
- ..Default::default()
2372
- })
2373
- .await;
2374
- 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
+ }
2375
2745
  }
2376
2746
 
2377
2747
  #[rstest]
@@ -2440,53 +2810,81 @@ async fn one_la_success(#[case] replay: bool, #[case] completes_ok: bool) {
2440
2810
  });
2441
2811
 
2442
2812
  let mut worker = build_fake_sdk(mock_cfg);
2443
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
2444
- worker.register_activity(
2445
- DEFAULT_ACTIVITY_TYPE,
2446
- move |_ctx: ActContext, _: ()| async move {
2447
- 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 {
2448
2829
  panic!("Should not be invoked on replay");
2449
2830
  }
2450
- if completes_ok {
2451
- Ok("hi")
2831
+ if self.completes_ok {
2832
+ Ok("hi".to_string())
2452
2833
  } else {
2453
2834
  Err(anyhow!("Oh no I failed!").into())
2454
2835
  }
2455
- },
2456
- );
2836
+ }
2837
+ }
2838
+
2839
+ worker.register_activities(ActivityWithReplayCheck {
2840
+ replay,
2841
+ completes_ok,
2842
+ });
2457
2843
  worker.run().await.unwrap();
2458
2844
  }
2459
2845
 
2460
- async fn two_la_wf(ctx: WfContext) -> WorkflowResult<()> {
2461
- ctx.local_activity(LocalActivityOptions {
2462
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2463
- input: ().as_json_payload().unwrap(),
2464
- ..Default::default()
2465
- })
2466
- .await;
2467
- ctx.local_activity(LocalActivityOptions {
2468
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2469
- input: ().as_json_payload().unwrap(),
2470
- ..Default::default()
2471
- })
2472
- .await;
2473
- 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
+ }
2474
2862
  }
2475
2863
 
2476
- async fn two_la_wf_parallel(ctx: WfContext) -> WorkflowResult<()> {
2477
- tokio::join!(
2478
- ctx.local_activity(LocalActivityOptions {
2479
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2480
- input: ().as_json_payload().unwrap(),
2481
- ..Default::default()
2482
- }),
2483
- ctx.local_activity(LocalActivityOptions {
2484
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2485
- input: ().as_json_payload().unwrap(),
2486
- ..Default::default()
2487
- })
2488
- );
2489
- 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
+ }
2490
2888
  }
2491
2889
 
2492
2890
  #[rstest]
@@ -2574,32 +2972,31 @@ async fn two_sequential_las(
2574
2972
  let mut worker = build_fake_sdk(mock_cfg);
2575
2973
  worker.set_worker_interceptor(aai);
2576
2974
  if parallel {
2577
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf_parallel);
2975
+ worker.register_workflow::<TwoLaWfParallel>();
2578
2976
  } else {
2579
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf);
2977
+ worker.register_workflow::<TwoLaWf>();
2580
2978
  }
2581
- worker.register_activity(
2582
- DEFAULT_ACTIVITY_TYPE,
2583
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2584
- );
2979
+ worker.register_activities(ResolvedActivity);
2585
2980
  worker.run().await.unwrap();
2586
2981
  }
2587
2982
 
2588
- async fn la_timer_la(ctx: WfContext) -> WorkflowResult<()> {
2589
- ctx.local_activity(LocalActivityOptions {
2590
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2591
- input: ().as_json_payload().unwrap(),
2592
- ..Default::default()
2593
- })
2594
- .await;
2595
- ctx.timer(Duration::from_secs(5)).await;
2596
- ctx.local_activity(LocalActivityOptions {
2597
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2598
- input: ().as_json_payload().unwrap(),
2599
- ..Default::default()
2600
- })
2601
- .await;
2602
- 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
+ }
2603
3000
  }
2604
3001
 
2605
3002
  #[rstest]
@@ -2672,11 +3069,8 @@ async fn las_separated_by_timer(#[case] replay: bool) {
2672
3069
 
2673
3070
  let mut worker = build_fake_sdk(mock_cfg);
2674
3071
  worker.set_worker_interceptor(aai);
2675
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_timer_la);
2676
- worker.register_activity(
2677
- DEFAULT_ACTIVITY_TYPE,
2678
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2679
- );
3072
+ worker.register_workflow::<LaTimerLaWf>();
3073
+ worker.register_activities(ResolvedActivity);
2680
3074
  worker.run().await.unwrap();
2681
3075
  }
2682
3076
 
@@ -2707,11 +3101,8 @@ async fn one_la_heartbeating_wft_failure_still_executes() {
2707
3101
  });
2708
3102
 
2709
3103
  let mut worker = build_fake_sdk(mock_cfg);
2710
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
2711
- worker.register_activity(
2712
- DEFAULT_ACTIVITY_TYPE,
2713
- move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
2714
- );
3104
+ worker.register_workflow::<LaWf>();
3105
+ worker.register_activities(ResolvedActivity);
2715
3106
  worker.run().await.unwrap();
2716
3107
  }
2717
3108
 
@@ -2727,6 +3118,7 @@ async fn immediate_cancel(
2727
3118
  ) {
2728
3119
  let mut t = TestHistoryBuilder::default();
2729
3120
  t.add_by_type(EventType::WorkflowExecutionStarted);
3121
+ t.set_wf_input(cancel_type.as_json_payload().unwrap());
2730
3122
  t.add_full_wf_task();
2731
3123
  t.add_workflow_execution_completed();
2732
3124
 
@@ -2744,16 +3136,33 @@ async fn immediate_cancel(
2744
3136
  });
2745
3137
 
2746
3138
  let mut worker = build_fake_sdk(mock_cfg);
2747
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
2748
- let la = ctx.local_activity(LocalActivityOptions {
2749
- cancel_type,
2750
- ..Default::default()
2751
- });
2752
- la.cancel(&ctx);
2753
- la.await;
2754
- Ok(().into())
2755
- });
2756
- // 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>();
2757
3166
  worker.run().await.unwrap();
2758
3167
  }
2759
3168
 
@@ -2772,6 +3181,7 @@ async fn cancel_after_act_starts_canned(
2772
3181
  ) {
2773
3182
  let mut t = TestHistoryBuilder::default();
2774
3183
  t.add_wfe_started_with_wft_timeout(Duration::from_millis(100));
3184
+ t.set_wf_input(cancel_type.as_json_payload().unwrap());
2775
3185
  t.add_full_wf_task();
2776
3186
  let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
2777
3187
  t.add_timer_fired(timer_started_event_id, "1".to_string());
@@ -2841,40 +3251,69 @@ async fn cancel_after_act_starts_canned(
2841
3251
  }
2842
3252
 
2843
3253
  let mut worker = build_fake_sdk(mock_cfg);
2844
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
2845
- let la = ctx.local_activity(LocalActivityOptions {
2846
- cancel_type,
2847
- input: ().as_json_payload().unwrap(),
2848
- activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
2849
- ..Default::default()
2850
- });
2851
- ctx.timer(Duration::from_secs(1)).await;
2852
- la.cancel(&ctx);
2853
- // This extra timer is here to ensure the presence of another WF task doesn't mess up
2854
- // resolving the LA with cancel on replay
2855
- ctx.timer(Duration::from_secs(1)).await;
2856
- let resolution = la.await;
2857
- assert!(resolution.cancelled());
2858
- let rfail = resolution.unwrap_failure();
2859
- assert_matches!(
2860
- rfail.failure_info,
2861
- Some(FailureInfo::ActivityFailureInfo(_))
2862
- );
2863
- assert_matches!(
2864
- rfail.cause.unwrap().failure_info,
2865
- Some(FailureInfo::CanceledFailureInfo(_))
2866
- );
2867
- Ok(().into())
2868
- });
2869
- worker.register_activity(DEFAULT_ACTIVITY_TYPE, move |ctx: ActContext, _: ()| {
2870
- let allow_cancel_barr_clone = allow_cancel_barr_clone.clone();
2871
- async move {
2872
- 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 {
2873
3307
  ctx.cancelled().await;
2874
3308
  }
2875
- allow_cancel_barr_clone.cancelled().await;
2876
- Result::<(), _>::Err(ActivityError::cancelled())
3309
+ self.allow_cancel_barr.cancelled().await;
3310
+ Err(ActivityError::cancelled())
2877
3311
  }
3312
+ }
3313
+
3314
+ worker.register_activities(ActivityWithConditionalCancelWait {
3315
+ cancel_type,
3316
+ allow_cancel_barr: allow_cancel_barr_clone,
2878
3317
  });
2879
3318
  worker.run().await.unwrap();
2880
3319
  }