temporalio 0.0.0 → 0.0.2

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