temporalio 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +130 -0
  3. data/bridge/Cargo.lock +2865 -0
  4. data/bridge/Cargo.toml +26 -0
  5. data/bridge/sdk-core/ARCHITECTURE.md +76 -0
  6. data/bridge/sdk-core/Cargo.lock +2606 -0
  7. data/bridge/sdk-core/Cargo.toml +2 -0
  8. data/bridge/sdk-core/LICENSE.txt +23 -0
  9. data/bridge/sdk-core/README.md +107 -0
  10. data/bridge/sdk-core/arch_docs/diagrams/README.md +10 -0
  11. data/bridge/sdk-core/arch_docs/diagrams/sticky_queues.puml +40 -0
  12. data/bridge/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
  13. data/bridge/sdk-core/arch_docs/sticky_queues.md +51 -0
  14. data/bridge/sdk-core/bridge-ffi/Cargo.toml +24 -0
  15. data/bridge/sdk-core/bridge-ffi/LICENSE.txt +23 -0
  16. data/bridge/sdk-core/bridge-ffi/build.rs +25 -0
  17. data/bridge/sdk-core/bridge-ffi/include/sdk-core-bridge.h +249 -0
  18. data/bridge/sdk-core/bridge-ffi/src/lib.rs +825 -0
  19. data/bridge/sdk-core/bridge-ffi/src/wrappers.rs +211 -0
  20. data/bridge/sdk-core/client/Cargo.toml +40 -0
  21. data/bridge/sdk-core/client/LICENSE.txt +23 -0
  22. data/bridge/sdk-core/client/src/lib.rs +1294 -0
  23. data/bridge/sdk-core/client/src/metrics.rs +165 -0
  24. data/bridge/sdk-core/client/src/raw.rs +931 -0
  25. data/bridge/sdk-core/client/src/retry.rs +674 -0
  26. data/bridge/sdk-core/client/src/workflow_handle/mod.rs +185 -0
  27. data/bridge/sdk-core/core/Cargo.toml +116 -0
  28. data/bridge/sdk-core/core/LICENSE.txt +23 -0
  29. data/bridge/sdk-core/core/benches/workflow_replay.rs +73 -0
  30. data/bridge/sdk-core/core/src/abstractions.rs +166 -0
  31. data/bridge/sdk-core/core/src/core_tests/activity_tasks.rs +911 -0
  32. data/bridge/sdk-core/core/src/core_tests/child_workflows.rs +221 -0
  33. data/bridge/sdk-core/core/src/core_tests/determinism.rs +107 -0
  34. data/bridge/sdk-core/core/src/core_tests/local_activities.rs +515 -0
  35. data/bridge/sdk-core/core/src/core_tests/mod.rs +100 -0
  36. data/bridge/sdk-core/core/src/core_tests/queries.rs +736 -0
  37. data/bridge/sdk-core/core/src/core_tests/replay_flag.rs +65 -0
  38. data/bridge/sdk-core/core/src/core_tests/workers.rs +259 -0
  39. data/bridge/sdk-core/core/src/core_tests/workflow_cancels.rs +124 -0
  40. data/bridge/sdk-core/core/src/core_tests/workflow_tasks.rs +2070 -0
  41. data/bridge/sdk-core/core/src/ephemeral_server/mod.rs +515 -0
  42. data/bridge/sdk-core/core/src/lib.rs +175 -0
  43. data/bridge/sdk-core/core/src/log_export.rs +62 -0
  44. data/bridge/sdk-core/core/src/pollers/mod.rs +54 -0
  45. data/bridge/sdk-core/core/src/pollers/poll_buffer.rs +297 -0
  46. data/bridge/sdk-core/core/src/protosext/mod.rs +428 -0
  47. data/bridge/sdk-core/core/src/replay/mod.rs +71 -0
  48. data/bridge/sdk-core/core/src/retry_logic.rs +202 -0
  49. data/bridge/sdk-core/core/src/telemetry/metrics.rs +383 -0
  50. data/bridge/sdk-core/core/src/telemetry/mod.rs +412 -0
  51. data/bridge/sdk-core/core/src/telemetry/prometheus_server.rs +77 -0
  52. data/bridge/sdk-core/core/src/test_help/mod.rs +875 -0
  53. data/bridge/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +580 -0
  54. data/bridge/sdk-core/core/src/worker/activities/local_activities.rs +1042 -0
  55. data/bridge/sdk-core/core/src/worker/activities.rs +464 -0
  56. data/bridge/sdk-core/core/src/worker/client/mocks.rs +87 -0
  57. data/bridge/sdk-core/core/src/worker/client.rs +347 -0
  58. data/bridge/sdk-core/core/src/worker/mod.rs +566 -0
  59. data/bridge/sdk-core/core/src/worker/workflow/bridge.rs +37 -0
  60. data/bridge/sdk-core/core/src/worker/workflow/driven_workflow.rs +110 -0
  61. data/bridge/sdk-core/core/src/worker/workflow/history_update.rs +458 -0
  62. data/bridge/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +911 -0
  63. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +298 -0
  64. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +171 -0
  65. data/bridge/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +860 -0
  66. data/bridge/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +140 -0
  67. data/bridge/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +161 -0
  68. data/bridge/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +133 -0
  69. data/bridge/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +1448 -0
  70. data/bridge/sdk-core/core/src/worker/workflow/machines/mod.rs +342 -0
  71. data/bridge/sdk-core/core/src/worker/workflow/machines/mutable_side_effect_state_machine.rs +127 -0
  72. data/bridge/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +712 -0
  73. data/bridge/sdk-core/core/src/worker/workflow/machines/side_effect_state_machine.rs +71 -0
  74. data/bridge/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +443 -0
  75. data/bridge/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +439 -0
  76. data/bridge/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +169 -0
  77. data/bridge/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +246 -0
  78. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +96 -0
  79. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +1184 -0
  80. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +277 -0
  81. data/bridge/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
  82. data/bridge/sdk-core/core/src/worker/workflow/managed_run.rs +647 -0
  83. data/bridge/sdk-core/core/src/worker/workflow/mod.rs +1143 -0
  84. data/bridge/sdk-core/core/src/worker/workflow/run_cache.rs +145 -0
  85. data/bridge/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
  86. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream.rs +940 -0
  87. data/bridge/sdk-core/core-api/Cargo.toml +31 -0
  88. data/bridge/sdk-core/core-api/LICENSE.txt +23 -0
  89. data/bridge/sdk-core/core-api/src/errors.rs +95 -0
  90. data/bridge/sdk-core/core-api/src/lib.rs +151 -0
  91. data/bridge/sdk-core/core-api/src/worker.rs +135 -0
  92. data/bridge/sdk-core/etc/deps.svg +187 -0
  93. data/bridge/sdk-core/etc/dynamic-config.yaml +2 -0
  94. data/bridge/sdk-core/etc/otel-collector-config.yaml +36 -0
  95. data/bridge/sdk-core/etc/prometheus.yaml +6 -0
  96. data/bridge/sdk-core/fsm/Cargo.toml +18 -0
  97. data/bridge/sdk-core/fsm/LICENSE.txt +23 -0
  98. data/bridge/sdk-core/fsm/README.md +3 -0
  99. data/bridge/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +27 -0
  100. data/bridge/sdk-core/fsm/rustfsm_procmacro/LICENSE.txt +23 -0
  101. data/bridge/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +647 -0
  102. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/progress.rs +8 -0
  103. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.rs +18 -0
  104. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +12 -0
  105. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dynamic_dest_pass.rs +41 -0
  106. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/forgot_name_fail.rs +14 -0
  107. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/forgot_name_fail.stderr +11 -0
  108. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/handler_arg_pass.rs +32 -0
  109. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/handler_pass.rs +31 -0
  110. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/medium_complex_pass.rs +46 -0
  111. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.rs +29 -0
  112. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +12 -0
  113. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/simple_pass.rs +32 -0
  114. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/struct_event_variant_fail.rs +18 -0
  115. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/struct_event_variant_fail.stderr +5 -0
  116. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_more_item_event_variant_fail.rs +11 -0
  117. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_more_item_event_variant_fail.stderr +5 -0
  118. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_zero_item_event_variant_fail.rs +11 -0
  119. data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_zero_item_event_variant_fail.stderr +5 -0
  120. data/bridge/sdk-core/fsm/rustfsm_trait/Cargo.toml +14 -0
  121. data/bridge/sdk-core/fsm/rustfsm_trait/LICENSE.txt +23 -0
  122. data/bridge/sdk-core/fsm/rustfsm_trait/src/lib.rs +249 -0
  123. data/bridge/sdk-core/fsm/src/lib.rs +2 -0
  124. data/bridge/sdk-core/histories/fail_wf_task.bin +0 -0
  125. data/bridge/sdk-core/histories/timer_workflow_history.bin +0 -0
  126. data/bridge/sdk-core/integ-with-otel.sh +7 -0
  127. data/bridge/sdk-core/protos/api_upstream/README.md +9 -0
  128. data/bridge/sdk-core/protos/api_upstream/api-linter.yaml +40 -0
  129. data/bridge/sdk-core/protos/api_upstream/buf.yaml +12 -0
  130. data/bridge/sdk-core/protos/api_upstream/dependencies/gogoproto/gogo.proto +141 -0
  131. data/bridge/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +86 -0
  132. data/bridge/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
  133. data/bridge/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +259 -0
  134. data/bridge/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +112 -0
  135. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +46 -0
  136. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
  137. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +57 -0
  138. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +55 -0
  139. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +168 -0
  140. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +97 -0
  141. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +51 -0
  142. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +50 -0
  143. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +41 -0
  144. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
  145. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +59 -0
  146. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +51 -0
  147. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +122 -0
  148. data/bridge/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +108 -0
  149. data/bridge/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +114 -0
  150. data/bridge/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +56 -0
  151. data/bridge/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +751 -0
  152. data/bridge/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +97 -0
  153. data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +161 -0
  154. data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +99 -0
  155. data/bridge/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +61 -0
  156. data/bridge/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +55 -0
  157. data/bridge/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
  158. data/bridge/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +108 -0
  159. data/bridge/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +46 -0
  160. data/bridge/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +59 -0
  161. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +145 -0
  162. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +1124 -0
  163. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +401 -0
  164. data/bridge/sdk-core/protos/grpc/health/v1/health.proto +63 -0
  165. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +78 -0
  166. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +79 -0
  167. data/bridge/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +210 -0
  168. data/bridge/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +77 -0
  169. data/bridge/sdk-core/protos/local/temporal/sdk/core/common/common.proto +15 -0
  170. data/bridge/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +30 -0
  171. data/bridge/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +30 -0
  172. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +261 -0
  173. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +297 -0
  174. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +29 -0
  175. data/bridge/sdk-core/protos/testsrv_upstream/api-linter.yaml +38 -0
  176. data/bridge/sdk-core/protos/testsrv_upstream/buf.yaml +13 -0
  177. data/bridge/sdk-core/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +141 -0
  178. data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +63 -0
  179. data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +90 -0
  180. data/bridge/sdk-core/rustfmt.toml +1 -0
  181. data/bridge/sdk-core/sdk/Cargo.toml +47 -0
  182. data/bridge/sdk-core/sdk/LICENSE.txt +23 -0
  183. data/bridge/sdk-core/sdk/src/activity_context.rs +230 -0
  184. data/bridge/sdk-core/sdk/src/app_data.rs +37 -0
  185. data/bridge/sdk-core/sdk/src/conversions.rs +8 -0
  186. data/bridge/sdk-core/sdk/src/interceptors.rs +17 -0
  187. data/bridge/sdk-core/sdk/src/lib.rs +792 -0
  188. data/bridge/sdk-core/sdk/src/payload_converter.rs +11 -0
  189. data/bridge/sdk-core/sdk/src/workflow_context/options.rs +295 -0
  190. data/bridge/sdk-core/sdk/src/workflow_context.rs +683 -0
  191. data/bridge/sdk-core/sdk/src/workflow_future.rs +503 -0
  192. data/bridge/sdk-core/sdk-core-protos/Cargo.toml +30 -0
  193. data/bridge/sdk-core/sdk-core-protos/LICENSE.txt +23 -0
  194. data/bridge/sdk-core/sdk-core-protos/build.rs +108 -0
  195. data/bridge/sdk-core/sdk-core-protos/src/constants.rs +7 -0
  196. data/bridge/sdk-core/sdk-core-protos/src/history_builder.rs +497 -0
  197. data/bridge/sdk-core/sdk-core-protos/src/history_info.rs +230 -0
  198. data/bridge/sdk-core/sdk-core-protos/src/lib.rs +1910 -0
  199. data/bridge/sdk-core/sdk-core-protos/src/task_token.rs +38 -0
  200. data/bridge/sdk-core/sdk-core-protos/src/utilities.rs +14 -0
  201. data/bridge/sdk-core/test-utils/Cargo.toml +35 -0
  202. data/bridge/sdk-core/test-utils/src/canned_histories.rs +1579 -0
  203. data/bridge/sdk-core/test-utils/src/histfetch.rs +28 -0
  204. data/bridge/sdk-core/test-utils/src/lib.rs +598 -0
  205. data/bridge/sdk-core/tests/integ_tests/client_tests.rs +36 -0
  206. data/bridge/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +128 -0
  207. data/bridge/sdk-core/tests/integ_tests/heartbeat_tests.rs +218 -0
  208. data/bridge/sdk-core/tests/integ_tests/polling_tests.rs +146 -0
  209. data/bridge/sdk-core/tests/integ_tests/queries_tests.rs +437 -0
  210. data/bridge/sdk-core/tests/integ_tests/visibility_tests.rs +93 -0
  211. data/bridge/sdk-core/tests/integ_tests/workflow_tests/activities.rs +878 -0
  212. data/bridge/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
  213. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +59 -0
  214. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +58 -0
  215. data/bridge/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +50 -0
  216. data/bridge/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +60 -0
  217. data/bridge/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +54 -0
  218. data/bridge/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +634 -0
  219. data/bridge/sdk-core/tests/integ_tests/workflow_tests/patches.rs +113 -0
  220. data/bridge/sdk-core/tests/integ_tests/workflow_tests/replay.rs +137 -0
  221. data/bridge/sdk-core/tests/integ_tests/workflow_tests/resets.rs +93 -0
  222. data/bridge/sdk-core/tests/integ_tests/workflow_tests/signals.rs +167 -0
  223. data/bridge/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +99 -0
  224. data/bridge/sdk-core/tests/integ_tests/workflow_tests/timers.rs +131 -0
  225. data/bridge/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +75 -0
  226. data/bridge/sdk-core/tests/integ_tests/workflow_tests.rs +587 -0
  227. data/bridge/sdk-core/tests/load_tests.rs +191 -0
  228. data/bridge/sdk-core/tests/main.rs +111 -0
  229. data/bridge/sdk-core/tests/runner.rs +93 -0
  230. data/bridge/src/connection.rs +167 -0
  231. data/bridge/src/lib.rs +180 -0
  232. data/bridge/src/runtime.rs +47 -0
  233. data/bridge/src/worker.rs +73 -0
  234. data/ext/Rakefile +9 -0
  235. data/lib/bridge.so +0 -0
  236. data/lib/gen/dependencies/gogoproto/gogo_pb.rb +14 -0
  237. data/lib/gen/temporal/api/batch/v1/message_pb.rb +48 -0
  238. data/lib/gen/temporal/api/cluster/v1/message_pb.rb +67 -0
  239. data/lib/gen/temporal/api/command/v1/message_pb.rb +166 -0
  240. data/lib/gen/temporal/api/common/v1/message_pb.rb +69 -0
  241. data/lib/gen/temporal/api/enums/v1/batch_operation_pb.rb +32 -0
  242. data/lib/gen/temporal/api/enums/v1/cluster_pb.rb +26 -0
  243. data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +37 -0
  244. data/lib/gen/temporal/api/enums/v1/common_pb.rb +41 -0
  245. data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +67 -0
  246. data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +71 -0
  247. data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +37 -0
  248. data/lib/gen/temporal/api/enums/v1/query_pb.rb +31 -0
  249. data/lib/gen/temporal/api/enums/v1/reset_pb.rb +24 -0
  250. data/lib/gen/temporal/api/enums/v1/schedule_pb.rb +28 -0
  251. data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +30 -0
  252. data/lib/gen/temporal/api/enums/v1/update_pb.rb +28 -0
  253. data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +89 -0
  254. data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +84 -0
  255. data/lib/gen/temporal/api/failure/v1/message_pb.rb +83 -0
  256. data/lib/gen/temporal/api/filter/v1/message_pb.rb +40 -0
  257. data/lib/gen/temporal/api/history/v1/message_pb.rb +489 -0
  258. data/lib/gen/temporal/api/namespace/v1/message_pb.rb +63 -0
  259. data/lib/gen/temporal/api/operatorservice/v1/request_response_pb.rb +125 -0
  260. data/lib/gen/temporal/api/operatorservice/v1/service_pb.rb +20 -0
  261. data/lib/gen/temporal/api/query/v1/message_pb.rb +38 -0
  262. data/lib/gen/temporal/api/replication/v1/message_pb.rb +37 -0
  263. data/lib/gen/temporal/api/schedule/v1/message_pb.rb +128 -0
  264. data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +73 -0
  265. data/lib/gen/temporal/api/update/v1/message_pb.rb +26 -0
  266. data/lib/gen/temporal/api/version/v1/message_pb.rb +41 -0
  267. data/lib/gen/temporal/api/workflow/v1/message_pb.rb +110 -0
  268. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +771 -0
  269. data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +20 -0
  270. data/lib/gen/temporal/sdk/core/activity_result/activity_result_pb.rb +58 -0
  271. data/lib/gen/temporal/sdk/core/activity_task/activity_task_pb.rb +57 -0
  272. data/lib/gen/temporal/sdk/core/bridge/bridge_pb.rb +222 -0
  273. data/lib/gen/temporal/sdk/core/child_workflow/child_workflow_pb.rb +57 -0
  274. data/lib/gen/temporal/sdk/core/common/common_pb.rb +22 -0
  275. data/lib/gen/temporal/sdk/core/core_interface_pb.rb +34 -0
  276. data/lib/gen/temporal/sdk/core/external_data/external_data_pb.rb +27 -0
  277. data/lib/gen/temporal/sdk/core/workflow_activation/workflow_activation_pb.rb +164 -0
  278. data/lib/gen/temporal/sdk/core/workflow_commands/workflow_commands_pb.rb +192 -0
  279. data/lib/gen/temporal/sdk/core/workflow_completion/workflow_completion_pb.rb +34 -0
  280. data/lib/temporal/bridge.rb +14 -0
  281. data/lib/temporal/client/implementation.rb +339 -0
  282. data/lib/temporal/client/workflow_handle.rb +243 -0
  283. data/lib/temporal/client.rb +144 -0
  284. data/lib/temporal/connection.rb +736 -0
  285. data/lib/temporal/data_converter.rb +150 -0
  286. data/lib/temporal/error/failure.rb +194 -0
  287. data/lib/temporal/error/workflow_failure.rb +17 -0
  288. data/lib/temporal/errors.rb +22 -0
  289. data/lib/temporal/failure_converter/base.rb +26 -0
  290. data/lib/temporal/failure_converter/basic.rb +313 -0
  291. data/lib/temporal/failure_converter.rb +8 -0
  292. data/lib/temporal/interceptor/chain.rb +27 -0
  293. data/lib/temporal/interceptor/client.rb +102 -0
  294. data/lib/temporal/payload_codec/base.rb +32 -0
  295. data/lib/temporal/payload_converter/base.rb +24 -0
  296. data/lib/temporal/payload_converter/bytes.rb +26 -0
  297. data/lib/temporal/payload_converter/composite.rb +47 -0
  298. data/lib/temporal/payload_converter/encoding_base.rb +35 -0
  299. data/lib/temporal/payload_converter/json.rb +25 -0
  300. data/lib/temporal/payload_converter/nil.rb +25 -0
  301. data/lib/temporal/payload_converter.rb +14 -0
  302. data/lib/temporal/retry_policy.rb +82 -0
  303. data/lib/temporal/retry_state.rb +35 -0
  304. data/lib/temporal/runtime.rb +22 -0
  305. data/lib/temporal/timeout_type.rb +29 -0
  306. data/lib/temporal/version.rb +1 -1
  307. data/lib/temporal/workflow/execution_info.rb +54 -0
  308. data/lib/temporal/workflow/execution_status.rb +36 -0
  309. data/lib/temporal/workflow/id_reuse_policy.rb +36 -0
  310. data/lib/temporal/workflow/query_reject_condition.rb +33 -0
  311. data/lib/temporal.rb +4 -0
  312. data/lib/temporalio.rb +3 -1
  313. data/lib/thermite_patch.rb +23 -0
  314. data/temporalio.gemspec +41 -0
  315. metadata +543 -9
  316. data/temporal.gemspec +0 -20
