temporalio 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +130 -0
- data/bridge/Cargo.lock +2865 -0
- data/bridge/Cargo.toml +26 -0
- data/bridge/sdk-core/ARCHITECTURE.md +76 -0
- data/bridge/sdk-core/Cargo.lock +2606 -0
- data/bridge/sdk-core/Cargo.toml +2 -0
- data/bridge/sdk-core/LICENSE.txt +23 -0
- data/bridge/sdk-core/README.md +107 -0
- data/bridge/sdk-core/arch_docs/diagrams/README.md +10 -0
- data/bridge/sdk-core/arch_docs/diagrams/sticky_queues.puml +40 -0
- data/bridge/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
- data/bridge/sdk-core/arch_docs/sticky_queues.md +51 -0
- data/bridge/sdk-core/bridge-ffi/Cargo.toml +24 -0
- data/bridge/sdk-core/bridge-ffi/LICENSE.txt +23 -0
- data/bridge/sdk-core/bridge-ffi/build.rs +25 -0
- data/bridge/sdk-core/bridge-ffi/include/sdk-core-bridge.h +249 -0
- data/bridge/sdk-core/bridge-ffi/src/lib.rs +825 -0
- data/bridge/sdk-core/bridge-ffi/src/wrappers.rs +211 -0
- data/bridge/sdk-core/client/Cargo.toml +40 -0
- data/bridge/sdk-core/client/LICENSE.txt +23 -0
- data/bridge/sdk-core/client/src/lib.rs +1294 -0
- data/bridge/sdk-core/client/src/metrics.rs +165 -0
- data/bridge/sdk-core/client/src/raw.rs +931 -0
- data/bridge/sdk-core/client/src/retry.rs +674 -0
- data/bridge/sdk-core/client/src/workflow_handle/mod.rs +185 -0
- data/bridge/sdk-core/core/Cargo.toml +116 -0
- data/bridge/sdk-core/core/LICENSE.txt +23 -0
- data/bridge/sdk-core/core/benches/workflow_replay.rs +73 -0
- data/bridge/sdk-core/core/src/abstractions.rs +166 -0
- data/bridge/sdk-core/core/src/core_tests/activity_tasks.rs +911 -0
- data/bridge/sdk-core/core/src/core_tests/child_workflows.rs +221 -0
- data/bridge/sdk-core/core/src/core_tests/determinism.rs +107 -0
- data/bridge/sdk-core/core/src/core_tests/local_activities.rs +515 -0
- data/bridge/sdk-core/core/src/core_tests/mod.rs +100 -0
- data/bridge/sdk-core/core/src/core_tests/queries.rs +736 -0
- data/bridge/sdk-core/core/src/core_tests/replay_flag.rs +65 -0
- data/bridge/sdk-core/core/src/core_tests/workers.rs +259 -0
- data/bridge/sdk-core/core/src/core_tests/workflow_cancels.rs +124 -0
- data/bridge/sdk-core/core/src/core_tests/workflow_tasks.rs +2070 -0
- data/bridge/sdk-core/core/src/ephemeral_server/mod.rs +515 -0
- data/bridge/sdk-core/core/src/lib.rs +175 -0
- data/bridge/sdk-core/core/src/log_export.rs +62 -0
- data/bridge/sdk-core/core/src/pollers/mod.rs +54 -0
- data/bridge/sdk-core/core/src/pollers/poll_buffer.rs +297 -0
- data/bridge/sdk-core/core/src/protosext/mod.rs +428 -0
- data/bridge/sdk-core/core/src/replay/mod.rs +71 -0
- data/bridge/sdk-core/core/src/retry_logic.rs +202 -0
- data/bridge/sdk-core/core/src/telemetry/metrics.rs +383 -0
- data/bridge/sdk-core/core/src/telemetry/mod.rs +412 -0
- data/bridge/sdk-core/core/src/telemetry/prometheus_server.rs +77 -0
- data/bridge/sdk-core/core/src/test_help/mod.rs +875 -0
- data/bridge/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +580 -0
- data/bridge/sdk-core/core/src/worker/activities/local_activities.rs +1042 -0
- data/bridge/sdk-core/core/src/worker/activities.rs +464 -0
- data/bridge/sdk-core/core/src/worker/client/mocks.rs +87 -0
- data/bridge/sdk-core/core/src/worker/client.rs +347 -0
- data/bridge/sdk-core/core/src/worker/mod.rs +566 -0
- data/bridge/sdk-core/core/src/worker/workflow/bridge.rs +37 -0
- data/bridge/sdk-core/core/src/worker/workflow/driven_workflow.rs +110 -0
- data/bridge/sdk-core/core/src/worker/workflow/history_update.rs +458 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +911 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +298 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +171 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +860 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +140 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +161 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +133 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +1448 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/mod.rs +342 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/mutable_side_effect_state_machine.rs +127 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +712 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/side_effect_state_machine.rs +71 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +443 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +439 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +169 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +246 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +96 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +1184 -0
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +277 -0
- data/bridge/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
- data/bridge/sdk-core/core/src/worker/workflow/managed_run.rs +647 -0
- data/bridge/sdk-core/core/src/worker/workflow/mod.rs +1143 -0
- data/bridge/sdk-core/core/src/worker/workflow/run_cache.rs +145 -0
- data/bridge/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
- data/bridge/sdk-core/core/src/worker/workflow/workflow_stream.rs +940 -0
- data/bridge/sdk-core/core-api/Cargo.toml +31 -0
- data/bridge/sdk-core/core-api/LICENSE.txt +23 -0
- data/bridge/sdk-core/core-api/src/errors.rs +95 -0
- data/bridge/sdk-core/core-api/src/lib.rs +151 -0
- data/bridge/sdk-core/core-api/src/worker.rs +135 -0
- data/bridge/sdk-core/etc/deps.svg +187 -0
- data/bridge/sdk-core/etc/dynamic-config.yaml +2 -0
- data/bridge/sdk-core/etc/otel-collector-config.yaml +36 -0
- data/bridge/sdk-core/etc/prometheus.yaml +6 -0
- data/bridge/sdk-core/fsm/Cargo.toml +18 -0
- data/bridge/sdk-core/fsm/LICENSE.txt +23 -0
- data/bridge/sdk-core/fsm/README.md +3 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +27 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/LICENSE.txt +23 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +647 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/progress.rs +8 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.rs +18 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +12 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dynamic_dest_pass.rs +41 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/forgot_name_fail.rs +14 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/forgot_name_fail.stderr +11 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/handler_arg_pass.rs +32 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/handler_pass.rs +31 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/medium_complex_pass.rs +46 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.rs +29 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +12 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/simple_pass.rs +32 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/struct_event_variant_fail.rs +18 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/struct_event_variant_fail.stderr +5 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_more_item_event_variant_fail.rs +11 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_more_item_event_variant_fail.stderr +5 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_zero_item_event_variant_fail.rs +11 -0
- data/bridge/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/tuple_zero_item_event_variant_fail.stderr +5 -0
- data/bridge/sdk-core/fsm/rustfsm_trait/Cargo.toml +14 -0
- data/bridge/sdk-core/fsm/rustfsm_trait/LICENSE.txt +23 -0
- data/bridge/sdk-core/fsm/rustfsm_trait/src/lib.rs +249 -0
- data/bridge/sdk-core/fsm/src/lib.rs +2 -0
- data/bridge/sdk-core/histories/fail_wf_task.bin +0 -0
- data/bridge/sdk-core/histories/timer_workflow_history.bin +0 -0
- data/bridge/sdk-core/integ-with-otel.sh +7 -0
- data/bridge/sdk-core/protos/api_upstream/README.md +9 -0
- data/bridge/sdk-core/protos/api_upstream/api-linter.yaml +40 -0
- data/bridge/sdk-core/protos/api_upstream/buf.yaml +12 -0
- data/bridge/sdk-core/protos/api_upstream/dependencies/gogoproto/gogo.proto +141 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +86 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +259 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +112 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +46 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +57 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +55 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +168 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +97 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +51 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +50 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +41 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +59 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +51 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +122 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +108 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +114 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +56 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +751 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +97 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +161 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +99 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +61 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +55 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +108 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +46 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +59 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +145 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +1124 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +401 -0
- data/bridge/sdk-core/protos/grpc/health/v1/health.proto +63 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +78 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +79 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +210 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +77 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/common/common.proto +15 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +30 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +30 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +261 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +297 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +29 -0
- data/bridge/sdk-core/protos/testsrv_upstream/api-linter.yaml +38 -0
- data/bridge/sdk-core/protos/testsrv_upstream/buf.yaml +13 -0
- data/bridge/sdk-core/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +141 -0
- data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +63 -0
- data/bridge/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +90 -0
- data/bridge/sdk-core/rustfmt.toml +1 -0
- data/bridge/sdk-core/sdk/Cargo.toml +47 -0
- data/bridge/sdk-core/sdk/LICENSE.txt +23 -0
- data/bridge/sdk-core/sdk/src/activity_context.rs +230 -0
- data/bridge/sdk-core/sdk/src/app_data.rs +37 -0
- data/bridge/sdk-core/sdk/src/conversions.rs +8 -0
- data/bridge/sdk-core/sdk/src/interceptors.rs +17 -0
- data/bridge/sdk-core/sdk/src/lib.rs +792 -0
- data/bridge/sdk-core/sdk/src/payload_converter.rs +11 -0
- data/bridge/sdk-core/sdk/src/workflow_context/options.rs +295 -0
- data/bridge/sdk-core/sdk/src/workflow_context.rs +683 -0
- data/bridge/sdk-core/sdk/src/workflow_future.rs +503 -0
- data/bridge/sdk-core/sdk-core-protos/Cargo.toml +30 -0
- data/bridge/sdk-core/sdk-core-protos/LICENSE.txt +23 -0
- data/bridge/sdk-core/sdk-core-protos/build.rs +108 -0
- data/bridge/sdk-core/sdk-core-protos/src/constants.rs +7 -0
- data/bridge/sdk-core/sdk-core-protos/src/history_builder.rs +497 -0
- data/bridge/sdk-core/sdk-core-protos/src/history_info.rs +230 -0
- data/bridge/sdk-core/sdk-core-protos/src/lib.rs +1910 -0
- data/bridge/sdk-core/sdk-core-protos/src/task_token.rs +38 -0
- data/bridge/sdk-core/sdk-core-protos/src/utilities.rs +14 -0
- data/bridge/sdk-core/test-utils/Cargo.toml +35 -0
- data/bridge/sdk-core/test-utils/src/canned_histories.rs +1579 -0
- data/bridge/sdk-core/test-utils/src/histfetch.rs +28 -0
- data/bridge/sdk-core/test-utils/src/lib.rs +598 -0
- data/bridge/sdk-core/tests/integ_tests/client_tests.rs +36 -0
- data/bridge/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +128 -0
- data/bridge/sdk-core/tests/integ_tests/heartbeat_tests.rs +218 -0
- data/bridge/sdk-core/tests/integ_tests/polling_tests.rs +146 -0
- data/bridge/sdk-core/tests/integ_tests/queries_tests.rs +437 -0
- data/bridge/sdk-core/tests/integ_tests/visibility_tests.rs +93 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/activities.rs +878 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +59 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +58 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +50 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +60 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +54 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +634 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/patches.rs +113 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/replay.rs +137 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/resets.rs +93 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/signals.rs +167 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +99 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/timers.rs +131 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +75 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests.rs +587 -0
- data/bridge/sdk-core/tests/load_tests.rs +191 -0
- data/bridge/sdk-core/tests/main.rs +111 -0
- data/bridge/sdk-core/tests/runner.rs +93 -0
- data/bridge/src/connection.rs +167 -0
- data/bridge/src/lib.rs +180 -0
- data/bridge/src/runtime.rs +47 -0
- data/bridge/src/worker.rs +73 -0
- data/ext/Rakefile +9 -0
- data/lib/bridge.so +0 -0
- data/lib/gen/dependencies/gogoproto/gogo_pb.rb +14 -0
- data/lib/gen/temporal/api/batch/v1/message_pb.rb +48 -0
- data/lib/gen/temporal/api/cluster/v1/message_pb.rb +67 -0
- data/lib/gen/temporal/api/command/v1/message_pb.rb +166 -0
- data/lib/gen/temporal/api/common/v1/message_pb.rb +69 -0
- data/lib/gen/temporal/api/enums/v1/batch_operation_pb.rb +32 -0
- data/lib/gen/temporal/api/enums/v1/cluster_pb.rb +26 -0
- data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +37 -0
- data/lib/gen/temporal/api/enums/v1/common_pb.rb +41 -0
- data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +67 -0
- data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +71 -0
- data/lib/gen/temporal/api/enums/v1/namespace_pb.rb +37 -0
- data/lib/gen/temporal/api/enums/v1/query_pb.rb +31 -0
- data/lib/gen/temporal/api/enums/v1/reset_pb.rb +24 -0
- data/lib/gen/temporal/api/enums/v1/schedule_pb.rb +28 -0
- data/lib/gen/temporal/api/enums/v1/task_queue_pb.rb +30 -0
- data/lib/gen/temporal/api/enums/v1/update_pb.rb +28 -0
- data/lib/gen/temporal/api/enums/v1/workflow_pb.rb +89 -0
- data/lib/gen/temporal/api/errordetails/v1/message_pb.rb +84 -0
- data/lib/gen/temporal/api/failure/v1/message_pb.rb +83 -0
- data/lib/gen/temporal/api/filter/v1/message_pb.rb +40 -0
- data/lib/gen/temporal/api/history/v1/message_pb.rb +489 -0
- data/lib/gen/temporal/api/namespace/v1/message_pb.rb +63 -0
- data/lib/gen/temporal/api/operatorservice/v1/request_response_pb.rb +125 -0
- data/lib/gen/temporal/api/operatorservice/v1/service_pb.rb +20 -0
- data/lib/gen/temporal/api/query/v1/message_pb.rb +38 -0
- data/lib/gen/temporal/api/replication/v1/message_pb.rb +37 -0
- data/lib/gen/temporal/api/schedule/v1/message_pb.rb +128 -0
- data/lib/gen/temporal/api/taskqueue/v1/message_pb.rb +73 -0
- data/lib/gen/temporal/api/update/v1/message_pb.rb +26 -0
- data/lib/gen/temporal/api/version/v1/message_pb.rb +41 -0
- data/lib/gen/temporal/api/workflow/v1/message_pb.rb +110 -0
- data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +771 -0
- data/lib/gen/temporal/api/workflowservice/v1/service_pb.rb +20 -0
- data/lib/gen/temporal/sdk/core/activity_result/activity_result_pb.rb +58 -0
- data/lib/gen/temporal/sdk/core/activity_task/activity_task_pb.rb +57 -0
- data/lib/gen/temporal/sdk/core/bridge/bridge_pb.rb +222 -0
- data/lib/gen/temporal/sdk/core/child_workflow/child_workflow_pb.rb +57 -0
- data/lib/gen/temporal/sdk/core/common/common_pb.rb +22 -0
- data/lib/gen/temporal/sdk/core/core_interface_pb.rb +34 -0
- data/lib/gen/temporal/sdk/core/external_data/external_data_pb.rb +27 -0
- data/lib/gen/temporal/sdk/core/workflow_activation/workflow_activation_pb.rb +164 -0
- data/lib/gen/temporal/sdk/core/workflow_commands/workflow_commands_pb.rb +192 -0
- data/lib/gen/temporal/sdk/core/workflow_completion/workflow_completion_pb.rb +34 -0
- data/lib/temporal/bridge.rb +14 -0
- data/lib/temporal/client/implementation.rb +339 -0
- data/lib/temporal/client/workflow_handle.rb +243 -0
- data/lib/temporal/client.rb +144 -0
- data/lib/temporal/connection.rb +736 -0
- data/lib/temporal/data_converter.rb +150 -0
- data/lib/temporal/error/failure.rb +194 -0
- data/lib/temporal/error/workflow_failure.rb +17 -0
- data/lib/temporal/errors.rb +22 -0
- data/lib/temporal/failure_converter/base.rb +26 -0
- data/lib/temporal/failure_converter/basic.rb +313 -0
- data/lib/temporal/failure_converter.rb +8 -0
- data/lib/temporal/interceptor/chain.rb +27 -0
- data/lib/temporal/interceptor/client.rb +102 -0
- data/lib/temporal/payload_codec/base.rb +32 -0
- data/lib/temporal/payload_converter/base.rb +24 -0
- data/lib/temporal/payload_converter/bytes.rb +26 -0
- data/lib/temporal/payload_converter/composite.rb +47 -0
- data/lib/temporal/payload_converter/encoding_base.rb +35 -0
- data/lib/temporal/payload_converter/json.rb +25 -0
- data/lib/temporal/payload_converter/nil.rb +25 -0
- data/lib/temporal/payload_converter.rb +14 -0
- data/lib/temporal/retry_policy.rb +82 -0
- data/lib/temporal/retry_state.rb +35 -0
- data/lib/temporal/runtime.rb +22 -0
- data/lib/temporal/timeout_type.rb +29 -0
- data/lib/temporal/version.rb +3 -0
- data/lib/temporal/workflow/execution_info.rb +54 -0
- data/lib/temporal/workflow/execution_status.rb +36 -0
- data/lib/temporal/workflow/id_reuse_policy.rb +36 -0
- data/lib/temporal/workflow/query_reject_condition.rb +33 -0
- data/lib/temporal.rb +8 -0
- data/lib/temporalio.rb +3 -0
- data/lib/thermite_patch.rb +23 -0
- data/temporalio.gemspec +41 -0
- metadata +583 -0
@@ -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
|
+
}
|