@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,4 +1,6 @@
1
- use crate::common::{ActivationAssertionsInterceptor, CoreWfStarter, build_fake_sdk};
1
+ use crate::common::{
2
+ ActivationAssertionsInterceptor, CoreWfStarter, WorkflowHandleExt, build_fake_sdk,
3
+ };
2
4
  use std::{
3
5
  collections::{HashSet, VecDeque, hash_map::RandomState},
4
6
  sync::{
@@ -7,59 +9,74 @@ use std::{
7
9
  },
8
10
  time::Duration,
9
11
  };
10
- use temporalio_client::WorkflowClientTrait;
11
- use temporalio_common::protos::{
12
- DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, VERSION_SEARCH_ATTR_KEY,
13
- constants::PATCH_MARKER_NAME,
14
- coresdk::{
15
- AsJsonPayloadExt, FromJsonPayloadExt,
16
- common::decode_change_marker_details,
17
- workflow_activation::{NotifyHasPatch, WorkflowActivationJob, workflow_activation_job},
18
- },
19
- temporal::api::{
20
- command::v1::{
21
- RecordMarkerCommandAttributes, ScheduleActivityTaskCommandAttributes,
22
- UpsertWorkflowSearchAttributesCommandAttributes, command::Attributes,
12
+ use temporalio_client::{WorkflowSignalOptions, WorkflowStartOptions};
13
+ use temporalio_common::{
14
+ data_converters::RawValue,
15
+ protos::{
16
+ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, VERSION_SEARCH_ATTR_KEY,
17
+ constants::PATCH_MARKER_NAME,
18
+ coresdk::{
19
+ AsJsonPayloadExt, FromJsonPayloadExt,
20
+ common::decode_change_marker_details,
21
+ workflow_activation::{NotifyHasPatch, WorkflowActivationJob, workflow_activation_job},
23
22
  },
24
- common::v1::ActivityType,
25
- enums::v1::{CommandType, EventType},
26
- history::v1::{
27
- ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes,
28
- ActivityTaskStartedEventAttributes, TimerFiredEventAttributes,
23
+ temporal::api::{
24
+ command::v1::{
25
+ RecordMarkerCommandAttributes, ScheduleActivityTaskCommandAttributes,
26
+ UpsertWorkflowSearchAttributesCommandAttributes, command::Attributes,
27
+ },
28
+ common::v1::ActivityType,
29
+ enums::v1::{CommandType, EventType},
30
+ history::v1::{
31
+ ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes,
32
+ ActivityTaskStartedEventAttributes, TimerFiredEventAttributes,
33
+ history_event::Attributes as EventAttributes,
34
+ },
29
35
  },
30
36
  },
31
37
  };
32
38
 
33
39
  use temporalio_common::worker::WorkerTaskTypes;
34
- use temporalio_sdk::{ActivityOptions, WfContext, WorkflowResult};
40
+ use temporalio_macros::{activities, workflow, workflow_methods};
41
+ use temporalio_sdk::{
42
+ ActivityOptions, SyncWorkflowContext, WorkflowContext, WorkflowResult,
43
+ activities::{ActivityContext, ActivityError},
44
+ };
35
45
  use temporalio_sdk_core::test_help::{CoreInternalFlags, MockPollCfg, ResponseType};
36
46
  use tokio::{join, sync::Notify};
37
- use tokio_stream::StreamExt;
38
47
 
39
48
  const MY_PATCH_ID: &str = "integ_test_change_name";
40
49
 
41
- pub(crate) async fn changes_wf(ctx: WfContext) -> WorkflowResult<()> {
42
- if ctx.patched(MY_PATCH_ID) {
43
- ctx.timer(Duration::from_millis(100)).await;
44
- } else {
45
- ctx.timer(Duration::from_millis(200)).await;
46
- }
47
- ctx.timer(Duration::from_millis(200)).await;
48
- if ctx.patched(MY_PATCH_ID) {
49
- ctx.timer(Duration::from_millis(100)).await;
50
- } else {
50
+ #[workflow]
51
+ #[derive(Default)]
52
+ pub(crate) struct ChangesWf;
53
+
54
+ #[workflow_methods]
55
+ impl ChangesWf {
56
+ #[run(name = "writes_change_markers")]
57
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
58
+ if ctx.patched(MY_PATCH_ID) {
59
+ ctx.timer(Duration::from_millis(100)).await;
60
+ } else {
61
+ ctx.timer(Duration::from_millis(200)).await;
62
+ }
51
63
  ctx.timer(Duration::from_millis(200)).await;
64
+ if ctx.patched(MY_PATCH_ID) {
65
+ ctx.timer(Duration::from_millis(100)).await;
66
+ } else {
67
+ ctx.timer(Duration::from_millis(200)).await;
68
+ }
69
+ Ok(())
52
70
  }
53
- Ok(().into())
54
71
  }
55
72
 
56
73
  #[tokio::test]
57
74
  async fn writes_change_markers() {
58
75
  let wf_name = "writes_change_markers";
59
76
  let mut starter = CoreWfStarter::new(wf_name);
60
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
77
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
61
78
  let mut worker = starter.worker().await;
62
- worker.register_wf(wf_name.to_owned(), changes_wf);
79
+ worker.register_workflow::<ChangesWf>();
63
80
 
64
81
  starter.start_with_worker(wf_name, &mut worker).await;
65
82
  worker.run_until_done().await.unwrap();
@@ -67,57 +84,77 @@ async fn writes_change_markers() {
67
84
 
68
85
  /// This one simulates a run as if the worker had the "old" code, then it fails at the end as
69
86
  /// a cheapo way of being re-run, at which point it runs with change checks and the "new" code.
70
- static DID_DIE: AtomicBool = AtomicBool::new(false);
87
+ #[workflow]
88
+ pub(crate) struct NoChangeThenChangeWf {
89
+ did_die: Arc<AtomicBool>,
90
+ }
71
91
 
72
- pub(crate) async fn no_change_then_change_wf(ctx: WfContext) -> WorkflowResult<()> {
73
- if DID_DIE.load(Ordering::Acquire) {
74
- assert!(!ctx.patched(MY_PATCH_ID));
75
- }
76
- ctx.timer(Duration::from_millis(200)).await;
77
- ctx.timer(Duration::from_millis(200)).await;
78
- if DID_DIE.load(Ordering::Acquire) {
79
- assert!(!ctx.patched(MY_PATCH_ID));
80
- }
81
- ctx.timer(Duration::from_millis(200)).await;
92
+ #[workflow_methods(factory_only)]
93
+ impl NoChangeThenChangeWf {
94
+ #[run(name = "can_add_change_markers")]
95
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
96
+ if ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
97
+ assert!(!ctx.patched(MY_PATCH_ID));
98
+ }
99
+ ctx.timer(Duration::from_millis(200)).await;
100
+ ctx.timer(Duration::from_millis(200)).await;
101
+ if ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
102
+ assert!(!ctx.patched(MY_PATCH_ID));
103
+ }
104
+ ctx.timer(Duration::from_millis(200)).await;
82
105
 
83
- if !DID_DIE.load(Ordering::Acquire) {
84
- DID_DIE.store(true, Ordering::Release);
85
- ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
106
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
107
+ ctx.state(|wf| wf.did_die.store(true, Ordering::Release));
108
+ ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
109
+ }
110
+ Ok(())
86
111
  }
87
- Ok(().into())
88
112
  }
89
113
 
90
114
  #[tokio::test]
91
115
  async fn can_add_change_markers() {
92
116
  let wf_name = "can_add_change_markers";
93
117
  let mut starter = CoreWfStarter::new(wf_name);
94
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
118
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
95
119
  let mut worker = starter.worker().await;
96
- worker.register_wf(wf_name.to_owned(), no_change_then_change_wf);
120
+ let did_die = Arc::new(AtomicBool::new(false));
121
+ worker.register_workflow_with_factory(move || NoChangeThenChangeWf {
122
+ did_die: did_die.clone(),
123
+ });
97
124
 
98
125
  starter.start_with_worker(wf_name, &mut worker).await;
99
126
  worker.run_until_done().await.unwrap();
100
127
  }
101
128
 
102
- static DID_DIE_2: AtomicBool = AtomicBool::new(false);
129
+ #[workflow]
130
+ pub(crate) struct ReplayWithChangeMarkerWf {
131
+ did_die: Arc<AtomicBool>,
132
+ }
103
133
 
104
- pub(crate) async fn replay_with_change_marker_wf(ctx: WfContext) -> WorkflowResult<()> {
105
- assert!(ctx.patched(MY_PATCH_ID));
106
- ctx.timer(Duration::from_millis(200)).await;
107
- if !DID_DIE_2.load(Ordering::Acquire) {
108
- DID_DIE_2.store(true, Ordering::Release);
109
- ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
134
+ #[workflow_methods(factory_only)]
135
+ impl ReplayWithChangeMarkerWf {
136
+ #[run(name = "replaying_with_patch_marker")]
137
+ pub(crate) async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
138
+ assert!(ctx.patched(MY_PATCH_ID));
139
+ ctx.timer(Duration::from_millis(200)).await;
140
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
141
+ ctx.state(|wf| wf.did_die.store(true, Ordering::Release));
142
+ ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
143
+ }
144
+ Ok(())
110
145
  }
111
- Ok(().into())
112
146
  }
113
147
 
114
148
  #[tokio::test]
115
149
  async fn replaying_with_patch_marker() {
116
150
  let wf_name = "replaying_with_patch_marker";
117
151
  let mut starter = CoreWfStarter::new(wf_name);
118
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
152
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
119
153
  let mut worker = starter.worker().await;
120
- worker.register_wf(wf_name.to_owned(), replay_with_change_marker_wf);
154
+ let did_die = Arc::new(AtomicBool::new(false));
155
+ worker.register_workflow_with_factory(move || ReplayWithChangeMarkerWf {
156
+ did_die: did_die.clone(),
157
+ });
121
158
 
122
159
  starter.start_with_worker(wf_name, &mut worker).await;
123
160
  worker.run_until_done().await.unwrap();
@@ -126,98 +163,148 @@ async fn replaying_with_patch_marker() {
126
163
  /// Test that the internal patching mechanism works on the second workflow task when replaying.
127
164
  /// Used as regression test for a bug that detected that we did not look ahead far enough to find
128
165
  /// the next workflow task completion, which the flags are attached to.
166
+ #[workflow]
167
+ struct TimerPatchedTimerWf {
168
+ fail_once: Arc<AtomicBool>,
169
+ }
170
+
171
+ #[workflow_methods(factory_only)]
172
+ impl TimerPatchedTimerWf {
173
+ #[run(name = "timer_patched_timer")]
174
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
175
+ ctx.timer(Duration::from_millis(1)).await;
176
+ if ctx.state(|wf| wf.fail_once.load(Ordering::Acquire)) {
177
+ ctx.state(|wf| wf.fail_once.store(false, Ordering::Release));
178
+ panic!("Enchi is hungry!");
179
+ }
180
+ assert!(ctx.patched(MY_PATCH_ID));
181
+ ctx.timer(Duration::from_millis(1)).await;
182
+ Ok(())
183
+ }
184
+ }
185
+
129
186
  #[tokio::test]
130
187
  async fn patched_on_second_workflow_task_is_deterministic() {
131
188
  let wf_name = "timer_patched_timer";
132
189
  let mut starter = CoreWfStarter::new(wf_name);
133
190
  // Disable caching to force replay from beginning
134
- starter.worker_config.max_cached_workflows = 0_usize;
135
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
191
+ starter.sdk_config.max_cached_workflows = 0_usize;
192
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
136
193
  let mut worker = starter.worker().await;
137
- // Include a task failure as well to make sure that works
138
- static FAIL_ONCE: AtomicBool = AtomicBool::new(true);
139
- worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
140
- ctx.timer(Duration::from_millis(1)).await;
141
- if FAIL_ONCE.load(Ordering::Acquire) {
142
- FAIL_ONCE.store(false, Ordering::Release);
143
- panic!("Enchi is hungry!");
144
- }
145
- assert!(ctx.patched(MY_PATCH_ID));
146
- ctx.timer(Duration::from_millis(1)).await;
147
- Ok(().into())
194
+ let fail_once = Arc::new(AtomicBool::new(true));
195
+ worker.register_workflow_with_factory(move || TimerPatchedTimerWf {
196
+ fail_once: fail_once.clone(),
148
197
  });
149
198
 
150
199
  starter.start_with_worker(wf_name, &mut worker).await;
151
200
  worker.run_until_done().await.unwrap();
152
201
  }
153
202
 
203
+ #[workflow]
204
+ struct RemoveDeprecatedPatchNearOtherPatchWf {
205
+ did_die: Arc<AtomicBool>,
206
+ }
207
+
208
+ #[workflow_methods(factory_only)]
209
+ impl RemoveDeprecatedPatchNearOtherPatchWf {
210
+ #[run(name = "can_add_change_markers")]
211
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
212
+ ctx.timer(Duration::from_millis(200)).await;
213
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
214
+ assert!(ctx.deprecate_patch("getting-deprecated"));
215
+ assert!(ctx.patched("staying"));
216
+ } else {
217
+ assert!(ctx.patched("staying"));
218
+ }
219
+ ctx.timer(Duration::from_millis(200)).await;
220
+
221
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
222
+ ctx.state(|wf| wf.did_die.store(true, Ordering::Release));
223
+ ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
224
+ }
225
+ Ok(())
226
+ }
227
+ }
228
+
154
229
  #[tokio::test]
155
230
  async fn can_remove_deprecated_patch_near_other_patch() {
156
231
  let wf_name = "can_add_change_markers";
157
232
  let mut starter = CoreWfStarter::new(wf_name);
158
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
233
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
159
234
  let mut worker = starter.worker().await;
160
235
  let did_die = Arc::new(AtomicBool::new(false));
161
- worker.register_wf(wf_name.to_owned(), move |ctx: WfContext| {
162
- let did_die = did_die.clone();
163
- async move {
164
- ctx.timer(Duration::from_millis(200)).await;
165
- if !did_die.load(Ordering::Acquire) {
166
- assert!(ctx.deprecate_patch("getting-deprecated"));
167
- assert!(ctx.patched("staying"));
168
- } else {
169
- assert!(ctx.patched("staying"));
170
- }
171
- ctx.timer(Duration::from_millis(200)).await;
172
-
173
- if !did_die.load(Ordering::Acquire) {
174
- did_die.store(true, Ordering::Release);
175
- ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
176
- }
177
- Ok(().into())
178
- }
236
+ worker.register_workflow_with_factory(move || RemoveDeprecatedPatchNearOtherPatchWf {
237
+ did_die: did_die.clone(),
179
238
  });
180
239
 
181
240
  starter.start_with_worker(wf_name, &mut worker).await;
182
241
  worker.run_until_done().await.unwrap();
183
242
  }
184
243
 
244
+ #[workflow]
245
+ struct DeprecatedPatchRemovalWf {
246
+ did_die: Arc<AtomicBool>,
247
+ notify: Arc<Notify>,
248
+ signal_received: bool,
249
+ }
250
+
251
+ #[workflow_methods(factory_only)]
252
+ impl DeprecatedPatchRemovalWf {
253
+ #[run(name = "deprecated_patch_removal")]
254
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
255
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
256
+ assert!(ctx.deprecate_patch("getting-deprecated"));
257
+ }
258
+ ctx.state(|wf| wf.notify.notify_one());
259
+ ctx.wait_condition(|s| s.signal_received).await;
260
+
261
+ ctx.timer(Duration::from_millis(1)).await;
262
+
263
+ if !ctx.state(|wf| wf.did_die.load(Ordering::Acquire)) {
264
+ ctx.state(|wf| wf.did_die.store(true, Ordering::Release));
265
+ ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
266
+ }
267
+ Ok(())
268
+ }
269
+
270
+ #[signal]
271
+ fn handle_sig(&mut self, _ctx: &mut SyncWorkflowContext<Self>) {
272
+ self.signal_received = true;
273
+ }
274
+ }
275
+
185
276
  #[tokio::test]
186
277
  async fn deprecated_patch_removal() {
187
278
  let wf_name = "deprecated_patch_removal";
188
279
  let mut starter = CoreWfStarter::new(wf_name);
189
- starter.worker_config.task_types = WorkerTaskTypes::workflow_only();
280
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
190
281
  let mut worker = starter.worker().await;
191
- let client = starter.get_client().await;
192
282
  let wf_id = starter.get_task_queue().to_string();
193
283
  let did_die = Arc::new(AtomicBool::new(false));
194
284
  let send_sig = Arc::new(Notify::new());
195
- let send_sig_c = send_sig.clone();
196
- worker.register_wf(wf_name, move |ctx: WfContext| {
197
- let did_die = did_die.clone();
198
- let send_sig_c = send_sig_c.clone();
199
- async move {
200
- if !did_die.load(Ordering::Acquire) {
201
- assert!(ctx.deprecate_patch("getting-deprecated"));
202
- }
203
- send_sig_c.notify_one();
204
- ctx.make_signal_channel("sig").next().await;
205
-
206
- ctx.timer(Duration::from_millis(1)).await;
207
-
208
- if !did_die.load(Ordering::Acquire) {
209
- did_die.store(true, Ordering::Release);
210
- ctx.force_task_fail(anyhow::anyhow!("i'm ded"));
211
- }
212
- Ok(().into())
213
- }
285
+ let send_sig_clone = send_sig.clone();
286
+ worker.register_workflow_with_factory(move || DeprecatedPatchRemovalWf {
287
+ did_die: did_die.clone(),
288
+ notify: send_sig_clone.clone(),
289
+ signal_received: false,
214
290
  });
215
291
 
216
- starter.start_with_worker(wf_name, &mut worker).await;
292
+ let handle = worker
293
+ .submit_workflow(
294
+ DeprecatedPatchRemovalWf::run,
295
+ (),
296
+ WorkflowStartOptions::new(wf_id.clone(), wf_id).build(),
297
+ )
298
+ .await
299
+ .unwrap();
217
300
  let sig_fut = async {
218
301
  send_sig.notified().await;
219
- client
220
- .signal_workflow_execution(wf_id, "".to_string(), "sig".to_string(), None, None)
302
+ handle
303
+ .signal(
304
+ DeprecatedPatchRemovalWf::handle_sig,
305
+ (),
306
+ WorkflowSignalOptions::default(),
307
+ )
221
308
  .await
222
309
  .unwrap()
223
310
  };
@@ -312,47 +399,81 @@ fn patch_marker_single_activity(
312
399
  t
313
400
  }
314
401
 
315
- async fn v1(ctx: &mut WfContext) {
316
- ctx.activity(ActivityOptions {
317
- activity_id: Some("no_change".to_owned()),
318
- ..Default::default()
319
- })
320
- .await;
402
+ struct FakeAct;
403
+ #[activities]
404
+ impl FakeAct {
405
+ #[activity(name = "")]
406
+ fn nameless(_: ActivityContext) -> Result<RawValue, ActivityError> {
407
+ unimplemented!()
408
+ }
321
409
  }
322
410
 
323
- async fn v2(ctx: &mut WfContext) -> bool {
324
- if ctx.patched(MY_PATCH_ID) {
325
- ctx.activity(ActivityOptions {
326
- activity_id: Some("had_change".to_owned()),
327
- ..Default::default()
328
- })
411
+ async fn v1(ctx: &mut WorkflowContext<PatchWf>) {
412
+ let _ = ctx
413
+ .start_activity(
414
+ FakeAct::nameless,
415
+ (),
416
+ ActivityOptions {
417
+ activity_id: Some("no_change".to_owned()),
418
+ ..Default::default()
419
+ },
420
+ )
329
421
  .await;
422
+ }
423
+
424
+ async fn v2(ctx: &mut WorkflowContext<PatchWf>) -> bool {
425
+ if ctx.patched(MY_PATCH_ID) {
426
+ let _ = ctx
427
+ .start_activity(
428
+ FakeAct::nameless,
429
+ (),
430
+ ActivityOptions {
431
+ activity_id: Some("had_change".to_owned()),
432
+ ..Default::default()
433
+ },
434
+ )
435
+ .await;
330
436
  true
331
437
  } else {
332
- ctx.activity(ActivityOptions {
333
- activity_id: Some("no_change".to_owned()),
334
- ..Default::default()
335
- })
336
- .await;
438
+ let _ = ctx
439
+ .start_activity(
440
+ FakeAct::nameless,
441
+ (),
442
+ ActivityOptions {
443
+ activity_id: Some("no_change".to_owned()),
444
+ ..Default::default()
445
+ },
446
+ )
447
+ .await;
337
448
  false
338
449
  }
339
450
  }
340
451
 
341
- async fn v3(ctx: &mut WfContext) {
452
+ async fn v3(ctx: &mut WorkflowContext<PatchWf>) {
342
453
  ctx.deprecate_patch(MY_PATCH_ID);
343
- ctx.activity(ActivityOptions {
344
- activity_id: Some("had_change".to_owned()),
345
- ..Default::default()
346
- })
347
- .await;
454
+ let _ = ctx
455
+ .start_activity(
456
+ FakeAct::nameless,
457
+ (),
458
+ ActivityOptions {
459
+ activity_id: Some("had_change".to_owned()),
460
+ ..Default::default()
461
+ },
462
+ )
463
+ .await;
348
464
  }
349
465
 
350
- async fn v4(ctx: &mut WfContext) {
351
- ctx.activity(ActivityOptions {
352
- activity_id: Some("had_change".to_owned()),
353
- ..Default::default()
354
- })
355
- .await;
466
+ async fn v4(ctx: &mut WorkflowContext<PatchWf>) {
467
+ let _ = ctx
468
+ .start_activity(
469
+ FakeAct::nameless,
470
+ (),
471
+ ActivityOptions {
472
+ activity_id: Some("had_change".to_owned()),
473
+ ..Default::default()
474
+ },
475
+ )
476
+ .await;
356
477
  }
357
478
 
358
479
  fn patch_setup(replaying: bool, marker_type: MarkerType, workflow_version: usize) -> MockPollCfg {
@@ -364,27 +485,32 @@ fn patch_setup(replaying: bool, marker_type: MarkerType, workflow_version: usize
364
485
  }
365
486
  }
366
487
 
367
- macro_rules! patch_wf {
368
- ($workflow_version:ident) => {
369
- move |mut ctx: WfContext| async move {
370
- match $workflow_version {
371
- 1 => {
372
- v1(&mut ctx).await;
373
- }
374
- 2 => {
375
- v2(&mut ctx).await;
376
- }
377
- 3 => {
378
- v3(&mut ctx).await;
379
- }
380
- 4 => {
381
- v4(&mut ctx).await;
382
- }
383
- _ => panic!("Invalid workflow version for test setup"),
488
+ #[workflow]
489
+ struct PatchWf {
490
+ version: usize,
491
+ }
492
+
493
+ #[workflow_methods(factory_only)]
494
+ impl PatchWf {
495
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
496
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
497
+ match ctx.state(|wf| wf.version) {
498
+ 1 => {
499
+ v1(ctx).await;
500
+ }
501
+ 2 => {
502
+ v2(ctx).await;
503
+ }
504
+ 3 => {
505
+ v3(ctx).await;
506
+ }
507
+ 4 => {
508
+ v4(ctx).await;
384
509
  }
385
- Ok(().into())
510
+ _ => panic!("Invalid workflow version for test setup"),
386
511
  }
387
- };
512
+ Ok(())
513
+ }
388
514
  }
389
515
 
390
516
  #[rstest]
@@ -436,7 +562,9 @@ async fn v1_and_v4_changes(
436
562
 
437
563
  let mut worker = build_fake_sdk(mock_cfg);
438
564
  worker.set_worker_interceptor(aai);
439
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, patch_wf!(wf_version));
565
+ worker.register_workflow_with_factory(move || PatchWf {
566
+ version: wf_version,
567
+ });
440
568
  worker.run().await.unwrap();
441
569
  }
442
570
 
@@ -541,10 +669,39 @@ async fn v2_and_v3_changes(
541
669
 
542
670
  let mut worker = build_fake_sdk(mock_cfg);
543
671
  worker.set_worker_interceptor(aai);
544
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, patch_wf!(wf_version));
672
+ worker.register_workflow_with_factory(move || PatchWf {
673
+ version: wf_version,
674
+ });
545
675
  worker.run().await.unwrap();
546
676
  }
547
677
 
678
+ #[workflow]
679
+ #[derive(Default)]
680
+ struct SameChangeMultipleSpotsWf;
681
+
682
+ #[workflow_methods]
683
+ impl SameChangeMultipleSpotsWf {
684
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
685
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
686
+ if ctx.patched(MY_PATCH_ID) {
687
+ let _ = ctx
688
+ .start_activity(FakeAct::nameless, (), ActivityOptions::default())
689
+ .await;
690
+ } else {
691
+ ctx.timer(ONE_SECOND).await;
692
+ }
693
+ ctx.timer(ONE_SECOND).await;
694
+ if ctx.patched(MY_PATCH_ID) {
695
+ let _ = ctx
696
+ .start_activity(FakeAct::nameless, (), ActivityOptions::default())
697
+ .await;
698
+ } else {
699
+ ctx.timer(ONE_SECOND).await;
700
+ }
701
+ Ok(())
702
+ }
703
+ }
704
+
548
705
  #[rstest]
549
706
  #[case::has_change_replay(true, true)]
550
707
  #[case::no_change_replay(false, true)]
@@ -634,26 +791,30 @@ async fn same_change_multiple_spots(#[case] have_marker_in_hist: bool, #[case] r
634
791
  MockPollCfg::from_hist_builder(t)
635
792
  };
636
793
 
637
- // Errors would appear as nondeterminism problems, so just run it.
638
794
  let mut worker = build_fake_sdk(mock_cfg);
639
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
640
- if ctx.patched(MY_PATCH_ID) {
641
- ctx.activity(ActivityOptions::default()).await;
642
- } else {
643
- ctx.timer(ONE_SECOND).await;
644
- }
645
- ctx.timer(ONE_SECOND).await;
646
- if ctx.patched(MY_PATCH_ID) {
647
- ctx.activity(ActivityOptions::default()).await;
648
- } else {
649
- ctx.timer(ONE_SECOND).await;
650
- }
651
- Ok(().into())
652
- });
795
+ worker.register_workflow::<SameChangeMultipleSpotsWf>();
653
796
  worker.run().await.unwrap();
654
797
  }
655
798
 
656
799
  const SIZE_OVERFLOW_PATCH_AMOUNT: usize = 180;
800
+
801
+ #[workflow]
802
+ struct ManyPatchesWf {
803
+ num_patches: usize,
804
+ }
805
+
806
+ #[workflow_methods(factory_only)]
807
+ impl ManyPatchesWf {
808
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
809
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
810
+ for i in 1..=ctx.state(|wf| wf.num_patches) {
811
+ let _dontcare = ctx.patched(&format!("patch-{i}"));
812
+ ctx.timer(ONE_SECOND).await;
813
+ }
814
+ Ok(())
815
+ }
816
+ }
817
+
657
818
  #[rstest]
658
819
  #[case::happy_path(50)]
659
820
  // We start exceeding the 2k size limit at 180 patches with this format
@@ -713,12 +874,76 @@ async fn many_patches_combine_in_search_attrib_update(#[case] num_patches: usize
713
874
  });
714
875
 
715
876
  let mut worker = build_fake_sdk(mock_cfg);
716
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
717
- for i in 1..=num_patches {
718
- let _dontcare = ctx.patched(&format!("patch-{i}"));
719
- ctx.timer(ONE_SECOND).await;
720
- }
721
- Ok(().into())
722
- });
877
+ worker.register_workflow_with_factory(move || ManyPatchesWf { num_patches });
723
878
  worker.run().await.unwrap();
724
879
  }
880
+
881
+ const MANY_PATCHES_IN_ONE_WFT_COUNT: usize = 200;
882
+
883
+ #[workflow]
884
+ #[derive(Default)]
885
+ struct ManyPatchesInOneWftWf;
886
+
887
+ #[workflow_methods]
888
+ impl ManyPatchesInOneWftWf {
889
+ #[run(name = DEFAULT_WORKFLOW_TYPE)]
890
+ async fn run(ctx: &mut WorkflowContext<Self>) -> WorkflowResult<()> {
891
+ for i in 1..=MANY_PATCHES_IN_ONE_WFT_COUNT {
892
+ let _ = ctx.patched(&format!("patch-{i}"));
893
+ }
894
+ ctx.timer(Duration::from_millis(1)).await;
895
+ Ok(())
896
+ }
897
+ }
898
+
899
+ // The main difference with many_patches_combine_in_search_attrib_update are that
900
+ // this one creates multiple patches in a single WFT, rather than spread them out
901
+ // over multiple WFTs. See https://github.com/temporalio/sdk-core/issues/1223.
902
+ #[tokio::test]
903
+ async fn patch_marker_size_overflow_replay_is_deterministic() {
904
+ let wf_name = "patch_marker_size_overflow_replay_is_deterministic";
905
+ let mut starter = CoreWfStarter::new(wf_name);
906
+ starter.sdk_config.task_types = WorkerTaskTypes::workflow_only();
907
+ let mut worker = starter.worker().await;
908
+ worker.register_workflow::<ManyPatchesInOneWftWf>();
909
+
910
+ let task_queue = starter.get_task_queue().to_owned();
911
+ let handle = worker
912
+ .submit_workflow(
913
+ ManyPatchesInOneWftWf::run,
914
+ (),
915
+ WorkflowStartOptions::new(task_queue, wf_name.to_owned()).build(),
916
+ )
917
+ .await
918
+ .unwrap();
919
+ worker.run_until_done().await.unwrap();
920
+
921
+ // Confirm that the original execution did in fact hit the size limit: the last upsert SA
922
+ // event in history must contain fewer than the total number of patches issued by the workflow.
923
+ let history = handle.fetch_history(Default::default()).await.unwrap();
924
+ let last_upsert_patches = history
925
+ .events()
926
+ .iter()
927
+ .rev()
928
+ .find_map(|e| match &e.attributes {
929
+ Some(EventAttributes::UpsertWorkflowSearchAttributesEventAttributes(a)) => a
930
+ .search_attributes
931
+ .as_ref()
932
+ .and_then(|sa| sa.indexed_fields.get(VERSION_SEARCH_ATTR_KEY))
933
+ .map(|p| HashSet::<String, RandomState>::from_json_payload(p).unwrap()),
934
+ _ => None,
935
+ })
936
+ .expect("history should contain at least one UpsertWorkflowSearchAttributes event");
937
+ assert!(
938
+ last_upsert_patches.len() < MANY_PATCHES_IN_ONE_WFT_COUNT,
939
+ "expected the last upsert SA event to be missing patches due to size overflow, \
940
+ but it contained all {MANY_PATCHES_IN_ONE_WFT_COUNT} of them",
941
+ );
942
+
943
+ // Replay the workflow from the fetched history. This must succeed: the SDK must produce the
944
+ // same sequence of upsert SA commands during replay as it did during the original execution.
945
+ handle
946
+ .fetch_history_and_replay(worker.inner_mut())
947
+ .await
948
+ .unwrap();
949
+ }