@@ -0,0 +1,1448 @@
1
+ use super::{
2
+ workflow_machines::MachineResponse, Cancellable, EventInfo, MachineKind, OnEventWrapper,
3
+ WFMachinesAdapter, WFMachinesError,
4
+ };
5
+ use crate::{
6
+ protosext::{CompleteLocalActivityData, HistoryEventExt, ValidScheduleLA},
7
+ worker::LocalActivityExecutionResult,
8
+ };
9
+ use rustfsm::{fsm, MachineError, StateMachine, TransitionResult};
10
+ use std::{
11
+ convert::TryFrom,
12
+ time::{Duration, SystemTime},
13
+ };
14
+ use temporal_sdk_core_protos::{
15
+ constants::LOCAL_ACTIVITY_MARKER_NAME,
16
+ coresdk::{
17
+ activity_result::{
18
+ ActivityResolution, Cancellation, DoBackoff, Failure as ActFail, Success,
19
+ },
20
+ common::build_local_activity_marker_details,
21
+ external_data::LocalActivityMarkerData,
22
+ workflow_activation::ResolveActivity,
23
+ workflow_commands::ActivityCancellationType,
24
+ },
25
+ temporal::api::{
26
+ command::v1::{Command, RecordMarkerCommandAttributes},
27
+ enums::v1::{CommandType, EventType},
28
+ failure::v1::failure::FailureInfo,
29
+ history::v1::HistoryEvent,
30
+ },
31
+ utilities::TryIntoOrNone,
32
+ };
33
+
34
+ fsm! {
35
+ pub(super) name LocalActivityMachine;
36
+ command LocalActivityCommand;
37
+ error WFMachinesError;
38
+ shared_state SharedState;
39
+
40
+ // Machine is created in either executing or replaying (referring to whether or not the workflow
41
+ // is replaying), and then immediately scheduled and transitions to either requesting that lang
42
+ // execute the activity, or waiting for the marker from history.
43
+ Executing --(Schedule, shared on_schedule) --> RequestSent;
44
+ Replaying --(Schedule, on_schedule) --> WaitingMarkerEvent;
45
+ ReplayingPreResolved --(Schedule, on_schedule) --> WaitingMarkerEventPreResolved;
46
+
47
+ // Execution path =============================================================================
48
+ RequestSent --(HandleResult(ResolveDat), on_handle_result) --> MarkerCommandCreated;
49
+ // We loop back on RequestSent here because the LA needs to report its result
50
+ RequestSent --(Cancel, on_cancel_requested) --> RequestSent;
51
+ // No wait cancels skip waiting for the LA to report the result, but do generate a command
52
+ // to record the cancel marker
53
+ RequestSent --(NoWaitCancel(ActivityCancellationType), shared on_no_wait_cancel)
54
+ --> MarkerCommandCreated;
55
+
56
+ MarkerCommandCreated --(CommandRecordMarker, on_command_record_marker) --> ResultNotified;
57
+
58
+ ResultNotified --(MarkerRecorded(CompleteLocalActivityData), shared on_marker_recorded)
59
+ --> MarkerCommandRecorded;
60
+
61
+ // Replay path ================================================================================
62
+ // LAs on the replay path should never have handle result explicitly called on them, but do need
63
+ // to eventually see the marker
64
+ WaitingMarkerEvent --(MarkerRecorded(CompleteLocalActivityData), shared on_marker_recorded)
65
+ --> MarkerCommandRecorded;
66
+ // If we are told to cancel while waiting for the marker, we still need to wait for the marker.
67
+ WaitingMarkerEvent --(Cancel, on_cancel_requested) --> WaitingMarkerEventCancelled;
68
+ WaitingMarkerEvent --(NoWaitCancel(ActivityCancellationType),
69
+ on_no_wait_cancel) --> WaitingMarkerEventCancelled;
70
+ WaitingMarkerEventCancelled --(HandleResult(ResolveDat), on_handle_result) --> WaitingMarkerEvent;
71
+
72
+ // It is entirely possible to have started the LA while replaying, only to find that we have
73
+ // reached a new WFT and there still was no marker. In such cases we need to execute the LA.
74
+ // This can easily happen if upon first execution, the worker does WFT heartbeating but then
75
+ // dies for some reason.
76
+ WaitingMarkerEvent --(StartedNonReplayWFT, shared on_started_non_replay_wft) --> RequestSent;
77
+
78
+ // If the activity is pre resolved we still expect to see marker recorded event at some point,
79
+ // even though we already resolved the activity.
80
+ WaitingMarkerEventPreResolved --(MarkerRecorded(CompleteLocalActivityData),
81
+ shared on_marker_recorded) --> MarkerCommandRecorded;
82
+
83
+ // Ignore cancellation in final state
84
+ MarkerCommandRecorded --(Cancel, on_cancel_requested) --> MarkerCommandRecorded;
85
+ MarkerCommandRecorded --(NoWaitCancel(ActivityCancellationType),
86
+ on_no_wait_cancel) --> MarkerCommandRecorded;
87
+
88
+ // LAs reporting status after they've handled their result can simply be ignored. We could
89
+ // optimize this away higher up but that feels very overkill.
90
+ MarkerCommandCreated --(HandleResult(ResolveDat)) --> MarkerCommandCreated;
91
+ ResultNotified --(HandleResult(ResolveDat)) --> ResultNotified;
92
+ MarkerCommandRecorded --(HandleResult(ResolveDat)) --> MarkerCommandRecorded;
93
+ }
94
+
95
+ #[derive(Debug, Clone)]
96
+ pub(super) struct ResolveDat {
97
+ pub(super) result: LocalActivityExecutionResult,
98
+ pub(super) complete_time: Option<SystemTime>,
99
+ pub(super) attempt: u32,
100
+ pub(super) backoff: Option<prost_types::Duration>,
101
+ pub(super) original_schedule_time: Option<SystemTime>,
102
+ }
103
+
104
+ impl From<CompleteLocalActivityData> for ResolveDat {
105
+ fn from(d: CompleteLocalActivityData) -> Self {
106
+ ResolveDat {
107
+ result: match d.result {
108
+ Ok(res) => LocalActivityExecutionResult::Completed(Success { result: Some(res) }),
109
+ Err(fail) => {
110
+ if matches!(fail.failure_info, Some(FailureInfo::CanceledFailureInfo(_))) {
111
+ LocalActivityExecutionResult::Cancelled(Cancellation {
112
+ failure: Some(fail),
113
+ })
114
+ } else {
115
+ LocalActivityExecutionResult::Failed(ActFail {
116
+ failure: Some(fail),
117
+ })
118
+ }
119
+ }
120
+ },
121
+ complete_time: d.marker_dat.complete_time.try_into_or_none(),
122
+ attempt: d.marker_dat.attempt,
123
+ backoff: d.marker_dat.backoff,
124
+ original_schedule_time: d.marker_dat.original_schedule_time.try_into_or_none(),
125
+ }
126
+ }
127
+ }
128
+
129
+ /// Creates a new local activity state machine & immediately schedules the local activity for
130
+ /// execution. No command is produced immediately to be sent to the server, as the local activity
131
+ /// must resolve before we send a record marker command. A [MachineResponse] may be produced,
132
+ /// to queue the LA for execution if it needs to be.
133
+ pub(super) fn new_local_activity(
134
+ attrs: ValidScheduleLA,
135
+ replaying_when_invoked: bool,
136
+ maybe_pre_resolved: Option<ResolveDat>,
137
+ wf_time: Option<SystemTime>,
138
+ ) -> Result<(LocalActivityMachine, Vec<MachineResponse>), WFMachinesError> {
139
+ let initial_state = if replaying_when_invoked {
140
+ if let Some(dat) = maybe_pre_resolved {
141
+ ReplayingPreResolved { dat }.into()
142
+ } else {
143
+ Replaying {}.into()
144
+ }
145
+ } else {
146
+ if maybe_pre_resolved.is_some() {
147
+ return Err(WFMachinesError::Nondeterminism(
148
+ "Local activity cannot be created as pre-resolved while not replaying".to_string(),
149
+ ));
150
+ }
151
+ Executing {}.into()
152
+ };
153
+
154
+ let mut machine = LocalActivityMachine {
155
+ state: initial_state,
156
+ shared_state: SharedState {
157
+ attrs,
158
+ replaying_when_invoked,
159
+ wf_time_when_started: wf_time,
160
+ },
161
+ };
162
+
163
+ let mut res = OnEventWrapper::on_event_mut(&mut machine, LocalActivityMachineEvents::Schedule)
164
+ .expect("Scheduling local activities doesn't fail");
165
+ let mr = if let Some(res) = res.pop() {
166
+ machine
167
+ .adapt_response(res, None)
168
+ .expect("Adapting LA schedule response doesn't fail")
169
+ } else {
170
+ vec![]
171
+ };
172
+ Ok((machine, mr))
173
+ }
174
+
175
+ impl LocalActivityMachine {
176
+ /// Is called to check if, while handling the LA marker event, we should avoid doing normal
177
+ /// command-event processing - instead simply applying the event to this machine and then
178
+ /// skipping over the rest. If this machine is in the `ResultNotified` state, that means
179
+ /// command handling should proceed as normal (ie: The command needs to be matched and removed).
180
+ /// The other valid states to make this check in are the `WaitingMarkerEvent[PreResolved]`
181
+ /// states, which will return true.
182
+ ///
183
+ /// Attempting the check in any other state likely means a bug in the SDK.
184
+ pub(super) fn marker_should_get_special_handling(&self) -> Result<bool, WFMachinesError> {
185
+ match &self.state {
186
+ LocalActivityMachineState::ResultNotified(_) => Ok(false),
187
+ LocalActivityMachineState::WaitingMarkerEvent(_) => Ok(true),
188
+ LocalActivityMachineState::WaitingMarkerEventPreResolved(_) => Ok(true),
189
+ _ => Err(WFMachinesError::Fatal(format!(
190
+ "Attempted to check for LA marker handling in invalid state {}",
191
+ self.state
192
+ ))),
193
+ }
194
+ }
195
+
196
+ /// Must be called if the workflow encounters a non-replay workflow task
197
+ pub(super) fn encountered_non_replay_wft(
198
+ &mut self,
199
+ ) -> Result<Vec<MachineResponse>, WFMachinesError> {
200
+ // This only applies to the waiting-for-marker state. It can safely be ignored in the others
201
+ if !matches!(
202
+ self.state(),
203
+ LocalActivityMachineState::WaitingMarkerEvent(_)
204
+ ) {
205
+ return Ok(vec![]);
206
+ }
207
+
208
+ let mut res =
209
+ OnEventWrapper::on_event_mut(self, LocalActivityMachineEvents::StartedNonReplayWFT)
210
+ .map_err(|e| match e {
211
+ MachineError::InvalidTransition => WFMachinesError::Fatal(format!(
212
+ "Invalid transition while notifying local activity (seq {})\
213
+ of non-replay-wft-started in {}",
214
+ self.shared_state.attrs.seq,
215
+ self.state(),
216
+ )),
217
+ MachineError::Underlying(e) => e,
218
+ })?;
219
+ let res = res.pop().expect("Always produces one response");
220
+ Ok(self
221
+ .adapt_response(res, None)
222
+ .expect("Adapting LA wft-non-replay response doesn't fail"))
223
+ }
224
+
225
+ /// Attempt to resolve the local activity with a result from execution (not from history)
226
+ pub(super) fn try_resolve(
227
+ &mut self,
228
+ result: LocalActivityExecutionResult,
229
+ runtime: Duration,
230
+ attempt: u32,
231
+ backoff: Option<prost_types::Duration>,
232
+ original_schedule_time: Option<SystemTime>,
233
+ ) -> Result<Vec<MachineResponse>, WFMachinesError> {
234
+ self.try_resolve_with_dat(ResolveDat {
235
+ result,
236
+ complete_time: self.shared_state.wf_time_when_started.map(|t| t + runtime),
237
+ attempt,
238
+ backoff,
239
+ original_schedule_time,
240
+ })
241
+ }
242
+ /// Attempt to resolve the local activity with already known data, ex pre-resolved data
243
+ pub(super) fn try_resolve_with_dat(
244
+ &mut self,
245
+ dat: ResolveDat,
246
+ ) -> Result<Vec<MachineResponse>, WFMachinesError> {
247
+ let res = OnEventWrapper::on_event_mut(self, LocalActivityMachineEvents::HandleResult(dat))
248
+ .map_err(|e| match e {
249
+ MachineError::InvalidTransition => WFMachinesError::Fatal(format!(
250
+ "Invalid transition resolving local activity (seq {}) in {}",
251
+ self.shared_state.attrs.seq,
252
+ self.state(),
253
+ )),
254
+ MachineError::Underlying(e) => e,
255
+ })?;
256
+
257
+ Ok(res
258
+ .into_iter()
259
+ .flat_map(|res| {
260
+ self.adapt_response(res, None)
261
+ .expect("Adapting LA resolve response doesn't fail")
262
+ })
263
+ .collect())
264
+ }
265
+ }
266
+
267
+ #[derive(Clone)]
268
+ pub(super) struct SharedState {
269
+ attrs: ValidScheduleLA,
270
+ replaying_when_invoked: bool,
271
+ wf_time_when_started: Option<SystemTime>,
272
+ }
273
+
274
+ impl SharedState {
275
+ fn produce_no_wait_cancel_resolve_dat(&self) -> ResolveDat {
276
+ ResolveDat {
277
+ result: LocalActivityExecutionResult::empty_cancel(),
278
+ // Just don't provide a complete time, which means try-cancel/abandon cancels won't
279
+ // advance the clock. Seems like that's fine, since you can only cancel after awaiting
280
+ // some other command, which would have appropriately advanced the clock anyway.
281
+ complete_time: None,
282
+ attempt: self.attrs.attempt,
283
+ backoff: None,
284
+ original_schedule_time: self.attrs.original_schedule_time,
285
+ }
286
+ }
287
+ }
288
+
289
+ #[derive(Debug, derive_more::Display)]
290
+ pub(super) enum LocalActivityCommand {
291
+ RequestActivityExecution(ValidScheduleLA),
292
+ #[display(fmt = "Resolved")]
293
+ Resolved(ResolveDat),
294
+ /// The fake marker is used to avoid special casing marker recorded event handling.
295
+ /// If we didn't have the fake marker, there would be no "outgoing command" to match
296
+ /// against the event. This way there is, but the command never will be issued to
297
+ /// server because it is understood to be meaningless.
298
+ #[display(fmt = "FakeMarker")]
299
+ FakeMarker,
300
+ /// Indicate we want to cancel an LA that is currently executing, or look up if we have
301
+ /// processed a marker with resolution data since the machine was constructed.
302
+ #[display(fmt = "Cancel")]
303
+ RequestCancel,
304
+ }
305
+
306
+ #[derive(Default, Clone)]
307
+ pub(super) struct Executing {}
308
+
309
+ impl Executing {
310
+ pub(super) fn on_schedule(
311
+ self,
312
+ dat: SharedState,
313
+ ) -> LocalActivityMachineTransition<RequestSent> {
314
+ TransitionResult::commands([LocalActivityCommand::RequestActivityExecution(dat.attrs)])
315
+ }
316
+ }
317
+
318
+ #[derive(Clone, Copy, PartialEq, Eq)]
319
+ enum ResultType {
320
+ Completed,
321
+ Cancelled,
322
+ Failed,
323
+ }
324
+ #[derive(Clone)]
325
+ pub(super) struct MarkerCommandCreated {
326
+ result_type: ResultType,
327
+ }
328
+ impl From<MarkerCommandCreated> for ResultNotified {
329
+ fn from(mc: MarkerCommandCreated) -> Self {
330
+ Self {
331
+ result_type: mc.result_type,
332
+ }
333
+ }
334
+ }
335
+
336
+ impl MarkerCommandCreated {
337
+ pub(super) fn on_command_record_marker(self) -> LocalActivityMachineTransition<ResultNotified> {
338
+ TransitionResult::from(self)
339
+ }
340
+ }
341
+
342
+ #[derive(Default, Clone)]
343
+ pub(super) struct MarkerCommandRecorded {}
344
+ impl MarkerCommandRecorded {
345
+ fn on_cancel_requested(self) -> LocalActivityMachineTransition<MarkerCommandRecorded> {
346
+ // We still must issue a cancel request even if this command is resolved, because if it
347
+ // failed and we are backing off locally, we must tell the LA dispatcher to quit retrying.
348
+ TransitionResult::ok([LocalActivityCommand::RequestCancel], self)
349
+ }
350
+
351
+ fn on_no_wait_cancel(
352
+ self,
353
+ cancel_type: ActivityCancellationType,
354
+ ) -> LocalActivityMachineTransition<MarkerCommandRecorded> {
355
+ if matches!(cancel_type, ActivityCancellationType::TryCancel) {
356
+ // We still must issue a cancel request even if this command is resolved, because if it
357
+ // failed and we are backing off locally, we must tell the LA dispatcher to quit
358
+ // retrying.
359
+ TransitionResult::ok(
360
+ [LocalActivityCommand::RequestCancel],
361
+ MarkerCommandRecorded::default(),
362
+ )
363
+ } else {
364
+ TransitionResult::default()
365
+ }
366
+ }
367
+ }
368
+
369
+ #[derive(Default, Clone)]
370
+ pub(super) struct Replaying {}
371
+ impl Replaying {
372
+ pub(super) fn on_schedule(self) -> LocalActivityMachineTransition<WaitingMarkerEvent> {
373
+ TransitionResult::ok(
374
+ [],
375
+ WaitingMarkerEvent {
376
+ already_resolved: false,
377
+ },
378
+ )
379
+ }
380
+ }
381
+
382
+ #[derive(Clone)]
383
+ pub(super) struct ReplayingPreResolved {
384
+ dat: ResolveDat,
385
+ }
386
+ impl ReplayingPreResolved {
387
+ pub(super) fn on_schedule(
388
+ self,
389
+ ) -> LocalActivityMachineTransition<WaitingMarkerEventPreResolved> {
390
+ TransitionResult::ok(
391
+ [
392
+ LocalActivityCommand::FakeMarker,
393
+ LocalActivityCommand::Resolved(self.dat),
394
+ ],
395
+ WaitingMarkerEventPreResolved {},
396
+ )
397
+ }
398
+ }
399
+
400
+ #[derive(Default, Clone)]
401
+ pub(super) struct RequestSent {}
402
+
403
+ impl RequestSent {
404
+ fn on_handle_result(
405
+ self,
406
+ dat: ResolveDat,
407
+ ) -> LocalActivityMachineTransition<MarkerCommandCreated> {
408
+ let result_type = match &dat.result {
409
+ LocalActivityExecutionResult::Completed(_) => ResultType::Completed,
410
+ LocalActivityExecutionResult::Failed(_) => ResultType::Failed,
411
+ LocalActivityExecutionResult::TimedOut(_) => ResultType::Failed,
412
+ LocalActivityExecutionResult::Cancelled { .. } => ResultType::Cancelled,
413
+ };
414
+ let new_state = MarkerCommandCreated { result_type };
415
+ TransitionResult::ok([LocalActivityCommand::Resolved(dat)], new_state)
416
+ }
417
+
418
+ fn on_cancel_requested(self) -> LocalActivityMachineTransition<RequestSent> {
419
+ TransitionResult::ok([LocalActivityCommand::RequestCancel], self)
420
+ }
421
+
422
+ fn on_no_wait_cancel(
423
+ self,
424
+ shared: SharedState,
425
+ cancel_type: ActivityCancellationType,
426
+ ) -> LocalActivityMachineTransition<MarkerCommandCreated> {
427
+ let mut cmds = vec![];
428
+ if matches!(cancel_type, ActivityCancellationType::TryCancel) {
429
+ // For try-cancels also request the cancel
430
+ cmds.push(LocalActivityCommand::RequestCancel);
431
+ }
432
+ // Immediately resolve
433
+ cmds.push(LocalActivityCommand::Resolved(
434
+ shared.produce_no_wait_cancel_resolve_dat(),
435
+ ));
436
+ TransitionResult::ok(
437
+ cmds,
438
+ MarkerCommandCreated {
439
+ result_type: ResultType::Cancelled,
440
+ },
441
+ )
442
+ }
443
+ }
444
+
445
+ macro_rules! verify_marker_dat {
446
+ ($shared:expr, $dat:expr, $ok_expr:expr) => {
447
+ if let Err(err) = verify_marker_data_matches($shared, $dat) {
448
+ TransitionResult::Err(err)
449
+ } else {
450
+ $ok_expr
451
+ }
452
+ };
453
+ }
454
+
455
+ #[derive(Clone)]
456
+ pub(super) struct ResultNotified {
457
+ result_type: ResultType,
458
+ }
459
+
460
+ impl ResultNotified {
461
+ pub(super) fn on_marker_recorded(
462
+ self,
463
+ shared: SharedState,
464
+ dat: CompleteLocalActivityData,
465
+ ) -> LocalActivityMachineTransition<MarkerCommandRecorded> {
466
+ if self.result_type == ResultType::Completed && dat.result.is_err() {
467
+ return TransitionResult::Err(WFMachinesError::Nondeterminism(format!(
468
+ "Local activity (seq {}) completed successfully locally, but history said \
469
+ it failed!",
470
+ shared.attrs.seq
471
+ )));
472
+ } else if self.result_type == ResultType::Failed && dat.result.is_ok() {
473
+ return TransitionResult::Err(WFMachinesError::Nondeterminism(format!(
474
+ "Local activity (seq {}) failed locally, but history said it completed!",
475
+ shared.attrs.seq
476
+ )));
477
+ }
478
+ verify_marker_dat!(&shared, &dat, TransitionResult::default())
479
+ }
480
+ }
481
+
482
+ #[derive(Default, Clone)]
483
+ pub(super) struct WaitingMarkerEvent {
484
+ already_resolved: bool,
485
+ }
486
+
487
+ impl WaitingMarkerEvent {
488
+ pub(super) fn on_marker_recorded(
489
+ self,
490
+ shared: SharedState,
491
+ dat: CompleteLocalActivityData,
492
+ ) -> LocalActivityMachineTransition<MarkerCommandRecorded> {
493
+ verify_marker_dat!(
494
+ &shared,
495
+ &dat,
496
+ TransitionResult::commands(if self.already_resolved {
497
+ vec![]
498
+ } else {
499
+ vec![LocalActivityCommand::Resolved(dat.into())]
500
+ })
501
+ )
502
+ }
503
+ pub(super) fn on_started_non_replay_wft(
504
+ self,
505
+ mut dat: SharedState,
506
+ ) -> LocalActivityMachineTransition<RequestSent> {
507
+ // We aren't really "replaying" anymore for our purposes, and want to record the marker.
508
+ dat.replaying_when_invoked = false;
509
+ TransitionResult::ok_shared(
510
+ [LocalActivityCommand::RequestActivityExecution(
511
+ dat.attrs.clone(),
512
+ )],
513
+ RequestSent::default(),
514
+ dat,
515
+ )
516
+ }
517
+
518
+ fn on_cancel_requested(self) -> LocalActivityMachineTransition<WaitingMarkerEventCancelled> {
519
+ // We still "request a cancel" even though we know the local activity should not be running
520
+ // because the data might be in the pre-resolved list.
521
+ TransitionResult::ok(
522
+ [LocalActivityCommand::RequestCancel],
523
+ WaitingMarkerEventCancelled {},
524
+ )
525
+ }
526
+
527
+ fn on_no_wait_cancel(
528
+ self,
529
+ _: ActivityCancellationType,
530
+ ) -> LocalActivityMachineTransition<WaitingMarkerEventCancelled> {
531
+ // Markers are always recorded when cancelling, so this is the same as a normal cancel on
532
+ // the replay path
533
+ self.on_cancel_requested()
534
+ }
535
+ }
536
+
537
+ #[derive(Default, Clone)]
538
+ pub(super) struct WaitingMarkerEventCancelled {}
539
+ impl WaitingMarkerEventCancelled {
540
+ fn on_handle_result(
541
+ self,
542
+ dat: ResolveDat,
543
+ ) -> LocalActivityMachineTransition<WaitingMarkerEvent> {
544
+ TransitionResult::ok(
545
+ [LocalActivityCommand::Resolved(dat)],
546
+ WaitingMarkerEvent {
547
+ already_resolved: true,
548
+ },
549
+ )
550
+ }
551
+ }
552
+
553
+ #[derive(Default, Clone)]
554
+ pub(super) struct WaitingMarkerEventPreResolved {}
555
+ impl WaitingMarkerEventPreResolved {
556
+ pub(super) fn on_marker_recorded(
557
+ self,
558
+ shared: SharedState,
559
+ dat: CompleteLocalActivityData,
560
+ ) -> LocalActivityMachineTransition<MarkerCommandRecorded> {
561
+ verify_marker_dat!(&shared, &dat, TransitionResult::default())
562
+ }
563
+ }
564
+
565
+ impl Cancellable for LocalActivityMachine {
566
+ fn cancel(&mut self) -> Result<Vec<MachineResponse>, MachineError<Self::Error>> {
567
+ let event = match self.shared_state.attrs.cancellation_type {
568
+ ct @ ActivityCancellationType::TryCancel | ct @ ActivityCancellationType::Abandon => {
569
+ LocalActivityMachineEvents::NoWaitCancel(ct)
570
+ }
571
+ _ => LocalActivityMachineEvents::Cancel,
572
+ };
573
+ let cmds = OnEventWrapper::on_event_mut(self, event)?;
574
+ let mach_resps = cmds
575
+ .into_iter()
576
+ .map(|mc| self.adapt_response(mc, None))
577
+ .collect::<Result<Vec<_>, _>>()?
578
+ .into_iter()
579
+ .flatten()
580
+ .collect();
581
+ Ok(mach_resps)
582
+ }
583
+
584
+ fn was_cancelled_before_sent_to_server(&self) -> bool {
585
+ // This needs to always be false because for the situation where we cancel in the same WFT,
586
+ // no command of any kind is created and no LA request is queued. Otherwise, the command we
587
+ // create to record a cancel marker *needs* to be sent to the server still, which returning
588
+ // true here would prevent.
589
+ false
590
+ }
591
+ }
592
+
593
+ #[derive(Default, Clone)]
594
+ pub(super) struct Abandoned {}
595
+
596
+ impl WFMachinesAdapter for LocalActivityMachine {
597
+ fn adapt_response(
598
+ &self,
599
+ my_command: Self::Command,
600
+ _event_info: Option<EventInfo>,
601
+ ) -> Result<Vec<MachineResponse>, WFMachinesError> {
602
+ match my_command {
603
+ LocalActivityCommand::RequestActivityExecution(act) => {
604
+ Ok(vec![MachineResponse::QueueLocalActivity(act)])
605
+ }
606
+ LocalActivityCommand::Resolved(ResolveDat {
607
+ result,
608
+ complete_time,
609
+ attempt,
610
+ backoff,
611
+ original_schedule_time,
612
+ }) => {
613
+ let mut maybe_ok_result = None;
614
+ let mut maybe_failure = None;
615
+ // Only issue record marker commands if we weren't replaying
616
+ let record_marker = !self.shared_state.replaying_when_invoked;
617
+ let mut will_not_run_again = false;
618
+ match result.clone() {
619
+ LocalActivityExecutionResult::Completed(suc) => {
620
+ maybe_ok_result = suc.result;
621
+ }
622
+ LocalActivityExecutionResult::Failed(fail) => {
623
+ maybe_failure = fail.failure;
624
+ }
625
+ LocalActivityExecutionResult::Cancelled(Cancellation { failure })
626
+ | LocalActivityExecutionResult::TimedOut(ActFail { failure }) => {
627
+ will_not_run_again = true;
628
+ maybe_failure = failure;
629
+ }
630
+ };
631
+ let resolution = if let Some(b) = backoff.as_ref() {
632
+ ActivityResolution {
633
+ status: Some(
634
+ DoBackoff {
635
+ attempt: attempt + 1,
636
+ backoff_duration: Some(b.clone()),
637
+ original_schedule_time: original_schedule_time.map(Into::into),
638
+ }
639
+ .into(),
640
+ ),
641
+ }
642
+ } else {
643
+ result.into()
644
+ };
645
+ let mut responses = vec![
646
+ MachineResponse::PushWFJob(
647
+ ResolveActivity {
648
+ seq: self.shared_state.attrs.seq,
649
+ result: Some(resolution),
650
+ }
651
+ .into(),
652
+ ),
653
+ MachineResponse::UpdateWFTime(complete_time),
654
+ ];
655
+
656
+ // Cancel-resolves of abandoned activities must be explicitly dropped from tracking
657
+ // to avoid unnecessary WFT heartbeating.
658
+ if will_not_run_again
659
+ && matches!(
660
+ self.shared_state.attrs.cancellation_type,
661
+ ActivityCancellationType::Abandon
662
+ )
663
+ {
664
+ responses.push(MachineResponse::AbandonLocalActivity(
665
+ self.shared_state.attrs.seq,
666
+ ));
667
+ }
668
+
669
+ if record_marker {
670
+ let marker_data = RecordMarkerCommandAttributes {
671
+ marker_name: LOCAL_ACTIVITY_MARKER_NAME.to_string(),
672
+ details: build_local_activity_marker_details(
673
+ LocalActivityMarkerData {
674
+ seq: self.shared_state.attrs.seq,
675
+ attempt,
676
+ activity_id: self.shared_state.attrs.activity_id.clone(),
677
+ activity_type: self.shared_state.attrs.activity_type.clone(),
678
+ complete_time: complete_time.map(Into::into),
679
+ backoff,
680
+ original_schedule_time: original_schedule_time.map(Into::into),
681
+ },
682
+ maybe_ok_result,
683
+ ),
684
+ header: None,
685
+ failure: maybe_failure,
686
+ };
687
+ responses.push(MachineResponse::IssueNewCommand(Command {
688
+ command_type: CommandType::RecordMarker as i32,
689
+ attributes: Some(marker_data.into()),
690
+ }));
691
+ }
692
+ Ok(responses)
693
+ }
694
+ LocalActivityCommand::FakeMarker => {
695
+ // See docs for `FakeMarker` for more
696
+ Ok(vec![MachineResponse::IssueFakeLocalActivityMarker(
697
+ self.shared_state.attrs.seq,
698
+ )])
699
+ }
700
+ LocalActivityCommand::RequestCancel => {
701
+ Ok(vec![MachineResponse::RequestCancelLocalActivity(
702
+ self.shared_state.attrs.seq,
703
+ )])
704
+ }
705
+ }
706
+ }
707
+
708
+ fn matches_event(&self, event: &HistoryEvent) -> bool {
709
+ event.is_local_activity_marker()
710
+ }
711
+
712
+ fn kind(&self) -> MachineKind {
713
+ MachineKind::LocalActivity
714
+ }
715
+ }
716
+
717
+ impl TryFrom<CommandType> for LocalActivityMachineEvents {
718
+ type Error = ();
719
+
720
+ fn try_from(c: CommandType) -> Result<Self, Self::Error> {
721
+ Ok(match c {
722
+ CommandType::RecordMarker => Self::CommandRecordMarker,
723
+ _ => return Err(()),
724
+ })
725
+ }
726
+ }
727
+
728
+ impl TryFrom<HistoryEvent> for LocalActivityMachineEvents {
729
+ type Error = WFMachinesError;
730
+
731
+ fn try_from(e: HistoryEvent) -> Result<Self, Self::Error> {
732
+ if e.event_type() != EventType::MarkerRecorded {
733
+ return Err(WFMachinesError::Nondeterminism(format!(
734
+ "Local activity machine cannot handle this event: {}",
735
+ e
736
+ )));
737
+ }
738
+
739
+ match e.into_local_activity_marker_details() {
740
+ Some(marker_dat) => Ok(LocalActivityMachineEvents::MarkerRecorded(marker_dat)),
741
+ _ => Err(WFMachinesError::Nondeterminism(
742
+ "Local activity machine encountered an unparsable marker".to_string(),
743
+ )),
744
+ }
745
+ }
746
+ }
747
+
748
+ fn verify_marker_data_matches(
749
+ shared: &SharedState,
750
+ dat: &CompleteLocalActivityData,
751
+ ) -> Result<(), WFMachinesError> {
752
+ if shared.attrs.seq != dat.marker_dat.seq {
753
+ return Err(WFMachinesError::Nondeterminism(format!(
754
+ "Local activity marker data has sequence number {} but matched against LA \
755
+ command with sequence number {}",
756
+ dat.marker_dat.seq, shared.attrs.seq
757
+ )));
758
+ }
759
+
760
+ Ok(())
761
+ }
762
+
763
+ impl From<LocalActivityExecutionResult> for ActivityResolution {
764
+ fn from(lar: LocalActivityExecutionResult) -> Self {
765
+ match lar {
766
+ LocalActivityExecutionResult::Completed(c) => ActivityResolution {
767
+ status: Some(c.into()),
768
+ },
769
+ LocalActivityExecutionResult::Failed(f) | LocalActivityExecutionResult::TimedOut(f) => {
770
+ ActivityResolution {
771
+ status: Some(f.into()),
772
+ }
773
+ }
774
+ LocalActivityExecutionResult::Cancelled(cancel) => ActivityResolution {
775
+ status: Some(cancel.into()),
776
+ },
777
+ }
778
+ }
779
+ }
780
+
781
+ #[cfg(test)]
782
+ mod tests {
783
+ use super::*;
784
+ use crate::{
785
+ replay::TestHistoryBuilder, test_help::canned_histories, worker::workflow::ManagedWFFunc,
786
+ };
787
+ use rstest::rstest;
788
+ use std::time::Duration;
789
+ use temporal_sdk::{
790
+ CancellableFuture, LocalActivityOptions, WfContext, WorkflowFunction, WorkflowResult,
791
+ };
792
+ use temporal_sdk_core_protos::{
793
+ coresdk::{
794
+ activity_result::ActivityExecutionResult,
795
+ workflow_activation::{workflow_activation_job, WorkflowActivationJob},
796
+ workflow_commands::ActivityCancellationType::WaitCancellationCompleted,
797
+ },
798
+ temporal::api::{
799
+ command::v1::command, enums::v1::WorkflowTaskFailedCause, failure::v1::Failure,
800
+ },
801
+ };
802
+
803
+ async fn la_wf(ctx: WfContext) -> WorkflowResult<()> {
804
+ ctx.local_activity(LocalActivityOptions::default()).await;
805
+ Ok(().into())
806
+ }
807
+
808
+ #[rstest]
809
+ #[case::incremental(false, true)]
810
+ #[case::replay(true, true)]
811
+ #[case::incremental_fail(false, false)]
812
+ #[case::replay_fail(true, false)]
813
+ #[tokio::test]
814
+ async fn one_la_success(#[case] replay: bool, #[case] completes_ok: bool) {
815
+ let func = WorkflowFunction::new(la_wf);
816
+ let activity_id = "1";
817
+ let mut t = TestHistoryBuilder::default();
818
+ t.add_by_type(EventType::WorkflowExecutionStarted);
819
+ t.add_full_wf_task();
820
+ if completes_ok {
821
+ t.add_local_activity_result_marker(1, activity_id, b"hi".into());
822
+ } else {
823
+ t.add_local_activity_fail_marker(
824
+ 1,
825
+ activity_id,
826
+ Failure::application_failure("I failed".to_string(), false),
827
+ );
828
+ }
829
+ t.add_workflow_execution_completed();
830
+
831
+ let histinfo = if replay {
832
+ t.get_full_history_info().unwrap().into()
833
+ } else {
834
+ t.get_history_info(1).unwrap().into()
835
+ };
836
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
837
+
838
+ // First activation will have no server commands. Activity will be put into the activity
839
+ // queue locally
840
+ wfm.get_next_activation().await.unwrap();
841
+ let commands = wfm.get_server_commands().commands;
842
+ assert_eq!(commands.len(), 0);
843
+
844
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
845
+ if !replay {
846
+ assert_eq!(ready_to_execute_las.len(), 1);
847
+ } else {
848
+ assert_eq!(ready_to_execute_las.len(), 0);
849
+ }
850
+
851
+ if !replay {
852
+ if completes_ok {
853
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"hi".into()))
854
+ .unwrap();
855
+ } else {
856
+ wfm.complete_local_activity(
857
+ 1,
858
+ ActivityExecutionResult::fail(Failure {
859
+ message: "I failed".to_string(),
860
+ ..Default::default()
861
+ }),
862
+ )
863
+ .unwrap();
864
+ }
865
+ }
866
+
867
+ // Now the next activation will unblock the local activity
868
+ wfm.get_next_activation().await.unwrap();
869
+ let commands = wfm.get_server_commands().commands;
870
+ if replay {
871
+ assert_eq!(commands.len(), 1);
872
+ assert_eq!(
873
+ commands[0].command_type,
874
+ CommandType::CompleteWorkflowExecution as i32
875
+ );
876
+ } else {
877
+ assert_eq!(commands.len(), 2);
878
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
879
+ if completes_ok {
880
+ assert_matches!(
881
+ commands[0].attributes.as_ref().unwrap(),
882
+ command::Attributes::RecordMarkerCommandAttributes(
883
+ RecordMarkerCommandAttributes { failure: None, .. }
884
+ )
885
+ );
886
+ } else {
887
+ assert_matches!(
888
+ commands[0].attributes.as_ref().unwrap(),
889
+ command::Attributes::RecordMarkerCommandAttributes(
890
+ RecordMarkerCommandAttributes {
891
+ failure: Some(_),
892
+ ..
893
+ }
894
+ )
895
+ );
896
+ }
897
+ assert_eq!(
898
+ commands[1].command_type,
899
+ CommandType::CompleteWorkflowExecution as i32
900
+ );
901
+ }
902
+
903
+ if !replay {
904
+ wfm.new_history(t.get_full_history_info().unwrap().into())
905
+ .await
906
+ .unwrap();
907
+ }
908
+ assert_eq!(wfm.drain_queued_local_activities().len(), 0);
909
+ assert_eq!(wfm.get_next_activation().await.unwrap().jobs.len(), 0);
910
+ let commands = wfm.get_server_commands().commands;
911
+ assert_eq!(commands.len(), 0);
912
+
913
+ wfm.shutdown().await.unwrap();
914
+ }
915
+
916
+ async fn two_la_wf(ctx: WfContext) -> WorkflowResult<()> {
917
+ ctx.local_activity(LocalActivityOptions::default()).await;
918
+ ctx.local_activity(LocalActivityOptions::default()).await;
919
+ Ok(().into())
920
+ }
921
+
922
+ #[rstest]
923
+ #[case::incremental(false)]
924
+ #[case::replay(true)]
925
+ #[tokio::test]
926
+ async fn two_sequential_las(#[case] replay: bool) {
927
+ let func = WorkflowFunction::new(two_la_wf);
928
+ let t = canned_histories::two_local_activities_one_wft(false);
929
+ let histinfo = if replay {
930
+ t.get_full_history_info().unwrap().into()
931
+ } else {
932
+ t.get_history_info(1).unwrap().into()
933
+ };
934
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
935
+
936
+ // First activation will have no server commands. Activity will be put into the activity
937
+ // queue locally
938
+ let act = wfm.get_next_activation().await.unwrap();
939
+ let first_act_ts = act.timestamp.unwrap();
940
+ let commands = wfm.get_server_commands().commands;
941
+ assert_eq!(commands.len(), 0);
942
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
943
+ let num_queued = if !replay { 1 } else { 0 };
944
+ assert_eq!(ready_to_execute_las.len(), num_queued);
945
+
946
+ if !replay {
947
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"Resolved".into()))
948
+ .unwrap();
949
+ }
950
+
951
+ let act = wfm.get_next_activation().await.unwrap();
952
+ // Verify LAs advance time (they take 1s in this test)
953
+ assert_eq!(act.timestamp.unwrap().seconds, first_act_ts.seconds + 1);
954
+ assert_matches!(
955
+ act.jobs.as_slice(),
956
+ [WorkflowActivationJob {
957
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
958
+ }] => assert_eq!(ra.seq, 1)
959
+ );
960
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
961
+ if !replay {
962
+ assert_eq!(ready_to_execute_las.len(), 1);
963
+ } else {
964
+ assert_eq!(ready_to_execute_las.len(), 0);
965
+ }
966
+
967
+ if !replay {
968
+ wfm.complete_local_activity(2, ActivityExecutionResult::ok(b"Resolved".into()))
969
+ .unwrap();
970
+ }
971
+
972
+ let act = wfm.get_next_activation().await.unwrap();
973
+ assert_eq!(act.timestamp.unwrap().seconds, first_act_ts.seconds + 2);
974
+ assert_matches!(
975
+ act.jobs.as_slice(),
976
+ [WorkflowActivationJob {
977
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
978
+ }] => assert_eq!(ra.seq, 2)
979
+ );
980
+ let commands = wfm.get_server_commands().commands;
981
+ if replay {
982
+ assert_eq!(commands.len(), 1);
983
+ assert_eq!(
984
+ commands[0].command_type,
985
+ CommandType::CompleteWorkflowExecution as i32
986
+ );
987
+ } else {
988
+ assert_eq!(commands.len(), 3);
989
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
990
+ assert_eq!(commands[1].command_type, CommandType::RecordMarker as i32);
991
+ assert_eq!(
992
+ commands[2].command_type,
993
+ CommandType::CompleteWorkflowExecution as i32
994
+ );
995
+ }
996
+
997
+ if !replay {
998
+ wfm.new_history(t.get_full_history_info().unwrap().into())
999
+ .await
1000
+ .unwrap();
1001
+ }
1002
+ assert_eq!(wfm.get_next_activation().await.unwrap().jobs.len(), 0);
1003
+ let commands = wfm.get_server_commands().commands;
1004
+ assert_eq!(commands.len(), 0);
1005
+
1006
+ wfm.shutdown().await.unwrap();
1007
+ }
1008
+
1009
+ async fn two_la_wf_parallel(ctx: WfContext) -> WorkflowResult<()> {
1010
+ tokio::join!(
1011
+ ctx.local_activity(LocalActivityOptions::default()),
1012
+ ctx.local_activity(LocalActivityOptions::default())
1013
+ );
1014
+ Ok(().into())
1015
+ }
1016
+
1017
+ #[rstest]
1018
+ #[case::incremental(false)]
1019
+ #[case::replay(true)]
1020
+ #[tokio::test]
1021
+ async fn two_parallel_las(#[case] replay: bool) {
1022
+ let func = WorkflowFunction::new(two_la_wf_parallel);
1023
+ let t = canned_histories::two_local_activities_one_wft(true);
1024
+ let histinfo = if replay {
1025
+ t.get_full_history_info().unwrap().into()
1026
+ } else {
1027
+ t.get_history_info(1).unwrap().into()
1028
+ };
1029
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1030
+
1031
+ // First activation will have no server commands. Activity(ies) will be put into the queue
1032
+ // for execution
1033
+ let act = wfm.get_next_activation().await.unwrap();
1034
+ let first_act_ts = act.timestamp.unwrap();
1035
+ let commands = wfm.get_server_commands().commands;
1036
+ assert_eq!(commands.len(), 0);
1037
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1038
+ let num_queued = if !replay { 2 } else { 0 };
1039
+ assert_eq!(ready_to_execute_las.len(), num_queued);
1040
+
1041
+ if !replay {
1042
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"Resolved".into()))
1043
+ .unwrap();
1044
+ wfm.complete_local_activity(2, ActivityExecutionResult::ok(b"Resolved".into()))
1045
+ .unwrap();
1046
+ }
1047
+
1048
+ let act = wfm.get_next_activation().await.unwrap();
1049
+ assert_eq!(act.timestamp.unwrap().seconds, first_act_ts.seconds + 1);
1050
+ assert_matches!(
1051
+ act.jobs.as_slice(),
1052
+ [WorkflowActivationJob {
1053
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
1054
+ },
1055
+ WorkflowActivationJob {
1056
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra2))
1057
+ }] => {assert_eq!(ra.seq, 1); assert_eq!(ra2.seq, 2)}
1058
+ );
1059
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1060
+ assert_eq!(ready_to_execute_las.len(), 0);
1061
+
1062
+ let commands = wfm.get_server_commands().commands;
1063
+ if replay {
1064
+ assert_eq!(commands.len(), 1);
1065
+ assert_eq!(
1066
+ commands[0].command_type,
1067
+ CommandType::CompleteWorkflowExecution as i32
1068
+ );
1069
+ } else {
1070
+ assert_eq!(commands.len(), 3);
1071
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1072
+ assert_eq!(commands[1].command_type, CommandType::RecordMarker as i32);
1073
+ assert_eq!(
1074
+ commands[2].command_type,
1075
+ CommandType::CompleteWorkflowExecution as i32
1076
+ );
1077
+ }
1078
+
1079
+ if !replay {
1080
+ wfm.new_history(t.get_full_history_info().unwrap().into())
1081
+ .await
1082
+ .unwrap();
1083
+ }
1084
+ let act = wfm.get_next_activation().await.unwrap();
1085
+ // Still only 1s ahead b/c parallel
1086
+ assert_eq!(act.timestamp.unwrap().seconds, first_act_ts.seconds + 1);
1087
+ assert_eq!(act.jobs.len(), 0);
1088
+ let commands = wfm.get_server_commands().commands;
1089
+ assert_eq!(commands.len(), 0);
1090
+
1091
+ wfm.shutdown().await.unwrap();
1092
+ }
1093
+
1094
+ async fn la_timer_la(ctx: WfContext) -> WorkflowResult<()> {
1095
+ ctx.local_activity(LocalActivityOptions::default()).await;
1096
+ ctx.timer(Duration::from_secs(5)).await;
1097
+ ctx.local_activity(LocalActivityOptions::default()).await;
1098
+ Ok(().into())
1099
+ }
1100
+
1101
+ #[rstest]
1102
+ #[case::incremental(false)]
1103
+ #[case::replay(true)]
1104
+ #[tokio::test]
1105
+ async fn las_separated_by_timer(#[case] replay: bool) {
1106
+ let func = WorkflowFunction::new(la_timer_la);
1107
+ let t = canned_histories::two_local_activities_separated_by_timer();
1108
+ let histinfo = if replay {
1109
+ t.get_full_history_info().unwrap().into()
1110
+ } else {
1111
+ t.get_history_info(1).unwrap().into()
1112
+ };
1113
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1114
+
1115
+ wfm.get_next_activation().await.unwrap();
1116
+ let commands = wfm.get_server_commands().commands;
1117
+ assert_eq!(commands.len(), 0);
1118
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1119
+ let num_queued = if !replay { 1 } else { 0 };
1120
+ assert_eq!(ready_to_execute_las.len(), num_queued);
1121
+
1122
+ if !replay {
1123
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"Resolved".into()))
1124
+ .unwrap();
1125
+ }
1126
+
1127
+ let act = wfm.get_next_activation().await.unwrap();
1128
+ assert_matches!(
1129
+ act.jobs.as_slice(),
1130
+ [WorkflowActivationJob {
1131
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
1132
+ }] => assert_eq!(ra.seq, 1)
1133
+ );
1134
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1135
+ assert_eq!(ready_to_execute_las.len(), 0);
1136
+
1137
+ let commands = wfm.get_server_commands().commands;
1138
+ if replay {
1139
+ assert_eq!(commands.len(), 1);
1140
+ assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
1141
+ } else {
1142
+ assert_eq!(commands.len(), 2);
1143
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1144
+ assert_eq!(commands[1].command_type, CommandType::StartTimer as i32);
1145
+ }
1146
+
1147
+ let act = if !replay {
1148
+ wfm.new_history(t.get_history_info(2).unwrap().into())
1149
+ .await
1150
+ .unwrap()
1151
+ } else {
1152
+ wfm.get_next_activation().await.unwrap()
1153
+ };
1154
+ assert_matches!(
1155
+ act.jobs.as_slice(),
1156
+ [WorkflowActivationJob {
1157
+ variant: Some(workflow_activation_job::Variant::FireTimer(_))
1158
+ }]
1159
+ );
1160
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1161
+ let num_queued = if !replay { 1 } else { 0 };
1162
+ assert_eq!(ready_to_execute_las.len(), num_queued);
1163
+ if !replay {
1164
+ wfm.complete_local_activity(2, ActivityExecutionResult::ok(b"Resolved".into()))
1165
+ .unwrap();
1166
+ }
1167
+
1168
+ let act = wfm.get_next_activation().await.unwrap();
1169
+ assert_matches!(
1170
+ act.jobs.as_slice(),
1171
+ [WorkflowActivationJob {
1172
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
1173
+ }] => assert_eq!(ra.seq, 2)
1174
+ );
1175
+
1176
+ let commands = wfm.get_server_commands().commands;
1177
+ if replay {
1178
+ assert_eq!(commands.len(), 1);
1179
+ assert_eq!(
1180
+ commands[0].command_type,
1181
+ CommandType::CompleteWorkflowExecution as i32
1182
+ );
1183
+ } else {
1184
+ assert_eq!(commands.len(), 2);
1185
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1186
+ assert_eq!(
1187
+ commands[1].command_type,
1188
+ CommandType::CompleteWorkflowExecution as i32
1189
+ );
1190
+ }
1191
+
1192
+ wfm.shutdown().await.unwrap();
1193
+ }
1194
+
1195
+ #[tokio::test]
1196
+ async fn one_la_heartbeating_wft_failure_still_executes() {
1197
+ let func = WorkflowFunction::new(la_wf);
1198
+ let mut t = TestHistoryBuilder::default();
1199
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1200
+ // Heartbeats
1201
+ t.add_full_wf_task();
1202
+ // fails a wft for some reason
1203
+ t.add_workflow_task_scheduled_and_started();
1204
+ t.add_workflow_task_failed_with_failure(
1205
+ WorkflowTaskFailedCause::NonDeterministicError,
1206
+ Default::default(),
1207
+ );
1208
+ t.add_workflow_task_scheduled_and_started();
1209
+
1210
+ let histinfo = t.get_full_history_info().unwrap().into();
1211
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1212
+
1213
+ // First activation will request to run the LA, but it will *not* be queued for execution
1214
+ // yet as we're still replaying.
1215
+ wfm.get_next_activation().await.unwrap();
1216
+ let commands = wfm.get_server_commands().commands;
1217
+ assert_eq!(commands.len(), 0);
1218
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1219
+ assert_eq!(ready_to_execute_las.len(), 0);
1220
+
1221
+ // On the *next* activation, we are no longer replaying and the activity should be queued
1222
+ wfm.get_next_activation().await.unwrap();
1223
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1224
+ assert_eq!(ready_to_execute_las.len(), 1);
1225
+ // We can happily complete it now
1226
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"hi".into()))
1227
+ .unwrap();
1228
+
1229
+ wfm.shutdown().await.unwrap();
1230
+ }
1231
+
1232
+ /// This test verifies something that technically shouldn't really be possible but is worth
1233
+ /// checking anyway. What happens if in memory we think an LA passed but then the next history
1234
+ /// chunk comes back with it failing? We should fail with a mismatch.
1235
+ #[tokio::test]
1236
+ async fn exec_passes_but_history_has_fail() {
1237
+ let func = WorkflowFunction::new(la_wf);
1238
+ let mut t = TestHistoryBuilder::default();
1239
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1240
+ t.add_full_wf_task();
1241
+ t.add_local_activity_fail_marker(
1242
+ 1,
1243
+ "1",
1244
+ Failure::application_failure("I failed".to_string(), false),
1245
+ );
1246
+ t.add_workflow_execution_completed();
1247
+
1248
+ let histinfo = t.get_history_info(1).unwrap().into();
1249
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1250
+
1251
+ wfm.get_next_activation().await.unwrap();
1252
+ let commands = wfm.get_server_commands().commands;
1253
+ assert_eq!(commands.len(), 0);
1254
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1255
+ assert_eq!(ready_to_execute_las.len(), 1);
1256
+ // Completes OK
1257
+ wfm.complete_local_activity(1, ActivityExecutionResult::ok(b"hi".into()))
1258
+ .unwrap();
1259
+
1260
+ // next activation unblocks LA
1261
+ wfm.get_next_activation().await.unwrap();
1262
+ let commands = wfm.get_server_commands().commands;
1263
+ assert_eq!(commands.len(), 2);
1264
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1265
+ assert_eq!(
1266
+ commands[1].command_type,
1267
+ CommandType::CompleteWorkflowExecution as i32
1268
+ );
1269
+
1270
+ let err = wfm
1271
+ .new_history(t.get_full_history_info().unwrap().into())
1272
+ .await
1273
+ .unwrap_err();
1274
+ assert!(err.to_string().contains("Nondeterminism"));
1275
+ wfm.shutdown().await.unwrap();
1276
+ }
1277
+
1278
+ #[rstest]
1279
+ #[tokio::test]
1280
+ async fn immediate_cancel(
1281
+ #[values(
1282
+ ActivityCancellationType::WaitCancellationCompleted,
1283
+ ActivityCancellationType::TryCancel,
1284
+ ActivityCancellationType::Abandon
1285
+ )]
1286
+ cancel_type: ActivityCancellationType,
1287
+ ) {
1288
+ let func = WorkflowFunction::new(move |ctx| async move {
1289
+ let la = ctx.local_activity(LocalActivityOptions {
1290
+ cancel_type,
1291
+ ..Default::default()
1292
+ });
1293
+ la.cancel(&ctx);
1294
+ la.await;
1295
+ Ok(().into())
1296
+ });
1297
+ let mut t = TestHistoryBuilder::default();
1298
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1299
+ t.add_full_wf_task();
1300
+ t.add_workflow_execution_completed();
1301
+
1302
+ let histinfo = t.get_history_info(1).unwrap().into();
1303
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1304
+
1305
+ wfm.get_next_activation().await.unwrap();
1306
+ let commands = wfm.get_server_commands().commands;
1307
+ assert_eq!(commands.len(), 1);
1308
+ // We record the cancel marker
1309
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1310
+ // Importantly, the activity shouldn't get executed since it was insta-cancelled
1311
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1312
+ assert_eq!(ready_to_execute_las.len(), 0);
1313
+
1314
+ // next activation unblocks LA, which is cancelled now.
1315
+ wfm.get_next_activation().await.unwrap();
1316
+ let commands = wfm.get_server_commands().commands;
1317
+ assert_eq!(commands.len(), 2);
1318
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1319
+ assert_eq!(
1320
+ commands[1].command_type,
1321
+ CommandType::CompleteWorkflowExecution as i32
1322
+ );
1323
+
1324
+ wfm.shutdown().await.unwrap();
1325
+ }
1326
+
1327
+ #[rstest]
1328
+ #[case::incremental(false)]
1329
+ #[case::replay(true)]
1330
+ #[tokio::test]
1331
+ async fn cancel_after_act_starts(
1332
+ #[case] replay: bool,
1333
+ #[values(
1334
+ ActivityCancellationType::WaitCancellationCompleted,
1335
+ ActivityCancellationType::TryCancel,
1336
+ ActivityCancellationType::Abandon
1337
+ )]
1338
+ cancel_type: ActivityCancellationType,
1339
+ ) {
1340
+ let func = WorkflowFunction::new(move |ctx| async move {
1341
+ let la = ctx.local_activity(LocalActivityOptions {
1342
+ cancel_type,
1343
+ ..Default::default()
1344
+ });
1345
+ ctx.timer(Duration::from_secs(1)).await;
1346
+ la.cancel(&ctx);
1347
+ // This extra timer is here to ensure the presence of another WF task doesn't mess up
1348
+ // resolving the LA with cancel on replay
1349
+ ctx.timer(Duration::from_secs(1)).await;
1350
+ let resolution = la.await;
1351
+ assert!(resolution.cancelled());
1352
+ Ok(().into())
1353
+ });
1354
+
1355
+ let mut t = TestHistoryBuilder::default();
1356
+ t.add_by_type(EventType::WorkflowExecutionStarted);
1357
+ t.add_full_wf_task();
1358
+ let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
1359
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
1360
+ t.add_full_wf_task();
1361
+ if cancel_type != ActivityCancellationType::WaitCancellationCompleted {
1362
+ // With non-wait cancels, the cancel is immediate
1363
+ t.add_local_activity_cancel_marker(1, "1");
1364
+ }
1365
+ let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
1366
+ if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
1367
+ // With wait cancels, the cancel marker is not recorded until activity reports.
1368
+ t.add_local_activity_cancel_marker(1, "1");
1369
+ }
1370
+ t.add_timer_fired(timer_started_event_id, "2".to_string());
1371
+ t.add_full_wf_task();
1372
+ t.add_workflow_execution_completed();
1373
+
1374
+ let histinfo = if replay {
1375
+ t.get_full_history_info().unwrap().into()
1376
+ } else {
1377
+ t.get_history_info(1).unwrap().into()
1378
+ };
1379
+ let mut wfm = ManagedWFFunc::new_from_update(histinfo, func, vec![]);
1380
+
1381
+ wfm.get_next_activation().await.unwrap();
1382
+ let commands = wfm.get_server_commands().commands;
1383
+ assert_eq!(commands.len(), 1);
1384
+ assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
1385
+ let ready_to_execute_las = wfm.drain_queued_local_activities();
1386
+ let num_queued = if !replay { 1 } else { 0 };
1387
+ assert_eq!(ready_to_execute_las.len(), num_queued);
1388
+
1389
+ // Next activation timer fires and activity cancel will be requested
1390
+ if replay {
1391
+ wfm.get_next_activation().await.unwrap()
1392
+ } else {
1393
+ wfm.new_history(t.get_history_info(2).unwrap().into())
1394
+ .await
1395
+ .unwrap()
1396
+ };
1397
+
1398
+ let commands = wfm.get_server_commands().commands;
1399
+ if cancel_type == ActivityCancellationType::WaitCancellationCompleted || replay {
1400
+ assert_eq!(commands.len(), 1);
1401
+ assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
1402
+ } else {
1403
+ // Try-cancel/abandon will immediately record marker (when not replaying)
1404
+ assert_eq!(commands.len(), 2);
1405
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1406
+ assert_eq!(commands[1].command_type, CommandType::StartTimer as i32);
1407
+ }
1408
+
1409
+ if replay {
1410
+ wfm.get_next_activation().await.unwrap()
1411
+ } else {
1412
+ // On non replay, there's an additional activation, because completing with the cancel
1413
+ // wants to wake up the workflow to see if resolving the LA as cancelled did anything.
1414
+ // In this case, it doesn't really, because we just hit the next timer which is also
1415
+ // what would have happened if we woke up with new history -- but it does mean we
1416
+ // generate the commands at this point. This matters b/c we want to make sure the record
1417
+ // marker command is sent as soon as cancel happens.
1418
+ if cancel_type == WaitCancellationCompleted {
1419
+ wfm.complete_local_activity(1, ActivityExecutionResult::cancel_from_details(None))
1420
+ .unwrap();
1421
+ }
1422
+ wfm.get_next_activation().await.unwrap();
1423
+ let commands = wfm.get_server_commands().commands;
1424
+ assert_eq!(commands.len(), 2);
1425
+ if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
1426
+ assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
1427
+ assert_eq!(commands[1].command_type, CommandType::RecordMarker as i32);
1428
+ } else {
1429
+ assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
1430
+ assert_eq!(commands[1].command_type, CommandType::StartTimer as i32);
1431
+ }
1432
+
1433
+ wfm.new_history(t.get_history_info(3).unwrap().into())
1434
+ .await
1435
+ .unwrap()
1436
+ };
1437
+
1438
+ wfm.get_next_activation().await.unwrap();
1439
+ let commands = wfm.get_server_commands().commands;
1440
+ assert_eq!(commands.len(), 1);
1441
+ assert_eq!(
1442
+ commands[0].command_type,
1443
+ CommandType::CompleteWorkflowExecution as i32
1444
+ );
1445
+
1446
+ wfm.shutdown().await.unwrap();
1447
+ }
1448
+ }