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,1184 @@
|
|
1
|
+
mod local_acts;
|
2
|
+
|
3
|
+
pub(crate) use temporal_sdk_core_api::errors::WFMachinesError;
|
4
|
+
|
5
|
+
use super::{
|
6
|
+
activity_state_machine::new_activity, cancel_external_state_machine::new_external_cancel,
|
7
|
+
cancel_workflow_state_machine::cancel_workflow,
|
8
|
+
child_workflow_state_machine::new_child_workflow,
|
9
|
+
complete_workflow_state_machine::complete_workflow,
|
10
|
+
continue_as_new_workflow_state_machine::continue_as_new,
|
11
|
+
fail_workflow_state_machine::fail_workflow, local_activity_state_machine::new_local_activity,
|
12
|
+
patch_state_machine::has_change, signal_external_state_machine::new_external_signal,
|
13
|
+
timer_state_machine::new_timer, upsert_search_attributes_state_machine::upsert_search_attrs,
|
14
|
+
workflow_machines::local_acts::LocalActivityData,
|
15
|
+
workflow_task_state_machine::WorkflowTaskMachine, MachineKind, Machines, NewMachineWithCommand,
|
16
|
+
TemporalStateMachine,
|
17
|
+
};
|
18
|
+
use crate::{
|
19
|
+
protosext::{HistoryEventExt, ValidScheduleLA},
|
20
|
+
telemetry::{metrics::MetricsContext, VecDisplayer},
|
21
|
+
worker::{
|
22
|
+
workflow::{
|
23
|
+
CommandID, DrivenWorkflow, HistoryUpdate, LocalResolution, WFCommand, WorkflowFetcher,
|
24
|
+
WorkflowStartedInfo,
|
25
|
+
},
|
26
|
+
ExecutingLAId, LocalActRequest, LocalActivityExecutionResult, LocalActivityResolution,
|
27
|
+
},
|
28
|
+
};
|
29
|
+
use siphasher::sip::SipHasher13;
|
30
|
+
use slotmap::{SlotMap, SparseSecondaryMap};
|
31
|
+
use std::{
|
32
|
+
borrow::{Borrow, BorrowMut},
|
33
|
+
collections::{HashMap, VecDeque},
|
34
|
+
convert::TryInto,
|
35
|
+
hash::{Hash, Hasher},
|
36
|
+
time::{Duration, Instant, SystemTime},
|
37
|
+
};
|
38
|
+
use temporal_sdk_core_protos::{
|
39
|
+
coresdk::{
|
40
|
+
common::NamespacedWorkflowExecution,
|
41
|
+
workflow_activation::{
|
42
|
+
workflow_activation_job, NotifyHasPatch, UpdateRandomSeed, WorkflowActivation,
|
43
|
+
},
|
44
|
+
workflow_commands::{
|
45
|
+
request_cancel_external_workflow_execution as cancel_we, ContinueAsNewWorkflowExecution,
|
46
|
+
},
|
47
|
+
},
|
48
|
+
temporal::api::{
|
49
|
+
command::v1::{command::Attributes as ProtoCmdAttrs, Command as ProtoCommand},
|
50
|
+
enums::v1::EventType,
|
51
|
+
history::v1::{history_event, HistoryEvent},
|
52
|
+
},
|
53
|
+
};
|
54
|
+
|
55
|
+
type Result<T, E = WFMachinesError> = std::result::Result<T, E>;
|
56
|
+
|
57
|
+
slotmap::new_key_type! { struct MachineKey; }
|
58
|
+
/// Handles all the logic for driving a workflow. It orchestrates many state machines that together
|
59
|
+
/// comprise the logic of an executing workflow. One instance will exist per currently executing
|
60
|
+
/// (or cached) workflow on the worker.
|
61
|
+
pub(crate) struct WorkflowMachines {
|
62
|
+
/// The last recorded history we received from the server for this workflow run. This must be
|
63
|
+
/// kept because the lang side polls & completes for every workflow task, but we do not need
|
64
|
+
/// to poll the server that often during replay.
|
65
|
+
last_history_from_server: HistoryUpdate,
|
66
|
+
/// EventId of the last handled WorkflowTaskStarted event
|
67
|
+
current_started_event_id: i64,
|
68
|
+
/// The event id of the next workflow task started event that the machines need to process.
|
69
|
+
/// Eventually, this number should reach the started id in the latest history update, but
|
70
|
+
/// we must incrementally apply the history while communicating with lang.
|
71
|
+
next_started_event_id: i64,
|
72
|
+
/// The event id of the most recent event processed. It's possible in some situations (ex legacy
|
73
|
+
/// queries) to receive a history with no new workflow tasks. If the last history we processed
|
74
|
+
/// also had no new tasks, we need a way to know not to apply the same events over again.
|
75
|
+
pub last_processed_event: i64,
|
76
|
+
/// True if the workflow is replaying from history
|
77
|
+
pub replaying: bool,
|
78
|
+
/// Namespace this workflow exists in
|
79
|
+
pub namespace: String,
|
80
|
+
/// Workflow identifier
|
81
|
+
pub workflow_id: String,
|
82
|
+
/// Workflow type identifier. (Function name, class, etc)
|
83
|
+
pub workflow_type: String,
|
84
|
+
/// Identifies the current run
|
85
|
+
pub run_id: String,
|
86
|
+
/// The time the workflow execution began, as told by the WEStarted event
|
87
|
+
workflow_start_time: Option<SystemTime>,
|
88
|
+
/// The time the workflow execution finished, as determined by when the machines handled
|
89
|
+
/// a terminal workflow command. If this is `Some`, you know the workflow is ended.
|
90
|
+
workflow_end_time: Option<SystemTime>,
|
91
|
+
/// The WFT start time if it has been established
|
92
|
+
wft_start_time: Option<SystemTime>,
|
93
|
+
/// The current workflow time if it has been established. This may differ from the WFT start
|
94
|
+
/// time since local activities may advance the clock
|
95
|
+
current_wf_time: Option<SystemTime>,
|
96
|
+
|
97
|
+
all_machines: SlotMap<MachineKey, Machines>,
|
98
|
+
/// If a machine key is in this map, that machine was created internally by core, not as a
|
99
|
+
/// command from lang.
|
100
|
+
machine_is_core_created: SparseSecondaryMap<MachineKey, ()>,
|
101
|
+
|
102
|
+
/// A mapping for accessing machines associated to a particular event, where the key is the id
|
103
|
+
/// of the initiating event for that machine.
|
104
|
+
machines_by_event_id: HashMap<i64, MachineKey>,
|
105
|
+
|
106
|
+
/// Maps command ids as created by workflow authors to their associated machines.
|
107
|
+
id_to_machine: HashMap<CommandID, MachineKey>,
|
108
|
+
|
109
|
+
/// Queued commands which have been produced by machines and await processing / being sent to
|
110
|
+
/// the server.
|
111
|
+
commands: VecDeque<CommandAndMachine>,
|
112
|
+
/// Commands generated by the currently processing workflow task, which will eventually be
|
113
|
+
/// transferred to `commands` (and hence eventually sent to the server)
|
114
|
+
current_wf_task_commands: VecDeque<CommandAndMachine>,
|
115
|
+
|
116
|
+
/// Information about patch markers we have already seen while replaying history
|
117
|
+
encountered_change_markers: HashMap<String, ChangeInfo>,
|
118
|
+
|
119
|
+
/// Contains extra local-activity related data
|
120
|
+
local_activity_data: LocalActivityData,
|
121
|
+
|
122
|
+
/// The workflow that is being driven by this instance of the machines
|
123
|
+
drive_me: DrivenWorkflow,
|
124
|
+
|
125
|
+
/// Is set to true once we've seen the final event in workflow history, to avoid accidentally
|
126
|
+
/// re-applying the final workflow task.
|
127
|
+
pub have_seen_terminal_event: bool,
|
128
|
+
|
129
|
+
/// Metrics context
|
130
|
+
pub metrics: MetricsContext,
|
131
|
+
}
|
132
|
+
|
133
|
+
#[derive(Debug, derive_more::Display)]
|
134
|
+
#[display(fmt = "Cmd&Machine({})", "command")]
|
135
|
+
struct CommandAndMachine {
|
136
|
+
command: MachineAssociatedCommand,
|
137
|
+
machine: MachineKey,
|
138
|
+
}
|
139
|
+
|
140
|
+
#[derive(Debug, derive_more::Display)]
|
141
|
+
enum MachineAssociatedCommand {
|
142
|
+
Real(Box<ProtoCommand>),
|
143
|
+
#[display(fmt = "FakeLocalActivityMarker({})", "_0")]
|
144
|
+
FakeLocalActivityMarker(u32),
|
145
|
+
}
|
146
|
+
|
147
|
+
#[derive(Debug, Clone, Copy)]
|
148
|
+
struct ChangeInfo {
|
149
|
+
created_command: bool,
|
150
|
+
}
|
151
|
+
|
152
|
+
/// Returned by [TemporalStateMachine]s when handling events
|
153
|
+
#[derive(Debug, derive_more::Display)]
|
154
|
+
#[must_use]
|
155
|
+
#[allow(clippy::large_enum_variant)]
|
156
|
+
pub enum MachineResponse {
|
157
|
+
#[display(fmt = "PushWFJob({})", "_0")]
|
158
|
+
PushWFJob(workflow_activation_job::Variant),
|
159
|
+
|
160
|
+
/// Pushes a new command into the list that will be sent to server once we respond with the
|
161
|
+
/// workflow task completion
|
162
|
+
IssueNewCommand(ProtoCommand),
|
163
|
+
/// The machine requests the creation of another *different* machine. This acts as if lang
|
164
|
+
/// had replied to the activation with a command, but we use a special set of IDs to avoid
|
165
|
+
/// collisions.
|
166
|
+
#[display(fmt = "NewCoreOriginatedCommand({:?})", "_0")]
|
167
|
+
NewCoreOriginatedCommand(ProtoCmdAttrs),
|
168
|
+
#[display(fmt = "IssueFakeLocalActivityMarker({})", "_0")]
|
169
|
+
IssueFakeLocalActivityMarker(u32),
|
170
|
+
#[display(fmt = "TriggerWFTaskStarted")]
|
171
|
+
TriggerWFTaskStarted {
|
172
|
+
task_started_event_id: i64,
|
173
|
+
time: SystemTime,
|
174
|
+
},
|
175
|
+
#[display(fmt = "UpdateRunIdOnWorkflowReset({})", run_id)]
|
176
|
+
UpdateRunIdOnWorkflowReset { run_id: String },
|
177
|
+
|
178
|
+
/// Queue a local activity to be processed by the worker
|
179
|
+
#[display(fmt = "QueueLocalActivity")]
|
180
|
+
QueueLocalActivity(ValidScheduleLA),
|
181
|
+
/// Request cancellation of an executing local activity
|
182
|
+
#[display(fmt = "RequestCancelLocalActivity({})", "_0")]
|
183
|
+
RequestCancelLocalActivity(u32),
|
184
|
+
/// Indicates we are abandoning the indicated LA, so we can remove it from "outstanding" LAs
|
185
|
+
/// and we will not try to WFT heartbeat because of it.
|
186
|
+
#[display(fmt = "AbandonLocalActivity({:?})", "_0")]
|
187
|
+
AbandonLocalActivity(u32),
|
188
|
+
|
189
|
+
/// Set the workflow time to the provided time
|
190
|
+
#[display(fmt = "UpdateWFTime({:?})", "_0")]
|
191
|
+
UpdateWFTime(Option<SystemTime>),
|
192
|
+
}
|
193
|
+
|
194
|
+
impl<T> From<T> for MachineResponse
|
195
|
+
where
|
196
|
+
T: Into<workflow_activation_job::Variant>,
|
197
|
+
{
|
198
|
+
fn from(v: T) -> Self {
|
199
|
+
Self::PushWFJob(v.into())
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
impl WorkflowMachines {
|
204
|
+
pub(crate) fn new(
|
205
|
+
namespace: String,
|
206
|
+
workflow_id: String,
|
207
|
+
workflow_type: String,
|
208
|
+
run_id: String,
|
209
|
+
history: HistoryUpdate,
|
210
|
+
driven_wf: DrivenWorkflow,
|
211
|
+
metrics: MetricsContext,
|
212
|
+
) -> Self {
|
213
|
+
let replaying = history.previous_started_event_id > 0;
|
214
|
+
Self {
|
215
|
+
last_history_from_server: history,
|
216
|
+
namespace,
|
217
|
+
workflow_id,
|
218
|
+
workflow_type,
|
219
|
+
run_id,
|
220
|
+
drive_me: driven_wf,
|
221
|
+
replaying,
|
222
|
+
metrics,
|
223
|
+
// In an ideal world one could say ..Default::default() here and it'd still work.
|
224
|
+
current_started_event_id: 0,
|
225
|
+
next_started_event_id: 0,
|
226
|
+
last_processed_event: 0,
|
227
|
+
workflow_start_time: None,
|
228
|
+
workflow_end_time: None,
|
229
|
+
wft_start_time: None,
|
230
|
+
current_wf_time: None,
|
231
|
+
all_machines: Default::default(),
|
232
|
+
machine_is_core_created: Default::default(),
|
233
|
+
machines_by_event_id: Default::default(),
|
234
|
+
id_to_machine: Default::default(),
|
235
|
+
commands: Default::default(),
|
236
|
+
current_wf_task_commands: Default::default(),
|
237
|
+
encountered_change_markers: Default::default(),
|
238
|
+
local_activity_data: LocalActivityData::default(),
|
239
|
+
have_seen_terminal_event: false,
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
/// Returns true if workflow has seen a terminal command
|
244
|
+
pub(crate) const fn workflow_is_finished(&self) -> bool {
|
245
|
+
self.workflow_end_time.is_some()
|
246
|
+
}
|
247
|
+
|
248
|
+
/// Returns the total time it took to execute the workflow. Returns `None` if workflow is
|
249
|
+
/// incomplete, or time went backwards.
|
250
|
+
pub(crate) fn total_runtime(&self) -> Option<Duration> {
|
251
|
+
self.workflow_start_time
|
252
|
+
.zip(self.workflow_end_time)
|
253
|
+
.and_then(|(st, et)| et.duration_since(st).ok())
|
254
|
+
}
|
255
|
+
|
256
|
+
pub(crate) async fn new_history_from_server(&mut self, update: HistoryUpdate) -> Result<()> {
|
257
|
+
self.last_history_from_server = update;
|
258
|
+
self.replaying = self.last_history_from_server.previous_started_event_id > 0;
|
259
|
+
self.apply_next_wft_from_history().await?;
|
260
|
+
Ok(())
|
261
|
+
}
|
262
|
+
|
263
|
+
/// Let this workflow know that something we've been waiting locally on has resolved, like a
|
264
|
+
/// local activity or side effect
|
265
|
+
///
|
266
|
+
/// Returns true if the resolution did anything. EX: If the activity is already canceled and
|
267
|
+
/// used the TryCancel or Abandon modes, the resolution is uninteresting.
|
268
|
+
pub(crate) fn local_resolution(&mut self, resolution: LocalResolution) -> Result<bool> {
|
269
|
+
let mut result_important = true;
|
270
|
+
match resolution {
|
271
|
+
LocalResolution::LocalActivity(LocalActivityResolution {
|
272
|
+
seq,
|
273
|
+
result,
|
274
|
+
runtime,
|
275
|
+
attempt,
|
276
|
+
backoff,
|
277
|
+
original_schedule_time,
|
278
|
+
}) => {
|
279
|
+
let act_id = CommandID::LocalActivity(seq);
|
280
|
+
let mk = self.get_machine_key(act_id)?;
|
281
|
+
let mach = self.machine_mut(mk);
|
282
|
+
if let Machines::LocalActivityMachine(ref mut lam) = *mach {
|
283
|
+
let resps =
|
284
|
+
lam.try_resolve(result, runtime, attempt, backoff, original_schedule_time)?;
|
285
|
+
if resps.is_empty() {
|
286
|
+
result_important = false;
|
287
|
+
}
|
288
|
+
self.process_machine_responses(mk, resps)?;
|
289
|
+
} else {
|
290
|
+
return Err(WFMachinesError::Nondeterminism(format!(
|
291
|
+
"Command matching activity with seq num {} existed but was not a \
|
292
|
+
local activity!",
|
293
|
+
seq
|
294
|
+
)));
|
295
|
+
}
|
296
|
+
self.local_activity_data.done_executing(seq);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
Ok(result_important)
|
300
|
+
}
|
301
|
+
|
302
|
+
/// Drain all queued local activities that need executing or cancellation
|
303
|
+
pub(crate) fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
|
304
|
+
self.local_activity_data
|
305
|
+
.take_all_reqs(&self.workflow_type, &self.workflow_id, &self.run_id)
|
306
|
+
}
|
307
|
+
|
308
|
+
/// Returns the number of local activities we know we need to execute but have not yet finished
|
309
|
+
pub(crate) fn outstanding_local_activity_count(&self) -> usize {
|
310
|
+
self.local_activity_data.outstanding_la_count()
|
311
|
+
}
|
312
|
+
|
313
|
+
/// Returns start info for the workflow if it has started
|
314
|
+
pub(crate) fn get_started_info(&self) -> Option<&WorkflowStartedInfo> {
|
315
|
+
self.drive_me.get_started_info()
|
316
|
+
}
|
317
|
+
|
318
|
+
/// Handle a single event from the workflow history. `has_next_event` should be false if `event`
|
319
|
+
/// is the last event in the history.
|
320
|
+
///
|
321
|
+
/// This function will attempt to apply the event to the workflow state machines. If there is
|
322
|
+
/// not a matching machine for the event, a nondeterminism error is returned. Otherwise, the
|
323
|
+
/// event is applied to the machine, which may also return a nondeterminism error if the machine
|
324
|
+
/// does not match the expected type. A fatal error may be returned if the machine is in an
|
325
|
+
/// invalid state.
|
326
|
+
#[instrument(level = "debug", skip(self, event), fields(event=%event))]
|
327
|
+
fn handle_event(&mut self, event: HistoryEvent, has_next_event: bool) -> Result<()> {
|
328
|
+
if event.event_type() == EventType::Unspecified {
|
329
|
+
return Err(WFMachinesError::Fatal(format!(
|
330
|
+
"Event type is unspecified! This history is invalid. Event detail: {:?}",
|
331
|
+
event
|
332
|
+
)));
|
333
|
+
}
|
334
|
+
if event.is_final_wf_execution_event() {
|
335
|
+
self.have_seen_terminal_event = true;
|
336
|
+
}
|
337
|
+
if matches!(
|
338
|
+
event.event_type(),
|
339
|
+
EventType::WorkflowExecutionTerminated | EventType::WorkflowExecutionTimedOut
|
340
|
+
) {
|
341
|
+
return if has_next_event {
|
342
|
+
Err(WFMachinesError::Fatal(
|
343
|
+
"Machines were fed a history which has an event after workflow execution was \
|
344
|
+
terminated!"
|
345
|
+
.to_string(),
|
346
|
+
))
|
347
|
+
} else {
|
348
|
+
Ok(())
|
349
|
+
};
|
350
|
+
}
|
351
|
+
|
352
|
+
if event.is_command_event() {
|
353
|
+
self.handle_command_event(event)?;
|
354
|
+
return Ok(());
|
355
|
+
}
|
356
|
+
if self.replaying
|
357
|
+
&& self.current_started_event_id
|
358
|
+
>= self.last_history_from_server.previous_started_event_id
|
359
|
+
&& event.event_type() != EventType::WorkflowTaskCompleted
|
360
|
+
{
|
361
|
+
// Replay is finished
|
362
|
+
self.replaying = false;
|
363
|
+
}
|
364
|
+
|
365
|
+
match event.get_initial_command_event_id() {
|
366
|
+
Some(initial_cmd_id) => {
|
367
|
+
// We remove the machine while we it handles events, then return it, to avoid
|
368
|
+
// borrowing from ourself mutably.
|
369
|
+
let maybe_machine = self.machines_by_event_id.remove(&initial_cmd_id);
|
370
|
+
match maybe_machine {
|
371
|
+
Some(sm) => {
|
372
|
+
self.submachine_handle_event(sm, event, has_next_event)?;
|
373
|
+
// Restore machine if not in it's final state
|
374
|
+
if !self.machine(sm).is_final_state() {
|
375
|
+
self.machines_by_event_id.insert(initial_cmd_id, sm);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
None => {
|
379
|
+
return Err(WFMachinesError::Nondeterminism(format!(
|
380
|
+
"During event handling, this event had an initial command ID but we \
|
381
|
+
could not find a matching command for it: {:?}",
|
382
|
+
event
|
383
|
+
)));
|
384
|
+
}
|
385
|
+
}
|
386
|
+
}
|
387
|
+
None => self.handle_non_stateful_event(event, has_next_event)?,
|
388
|
+
}
|
389
|
+
|
390
|
+
Ok(())
|
391
|
+
}
|
392
|
+
|
393
|
+
/// Called when a workflow task started event has triggered. Ensures we are tracking the ID
|
394
|
+
/// of the current started event as well as workflow time properly.
|
395
|
+
fn task_started(&mut self, task_started_event_id: i64, time: SystemTime) -> Result<()> {
|
396
|
+
self.current_started_event_id = task_started_event_id;
|
397
|
+
self.wft_start_time = Some(time);
|
398
|
+
self.set_current_time(time);
|
399
|
+
|
400
|
+
// Notify local activity machines that we started a non-replay WFT, which will allow any
|
401
|
+
// which were waiting for a marker to instead decide to execute the LA since it clearly
|
402
|
+
// will not be resolved via marker.
|
403
|
+
if !self.replaying {
|
404
|
+
let mut resps = vec![];
|
405
|
+
for (k, mach) in self.all_machines.iter_mut() {
|
406
|
+
if let Machines::LocalActivityMachine(lam) = mach {
|
407
|
+
resps.push((k, lam.encountered_non_replay_wft()?));
|
408
|
+
}
|
409
|
+
}
|
410
|
+
for (mkey, resp_set) in resps {
|
411
|
+
self.process_machine_responses(mkey, resp_set)?;
|
412
|
+
}
|
413
|
+
}
|
414
|
+
Ok(())
|
415
|
+
}
|
416
|
+
|
417
|
+
/// A command event is an event which is generated from a command emitted as a result of
|
418
|
+
/// performing a workflow task. Each command has a corresponding event. For example
|
419
|
+
/// ScheduleActivityTaskCommand is recorded to the history as ActivityTaskScheduledEvent.
|
420
|
+
///
|
421
|
+
/// Command events always follow WorkflowTaskCompletedEvent.
|
422
|
+
///
|
423
|
+
/// The handling consists of verifying that the next command in the commands queue is associated
|
424
|
+
/// with a state machine, which is then notified about the event and the command is removed from
|
425
|
+
/// the commands queue.
|
426
|
+
fn handle_command_event(&mut self, event: HistoryEvent) -> Result<()> {
|
427
|
+
if event.is_local_activity_marker() {
|
428
|
+
let deets = event.extract_local_activity_marker_data().ok_or_else(|| {
|
429
|
+
WFMachinesError::Fatal(format!("Local activity marker was unparsable: {:?}", event))
|
430
|
+
})?;
|
431
|
+
let cmdid = CommandID::LocalActivity(deets.seq);
|
432
|
+
let mkey = self.get_machine_key(cmdid)?;
|
433
|
+
if let Machines::LocalActivityMachine(lam) = self.machine(mkey) {
|
434
|
+
if lam.marker_should_get_special_handling()? {
|
435
|
+
self.submachine_handle_event(mkey, event, false)?;
|
436
|
+
return Ok(());
|
437
|
+
}
|
438
|
+
} else {
|
439
|
+
return Err(WFMachinesError::Fatal(format!(
|
440
|
+
"Encountered local activity marker but the associated machine was of the \
|
441
|
+
wrong type! {:?}",
|
442
|
+
event
|
443
|
+
)));
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
let event_id = event.event_id;
|
448
|
+
|
449
|
+
let consumed_cmd = loop {
|
450
|
+
if let Some(peek_machine) = self.commands.front() {
|
451
|
+
let mach = self.machine(peek_machine.machine);
|
452
|
+
match change_marker_handling(&event, mach)? {
|
453
|
+
ChangeMarkerOutcome::SkipEvent => return Ok(()),
|
454
|
+
ChangeMarkerOutcome::SkipCommand => {
|
455
|
+
self.commands.pop_front();
|
456
|
+
continue;
|
457
|
+
}
|
458
|
+
ChangeMarkerOutcome::Normal => {}
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
let maybe_command = self.commands.pop_front();
|
463
|
+
let command = if let Some(c) = maybe_command {
|
464
|
+
c
|
465
|
+
} else {
|
466
|
+
return Err(WFMachinesError::Nondeterminism(format!(
|
467
|
+
"No command scheduled for event {}",
|
468
|
+
event
|
469
|
+
)));
|
470
|
+
};
|
471
|
+
|
472
|
+
let canceled_before_sent = self
|
473
|
+
.machine(command.machine)
|
474
|
+
.was_cancelled_before_sent_to_server();
|
475
|
+
|
476
|
+
if !canceled_before_sent {
|
477
|
+
// Feed the machine the event
|
478
|
+
self.submachine_handle_event(command.machine, event, true)?;
|
479
|
+
break command;
|
480
|
+
}
|
481
|
+
};
|
482
|
+
|
483
|
+
if !self.machine(consumed_cmd.machine).is_final_state() {
|
484
|
+
self.machines_by_event_id
|
485
|
+
.insert(event_id, consumed_cmd.machine);
|
486
|
+
}
|
487
|
+
|
488
|
+
Ok(())
|
489
|
+
}
|
490
|
+
|
491
|
+
fn handle_non_stateful_event(
|
492
|
+
&mut self,
|
493
|
+
event: HistoryEvent,
|
494
|
+
has_next_event: bool,
|
495
|
+
) -> Result<()> {
|
496
|
+
trace!(
|
497
|
+
event = %event,
|
498
|
+
"handling non-stateful event"
|
499
|
+
);
|
500
|
+
let event_id = event.event_id;
|
501
|
+
match EventType::from_i32(event.event_type) {
|
502
|
+
Some(EventType::WorkflowExecutionStarted) => {
|
503
|
+
if let Some(history_event::Attributes::WorkflowExecutionStartedEventAttributes(
|
504
|
+
attrs,
|
505
|
+
)) = event.attributes
|
506
|
+
{
|
507
|
+
if let Some(st) = event.event_time {
|
508
|
+
let as_systime: SystemTime = st.try_into()?;
|
509
|
+
self.workflow_start_time = Some(as_systime);
|
510
|
+
// Set the workflow time to be the event time of the first event, so that
|
511
|
+
// if there is a query issued before first WFT started event, there is some
|
512
|
+
// workflow time set.
|
513
|
+
self.set_current_time(as_systime);
|
514
|
+
}
|
515
|
+
// Notify the lang sdk that it's time to kick off a workflow
|
516
|
+
self.drive_me.start(
|
517
|
+
self.workflow_id.clone(),
|
518
|
+
str_to_randomness_seed(&attrs.original_execution_run_id),
|
519
|
+
attrs,
|
520
|
+
);
|
521
|
+
} else {
|
522
|
+
return Err(WFMachinesError::Fatal(format!(
|
523
|
+
"WorkflowExecutionStarted event did not have appropriate attributes: {}",
|
524
|
+
event
|
525
|
+
)));
|
526
|
+
}
|
527
|
+
}
|
528
|
+
Some(EventType::WorkflowTaskScheduled) => {
|
529
|
+
let wf_task_sm = WorkflowTaskMachine::new(self.next_started_event_id);
|
530
|
+
let key = self.all_machines.insert(wf_task_sm.into());
|
531
|
+
self.submachine_handle_event(key, event, has_next_event)?;
|
532
|
+
self.machines_by_event_id.insert(event_id, key);
|
533
|
+
}
|
534
|
+
Some(EventType::WorkflowExecutionSignaled) => {
|
535
|
+
if let Some(history_event::Attributes::WorkflowExecutionSignaledEventAttributes(
|
536
|
+
attrs,
|
537
|
+
)) = event.attributes
|
538
|
+
{
|
539
|
+
self.drive_me.signal(attrs.into());
|
540
|
+
} else {
|
541
|
+
// err
|
542
|
+
}
|
543
|
+
}
|
544
|
+
Some(EventType::WorkflowExecutionCancelRequested) => {
|
545
|
+
if let Some(
|
546
|
+
history_event::Attributes::WorkflowExecutionCancelRequestedEventAttributes(
|
547
|
+
attrs,
|
548
|
+
),
|
549
|
+
) = event.attributes
|
550
|
+
{
|
551
|
+
self.drive_me.cancel(attrs.into());
|
552
|
+
} else {
|
553
|
+
// err
|
554
|
+
}
|
555
|
+
}
|
556
|
+
_ => {
|
557
|
+
return Err(WFMachinesError::Fatal(format!(
|
558
|
+
"The event is not a non-stateful event, but we tried to handle it as one: {}",
|
559
|
+
event
|
560
|
+
)));
|
561
|
+
}
|
562
|
+
}
|
563
|
+
Ok(())
|
564
|
+
}
|
565
|
+
|
566
|
+
/// Fetches commands which are ready for processing from the state machines, generally to be
|
567
|
+
/// sent off to the server. They are not removed from the internal queue, that happens when
|
568
|
+
/// corresponding history events from the server are being handled.
|
569
|
+
pub(crate) fn get_commands(&self) -> Vec<ProtoCommand> {
|
570
|
+
self.commands
|
571
|
+
.iter()
|
572
|
+
.filter_map(|c| {
|
573
|
+
if !self.machine(c.machine).is_final_state() {
|
574
|
+
match &c.command {
|
575
|
+
MachineAssociatedCommand::Real(cmd) => Some((**cmd).clone()),
|
576
|
+
MachineAssociatedCommand::FakeLocalActivityMarker(_) => None,
|
577
|
+
}
|
578
|
+
} else {
|
579
|
+
None
|
580
|
+
}
|
581
|
+
})
|
582
|
+
.collect()
|
583
|
+
}
|
584
|
+
|
585
|
+
/// Returns the next activation that needs to be performed by the lang sdk. Things like unblock
|
586
|
+
/// timer, etc. This does *not* cause any advancement of the state machines, it merely drains
|
587
|
+
/// from the outgoing queue of activation jobs.
|
588
|
+
///
|
589
|
+
/// The job list may be empty, in which case it is expected the caller handles what to do in a
|
590
|
+
/// "no work" situation. Possibly, it may know about some work the machines don't, like queries.
|
591
|
+
pub(crate) fn get_wf_activation(&mut self) -> WorkflowActivation {
|
592
|
+
let jobs = self.drive_me.drain_jobs();
|
593
|
+
WorkflowActivation {
|
594
|
+
timestamp: self.current_wf_time.map(Into::into),
|
595
|
+
is_replaying: self.replaying,
|
596
|
+
run_id: self.run_id.clone(),
|
597
|
+
history_length: self.last_processed_event as u32,
|
598
|
+
jobs,
|
599
|
+
}
|
600
|
+
}
|
601
|
+
|
602
|
+
pub(crate) fn has_pending_jobs(&self) -> bool {
|
603
|
+
!self.drive_me.peek_pending_jobs().is_empty()
|
604
|
+
}
|
605
|
+
|
606
|
+
fn set_current_time(&mut self, time: SystemTime) -> SystemTime {
|
607
|
+
if self.current_wf_time.map_or(true, |t| t < time) {
|
608
|
+
self.current_wf_time = Some(time);
|
609
|
+
}
|
610
|
+
self.current_wf_time
|
611
|
+
.expect("We have just ensured this is populated")
|
612
|
+
}
|
613
|
+
|
614
|
+
/// Iterate the state machines, which consists of grabbing any pending outgoing commands from
|
615
|
+
/// the workflow code, handling them, and preparing them to be sent off to the server.
|
616
|
+
pub(crate) async fn iterate_machines(&mut self) -> Result<()> {
|
617
|
+
let results = self.drive_me.fetch_workflow_iteration_output().await;
|
618
|
+
self.handle_driven_results(results)?;
|
619
|
+
self.prepare_commands()?;
|
620
|
+
if self.workflow_is_finished() {
|
621
|
+
if let Some(rt) = self.total_runtime() {
|
622
|
+
self.metrics.wf_e2e_latency(rt);
|
623
|
+
}
|
624
|
+
}
|
625
|
+
Ok(())
|
626
|
+
}
|
627
|
+
|
628
|
+
/// Apply the next (unapplied) entire workflow task from history to these machines. Will replay
|
629
|
+
/// any events that need to be replayed until caught up to the newest WFT. May also fetch
|
630
|
+
/// history from server if needed.
|
631
|
+
pub(crate) async fn apply_next_wft_from_history(&mut self) -> Result<usize> {
|
632
|
+
// If we have already seen the terminal event for the entire workflow in a previous WFT,
|
633
|
+
// then we don't need to do anything here, and in fact we need to avoid re-applying the
|
634
|
+
// final WFT.
|
635
|
+
if self.have_seen_terminal_event {
|
636
|
+
return Ok(0);
|
637
|
+
}
|
638
|
+
|
639
|
+
let last_handled_wft_started_id = self.current_started_event_id;
|
640
|
+
let events = {
|
641
|
+
let mut evts = self
|
642
|
+
.last_history_from_server
|
643
|
+
.take_next_wft_sequence(last_handled_wft_started_id)
|
644
|
+
.await
|
645
|
+
.map_err(WFMachinesError::HistoryFetchingError)?;
|
646
|
+
// Do not re-process events we have already processed
|
647
|
+
evts.retain(|e| e.event_id > self.last_processed_event);
|
648
|
+
evts
|
649
|
+
};
|
650
|
+
let num_events_to_process = events.len();
|
651
|
+
|
652
|
+
// We're caught up on reply if there are no new events to process
|
653
|
+
// TODO: Probably this is unneeded if we evict whenever history is from non-sticky queue
|
654
|
+
if events.is_empty() {
|
655
|
+
self.replaying = false;
|
656
|
+
}
|
657
|
+
let replay_start = Instant::now();
|
658
|
+
|
659
|
+
if let Some(last_event) = events.last() {
|
660
|
+
if last_event.event_type == EventType::WorkflowTaskStarted as i32 {
|
661
|
+
self.next_started_event_id = last_event.event_id;
|
662
|
+
}
|
663
|
+
}
|
664
|
+
|
665
|
+
let mut history = events.into_iter().peekable();
|
666
|
+
while let Some(event) = history.next() {
|
667
|
+
if event.event_id != self.last_processed_event + 1 {
|
668
|
+
return Err(WFMachinesError::Fatal(format!(
|
669
|
+
"History is out of order. Last processed event: {}, event id: {}",
|
670
|
+
self.last_processed_event, event.event_id
|
671
|
+
)));
|
672
|
+
}
|
673
|
+
let next_event = history.peek();
|
674
|
+
let eid = event.event_id;
|
675
|
+
let etype = event.event_type;
|
676
|
+
self.handle_event(event, next_event.is_some())?;
|
677
|
+
self.last_processed_event = eid;
|
678
|
+
if etype == EventType::WorkflowTaskStarted as i32 && next_event.is_none() {
|
679
|
+
break;
|
680
|
+
}
|
681
|
+
}
|
682
|
+
|
683
|
+
// Scan through to the next WFT, searching for any patch markers, so that we can
|
684
|
+
// pre-resolve them.
|
685
|
+
for e in self.last_history_from_server.peek_next_wft_sequence() {
|
686
|
+
if let Some((patch_id, _)) = e.get_patch_marker_details() {
|
687
|
+
self.encountered_change_markers.insert(
|
688
|
+
patch_id.clone(),
|
689
|
+
ChangeInfo {
|
690
|
+
created_command: false,
|
691
|
+
},
|
692
|
+
);
|
693
|
+
// Found a patch marker
|
694
|
+
self.drive_me
|
695
|
+
.send_job(workflow_activation_job::Variant::NotifyHasPatch(
|
696
|
+
NotifyHasPatch { patch_id },
|
697
|
+
));
|
698
|
+
} else if e.is_local_activity_marker() {
|
699
|
+
self.local_activity_data.process_peekahead_marker(e)?;
|
700
|
+
}
|
701
|
+
}
|
702
|
+
|
703
|
+
if !self.replaying {
|
704
|
+
self.metrics.wf_task_replay_latency(replay_start.elapsed());
|
705
|
+
}
|
706
|
+
|
707
|
+
Ok(num_events_to_process)
|
708
|
+
}
|
709
|
+
|
710
|
+
/// Wrapper for calling [TemporalStateMachine::handle_event] which appropriately takes action
|
711
|
+
/// on the returned machine responses
|
712
|
+
fn submachine_handle_event(
|
713
|
+
&mut self,
|
714
|
+
sm: MachineKey,
|
715
|
+
event: HistoryEvent,
|
716
|
+
has_next_event: bool,
|
717
|
+
) -> Result<()> {
|
718
|
+
let machine_responses = self.machine_mut(sm).handle_event(event, has_next_event)?;
|
719
|
+
self.process_machine_responses(sm, machine_responses)?;
|
720
|
+
Ok(())
|
721
|
+
}
|
722
|
+
|
723
|
+
/// Transfer commands from `current_wf_task_commands` to `commands`, so they may be sent off
|
724
|
+
/// to the server. While doing so, [TemporalStateMachine::handle_command] is called on the
|
725
|
+
/// machine associated with the command.
|
726
|
+
fn prepare_commands(&mut self) -> Result<()> {
|
727
|
+
// It's possible we might prepare commands more than once before completing a WFT. (Because
|
728
|
+
// of local activities, of course). Some commands might have since been cancelled that we
|
729
|
+
// already prepared. Rip them out of the outgoing command list if so.
|
730
|
+
self.commands.retain(|c| {
|
731
|
+
!self
|
732
|
+
.all_machines
|
733
|
+
.get(c.machine)
|
734
|
+
.expect("Machine must exist")
|
735
|
+
.was_cancelled_before_sent_to_server()
|
736
|
+
});
|
737
|
+
|
738
|
+
while let Some(c) = self.current_wf_task_commands.pop_front() {
|
739
|
+
if !self
|
740
|
+
.machine(c.machine)
|
741
|
+
.was_cancelled_before_sent_to_server()
|
742
|
+
{
|
743
|
+
match &c.command {
|
744
|
+
MachineAssociatedCommand::Real(cmd) => {
|
745
|
+
let machine_responses = self
|
746
|
+
.machine_mut(c.machine)
|
747
|
+
.handle_command(cmd.command_type())?;
|
748
|
+
self.process_machine_responses(c.machine, machine_responses)?;
|
749
|
+
}
|
750
|
+
MachineAssociatedCommand::FakeLocalActivityMarker(_) => {}
|
751
|
+
}
|
752
|
+
self.commands.push_back(c);
|
753
|
+
}
|
754
|
+
}
|
755
|
+
debug!(commands = %self.commands.display(), "prepared commands");
|
756
|
+
Ok(())
|
757
|
+
}
|
758
|
+
|
759
|
+
/// After a machine handles either an event or a command, it produces [MachineResponses] which
|
760
|
+
/// this function uses to drive sending jobs to lang, triggering new workflow tasks, etc.
|
761
|
+
fn process_machine_responses(
|
762
|
+
&mut self,
|
763
|
+
smk: MachineKey,
|
764
|
+
machine_responses: Vec<MachineResponse>,
|
765
|
+
) -> Result<()> {
|
766
|
+
let sm = self.machine(smk);
|
767
|
+
if !machine_responses.is_empty() {
|
768
|
+
debug!(responses = %machine_responses.display(), machine_name = %sm.kind(),
|
769
|
+
"Machine produced responses");
|
770
|
+
}
|
771
|
+
self.process_machine_resps_impl(smk, machine_responses)
|
772
|
+
}
|
773
|
+
|
774
|
+
fn process_machine_resps_impl(
|
775
|
+
&mut self,
|
776
|
+
smk: MachineKey,
|
777
|
+
machine_responses: Vec<MachineResponse>,
|
778
|
+
) -> Result<()> {
|
779
|
+
for response in machine_responses {
|
780
|
+
match response {
|
781
|
+
MachineResponse::PushWFJob(a) => {
|
782
|
+
// We don't need to notify lang about jobs created by core-internal machines
|
783
|
+
if !self.machine_is_core_created.contains_key(smk) {
|
784
|
+
self.drive_me.send_job(a);
|
785
|
+
}
|
786
|
+
}
|
787
|
+
MachineResponse::TriggerWFTaskStarted {
|
788
|
+
task_started_event_id,
|
789
|
+
time,
|
790
|
+
} => {
|
791
|
+
self.task_started(task_started_event_id, time)?;
|
792
|
+
}
|
793
|
+
MachineResponse::UpdateRunIdOnWorkflowReset { run_id: new_run_id } => {
|
794
|
+
// TODO: Should this also update self.run_id? Should we track orig/current
|
795
|
+
// separately?
|
796
|
+
self.drive_me
|
797
|
+
.send_job(workflow_activation_job::Variant::UpdateRandomSeed(
|
798
|
+
UpdateRandomSeed {
|
799
|
+
randomness_seed: str_to_randomness_seed(&new_run_id),
|
800
|
+
},
|
801
|
+
));
|
802
|
+
}
|
803
|
+
MachineResponse::IssueNewCommand(c) => {
|
804
|
+
self.current_wf_task_commands.push_back(CommandAndMachine {
|
805
|
+
command: MachineAssociatedCommand::Real(Box::new(c)),
|
806
|
+
machine: smk,
|
807
|
+
})
|
808
|
+
}
|
809
|
+
MachineResponse::NewCoreOriginatedCommand(attrs) => match attrs {
|
810
|
+
ProtoCmdAttrs::RequestCancelExternalWorkflowExecutionCommandAttributes(
|
811
|
+
attrs,
|
812
|
+
) => {
|
813
|
+
let we = NamespacedWorkflowExecution {
|
814
|
+
namespace: attrs.namespace,
|
815
|
+
workflow_id: attrs.workflow_id,
|
816
|
+
run_id: attrs.run_id,
|
817
|
+
};
|
818
|
+
self.add_cmd_to_wf_task(
|
819
|
+
new_external_cancel(0, we, attrs.child_workflow_only, attrs.reason),
|
820
|
+
CommandIdKind::CoreInternal,
|
821
|
+
);
|
822
|
+
}
|
823
|
+
c => {
|
824
|
+
return Err(WFMachinesError::Fatal(format!(
|
825
|
+
"A machine requested to create a new command of an unsupported type: {:?}",
|
826
|
+
c
|
827
|
+
)))
|
828
|
+
}
|
829
|
+
},
|
830
|
+
MachineResponse::IssueFakeLocalActivityMarker(seq) => {
|
831
|
+
self.current_wf_task_commands.push_back(CommandAndMachine {
|
832
|
+
command: MachineAssociatedCommand::FakeLocalActivityMarker(seq),
|
833
|
+
machine: smk,
|
834
|
+
});
|
835
|
+
}
|
836
|
+
MachineResponse::QueueLocalActivity(act) => {
|
837
|
+
self.local_activity_data.enqueue(act);
|
838
|
+
}
|
839
|
+
MachineResponse::RequestCancelLocalActivity(seq) => {
|
840
|
+
// We might already know about the status from a pre-resolution. Apply it if so.
|
841
|
+
// We need to do this because otherwise we might need to perform additional
|
842
|
+
// activations during replay that didn't happen during execution, just like
|
843
|
+
// we sometimes pre-resolve activities when first requested.
|
844
|
+
if let Some(preres) = self.local_activity_data.take_preresolution(seq) {
|
845
|
+
if let Machines::LocalActivityMachine(lam) = self.machine_mut(smk) {
|
846
|
+
let more_responses = lam.try_resolve_with_dat(preres)?;
|
847
|
+
self.process_machine_responses(smk, more_responses)?;
|
848
|
+
} else {
|
849
|
+
panic!("A non local-activity machine returned a request cancel LA response");
|
850
|
+
}
|
851
|
+
}
|
852
|
+
// If it's in the request queue, just rip it out.
|
853
|
+
else if let Some(removed_act) =
|
854
|
+
self.local_activity_data.remove_from_queue(seq)
|
855
|
+
{
|
856
|
+
// We removed it. Notify the machine that the activity cancelled.
|
857
|
+
if let Machines::LocalActivityMachine(lam) = self.machine_mut(smk) {
|
858
|
+
let more_responses = lam.try_resolve(
|
859
|
+
LocalActivityExecutionResult::empty_cancel(),
|
860
|
+
Duration::from_secs(0),
|
861
|
+
removed_act.attempt,
|
862
|
+
None,
|
863
|
+
None,
|
864
|
+
)?;
|
865
|
+
self.process_machine_responses(smk, more_responses)?;
|
866
|
+
} else {
|
867
|
+
panic!("A non local-activity machine returned a request cancel LA response");
|
868
|
+
}
|
869
|
+
} else {
|
870
|
+
// Finally, if we know about the LA at all, it's currently running, so
|
871
|
+
// queue the cancel request to be given to the LA manager.
|
872
|
+
self.local_activity_data.enqueue_cancel(ExecutingLAId {
|
873
|
+
run_id: self.run_id.clone(),
|
874
|
+
seq_num: seq,
|
875
|
+
});
|
876
|
+
}
|
877
|
+
}
|
878
|
+
MachineResponse::AbandonLocalActivity(seq) => {
|
879
|
+
self.local_activity_data.done_executing(seq);
|
880
|
+
}
|
881
|
+
MachineResponse::UpdateWFTime(t) => {
|
882
|
+
if let Some(t) = t {
|
883
|
+
self.set_current_time(t);
|
884
|
+
}
|
885
|
+
}
|
886
|
+
}
|
887
|
+
}
|
888
|
+
Ok(())
|
889
|
+
}
|
890
|
+
|
891
|
+
/// Handles results of the workflow activation, delegating work to the appropriate state
|
892
|
+
/// machine. Returns a list of workflow jobs that should be queued in the pending activation for
|
893
|
+
/// the next poll. This list will be populated only if state machine produced lang activations
|
894
|
+
/// as part of command processing. For example some types of activity cancellation need to
|
895
|
+
/// immediately unblock lang side without having it to poll for an actual workflow task from the
|
896
|
+
/// server.
|
897
|
+
fn handle_driven_results(&mut self, results: Vec<WFCommand>) -> Result<()> {
|
898
|
+
for cmd in results {
|
899
|
+
match cmd {
|
900
|
+
WFCommand::AddTimer(attrs) => {
|
901
|
+
let seq = attrs.seq;
|
902
|
+
self.add_cmd_to_wf_task(new_timer(attrs), CommandID::Timer(seq).into());
|
903
|
+
}
|
904
|
+
WFCommand::UpsertSearchAttributes(attrs) => {
|
905
|
+
self.add_cmd_to_wf_task(
|
906
|
+
upsert_search_attrs(attrs),
|
907
|
+
CommandIdKind::NeverResolves,
|
908
|
+
);
|
909
|
+
}
|
910
|
+
WFCommand::CancelTimer(attrs) => {
|
911
|
+
self.process_cancellation(CommandID::Timer(attrs.seq))?;
|
912
|
+
}
|
913
|
+
WFCommand::AddActivity(attrs) => {
|
914
|
+
let seq = attrs.seq;
|
915
|
+
self.add_cmd_to_wf_task(new_activity(attrs), CommandID::Activity(seq).into());
|
916
|
+
}
|
917
|
+
WFCommand::AddLocalActivity(attrs) => {
|
918
|
+
let seq = attrs.seq;
|
919
|
+
let attrs: ValidScheduleLA = ValidScheduleLA::from_schedule_la(
|
920
|
+
attrs,
|
921
|
+
self.get_started_info()
|
922
|
+
.as_ref()
|
923
|
+
.and_then(|x| x.workflow_execution_timeout),
|
924
|
+
)
|
925
|
+
.map_err(|e| {
|
926
|
+
WFMachinesError::Fatal(format!(
|
927
|
+
"Invalid schedule local activity request (seq {}): {}",
|
928
|
+
seq, e
|
929
|
+
))
|
930
|
+
})?;
|
931
|
+
let (la, mach_resp) = new_local_activity(
|
932
|
+
attrs,
|
933
|
+
self.replaying,
|
934
|
+
self.local_activity_data.take_preresolution(seq),
|
935
|
+
self.current_wf_time,
|
936
|
+
)?;
|
937
|
+
let machkey = self.all_machines.insert(la.into());
|
938
|
+
self.id_to_machine
|
939
|
+
.insert(CommandID::LocalActivity(seq), machkey);
|
940
|
+
self.process_machine_responses(machkey, mach_resp)?;
|
941
|
+
}
|
942
|
+
WFCommand::RequestCancelActivity(attrs) => {
|
943
|
+
self.process_cancellation(CommandID::Activity(attrs.seq))?;
|
944
|
+
}
|
945
|
+
WFCommand::RequestCancelLocalActivity(attrs) => {
|
946
|
+
self.process_cancellation(CommandID::LocalActivity(attrs.seq))?;
|
947
|
+
}
|
948
|
+
WFCommand::CompleteWorkflow(attrs) => {
|
949
|
+
self.metrics.wf_completed();
|
950
|
+
self.add_terminal_command(complete_workflow(attrs));
|
951
|
+
}
|
952
|
+
WFCommand::FailWorkflow(attrs) => {
|
953
|
+
self.metrics.wf_failed();
|
954
|
+
self.add_terminal_command(fail_workflow(attrs));
|
955
|
+
}
|
956
|
+
WFCommand::ContinueAsNew(attrs) => {
|
957
|
+
self.metrics.wf_continued_as_new();
|
958
|
+
let attrs = self.augment_continue_as_new_with_current_values(attrs);
|
959
|
+
self.add_terminal_command(continue_as_new(attrs));
|
960
|
+
}
|
961
|
+
WFCommand::CancelWorkflow(attrs) => {
|
962
|
+
self.metrics.wf_canceled();
|
963
|
+
self.add_terminal_command(cancel_workflow(attrs));
|
964
|
+
}
|
965
|
+
WFCommand::SetPatchMarker(attrs) => {
|
966
|
+
// Do not create commands for change IDs that we have already created commands
|
967
|
+
// for.
|
968
|
+
if !matches!(self.encountered_change_markers.get(&attrs.patch_id),
|
969
|
+
Some(ChangeInfo {created_command}) if *created_command)
|
970
|
+
{
|
971
|
+
self.add_cmd_to_wf_task(
|
972
|
+
has_change(attrs.patch_id.clone(), self.replaying, attrs.deprecated),
|
973
|
+
CommandIdKind::NeverResolves,
|
974
|
+
);
|
975
|
+
|
976
|
+
if let Some(ci) = self.encountered_change_markers.get_mut(&attrs.patch_id) {
|
977
|
+
ci.created_command = true;
|
978
|
+
} else {
|
979
|
+
self.encountered_change_markers.insert(
|
980
|
+
attrs.patch_id,
|
981
|
+
ChangeInfo {
|
982
|
+
created_command: true,
|
983
|
+
},
|
984
|
+
);
|
985
|
+
}
|
986
|
+
}
|
987
|
+
}
|
988
|
+
WFCommand::AddChildWorkflow(attrs) => {
|
989
|
+
let seq = attrs.seq;
|
990
|
+
self.add_cmd_to_wf_task(
|
991
|
+
new_child_workflow(attrs),
|
992
|
+
CommandID::ChildWorkflowStart(seq).into(),
|
993
|
+
);
|
994
|
+
}
|
995
|
+
WFCommand::CancelChild(attrs) => self.process_cancellation(
|
996
|
+
CommandID::ChildWorkflowStart(attrs.child_workflow_seq),
|
997
|
+
)?,
|
998
|
+
WFCommand::RequestCancelExternalWorkflow(attrs) => {
|
999
|
+
let (we, only_child) = match attrs.target {
|
1000
|
+
None => {
|
1001
|
+
return Err(WFMachinesError::Fatal(
|
1002
|
+
"Cancel external workflow command had empty target field"
|
1003
|
+
.to_string(),
|
1004
|
+
))
|
1005
|
+
}
|
1006
|
+
Some(cancel_we::Target::ChildWorkflowId(wfid)) => (
|
1007
|
+
NamespacedWorkflowExecution {
|
1008
|
+
namespace: self.namespace.clone(),
|
1009
|
+
workflow_id: wfid,
|
1010
|
+
run_id: "".to_string(),
|
1011
|
+
},
|
1012
|
+
true,
|
1013
|
+
),
|
1014
|
+
Some(cancel_we::Target::WorkflowExecution(we)) => (we, false),
|
1015
|
+
};
|
1016
|
+
self.add_cmd_to_wf_task(
|
1017
|
+
new_external_cancel(
|
1018
|
+
attrs.seq,
|
1019
|
+
we,
|
1020
|
+
only_child,
|
1021
|
+
format!("Cancel requested by workflow with run id {}", self.run_id),
|
1022
|
+
),
|
1023
|
+
CommandID::CancelExternal(attrs.seq).into(),
|
1024
|
+
);
|
1025
|
+
}
|
1026
|
+
WFCommand::SignalExternalWorkflow(attrs) => {
|
1027
|
+
let seq = attrs.seq;
|
1028
|
+
self.add_cmd_to_wf_task(
|
1029
|
+
new_external_signal(attrs, &self.namespace)?,
|
1030
|
+
CommandID::SignalExternal(seq).into(),
|
1031
|
+
);
|
1032
|
+
}
|
1033
|
+
WFCommand::CancelSignalWorkflow(attrs) => {
|
1034
|
+
self.process_cancellation(CommandID::SignalExternal(attrs.seq))?;
|
1035
|
+
}
|
1036
|
+
WFCommand::QueryResponse(_) => {
|
1037
|
+
// Nothing to do here, queries are handled above the machine level
|
1038
|
+
unimplemented!("Query responses should not make it down into the machines")
|
1039
|
+
}
|
1040
|
+
WFCommand::NoCommandsFromLang => (),
|
1041
|
+
}
|
1042
|
+
}
|
1043
|
+
Ok(())
|
1044
|
+
}
|
1045
|
+
|
1046
|
+
/// Given a command id to attempt to cancel, try to cancel it and return any jobs that should
|
1047
|
+
/// be included in the activation
|
1048
|
+
fn process_cancellation(&mut self, id: CommandID) -> Result<()> {
|
1049
|
+
let m_key = self.get_machine_key(id)?;
|
1050
|
+
let mach = self.machine_mut(m_key);
|
1051
|
+
let machine_resps = mach.cancel()?;
|
1052
|
+
debug!(machine_responses = %machine_resps.display(), cmd_id = ?id,
|
1053
|
+
"Cancel request responses");
|
1054
|
+
self.process_machine_resps_impl(m_key, machine_resps)
|
1055
|
+
}
|
1056
|
+
|
1057
|
+
fn get_machine_key(&self, id: CommandID) -> Result<MachineKey> {
|
1058
|
+
Ok(*self.id_to_machine.get(&id).ok_or_else(|| {
|
1059
|
+
WFMachinesError::Fatal(format!("Missing associated machine for {:?}", id))
|
1060
|
+
})?)
|
1061
|
+
}
|
1062
|
+
|
1063
|
+
fn add_terminal_command(&mut self, machine: NewMachineWithCommand) {
|
1064
|
+
let cwfm = self.add_new_command_machine(machine);
|
1065
|
+
self.workflow_end_time = Some(SystemTime::now());
|
1066
|
+
self.current_wf_task_commands.push_back(cwfm);
|
1067
|
+
}
|
1068
|
+
|
1069
|
+
/// Add a new command/machines for that command to the current workflow task
|
1070
|
+
fn add_cmd_to_wf_task(&mut self, machine: NewMachineWithCommand, id: CommandIdKind) {
|
1071
|
+
let mach = self.add_new_command_machine(machine);
|
1072
|
+
if let CommandIdKind::LangIssued(id) = id {
|
1073
|
+
self.id_to_machine.insert(id, mach.machine);
|
1074
|
+
}
|
1075
|
+
if matches!(id, CommandIdKind::CoreInternal) {
|
1076
|
+
self.machine_is_core_created.insert(mach.machine, ());
|
1077
|
+
}
|
1078
|
+
self.current_wf_task_commands.push_back(mach);
|
1079
|
+
}
|
1080
|
+
|
1081
|
+
fn add_new_command_machine(&mut self, machine: NewMachineWithCommand) -> CommandAndMachine {
|
1082
|
+
let k = self.all_machines.insert(machine.machine);
|
1083
|
+
CommandAndMachine {
|
1084
|
+
command: MachineAssociatedCommand::Real(Box::new(machine.command)),
|
1085
|
+
machine: k,
|
1086
|
+
}
|
1087
|
+
}
|
1088
|
+
|
1089
|
+
fn machine(&self, m: MachineKey) -> &Machines {
|
1090
|
+
self.all_machines
|
1091
|
+
.get(m)
|
1092
|
+
.expect("Machine must exist")
|
1093
|
+
.borrow()
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
fn machine_mut(&mut self, m: MachineKey) -> &mut Machines {
|
1097
|
+
self.all_machines
|
1098
|
+
.get_mut(m)
|
1099
|
+
.expect("Machine must exist")
|
1100
|
+
.borrow_mut()
|
1101
|
+
}
|
1102
|
+
|
1103
|
+
fn augment_continue_as_new_with_current_values(
|
1104
|
+
&self,
|
1105
|
+
mut attrs: ContinueAsNewWorkflowExecution,
|
1106
|
+
) -> ContinueAsNewWorkflowExecution {
|
1107
|
+
if let Some(started_info) = self.drive_me.get_started_info() {
|
1108
|
+
if attrs.memo.is_empty() {
|
1109
|
+
attrs.memo = started_info
|
1110
|
+
.memo
|
1111
|
+
.clone()
|
1112
|
+
.map(Into::into)
|
1113
|
+
.unwrap_or_default();
|
1114
|
+
}
|
1115
|
+
if attrs.search_attributes.is_empty() {
|
1116
|
+
attrs.search_attributes = started_info
|
1117
|
+
.search_attrs
|
1118
|
+
.clone()
|
1119
|
+
.map(Into::into)
|
1120
|
+
.unwrap_or_default();
|
1121
|
+
}
|
1122
|
+
if attrs.retry_policy.is_none() {
|
1123
|
+
attrs.retry_policy = started_info.retry_policy.clone();
|
1124
|
+
}
|
1125
|
+
}
|
1126
|
+
attrs
|
1127
|
+
}
|
1128
|
+
}
|
1129
|
+
|
1130
|
+
fn str_to_randomness_seed(run_id: &str) -> u64 {
|
1131
|
+
// This was originally `DefaultHasher` but that is potentially unstable across Rust releases.
|
1132
|
+
// This must forever be `SipHasher13` now or we risk breaking history compat.
|
1133
|
+
let mut s = SipHasher13::new();
|
1134
|
+
run_id.hash(&mut s);
|
1135
|
+
s.finish()
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
enum ChangeMarkerOutcome {
|
1139
|
+
SkipEvent,
|
1140
|
+
SkipCommand,
|
1141
|
+
Normal,
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
/// Special handling for patch markers, when handling command events as in
|
1145
|
+
/// [WorkflowMachines::handle_command_event]
|
1146
|
+
fn change_marker_handling(
|
1147
|
+
event: &HistoryEvent,
|
1148
|
+
mach: &dyn TemporalStateMachine,
|
1149
|
+
) -> Result<ChangeMarkerOutcome> {
|
1150
|
+
if !mach.matches_event(event) {
|
1151
|
+
// Version markers can be skipped in the event they are deprecated
|
1152
|
+
if let Some((patch_name, deprecated)) = event.get_patch_marker_details() {
|
1153
|
+
// Is deprecated. We can simply ignore this event, as deprecated change
|
1154
|
+
// markers are allowed without matching changed calls.
|
1155
|
+
if deprecated {
|
1156
|
+
debug!("Deprecated patch marker tried against wrong machine, skipping.");
|
1157
|
+
return Ok(ChangeMarkerOutcome::SkipEvent);
|
1158
|
+
}
|
1159
|
+
return Err(WFMachinesError::Nondeterminism(format!(
|
1160
|
+
"Non-deprecated patch marker encountered for change {}, \
|
1161
|
+
but there is no corresponding change command!",
|
1162
|
+
patch_name
|
1163
|
+
)));
|
1164
|
+
}
|
1165
|
+
// Version machines themselves may also not *have* matching markers, where non-deprecated
|
1166
|
+
// calls take the old path, and deprecated calls assume history is produced by a new-code
|
1167
|
+
// worker.
|
1168
|
+
if mach.kind() == MachineKind::Patch {
|
1169
|
+
debug!("Skipping non-matching event against version machine");
|
1170
|
+
return Ok(ChangeMarkerOutcome::SkipCommand);
|
1171
|
+
}
|
1172
|
+
}
|
1173
|
+
Ok(ChangeMarkerOutcome::Normal)
|
1174
|
+
}
|
1175
|
+
|
1176
|
+
#[derive(derive_more::From)]
|
1177
|
+
enum CommandIdKind {
|
1178
|
+
/// A normal command, requested by lang
|
1179
|
+
LangIssued(CommandID),
|
1180
|
+
/// A command created internally
|
1181
|
+
CoreInternal,
|
1182
|
+
/// A command which is fire-and-forget (ex: Upsert search attribs)
|
1183
|
+
NeverResolves,
|
1184
|
+
}
|