@temporalio/core-bridge 1.13.0 → 1.13.2
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.
- package/Cargo.lock +239 -382
- package/Cargo.toml +11 -11
- package/lib/native.d.ts +10 -3
- package/package.json +3 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.cargo/config.toml +71 -11
- package/sdk-core/.clippy.toml +1 -0
- package/sdk-core/.github/workflows/heavy.yml +2 -0
- package/sdk-core/.github/workflows/per-pr.yml +50 -18
- package/sdk-core/ARCHITECTURE.md +44 -48
- package/sdk-core/Cargo.toml +26 -7
- package/sdk-core/README.md +4 -0
- package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
- package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
- package/sdk-core/arch_docs/sdks_intro.md +299 -0
- package/sdk-core/client/Cargo.toml +8 -7
- package/sdk-core/client/src/callback_based.rs +1 -2
- package/sdk-core/client/src/lib.rs +485 -299
- package/sdk-core/client/src/metrics.rs +32 -8
- package/sdk-core/client/src/proxy.rs +124 -5
- package/sdk-core/client/src/raw.rs +598 -307
- package/sdk-core/client/src/replaceable.rs +253 -0
- package/sdk-core/client/src/retry.rs +9 -6
- package/sdk-core/client/src/worker_registry/mod.rs +19 -3
- package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
- package/sdk-core/core/Cargo.toml +100 -31
- package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
- package/sdk-core/core/src/core_tests/mod.rs +2 -8
- package/sdk-core/core/src/core_tests/queries.rs +3 -5
- package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
- package/sdk-core/core/src/core_tests/updates.rs +4 -5
- package/sdk-core/core/src/core_tests/workers.rs +4 -3
- package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
- package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
- package/sdk-core/core/src/internal_flags.rs +11 -1
- package/sdk-core/core/src/lib.rs +50 -36
- package/sdk-core/core/src/pollers/mod.rs +5 -5
- package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
- package/sdk-core/core/src/protosext/mod.rs +13 -5
- package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
- package/sdk-core/core/src/retry_logic.rs +256 -108
- package/sdk-core/core/src/telemetry/metrics.rs +1 -0
- package/sdk-core/core/src/telemetry/mod.rs +8 -2
- package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
- package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
- package/sdk-core/core/src/test_help/mod.rs +10 -1100
- package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
- package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
- package/sdk-core/core/src/worker/activities.rs +10 -3
- package/sdk-core/core/src/worker/client/mocks.rs +3 -3
- package/sdk-core/core/src/worker/client.rs +130 -93
- package/sdk-core/core/src/worker/heartbeat.rs +12 -13
- package/sdk-core/core/src/worker/mod.rs +31 -21
- package/sdk-core/core/src/worker/nexus.rs +14 -3
- package/sdk-core/core/src/worker/slot_provider.rs +9 -0
- package/sdk-core/core/src/worker/tuner.rs +159 -0
- package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
- package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
- package/sdk-core/core-api/Cargo.toml +4 -4
- package/sdk-core/core-api/src/envconfig.rs +153 -54
- package/sdk-core/core-api/src/lib.rs +68 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
- package/sdk-core/core-api/src/telemetry.rs +13 -0
- package/sdk-core/core-c-bridge/Cargo.toml +13 -8
- package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
- package/sdk-core/core-c-bridge/src/client.rs +462 -184
- package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
- package/sdk-core/core-c-bridge/src/lib.rs +1 -0
- package/sdk-core/core-c-bridge/src/random.rs +4 -4
- package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
- package/sdk-core/core-c-bridge/src/testing.rs +1 -4
- package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
- package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
- package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
- package/sdk-core/core-c-bridge/src/worker.rs +319 -66
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
- package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
- package/sdk-core/sdk/Cargo.toml +8 -2
- package/sdk-core/sdk/src/activity_context.rs +1 -1
- package/sdk-core/sdk/src/app_data.rs +1 -1
- package/sdk-core/sdk/src/interceptors.rs +1 -4
- package/sdk-core/sdk/src/lib.rs +1 -5
- package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
- package/sdk-core/sdk/src/workflow_future.rs +1 -1
- package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
- package/sdk-core/sdk-core-protos/build.rs +10 -23
- package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
- package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
- package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
- package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
- package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
- package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
- package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
- package/sdk-core/tests/cloud_tests.rs +10 -8
- package/sdk-core/tests/common/http_proxy.rs +134 -0
- package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
- package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
- package/sdk-core/tests/fuzzy_workflow.rs +1 -1
- package/sdk-core/tests/global_metric_tests.rs +8 -7
- package/sdk-core/tests/heavy_tests.rs +7 -3
- package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
- package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
- package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
- package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
- package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
- package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
- package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
- package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
- package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
- package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
- package/sdk-core/tests/main.rs +26 -17
- package/sdk-core/tests/manual_tests.rs +5 -1
- package/sdk-core/tests/runner.rs +22 -40
- package/sdk-core/tests/shared_tests/mod.rs +1 -1
- package/sdk-core/tests/shared_tests/priority.rs +1 -1
- package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
- package/src/client.rs +97 -20
- package/src/helpers/callbacks.rs +4 -4
- package/src/helpers/errors.rs +7 -1
- package/src/helpers/handles.rs +1 -0
- package/src/helpers/try_from_js.rs +4 -3
- package/src/lib.rs +3 -2
- package/src/metrics.rs +3 -0
- package/src/runtime.rs +5 -2
- package/src/worker.rs +9 -12
- package/ts/native.ts +13 -3
- package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
- package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
- package/sdk-core/core/src/core_tests/determinism.rs +0 -318
- package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
- package/sdk-core/test-utils/Cargo.toml +0 -38
- package/sdk-core/test-utils/src/histfetch.rs +0 -28
- package/sdk-core/test-utils/src/interceptors.rs +0 -46
|
@@ -1,39 +1,64 @@
|
|
|
1
|
-
use crate::
|
|
1
|
+
use crate::common::{
|
|
2
|
+
ActivationAssertionsInterceptor, CoreWfStarter, WorkflowHandleExt, build_fake_sdk,
|
|
3
|
+
history_from_proto_binary, init_core_replay_preloaded, mock_sdk, mock_sdk_cfg,
|
|
4
|
+
replay_sdk_worker, workflows::la_problem_workflow,
|
|
5
|
+
};
|
|
2
6
|
use anyhow::anyhow;
|
|
3
|
-
use
|
|
7
|
+
use crossbeam_queue::SegQueue;
|
|
8
|
+
use futures_util::{FutureExt, future::join_all};
|
|
4
9
|
use rstest::Context;
|
|
5
10
|
use std::{
|
|
11
|
+
collections::HashMap,
|
|
12
|
+
ops::Sub,
|
|
6
13
|
sync::{
|
|
7
14
|
Arc,
|
|
8
|
-
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
|
|
15
|
+
atomic::{AtomicBool, AtomicI64, AtomicU8, AtomicUsize, Ordering},
|
|
9
16
|
},
|
|
10
|
-
time::Duration,
|
|
17
|
+
time::{Duration, Instant, SystemTime},
|
|
11
18
|
};
|
|
12
19
|
use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions};
|
|
13
20
|
use temporal_sdk::{
|
|
14
21
|
ActContext, ActivityError, ActivityOptions, CancellableFuture, LocalActivityOptions,
|
|
15
|
-
UpdateContext, WfContext, WorkflowResult,
|
|
22
|
+
UpdateContext, WfContext, WorkflowFunction, WorkflowResult,
|
|
16
23
|
interceptors::{FailOnNondeterminismInterceptor, WorkerInterceptor},
|
|
17
24
|
};
|
|
18
|
-
use temporal_sdk_core::
|
|
25
|
+
use temporal_sdk_core::{
|
|
26
|
+
prost_dur,
|
|
27
|
+
replay::{DEFAULT_WORKFLOW_TYPE, HistoryForReplay, TestHistoryBuilder, default_wes_attribs},
|
|
28
|
+
test_help::{
|
|
29
|
+
LEGACY_QUERY_ID, MockPollCfg, ResponseType, WorkerExt, WorkerTestHelpers,
|
|
30
|
+
build_mock_pollers, hist_to_poll_resp, mock_worker, mock_worker_client,
|
|
31
|
+
single_hist_mock_sg,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
use temporal_sdk_core_api::{Worker, errors::PollError};
|
|
19
35
|
use temporal_sdk_core_protos::{
|
|
20
|
-
|
|
36
|
+
DEFAULT_ACTIVITY_TYPE, canned_histories,
|
|
21
37
|
coresdk::{
|
|
22
|
-
AsJsonPayloadExt, IntoPayloadsExt,
|
|
23
|
-
|
|
38
|
+
ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt, IntoPayloadsExt,
|
|
39
|
+
activity_result::ActivityExecutionResult,
|
|
40
|
+
workflow_activation::{WorkflowActivationJob, workflow_activation_job},
|
|
41
|
+
workflow_commands::{
|
|
42
|
+
ActivityCancellationType, ScheduleLocalActivity, workflow_command::Variant,
|
|
43
|
+
},
|
|
24
44
|
workflow_completion,
|
|
25
45
|
workflow_completion::{WorkflowActivationCompletion, workflow_activation_completion},
|
|
26
46
|
},
|
|
27
47
|
temporal::api::{
|
|
48
|
+
command::v1::{RecordMarkerCommandAttributes, command},
|
|
28
49
|
common::v1::RetryPolicy,
|
|
29
|
-
enums::v1::{
|
|
50
|
+
enums::v1::{
|
|
51
|
+
CommandType, EventType, TimeoutType, UpdateWorkflowExecutionLifecycleStage,
|
|
52
|
+
WorkflowTaskFailedCause,
|
|
53
|
+
},
|
|
54
|
+
failure::v1::{Failure, failure::FailureInfo},
|
|
55
|
+
history::v1::history_event::Attributes::MarkerRecordedEventAttributes,
|
|
56
|
+
query::v1::WorkflowQuery,
|
|
30
57
|
update::v1::WaitPolicy,
|
|
31
58
|
},
|
|
59
|
+
test_utils::{query_ok, schedule_local_activity_cmd, start_timer_cmd},
|
|
32
60
|
};
|
|
33
|
-
use
|
|
34
|
-
CoreWfStarter, WorkflowHandleExt, history_from_proto_binary, init_core_replay_preloaded,
|
|
35
|
-
replay_sdk_worker, workflows::la_problem_workflow,
|
|
36
|
-
};
|
|
61
|
+
use tokio::{join, select, sync::Barrier};
|
|
37
62
|
use tokio_util::sync::CancellationToken;
|
|
38
63
|
|
|
39
64
|
pub(crate) async fn one_local_activity_wf(ctx: WfContext) -> WorkflowResult<()> {
|
|
@@ -906,3 +931,1954 @@ async fn local_activity_with_heartbeat_only_causes_one_wakeup() {
|
|
|
906
931
|
.unwrap();
|
|
907
932
|
assert_eq!(res[0], replay_res.unwrap());
|
|
908
933
|
}
|
|
934
|
+
|
|
935
|
+
pub(crate) async fn local_activity_with_summary_wf(ctx: WfContext) -> WorkflowResult<()> {
|
|
936
|
+
ctx.local_activity(LocalActivityOptions {
|
|
937
|
+
activity_type: "echo_activity".to_string(),
|
|
938
|
+
input: "hi!".as_json_payload().expect("serializes fine"),
|
|
939
|
+
summary: Some("Echo summary".to_string()),
|
|
940
|
+
..Default::default()
|
|
941
|
+
})
|
|
942
|
+
.await;
|
|
943
|
+
Ok(().into())
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
#[tokio::test]
|
|
947
|
+
async fn local_activity_with_summary() {
|
|
948
|
+
let wf_name = "local_activity_with_summary";
|
|
949
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
950
|
+
let mut worker = starter.worker().await;
|
|
951
|
+
worker.register_wf(wf_name.to_owned(), local_activity_with_summary_wf);
|
|
952
|
+
worker.register_activity("echo_activity", echo);
|
|
953
|
+
|
|
954
|
+
let handle = starter.start_with_worker(wf_name, &mut worker).await;
|
|
955
|
+
worker.run_until_done().await.unwrap();
|
|
956
|
+
handle
|
|
957
|
+
.fetch_history_and_replay(worker.inner_mut())
|
|
958
|
+
.await
|
|
959
|
+
.unwrap();
|
|
960
|
+
|
|
961
|
+
let la_events = starter
|
|
962
|
+
.get_history()
|
|
963
|
+
.await
|
|
964
|
+
.events
|
|
965
|
+
.into_iter()
|
|
966
|
+
.filter(|e| match e.attributes {
|
|
967
|
+
Some(MarkerRecordedEventAttributes(ref a)) => a.marker_name == "core_local_activity",
|
|
968
|
+
_ => false,
|
|
969
|
+
})
|
|
970
|
+
.collect::<Vec<_>>();
|
|
971
|
+
assert_eq!(la_events.len(), 1);
|
|
972
|
+
let summary = la_events[0]
|
|
973
|
+
.user_metadata
|
|
974
|
+
.as_ref()
|
|
975
|
+
.expect("metadata missing from local activity marker")
|
|
976
|
+
.summary
|
|
977
|
+
.as_ref()
|
|
978
|
+
.expect("summary missing from local activity marker");
|
|
979
|
+
assert_eq!(
|
|
980
|
+
"Echo summary",
|
|
981
|
+
String::from_json_payload(summary).expect("failed to parse summary")
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async fn echo(_ctx: ActContext, e: String) -> Result<String, ActivityError> {
|
|
986
|
+
Ok(e)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/// This test verifies that when replaying we are able to resolve local activities whose data we
|
|
990
|
+
/// don't see until after the workflow issues the command
|
|
991
|
+
#[rstest::rstest]
|
|
992
|
+
#[case::replay(true, true)]
|
|
993
|
+
#[case::not_replay(false, true)]
|
|
994
|
+
#[case::replay_cache_off(true, false)]
|
|
995
|
+
#[case::not_replay_cache_off(false, false)]
|
|
996
|
+
#[tokio::test]
|
|
997
|
+
async fn local_act_two_wfts_before_marker(#[case] replay: bool, #[case] cached: bool) {
|
|
998
|
+
let mut t = TestHistoryBuilder::default();
|
|
999
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1000
|
+
t.add_full_wf_task();
|
|
1001
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1002
|
+
t.add_full_wf_task();
|
|
1003
|
+
t.add_local_activity_result_marker(1, "1", b"echo".into());
|
|
1004
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1005
|
+
t.add_full_wf_task();
|
|
1006
|
+
t.add_workflow_execution_completed();
|
|
1007
|
+
|
|
1008
|
+
let wf_id = "fakeid";
|
|
1009
|
+
let mock = mock_worker_client();
|
|
1010
|
+
let resps = if replay {
|
|
1011
|
+
vec![ResponseType::AllHistory]
|
|
1012
|
+
} else {
|
|
1013
|
+
vec![1.into(), 2.into(), ResponseType::AllHistory]
|
|
1014
|
+
};
|
|
1015
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, resps, mock);
|
|
1016
|
+
let mut worker = mock_sdk_cfg(mh, |cfg| {
|
|
1017
|
+
if cached {
|
|
1018
|
+
cfg.max_cached_workflows = 1;
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
worker.register_wf(
|
|
1023
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1024
|
+
|ctx: WfContext| async move {
|
|
1025
|
+
let la = ctx.local_activity(LocalActivityOptions {
|
|
1026
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
1027
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1028
|
+
..Default::default()
|
|
1029
|
+
});
|
|
1030
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
1031
|
+
la.await;
|
|
1032
|
+
Ok(().into())
|
|
1033
|
+
},
|
|
1034
|
+
);
|
|
1035
|
+
worker.register_activity(DEFAULT_ACTIVITY_TYPE, echo);
|
|
1036
|
+
worker
|
|
1037
|
+
.submit_wf(
|
|
1038
|
+
wf_id.to_owned(),
|
|
1039
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1040
|
+
vec![],
|
|
1041
|
+
WorkflowOptions::default(),
|
|
1042
|
+
)
|
|
1043
|
+
.await
|
|
1044
|
+
.unwrap();
|
|
1045
|
+
worker.run_until_done().await.unwrap();
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
#[tokio::test]
|
|
1049
|
+
async fn local_act_many_concurrent() {
|
|
1050
|
+
let mut t = TestHistoryBuilder::default();
|
|
1051
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1052
|
+
t.add_full_wf_task();
|
|
1053
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1054
|
+
t.add_full_wf_task();
|
|
1055
|
+
for i in 1..=50 {
|
|
1056
|
+
t.add_local_activity_result_marker(i, &i.to_string(), b"echo".into());
|
|
1057
|
+
}
|
|
1058
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1059
|
+
t.add_full_wf_task();
|
|
1060
|
+
t.add_workflow_execution_completed();
|
|
1061
|
+
|
|
1062
|
+
let wf_id = "fakeid";
|
|
1063
|
+
let mock = mock_worker_client();
|
|
1064
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
|
|
1065
|
+
let mut worker = mock_sdk(mh);
|
|
1066
|
+
|
|
1067
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE.to_owned(), local_act_fanout_wf);
|
|
1068
|
+
worker.register_activity("echo_activity", echo);
|
|
1069
|
+
worker
|
|
1070
|
+
.submit_wf(
|
|
1071
|
+
wf_id.to_owned(),
|
|
1072
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1073
|
+
vec![],
|
|
1074
|
+
WorkflowOptions::default(),
|
|
1075
|
+
)
|
|
1076
|
+
.await
|
|
1077
|
+
.unwrap();
|
|
1078
|
+
worker.run_until_done().await.unwrap();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/// Verifies that local activities which take more than a workflow task timeout will cause
|
|
1082
|
+
/// us to issue additional (empty) WFT completions with the force flag on, thus preventing timeout
|
|
1083
|
+
/// of WFT while the local activity continues to execute.
|
|
1084
|
+
///
|
|
1085
|
+
/// The test with shutdown verifies if we call shutdown while the local activity is running that
|
|
1086
|
+
/// shutdown does not complete until it's finished.
|
|
1087
|
+
#[rstest::rstest]
|
|
1088
|
+
#[case::with_shutdown(true)]
|
|
1089
|
+
#[case::normal_complete(false)]
|
|
1090
|
+
#[tokio::test]
|
|
1091
|
+
async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
|
|
1092
|
+
let mut t = TestHistoryBuilder::default();
|
|
1093
|
+
let wft_timeout = Duration::from_millis(200);
|
|
1094
|
+
t.add_wfe_started_with_wft_timeout(wft_timeout);
|
|
1095
|
+
t.add_full_wf_task();
|
|
1096
|
+
// Task created by WFT heartbeat
|
|
1097
|
+
t.add_full_wf_task();
|
|
1098
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1099
|
+
|
|
1100
|
+
let wf_id = "fakeid";
|
|
1101
|
+
let mock = mock_worker_client();
|
|
1102
|
+
let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 2, 2], mock);
|
|
1103
|
+
mh.enforce_correct_number_of_polls = false;
|
|
1104
|
+
let mut worker = mock_sdk_cfg(mh, |wc| {
|
|
1105
|
+
wc.max_cached_workflows = 1;
|
|
1106
|
+
wc.max_outstanding_workflow_tasks = Some(1);
|
|
1107
|
+
});
|
|
1108
|
+
let core = worker.core_worker.clone();
|
|
1109
|
+
|
|
1110
|
+
let shutdown_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
|
|
1111
|
+
|
|
1112
|
+
worker.register_wf(
|
|
1113
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1114
|
+
|ctx: WfContext| async move {
|
|
1115
|
+
ctx.local_activity(LocalActivityOptions {
|
|
1116
|
+
activity_type: "echo".to_string(),
|
|
1117
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1118
|
+
..Default::default()
|
|
1119
|
+
})
|
|
1120
|
+
.await;
|
|
1121
|
+
Ok(().into())
|
|
1122
|
+
},
|
|
1123
|
+
);
|
|
1124
|
+
worker.register_activity("echo", move |_ctx: ActContext, str: String| async move {
|
|
1125
|
+
if shutdown_middle {
|
|
1126
|
+
shutdown_barr.wait().await;
|
|
1127
|
+
}
|
|
1128
|
+
// Take slightly more than two workflow tasks
|
|
1129
|
+
tokio::time::sleep(wft_timeout.mul_f32(2.2)).await;
|
|
1130
|
+
Ok(str)
|
|
1131
|
+
});
|
|
1132
|
+
worker
|
|
1133
|
+
.submit_wf(
|
|
1134
|
+
wf_id.to_owned(),
|
|
1135
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1136
|
+
vec![],
|
|
1137
|
+
WorkflowOptions::default(),
|
|
1138
|
+
)
|
|
1139
|
+
.await
|
|
1140
|
+
.unwrap();
|
|
1141
|
+
let (_, runres) = tokio::join!(
|
|
1142
|
+
async {
|
|
1143
|
+
if shutdown_middle {
|
|
1144
|
+
shutdown_barr.wait().await;
|
|
1145
|
+
core.shutdown().await;
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
worker.run_until_done()
|
|
1149
|
+
);
|
|
1150
|
+
runres.unwrap();
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
#[rstest::rstest]
|
|
1154
|
+
#[case::retry_then_pass(true)]
|
|
1155
|
+
#[case::retry_until_fail(false)]
|
|
1156
|
+
#[tokio::test]
|
|
1157
|
+
async fn local_act_fail_and_retry(#[case] eventually_pass: bool) {
|
|
1158
|
+
let mut t = TestHistoryBuilder::default();
|
|
1159
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1160
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1161
|
+
|
|
1162
|
+
let wf_id = "fakeid";
|
|
1163
|
+
let mock = mock_worker_client();
|
|
1164
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
|
|
1165
|
+
let mut worker = mock_sdk(mh);
|
|
1166
|
+
|
|
1167
|
+
worker.register_wf(
|
|
1168
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1169
|
+
move |ctx: WfContext| async move {
|
|
1170
|
+
let la_res = ctx
|
|
1171
|
+
.local_activity(LocalActivityOptions {
|
|
1172
|
+
activity_type: "echo".to_string(),
|
|
1173
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1174
|
+
retry_policy: RetryPolicy {
|
|
1175
|
+
initial_interval: Some(prost_dur!(from_millis(50))),
|
|
1176
|
+
backoff_coefficient: 1.2,
|
|
1177
|
+
maximum_interval: None,
|
|
1178
|
+
maximum_attempts: 5,
|
|
1179
|
+
non_retryable_error_types: vec![],
|
|
1180
|
+
},
|
|
1181
|
+
..Default::default()
|
|
1182
|
+
})
|
|
1183
|
+
.await;
|
|
1184
|
+
if eventually_pass {
|
|
1185
|
+
assert!(la_res.completed_ok())
|
|
1186
|
+
} else {
|
|
1187
|
+
assert!(la_res.failed())
|
|
1188
|
+
}
|
|
1189
|
+
Ok(().into())
|
|
1190
|
+
},
|
|
1191
|
+
);
|
|
1192
|
+
let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
1193
|
+
worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
|
|
1194
|
+
// Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
|
|
1195
|
+
if 2 == attempts.fetch_add(1, Ordering::Relaxed) && eventually_pass {
|
|
1196
|
+
Ok(())
|
|
1197
|
+
} else {
|
|
1198
|
+
Err(anyhow!("Oh no I failed!").into())
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
worker
|
|
1202
|
+
.submit_wf(
|
|
1203
|
+
wf_id.to_owned(),
|
|
1204
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1205
|
+
vec![],
|
|
1206
|
+
WorkflowOptions::default(),
|
|
1207
|
+
)
|
|
1208
|
+
.await
|
|
1209
|
+
.unwrap();
|
|
1210
|
+
worker.run_until_done().await.unwrap();
|
|
1211
|
+
let expected_attempts = if eventually_pass { 3 } else { 5 };
|
|
1212
|
+
assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
#[tokio::test]
|
|
1216
|
+
async fn local_act_retry_long_backoff_uses_timer() {
|
|
1217
|
+
let mut t = TestHistoryBuilder::default();
|
|
1218
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1219
|
+
t.add_full_wf_task();
|
|
1220
|
+
t.add_local_activity_fail_marker(
|
|
1221
|
+
1,
|
|
1222
|
+
"1",
|
|
1223
|
+
Failure::application_failure("la failed".to_string(), false),
|
|
1224
|
+
);
|
|
1225
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1226
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1227
|
+
t.add_full_wf_task();
|
|
1228
|
+
t.add_local_activity_fail_marker(
|
|
1229
|
+
2,
|
|
1230
|
+
"2",
|
|
1231
|
+
Failure::application_failure("la failed".to_string(), false),
|
|
1232
|
+
);
|
|
1233
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1234
|
+
t.add_timer_fired(timer_started_event_id, "2".to_string());
|
|
1235
|
+
t.add_full_wf_task();
|
|
1236
|
+
t.add_workflow_execution_completed();
|
|
1237
|
+
|
|
1238
|
+
let wf_id = "fakeid";
|
|
1239
|
+
let mock = mock_worker_client();
|
|
1240
|
+
let mh = MockPollCfg::from_resp_batches(
|
|
1241
|
+
wf_id,
|
|
1242
|
+
t,
|
|
1243
|
+
[1.into(), 2.into(), ResponseType::AllHistory],
|
|
1244
|
+
mock,
|
|
1245
|
+
);
|
|
1246
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1247
|
+
|
|
1248
|
+
worker.register_wf(
|
|
1249
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1250
|
+
|ctx: WfContext| async move {
|
|
1251
|
+
let la_res = ctx
|
|
1252
|
+
.local_activity(LocalActivityOptions {
|
|
1253
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
1254
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1255
|
+
retry_policy: RetryPolicy {
|
|
1256
|
+
initial_interval: Some(prost_dur!(from_millis(65))),
|
|
1257
|
+
// This will make the second backoff 65 seconds, plenty to use timer
|
|
1258
|
+
backoff_coefficient: 1_000.,
|
|
1259
|
+
maximum_interval: Some(prost_dur!(from_secs(600))),
|
|
1260
|
+
maximum_attempts: 3,
|
|
1261
|
+
non_retryable_error_types: vec![],
|
|
1262
|
+
},
|
|
1263
|
+
..Default::default()
|
|
1264
|
+
})
|
|
1265
|
+
.await;
|
|
1266
|
+
assert!(la_res.failed());
|
|
1267
|
+
// Extra timer just to have an extra workflow task which we can return full history for
|
|
1268
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
1269
|
+
Ok(().into())
|
|
1270
|
+
},
|
|
1271
|
+
);
|
|
1272
|
+
worker.register_activity(
|
|
1273
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
1274
|
+
move |_ctx: ActContext, _: String| async move {
|
|
1275
|
+
Result::<(), _>::Err(anyhow!("Oh no I failed!").into())
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1278
|
+
worker
|
|
1279
|
+
.submit_wf(
|
|
1280
|
+
wf_id.to_owned(),
|
|
1281
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1282
|
+
vec![],
|
|
1283
|
+
WorkflowOptions::default(),
|
|
1284
|
+
)
|
|
1285
|
+
.await
|
|
1286
|
+
.unwrap();
|
|
1287
|
+
worker.run_until_done().await.unwrap();
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
#[tokio::test]
|
|
1291
|
+
async fn local_act_null_result() {
|
|
1292
|
+
let mut t = TestHistoryBuilder::default();
|
|
1293
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1294
|
+
t.add_full_wf_task();
|
|
1295
|
+
t.add_local_activity_marker(1, "1", None, None, |_| {});
|
|
1296
|
+
t.add_workflow_execution_completed();
|
|
1297
|
+
|
|
1298
|
+
let wf_id = "fakeid";
|
|
1299
|
+
let mock = mock_worker_client();
|
|
1300
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
|
|
1301
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1302
|
+
|
|
1303
|
+
worker.register_wf(
|
|
1304
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1305
|
+
|ctx: WfContext| async move {
|
|
1306
|
+
ctx.local_activity(LocalActivityOptions {
|
|
1307
|
+
activity_type: "nullres".to_string(),
|
|
1308
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1309
|
+
..Default::default()
|
|
1310
|
+
})
|
|
1311
|
+
.await;
|
|
1312
|
+
Ok(().into())
|
|
1313
|
+
},
|
|
1314
|
+
);
|
|
1315
|
+
worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
|
|
1316
|
+
worker
|
|
1317
|
+
.submit_wf(
|
|
1318
|
+
wf_id.to_owned(),
|
|
1319
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1320
|
+
vec![],
|
|
1321
|
+
WorkflowOptions::default(),
|
|
1322
|
+
)
|
|
1323
|
+
.await
|
|
1324
|
+
.unwrap();
|
|
1325
|
+
worker.run_until_done().await.unwrap();
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
#[tokio::test]
|
|
1329
|
+
async fn local_act_command_immediately_follows_la_marker() {
|
|
1330
|
+
// This repro only works both when cache is off, and there is at least one heartbeat wft
|
|
1331
|
+
// before the marker & next command are recorded.
|
|
1332
|
+
let mut t = TestHistoryBuilder::default();
|
|
1333
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1334
|
+
t.add_full_wf_task();
|
|
1335
|
+
t.add_full_wf_task();
|
|
1336
|
+
t.add_local_activity_result_marker(1, "1", "done".into());
|
|
1337
|
+
t.add_by_type(EventType::TimerStarted);
|
|
1338
|
+
t.add_full_wf_task();
|
|
1339
|
+
|
|
1340
|
+
let wf_id = "fakeid";
|
|
1341
|
+
let mock = mock_worker_client();
|
|
1342
|
+
// Bug only repros when seeing history up to third wft
|
|
1343
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [3], mock);
|
|
1344
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 0);
|
|
1345
|
+
|
|
1346
|
+
worker.register_wf(
|
|
1347
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1348
|
+
|ctx: WfContext| async move {
|
|
1349
|
+
ctx.local_activity(LocalActivityOptions {
|
|
1350
|
+
activity_type: "nullres".to_string(),
|
|
1351
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1352
|
+
..Default::default()
|
|
1353
|
+
})
|
|
1354
|
+
.await;
|
|
1355
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
1356
|
+
Ok(().into())
|
|
1357
|
+
},
|
|
1358
|
+
);
|
|
1359
|
+
worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
|
|
1360
|
+
worker
|
|
1361
|
+
.submit_wf(
|
|
1362
|
+
wf_id.to_owned(),
|
|
1363
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1364
|
+
vec![],
|
|
1365
|
+
WorkflowOptions::default(),
|
|
1366
|
+
)
|
|
1367
|
+
.await
|
|
1368
|
+
.unwrap();
|
|
1369
|
+
worker.run_until_done().await.unwrap();
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
#[tokio::test]
|
|
1373
|
+
async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbeat() {
|
|
1374
|
+
let wfid = "fake_wf_id";
|
|
1375
|
+
let mut t = TestHistoryBuilder::default();
|
|
1376
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
|
|
1377
|
+
t.add_full_wf_task();
|
|
1378
|
+
// get query here
|
|
1379
|
+
t.add_full_wf_task();
|
|
1380
|
+
t.add_local_activity_result_marker(1, "1", "done".into());
|
|
1381
|
+
t.add_workflow_execution_completed();
|
|
1382
|
+
|
|
1383
|
+
let query_with_hist_task = {
|
|
1384
|
+
let mut pr = hist_to_poll_resp(&t, wfid, ResponseType::ToTaskNum(1));
|
|
1385
|
+
pr.queries = HashMap::new();
|
|
1386
|
+
pr.queries.insert(
|
|
1387
|
+
"the-query".to_string(),
|
|
1388
|
+
WorkflowQuery {
|
|
1389
|
+
query_type: "query-type".to_string(),
|
|
1390
|
+
query_args: Some(b"hi".into()),
|
|
1391
|
+
header: None,
|
|
1392
|
+
},
|
|
1393
|
+
);
|
|
1394
|
+
pr
|
|
1395
|
+
};
|
|
1396
|
+
let after_la_resolved = Arc::new(Barrier::new(2));
|
|
1397
|
+
let poll_barr = after_la_resolved.clone();
|
|
1398
|
+
let tasks = [
|
|
1399
|
+
query_with_hist_task,
|
|
1400
|
+
hist_to_poll_resp(
|
|
1401
|
+
&t,
|
|
1402
|
+
wfid,
|
|
1403
|
+
ResponseType::UntilResolved(
|
|
1404
|
+
async move {
|
|
1405
|
+
poll_barr.wait().await;
|
|
1406
|
+
}
|
|
1407
|
+
.boxed(),
|
|
1408
|
+
3,
|
|
1409
|
+
),
|
|
1410
|
+
),
|
|
1411
|
+
];
|
|
1412
|
+
let mock = mock_worker_client();
|
|
1413
|
+
let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
|
|
1414
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
|
|
1415
|
+
let core = mock_worker(mock);
|
|
1416
|
+
|
|
1417
|
+
let barrier = Barrier::new(2);
|
|
1418
|
+
|
|
1419
|
+
let wf_fut = async {
|
|
1420
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1421
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1422
|
+
task.run_id,
|
|
1423
|
+
schedule_local_activity_cmd(
|
|
1424
|
+
1,
|
|
1425
|
+
"1",
|
|
1426
|
+
ActivityCancellationType::TryCancel,
|
|
1427
|
+
Duration::from_secs(60),
|
|
1428
|
+
),
|
|
1429
|
+
))
|
|
1430
|
+
.await
|
|
1431
|
+
.unwrap();
|
|
1432
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1433
|
+
// Get query, and complete it
|
|
1434
|
+
let query = assert_matches!(
|
|
1435
|
+
task.jobs.as_slice(),
|
|
1436
|
+
[WorkflowActivationJob {
|
|
1437
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(q)),
|
|
1438
|
+
}] => q
|
|
1439
|
+
);
|
|
1440
|
+
// Now complete the LA
|
|
1441
|
+
barrier.wait().await;
|
|
1442
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1443
|
+
task.run_id,
|
|
1444
|
+
query_ok(&query.query_id, "whatev"),
|
|
1445
|
+
))
|
|
1446
|
+
.await
|
|
1447
|
+
.unwrap();
|
|
1448
|
+
// Activation with it resolving:
|
|
1449
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1450
|
+
assert_matches!(
|
|
1451
|
+
task.jobs.as_slice(),
|
|
1452
|
+
[WorkflowActivationJob {
|
|
1453
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
|
|
1454
|
+
}]
|
|
1455
|
+
);
|
|
1456
|
+
core.complete_execution(&task.run_id).await;
|
|
1457
|
+
};
|
|
1458
|
+
let act_fut = async {
|
|
1459
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
1460
|
+
barrier.wait().await;
|
|
1461
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
1462
|
+
task_token: act_task.task_token,
|
|
1463
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
1464
|
+
})
|
|
1465
|
+
.await
|
|
1466
|
+
.unwrap();
|
|
1467
|
+
after_la_resolved.wait().await;
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
tokio::join!(wf_fut, act_fut);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
#[rstest::rstest]
|
|
1474
|
+
#[case::impossible_query_in_task(true)]
|
|
1475
|
+
#[case::real_history(false)]
|
|
1476
|
+
#[tokio::test]
|
|
1477
|
+
async fn la_resolve_during_legacy_query_does_not_combine(#[case] impossible_query_in_task: bool) {
|
|
1478
|
+
// Ensures we do not send an activation with a legacy query and any other work, which should
|
|
1479
|
+
// never happen, but there was an issue where an LA resolving could trigger that.
|
|
1480
|
+
let wfid = "fake_wf_id";
|
|
1481
|
+
let mut t = TestHistoryBuilder::default();
|
|
1482
|
+
t.add(default_wes_attribs());
|
|
1483
|
+
// Since we don't send queries with start workflow, need one workflow task of something else
|
|
1484
|
+
// b/c we want to get an activation with a job and a nonlegacy query
|
|
1485
|
+
t.add_full_wf_task();
|
|
1486
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1487
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1488
|
+
|
|
1489
|
+
// nonlegacy query got here & LA started here
|
|
1490
|
+
// then next task is incremental w/ legacy query (for impossible query case)
|
|
1491
|
+
t.add_full_wf_task();
|
|
1492
|
+
|
|
1493
|
+
let tasks = [
|
|
1494
|
+
hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(1)),
|
|
1495
|
+
{
|
|
1496
|
+
let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2));
|
|
1497
|
+
pr.queries = HashMap::new();
|
|
1498
|
+
pr.queries.insert(
|
|
1499
|
+
"q1".to_string(),
|
|
1500
|
+
WorkflowQuery {
|
|
1501
|
+
query_type: "query-type".to_string(),
|
|
1502
|
+
query_args: Some(b"hi".into()),
|
|
1503
|
+
header: None,
|
|
1504
|
+
},
|
|
1505
|
+
);
|
|
1506
|
+
pr
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(2));
|
|
1510
|
+
// Strip beginning of history so the only events are WFT sched/started, we need to look
|
|
1511
|
+
// like we hit the cache
|
|
1512
|
+
{
|
|
1513
|
+
let h = pr.history.as_mut().unwrap();
|
|
1514
|
+
h.events = h.events.split_off(6);
|
|
1515
|
+
}
|
|
1516
|
+
// In the nonsense server response case, we attach a legacy query, otherwise this
|
|
1517
|
+
// response looks like a normal response to a forced WFT heartbeat.
|
|
1518
|
+
if impossible_query_in_task {
|
|
1519
|
+
pr.query = Some(WorkflowQuery {
|
|
1520
|
+
query_type: "query-type".to_string(),
|
|
1521
|
+
query_args: Some(b"hi".into()),
|
|
1522
|
+
header: None,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
pr
|
|
1526
|
+
},
|
|
1527
|
+
];
|
|
1528
|
+
let mut mock = mock_worker_client();
|
|
1529
|
+
if impossible_query_in_task {
|
|
1530
|
+
mock.expect_respond_legacy_query()
|
|
1531
|
+
.times(1)
|
|
1532
|
+
.returning(move |_, _| Ok(Default::default()));
|
|
1533
|
+
}
|
|
1534
|
+
let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
|
|
1535
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
|
|
1536
|
+
let taskmap = mock.outstanding_task_map.clone().unwrap();
|
|
1537
|
+
let core = mock_worker(mock);
|
|
1538
|
+
|
|
1539
|
+
let wf_fut = async {
|
|
1540
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1541
|
+
assert_matches!(
|
|
1542
|
+
task.jobs.as_slice(),
|
|
1543
|
+
&[WorkflowActivationJob {
|
|
1544
|
+
variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
|
|
1545
|
+
},]
|
|
1546
|
+
);
|
|
1547
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1548
|
+
task.run_id,
|
|
1549
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
1550
|
+
))
|
|
1551
|
+
.await
|
|
1552
|
+
.unwrap();
|
|
1553
|
+
|
|
1554
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1555
|
+
assert_matches!(
|
|
1556
|
+
task.jobs.as_slice(),
|
|
1557
|
+
&[WorkflowActivationJob {
|
|
1558
|
+
variant: Some(workflow_activation_job::Variant::FireTimer(_)),
|
|
1559
|
+
},]
|
|
1560
|
+
);
|
|
1561
|
+
// We want to make sure the weird-looking query gets received while we're working on other
|
|
1562
|
+
// stuff, so that we don't see the workflow complete and choose to evict.
|
|
1563
|
+
taskmap.release_run(&task.run_id);
|
|
1564
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1565
|
+
task.run_id,
|
|
1566
|
+
schedule_local_activity_cmd(
|
|
1567
|
+
1,
|
|
1568
|
+
"act-id",
|
|
1569
|
+
ActivityCancellationType::TryCancel,
|
|
1570
|
+
Duration::from_secs(60),
|
|
1571
|
+
),
|
|
1572
|
+
))
|
|
1573
|
+
.await
|
|
1574
|
+
.unwrap();
|
|
1575
|
+
|
|
1576
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1577
|
+
// The next task needs to be resolve, since the LA is completed immediately
|
|
1578
|
+
assert_matches!(
|
|
1579
|
+
task.jobs.as_slice(),
|
|
1580
|
+
[WorkflowActivationJob {
|
|
1581
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
|
|
1582
|
+
}]
|
|
1583
|
+
);
|
|
1584
|
+
// Complete workflow
|
|
1585
|
+
core.complete_execution(&task.run_id).await;
|
|
1586
|
+
|
|
1587
|
+
// Now we will get the query
|
|
1588
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1589
|
+
assert_matches!(
|
|
1590
|
+
task.jobs.as_slice(),
|
|
1591
|
+
&[WorkflowActivationJob {
|
|
1592
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
|
|
1593
|
+
}]
|
|
1594
|
+
if q.query_id == "q1"
|
|
1595
|
+
);
|
|
1596
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1597
|
+
task.run_id,
|
|
1598
|
+
query_ok("q1", "whatev"),
|
|
1599
|
+
))
|
|
1600
|
+
.await
|
|
1601
|
+
.unwrap();
|
|
1602
|
+
|
|
1603
|
+
if impossible_query_in_task {
|
|
1604
|
+
// finish last query
|
|
1605
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
1606
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1607
|
+
task.run_id,
|
|
1608
|
+
query_ok(LEGACY_QUERY_ID, "whatev"),
|
|
1609
|
+
))
|
|
1610
|
+
.await
|
|
1611
|
+
.unwrap();
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
let act_fut = async {
|
|
1615
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
1616
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
1617
|
+
task_token: act_task.task_token,
|
|
1618
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
1619
|
+
})
|
|
1620
|
+
.await
|
|
1621
|
+
.unwrap();
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
join!(wf_fut, act_fut);
|
|
1625
|
+
core.drain_pollers_and_shutdown().await;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
#[tokio::test]
|
|
1629
|
+
async fn test_schedule_to_start_timeout() {
|
|
1630
|
+
let mut t = TestHistoryBuilder::default();
|
|
1631
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1632
|
+
t.add_full_wf_task();
|
|
1633
|
+
|
|
1634
|
+
let wf_id = "fakeid";
|
|
1635
|
+
let mock = mock_worker_client();
|
|
1636
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::ToTaskNum(1)], mock);
|
|
1637
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1638
|
+
|
|
1639
|
+
worker.register_wf(
|
|
1640
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1641
|
+
|ctx: WfContext| async move {
|
|
1642
|
+
let la_res = ctx
|
|
1643
|
+
.local_activity(LocalActivityOptions {
|
|
1644
|
+
activity_type: "echo".to_string(),
|
|
1645
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1646
|
+
// Impossibly small timeout so we timeout in the queue
|
|
1647
|
+
schedule_to_start_timeout: prost_dur!(from_nanos(1)),
|
|
1648
|
+
..Default::default()
|
|
1649
|
+
})
|
|
1650
|
+
.await;
|
|
1651
|
+
assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToStart));
|
|
1652
|
+
let rfail = la_res.unwrap_failure();
|
|
1653
|
+
assert_matches!(
|
|
1654
|
+
rfail.failure_info,
|
|
1655
|
+
Some(FailureInfo::ActivityFailureInfo(_))
|
|
1656
|
+
);
|
|
1657
|
+
assert_matches!(
|
|
1658
|
+
rfail.cause.unwrap().failure_info,
|
|
1659
|
+
Some(FailureInfo::TimeoutFailureInfo(_))
|
|
1660
|
+
);
|
|
1661
|
+
Ok(().into())
|
|
1662
|
+
},
|
|
1663
|
+
);
|
|
1664
|
+
worker.register_activity(
|
|
1665
|
+
"echo",
|
|
1666
|
+
move |_ctx: ActContext, _: String| async move { Ok(()) },
|
|
1667
|
+
);
|
|
1668
|
+
worker
|
|
1669
|
+
.submit_wf(
|
|
1670
|
+
wf_id.to_owned(),
|
|
1671
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1672
|
+
vec![],
|
|
1673
|
+
WorkflowOptions::default(),
|
|
1674
|
+
)
|
|
1675
|
+
.await
|
|
1676
|
+
.unwrap();
|
|
1677
|
+
worker.run_until_done().await.unwrap();
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
#[rstest::rstest]
|
|
1681
|
+
#[case::sched_to_start(true)]
|
|
1682
|
+
#[case::sched_to_close(false)]
|
|
1683
|
+
#[tokio::test]
|
|
1684
|
+
async fn test_schedule_to_start_timeout_not_based_on_original_time(
|
|
1685
|
+
#[case] is_sched_to_start: bool,
|
|
1686
|
+
) {
|
|
1687
|
+
// We used to carry over the schedule time of LAs from the "original" schedule time if these LAs
|
|
1688
|
+
// created newly after backing off across a timer. That was a mistake, since schedule-to-start
|
|
1689
|
+
// timeouts should apply to when the new attempt was scheduled. This test verifies:
|
|
1690
|
+
// * we don't time out on s-t-s timeouts because of that, when the param is true.
|
|
1691
|
+
// * we do properly time out on s-t-c timeouts when the param is false
|
|
1692
|
+
|
|
1693
|
+
let mut t = TestHistoryBuilder::default();
|
|
1694
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1695
|
+
t.add_full_wf_task();
|
|
1696
|
+
let orig_sched = SystemTime::now().sub(Duration::from_secs(60 * 20));
|
|
1697
|
+
t.add_local_activity_marker(
|
|
1698
|
+
1,
|
|
1699
|
+
"1",
|
|
1700
|
+
None,
|
|
1701
|
+
Some(Failure::application_failure("la failed".to_string(), false)),
|
|
1702
|
+
|deets| {
|
|
1703
|
+
// Really old schedule time, which should _not_ count against schedule_to_start
|
|
1704
|
+
deets.original_schedule_time = Some(orig_sched.into());
|
|
1705
|
+
// Backoff value must be present since we're simulating timer backoff
|
|
1706
|
+
deets.backoff = Some(prost_dur!(from_secs(100)));
|
|
1707
|
+
},
|
|
1708
|
+
);
|
|
1709
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1710
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1711
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1712
|
+
|
|
1713
|
+
let wf_id = "fakeid";
|
|
1714
|
+
let mock = mock_worker_client();
|
|
1715
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
|
|
1716
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1717
|
+
|
|
1718
|
+
let schedule_to_close_timeout = Some(if is_sched_to_start {
|
|
1719
|
+
// This 60 minute timeout will not have elapsed according to the original
|
|
1720
|
+
// schedule time in the history.
|
|
1721
|
+
Duration::from_secs(60 * 60)
|
|
1722
|
+
} else {
|
|
1723
|
+
// This 10 minute timeout will have already elapsed
|
|
1724
|
+
Duration::from_secs(10 * 60)
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
worker.register_wf(
|
|
1728
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1729
|
+
move |ctx: WfContext| async move {
|
|
1730
|
+
let la_res = ctx
|
|
1731
|
+
.local_activity(LocalActivityOptions {
|
|
1732
|
+
activity_type: "echo".to_string(),
|
|
1733
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1734
|
+
retry_policy: RetryPolicy {
|
|
1735
|
+
initial_interval: Some(prost_dur!(from_millis(50))),
|
|
1736
|
+
backoff_coefficient: 1.2,
|
|
1737
|
+
maximum_interval: None,
|
|
1738
|
+
maximum_attempts: 5,
|
|
1739
|
+
non_retryable_error_types: vec![],
|
|
1740
|
+
},
|
|
1741
|
+
schedule_to_start_timeout: Some(Duration::from_secs(60)),
|
|
1742
|
+
schedule_to_close_timeout,
|
|
1743
|
+
..Default::default()
|
|
1744
|
+
})
|
|
1745
|
+
.await;
|
|
1746
|
+
if is_sched_to_start {
|
|
1747
|
+
assert!(la_res.completed_ok());
|
|
1748
|
+
} else {
|
|
1749
|
+
assert_eq!(la_res.timed_out(), Some(TimeoutType::ScheduleToClose));
|
|
1750
|
+
}
|
|
1751
|
+
Ok(().into())
|
|
1752
|
+
},
|
|
1753
|
+
);
|
|
1754
|
+
worker.register_activity(
|
|
1755
|
+
"echo",
|
|
1756
|
+
move |_ctx: ActContext, _: String| async move { Ok(()) },
|
|
1757
|
+
);
|
|
1758
|
+
worker
|
|
1759
|
+
.submit_wf(
|
|
1760
|
+
wf_id.to_owned(),
|
|
1761
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1762
|
+
vec![],
|
|
1763
|
+
WorkflowOptions::default(),
|
|
1764
|
+
)
|
|
1765
|
+
.await
|
|
1766
|
+
.unwrap();
|
|
1767
|
+
worker.run_until_done().await.unwrap();
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
#[rstest::rstest]
|
|
1771
|
+
#[tokio::test]
|
|
1772
|
+
async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_completes: bool) {
|
|
1773
|
+
let mut t = TestHistoryBuilder::default();
|
|
1774
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1775
|
+
t.add_full_wf_task();
|
|
1776
|
+
if la_completes {
|
|
1777
|
+
t.add_local_activity_marker(1, "1", Some("hi".into()), None, |_| {});
|
|
1778
|
+
} else {
|
|
1779
|
+
t.add_local_activity_marker(
|
|
1780
|
+
1,
|
|
1781
|
+
"1",
|
|
1782
|
+
None,
|
|
1783
|
+
Some(Failure::timeout(TimeoutType::StartToClose)),
|
|
1784
|
+
|_| {},
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
t.add_full_wf_task();
|
|
1788
|
+
t.add_workflow_execution_completed();
|
|
1789
|
+
|
|
1790
|
+
let wf_id = "fakeid";
|
|
1791
|
+
let mock = mock_worker_client();
|
|
1792
|
+
let mh = MockPollCfg::from_resp_batches(
|
|
1793
|
+
wf_id,
|
|
1794
|
+
t,
|
|
1795
|
+
[ResponseType::ToTaskNum(1), ResponseType::AllHistory],
|
|
1796
|
+
mock,
|
|
1797
|
+
);
|
|
1798
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1799
|
+
|
|
1800
|
+
worker.register_wf(
|
|
1801
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1802
|
+
move |ctx: WfContext| async move {
|
|
1803
|
+
let la_res = ctx
|
|
1804
|
+
.local_activity(LocalActivityOptions {
|
|
1805
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
1806
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1807
|
+
retry_policy: RetryPolicy {
|
|
1808
|
+
initial_interval: Some(prost_dur!(from_millis(20))),
|
|
1809
|
+
backoff_coefficient: 1.0,
|
|
1810
|
+
maximum_interval: None,
|
|
1811
|
+
maximum_attempts: 5,
|
|
1812
|
+
non_retryable_error_types: vec![],
|
|
1813
|
+
},
|
|
1814
|
+
start_to_close_timeout: Some(prost_dur!(from_millis(25))),
|
|
1815
|
+
..Default::default()
|
|
1816
|
+
})
|
|
1817
|
+
.await;
|
|
1818
|
+
if la_completes {
|
|
1819
|
+
assert!(la_res.completed_ok());
|
|
1820
|
+
} else {
|
|
1821
|
+
assert_eq!(la_res.timed_out(), Some(TimeoutType::StartToClose));
|
|
1822
|
+
}
|
|
1823
|
+
Ok(().into())
|
|
1824
|
+
},
|
|
1825
|
+
);
|
|
1826
|
+
let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
1827
|
+
let cancels: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
1828
|
+
worker.register_activity(
|
|
1829
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
1830
|
+
move |ctx: ActContext, _: String| async move {
|
|
1831
|
+
// Timeout the first 4 attempts, or all of them if we intend to fail
|
|
1832
|
+
if attempts.fetch_add(1, Ordering::AcqRel) < 4 || !la_completes {
|
|
1833
|
+
select! {
|
|
1834
|
+
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
|
|
1835
|
+
_ = ctx.cancelled() => {
|
|
1836
|
+
cancels.fetch_add(1, Ordering::AcqRel);
|
|
1837
|
+
return Err(ActivityError::cancelled());
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
Ok(())
|
|
1842
|
+
},
|
|
1843
|
+
);
|
|
1844
|
+
worker
|
|
1845
|
+
.submit_wf(
|
|
1846
|
+
wf_id.to_owned(),
|
|
1847
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1848
|
+
vec![],
|
|
1849
|
+
WorkflowOptions::default(),
|
|
1850
|
+
)
|
|
1851
|
+
.await
|
|
1852
|
+
.unwrap();
|
|
1853
|
+
worker.run_until_done().await.unwrap();
|
|
1854
|
+
// Activity should have been attempted all 5 times
|
|
1855
|
+
assert_eq!(attempts.load(Ordering::Acquire), 5);
|
|
1856
|
+
let num_cancels = if la_completes { 4 } else { 5 };
|
|
1857
|
+
assert_eq!(cancels.load(Ordering::Acquire), num_cancels);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
#[tokio::test]
|
|
1861
|
+
async fn wft_failure_cancels_running_las() {
|
|
1862
|
+
let mut t = TestHistoryBuilder::default();
|
|
1863
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
|
|
1864
|
+
t.add_full_wf_task();
|
|
1865
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
1866
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
1867
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1868
|
+
|
|
1869
|
+
let wf_id = "fakeid";
|
|
1870
|
+
let mock = mock_worker_client();
|
|
1871
|
+
let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2], mock);
|
|
1872
|
+
mh.num_expected_fails = 1;
|
|
1873
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1874
|
+
|
|
1875
|
+
worker.register_wf(
|
|
1876
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1877
|
+
|ctx: WfContext| async move {
|
|
1878
|
+
let la_handle = ctx.local_activity(LocalActivityOptions {
|
|
1879
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
1880
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1881
|
+
..Default::default()
|
|
1882
|
+
});
|
|
1883
|
+
tokio::join!(
|
|
1884
|
+
async {
|
|
1885
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
1886
|
+
panic!("ahhh I'm failing wft")
|
|
1887
|
+
},
|
|
1888
|
+
la_handle
|
|
1889
|
+
);
|
|
1890
|
+
Ok(().into())
|
|
1891
|
+
},
|
|
1892
|
+
);
|
|
1893
|
+
worker.register_activity(
|
|
1894
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
1895
|
+
move |ctx: ActContext, _: String| async move {
|
|
1896
|
+
let res = tokio::time::timeout(Duration::from_millis(500), ctx.cancelled()).await;
|
|
1897
|
+
if res.is_err() {
|
|
1898
|
+
panic!("Activity must be cancelled!!!!");
|
|
1899
|
+
}
|
|
1900
|
+
Result::<(), _>::Err(ActivityError::cancelled())
|
|
1901
|
+
},
|
|
1902
|
+
);
|
|
1903
|
+
worker
|
|
1904
|
+
.submit_wf(
|
|
1905
|
+
wf_id.to_owned(),
|
|
1906
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1907
|
+
vec![],
|
|
1908
|
+
WorkflowOptions::default(),
|
|
1909
|
+
)
|
|
1910
|
+
.await
|
|
1911
|
+
.unwrap();
|
|
1912
|
+
worker.run_until_done().await.unwrap();
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
#[tokio::test]
|
|
1916
|
+
async fn resolved_las_not_recorded_if_wft_fails_many_times() {
|
|
1917
|
+
// We shouldn't record any LA results if the workflow activation is repeatedly failing. There
|
|
1918
|
+
// was an issue that, because we stop reporting WFT failures after 2 tries, this meant the WFT
|
|
1919
|
+
// was not marked as "completed" and the WFT could accidentally be replied to with LA results.
|
|
1920
|
+
let mut t = TestHistoryBuilder::default();
|
|
1921
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1922
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1923
|
+
t.add_workflow_task_failed_with_failure(
|
|
1924
|
+
WorkflowTaskFailedCause::Unspecified,
|
|
1925
|
+
Default::default(),
|
|
1926
|
+
);
|
|
1927
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1928
|
+
|
|
1929
|
+
let wf_id = "fakeid";
|
|
1930
|
+
let mock = mock_worker_client();
|
|
1931
|
+
let mut mh = MockPollCfg::from_resp_batches(
|
|
1932
|
+
wf_id,
|
|
1933
|
+
t,
|
|
1934
|
+
[1.into(), ResponseType::AllHistory, ResponseType::AllHistory],
|
|
1935
|
+
mock,
|
|
1936
|
+
);
|
|
1937
|
+
mh.num_expected_fails = 2;
|
|
1938
|
+
mh.num_expected_completions = Some(0.into());
|
|
1939
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
1940
|
+
|
|
1941
|
+
#[allow(unreachable_code)]
|
|
1942
|
+
worker.register_wf(
|
|
1943
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1944
|
+
WorkflowFunction::new::<_, _, ()>(|ctx: WfContext| async move {
|
|
1945
|
+
ctx.local_activity(LocalActivityOptions {
|
|
1946
|
+
activity_type: "echo".to_string(),
|
|
1947
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
1948
|
+
..Default::default()
|
|
1949
|
+
})
|
|
1950
|
+
.await;
|
|
1951
|
+
panic!()
|
|
1952
|
+
}),
|
|
1953
|
+
);
|
|
1954
|
+
worker.register_activity(
|
|
1955
|
+
"echo",
|
|
1956
|
+
move |_: ActContext, _: String| async move { Ok(()) },
|
|
1957
|
+
);
|
|
1958
|
+
worker
|
|
1959
|
+
.submit_wf(
|
|
1960
|
+
wf_id.to_owned(),
|
|
1961
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1962
|
+
vec![],
|
|
1963
|
+
WorkflowOptions::default(),
|
|
1964
|
+
)
|
|
1965
|
+
.await
|
|
1966
|
+
.unwrap();
|
|
1967
|
+
worker.run_until_done().await.unwrap();
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
#[tokio::test]
|
|
1971
|
+
async fn local_act_records_nonfirst_attempts_ok() {
|
|
1972
|
+
let mut t = TestHistoryBuilder::default();
|
|
1973
|
+
let wft_timeout = Duration::from_millis(200);
|
|
1974
|
+
t.add_wfe_started_with_wft_timeout(wft_timeout);
|
|
1975
|
+
t.add_full_wf_task();
|
|
1976
|
+
t.add_full_wf_task();
|
|
1977
|
+
t.add_full_wf_task();
|
|
1978
|
+
t.add_workflow_task_scheduled_and_started();
|
|
1979
|
+
|
|
1980
|
+
let wf_id = "fakeid";
|
|
1981
|
+
let mock = mock_worker_client();
|
|
1982
|
+
let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 3], mock);
|
|
1983
|
+
let nonfirst_counts = Arc::new(SegQueue::new());
|
|
1984
|
+
let nfc_c = nonfirst_counts.clone();
|
|
1985
|
+
mh.completion_mock_fn = Some(Box::new(move |c| {
|
|
1986
|
+
nfc_c.push(
|
|
1987
|
+
c.metering_metadata
|
|
1988
|
+
.nonfirst_local_activity_execution_attempts,
|
|
1989
|
+
);
|
|
1990
|
+
Ok(Default::default())
|
|
1991
|
+
}));
|
|
1992
|
+
let mut worker = mock_sdk_cfg(mh, |wc| {
|
|
1993
|
+
wc.max_cached_workflows = 1;
|
|
1994
|
+
wc.max_outstanding_workflow_tasks = Some(1);
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
worker.register_wf(
|
|
1998
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
1999
|
+
|ctx: WfContext| async move {
|
|
2000
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2001
|
+
activity_type: "echo".to_string(),
|
|
2002
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
2003
|
+
retry_policy: RetryPolicy {
|
|
2004
|
+
initial_interval: Some(prost_dur!(from_millis(10))),
|
|
2005
|
+
backoff_coefficient: 1.0,
|
|
2006
|
+
maximum_interval: None,
|
|
2007
|
+
maximum_attempts: 0,
|
|
2008
|
+
non_retryable_error_types: vec![],
|
|
2009
|
+
},
|
|
2010
|
+
..Default::default()
|
|
2011
|
+
})
|
|
2012
|
+
.await;
|
|
2013
|
+
Ok(().into())
|
|
2014
|
+
},
|
|
2015
|
+
);
|
|
2016
|
+
worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
|
|
2017
|
+
Result::<(), _>::Err(anyhow!("I fail").into())
|
|
2018
|
+
});
|
|
2019
|
+
worker
|
|
2020
|
+
.submit_wf(
|
|
2021
|
+
wf_id.to_owned(),
|
|
2022
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
2023
|
+
vec![],
|
|
2024
|
+
WorkflowOptions::default(),
|
|
2025
|
+
)
|
|
2026
|
+
.await
|
|
2027
|
+
.unwrap();
|
|
2028
|
+
worker.run_until_done().await.unwrap();
|
|
2029
|
+
// 3 workflow tasks
|
|
2030
|
+
assert_eq!(nonfirst_counts.len(), 3);
|
|
2031
|
+
// First task's non-first count should, of course, be 0
|
|
2032
|
+
assert_eq!(nonfirst_counts.pop().unwrap(), 0);
|
|
2033
|
+
// Next two, some nonzero amount which could vary based on test load
|
|
2034
|
+
assert!(nonfirst_counts.pop().unwrap() > 0);
|
|
2035
|
+
assert!(nonfirst_counts.pop().unwrap() > 0);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
#[tokio::test]
|
|
2039
|
+
async fn local_activities_can_be_delivered_during_shutdown() {
|
|
2040
|
+
let wfid = "fake_wf_id";
|
|
2041
|
+
let mut t = TestHistoryBuilder::default();
|
|
2042
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
|
|
2043
|
+
t.add_full_wf_task();
|
|
2044
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
2045
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
2046
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2047
|
+
|
|
2048
|
+
let mock = mock_worker_client();
|
|
2049
|
+
let mut mock = single_hist_mock_sg(
|
|
2050
|
+
wfid,
|
|
2051
|
+
t,
|
|
2052
|
+
[ResponseType::ToTaskNum(1), ResponseType::AllHistory],
|
|
2053
|
+
mock,
|
|
2054
|
+
true,
|
|
2055
|
+
);
|
|
2056
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
|
|
2057
|
+
let core = mock_worker(mock);
|
|
2058
|
+
|
|
2059
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2060
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
2061
|
+
task.run_id,
|
|
2062
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
2063
|
+
))
|
|
2064
|
+
.await
|
|
2065
|
+
.unwrap();
|
|
2066
|
+
|
|
2067
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2068
|
+
// Initiate shutdown once we have the WF activation, but before replying that we want to do an
|
|
2069
|
+
// LA
|
|
2070
|
+
core.initiate_shutdown();
|
|
2071
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
2072
|
+
task.run_id,
|
|
2073
|
+
ScheduleLocalActivity {
|
|
2074
|
+
seq: 1,
|
|
2075
|
+
activity_id: "1".to_string(),
|
|
2076
|
+
activity_type: "test_act".to_string(),
|
|
2077
|
+
start_to_close_timeout: Some(prost_dur!(from_secs(30))),
|
|
2078
|
+
..Default::default()
|
|
2079
|
+
}
|
|
2080
|
+
.into(),
|
|
2081
|
+
))
|
|
2082
|
+
.await
|
|
2083
|
+
.unwrap();
|
|
2084
|
+
|
|
2085
|
+
let wf_poller = async { core.poll_workflow_activation().await };
|
|
2086
|
+
|
|
2087
|
+
let at_poller = async {
|
|
2088
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
2089
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
2090
|
+
task_token: act_task.task_token,
|
|
2091
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
2092
|
+
})
|
|
2093
|
+
.await
|
|
2094
|
+
.unwrap();
|
|
2095
|
+
core.poll_activity_task().await
|
|
2096
|
+
};
|
|
2097
|
+
|
|
2098
|
+
let (wf_r, act_r) = join!(wf_poller, at_poller);
|
|
2099
|
+
assert_matches!(wf_r.unwrap_err(), PollError::ShutDown);
|
|
2100
|
+
assert_matches!(act_r.unwrap_err(), PollError::ShutDown);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
#[tokio::test]
|
|
2104
|
+
async fn queries_can_be_received_while_heartbeating() {
|
|
2105
|
+
let wfid = "fake_wf_id";
|
|
2106
|
+
let mut t = TestHistoryBuilder::default();
|
|
2107
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
|
|
2108
|
+
t.add_full_wf_task();
|
|
2109
|
+
t.add_full_wf_task();
|
|
2110
|
+
t.add_full_wf_task();
|
|
2111
|
+
|
|
2112
|
+
let tasks = [
|
|
2113
|
+
hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::ToTaskNum(1)),
|
|
2114
|
+
{
|
|
2115
|
+
let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(2));
|
|
2116
|
+
pr.queries = HashMap::new();
|
|
2117
|
+
pr.queries.insert(
|
|
2118
|
+
"q1".to_string(),
|
|
2119
|
+
WorkflowQuery {
|
|
2120
|
+
query_type: "query-type".to_string(),
|
|
2121
|
+
query_args: Some(b"hi".into()),
|
|
2122
|
+
header: None,
|
|
2123
|
+
},
|
|
2124
|
+
);
|
|
2125
|
+
pr
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
let mut pr = hist_to_poll_resp(&t, wfid.to_owned(), ResponseType::OneTask(3));
|
|
2129
|
+
pr.query = Some(WorkflowQuery {
|
|
2130
|
+
query_type: "query-type".to_string(),
|
|
2131
|
+
query_args: Some(b"hi".into()),
|
|
2132
|
+
header: None,
|
|
2133
|
+
});
|
|
2134
|
+
pr
|
|
2135
|
+
},
|
|
2136
|
+
];
|
|
2137
|
+
let mut mock = mock_worker_client();
|
|
2138
|
+
mock.expect_respond_legacy_query()
|
|
2139
|
+
.times(1)
|
|
2140
|
+
.returning(move |_, _| Ok(Default::default()));
|
|
2141
|
+
let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
|
|
2142
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
|
|
2143
|
+
let core = mock_worker(mock);
|
|
2144
|
+
|
|
2145
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2146
|
+
assert_matches!(
|
|
2147
|
+
task.jobs.as_slice(),
|
|
2148
|
+
&[WorkflowActivationJob {
|
|
2149
|
+
variant: Some(workflow_activation_job::Variant::InitializeWorkflow(_)),
|
|
2150
|
+
},]
|
|
2151
|
+
);
|
|
2152
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
2153
|
+
task.run_id,
|
|
2154
|
+
schedule_local_activity_cmd(
|
|
2155
|
+
1,
|
|
2156
|
+
"act-id",
|
|
2157
|
+
ActivityCancellationType::TryCancel,
|
|
2158
|
+
Duration::from_secs(60),
|
|
2159
|
+
),
|
|
2160
|
+
))
|
|
2161
|
+
.await
|
|
2162
|
+
.unwrap();
|
|
2163
|
+
|
|
2164
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2165
|
+
assert_matches!(
|
|
2166
|
+
task.jobs.as_slice(),
|
|
2167
|
+
&[WorkflowActivationJob {
|
|
2168
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
|
|
2169
|
+
}]
|
|
2170
|
+
if q.query_id == "q1"
|
|
2171
|
+
);
|
|
2172
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
2173
|
+
task.run_id,
|
|
2174
|
+
query_ok("q1", "whatev"),
|
|
2175
|
+
))
|
|
2176
|
+
.await
|
|
2177
|
+
.unwrap();
|
|
2178
|
+
|
|
2179
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2180
|
+
assert_matches!(
|
|
2181
|
+
task.jobs.as_slice(),
|
|
2182
|
+
&[WorkflowActivationJob {
|
|
2183
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(ref q)),
|
|
2184
|
+
}]
|
|
2185
|
+
if q.query_id == LEGACY_QUERY_ID
|
|
2186
|
+
);
|
|
2187
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
2188
|
+
task.run_id,
|
|
2189
|
+
query_ok(LEGACY_QUERY_ID, "whatev"),
|
|
2190
|
+
))
|
|
2191
|
+
.await
|
|
2192
|
+
.unwrap();
|
|
2193
|
+
|
|
2194
|
+
// Handle the activity so we can shut down cleanly
|
|
2195
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
2196
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
2197
|
+
task_token: act_task.task_token,
|
|
2198
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
2199
|
+
})
|
|
2200
|
+
.await
|
|
2201
|
+
.unwrap();
|
|
2202
|
+
|
|
2203
|
+
core.drain_pollers_and_shutdown().await;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
#[tokio::test]
|
|
2207
|
+
async fn local_activity_after_wf_complete_is_discarded() {
|
|
2208
|
+
let wfid = "fake_wf_id";
|
|
2209
|
+
let mut t = TestHistoryBuilder::default();
|
|
2210
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(200));
|
|
2211
|
+
t.add_full_wf_task();
|
|
2212
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2213
|
+
|
|
2214
|
+
let mock = mock_worker_client();
|
|
2215
|
+
let mut mock_cfg = MockPollCfg::from_resp_batches(
|
|
2216
|
+
wfid,
|
|
2217
|
+
t,
|
|
2218
|
+
[ResponseType::ToTaskNum(1), ResponseType::ToTaskNum(2)],
|
|
2219
|
+
mock,
|
|
2220
|
+
);
|
|
2221
|
+
mock_cfg.make_poll_stream_interminable = true;
|
|
2222
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2223
|
+
asserts
|
|
2224
|
+
.then(move |wft| {
|
|
2225
|
+
assert_eq!(wft.commands.len(), 0);
|
|
2226
|
+
})
|
|
2227
|
+
.then(move |wft| {
|
|
2228
|
+
assert_eq!(wft.commands.len(), 2);
|
|
2229
|
+
assert_eq!(wft.commands[0].command_type(), CommandType::RecordMarker);
|
|
2230
|
+
assert_eq!(
|
|
2231
|
+
wft.commands[1].command_type(),
|
|
2232
|
+
CommandType::CompleteWorkflowExecution
|
|
2233
|
+
);
|
|
2234
|
+
});
|
|
2235
|
+
});
|
|
2236
|
+
let mut mock = build_mock_pollers(mock_cfg);
|
|
2237
|
+
mock.worker_cfg(|wc| {
|
|
2238
|
+
wc.max_cached_workflows = 1;
|
|
2239
|
+
wc.ignore_evicts_on_shutdown = false;
|
|
2240
|
+
});
|
|
2241
|
+
let core = mock_worker(mock);
|
|
2242
|
+
|
|
2243
|
+
let barr = Barrier::new(2);
|
|
2244
|
+
|
|
2245
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2246
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
2247
|
+
task.run_id,
|
|
2248
|
+
vec![
|
|
2249
|
+
ScheduleLocalActivity {
|
|
2250
|
+
seq: 1,
|
|
2251
|
+
activity_id: "1".to_string(),
|
|
2252
|
+
activity_type: "test_act".to_string(),
|
|
2253
|
+
start_to_close_timeout: Some(prost_dur!(from_secs(30))),
|
|
2254
|
+
..Default::default()
|
|
2255
|
+
}
|
|
2256
|
+
.into(),
|
|
2257
|
+
ScheduleLocalActivity {
|
|
2258
|
+
seq: 2,
|
|
2259
|
+
activity_id: "2".to_string(),
|
|
2260
|
+
activity_type: "test_act".to_string(),
|
|
2261
|
+
start_to_close_timeout: Some(prost_dur!(from_secs(30))),
|
|
2262
|
+
..Default::default()
|
|
2263
|
+
}
|
|
2264
|
+
.into(),
|
|
2265
|
+
],
|
|
2266
|
+
))
|
|
2267
|
+
.await
|
|
2268
|
+
.unwrap();
|
|
2269
|
+
|
|
2270
|
+
let wf_poller = async {
|
|
2271
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
2272
|
+
assert_matches!(
|
|
2273
|
+
task.jobs.as_slice(),
|
|
2274
|
+
[WorkflowActivationJob {
|
|
2275
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
|
|
2276
|
+
}]
|
|
2277
|
+
);
|
|
2278
|
+
barr.wait().await;
|
|
2279
|
+
core.complete_execution(&task.run_id).await;
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
let at_poller = async {
|
|
2283
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
2284
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
2285
|
+
task_token: act_task.task_token,
|
|
2286
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
2287
|
+
})
|
|
2288
|
+
.await
|
|
2289
|
+
.unwrap();
|
|
2290
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
2291
|
+
barr.wait().await;
|
|
2292
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
2293
|
+
task_token: act_task.task_token,
|
|
2294
|
+
result: Some(ActivityExecutionResult::ok(vec![2].into())),
|
|
2295
|
+
})
|
|
2296
|
+
.await
|
|
2297
|
+
.unwrap();
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
join!(wf_poller, at_poller);
|
|
2301
|
+
core.drain_pollers_and_shutdown().await;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
#[tokio::test]
|
|
2305
|
+
async fn local_act_retry_explicit_delay() {
|
|
2306
|
+
let mut t = TestHistoryBuilder::default();
|
|
2307
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2308
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2309
|
+
|
|
2310
|
+
let wf_id = "fakeid";
|
|
2311
|
+
let mock = mock_worker_client();
|
|
2312
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [1], mock);
|
|
2313
|
+
let mut worker = mock_sdk(mh);
|
|
2314
|
+
|
|
2315
|
+
worker.register_wf(
|
|
2316
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
2317
|
+
move |ctx: WfContext| async move {
|
|
2318
|
+
let la_res = ctx
|
|
2319
|
+
.local_activity(LocalActivityOptions {
|
|
2320
|
+
activity_type: "echo".to_string(),
|
|
2321
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
2322
|
+
retry_policy: RetryPolicy {
|
|
2323
|
+
initial_interval: Some(prost_dur!(from_millis(50))),
|
|
2324
|
+
backoff_coefficient: 1.0,
|
|
2325
|
+
maximum_attempts: 5,
|
|
2326
|
+
..Default::default()
|
|
2327
|
+
},
|
|
2328
|
+
..Default::default()
|
|
2329
|
+
})
|
|
2330
|
+
.await;
|
|
2331
|
+
assert!(la_res.completed_ok());
|
|
2332
|
+
Ok(().into())
|
|
2333
|
+
},
|
|
2334
|
+
);
|
|
2335
|
+
let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
2336
|
+
worker.register_activity("echo", move |_ctx: ActContext, _: String| async move {
|
|
2337
|
+
// Succeed on 3rd attempt (which is ==2 since fetch_add returns prev val)
|
|
2338
|
+
let last_attempt = attempts.fetch_add(1, Ordering::Relaxed);
|
|
2339
|
+
if 0 == last_attempt {
|
|
2340
|
+
Err(ActivityError::Retryable {
|
|
2341
|
+
source: anyhow!("Explicit backoff error"),
|
|
2342
|
+
explicit_delay: Some(Duration::from_millis(300)),
|
|
2343
|
+
})
|
|
2344
|
+
} else if 2 == last_attempt {
|
|
2345
|
+
Ok(())
|
|
2346
|
+
} else {
|
|
2347
|
+
Err(anyhow!("Oh no I failed!").into())
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
worker
|
|
2351
|
+
.submit_wf(
|
|
2352
|
+
wf_id.to_owned(),
|
|
2353
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
2354
|
+
vec![],
|
|
2355
|
+
WorkflowOptions::default(),
|
|
2356
|
+
)
|
|
2357
|
+
.await
|
|
2358
|
+
.unwrap();
|
|
2359
|
+
let start = Instant::now();
|
|
2360
|
+
worker.run_until_done().await.unwrap();
|
|
2361
|
+
let expected_attempts = 3;
|
|
2362
|
+
assert_eq!(expected_attempts, attempts.load(Ordering::Relaxed));
|
|
2363
|
+
// There will be one 300ms backoff and one 50s backoff, so things should take at least that long
|
|
2364
|
+
assert!(start.elapsed() > Duration::from_millis(350));
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
async fn la_wf(ctx: WfContext) -> WorkflowResult<()> {
|
|
2368
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2369
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2370
|
+
input: ().as_json_payload().unwrap(),
|
|
2371
|
+
retry_policy: RetryPolicy {
|
|
2372
|
+
maximum_attempts: 1,
|
|
2373
|
+
..Default::default()
|
|
2374
|
+
},
|
|
2375
|
+
..Default::default()
|
|
2376
|
+
})
|
|
2377
|
+
.await;
|
|
2378
|
+
Ok(().into())
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
#[rstest]
|
|
2382
|
+
#[case::incremental(false, true)]
|
|
2383
|
+
#[case::replay(true, true)]
|
|
2384
|
+
#[case::incremental_fail(false, false)]
|
|
2385
|
+
#[case::replay_fail(true, false)]
|
|
2386
|
+
#[tokio::test]
|
|
2387
|
+
async fn one_la_success(#[case] replay: bool, #[case] completes_ok: bool) {
|
|
2388
|
+
let activity_id = "1";
|
|
2389
|
+
let mut t = TestHistoryBuilder::default();
|
|
2390
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2391
|
+
t.add_full_wf_task();
|
|
2392
|
+
if completes_ok {
|
|
2393
|
+
t.add_local_activity_result_marker(1, activity_id, b"hi".into());
|
|
2394
|
+
} else {
|
|
2395
|
+
t.add_local_activity_fail_marker(
|
|
2396
|
+
1,
|
|
2397
|
+
activity_id,
|
|
2398
|
+
Failure::application_failure("I failed".to_string(), false),
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2402
|
+
|
|
2403
|
+
let mut mock_cfg = if replay {
|
|
2404
|
+
MockPollCfg::from_resps(t, [ResponseType::AllHistory])
|
|
2405
|
+
} else {
|
|
2406
|
+
MockPollCfg::from_hist_builder(t)
|
|
2407
|
+
};
|
|
2408
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2409
|
+
asserts.then(move |wft| {
|
|
2410
|
+
let commands = &wft.commands;
|
|
2411
|
+
if !replay {
|
|
2412
|
+
assert_eq!(commands.len(), 2);
|
|
2413
|
+
assert_eq!(commands[0].command_type(), CommandType::RecordMarker);
|
|
2414
|
+
if completes_ok {
|
|
2415
|
+
assert_matches!(
|
|
2416
|
+
commands[0].attributes.as_ref().unwrap(),
|
|
2417
|
+
command::Attributes::RecordMarkerCommandAttributes(
|
|
2418
|
+
RecordMarkerCommandAttributes { failure: None, .. }
|
|
2419
|
+
)
|
|
2420
|
+
);
|
|
2421
|
+
} else {
|
|
2422
|
+
assert_matches!(
|
|
2423
|
+
commands[0].attributes.as_ref().unwrap(),
|
|
2424
|
+
command::Attributes::RecordMarkerCommandAttributes(
|
|
2425
|
+
RecordMarkerCommandAttributes {
|
|
2426
|
+
failure: Some(_),
|
|
2427
|
+
..
|
|
2428
|
+
}
|
|
2429
|
+
)
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
assert_eq!(
|
|
2433
|
+
commands[1].command_type(),
|
|
2434
|
+
CommandType::CompleteWorkflowExecution
|
|
2435
|
+
);
|
|
2436
|
+
} else {
|
|
2437
|
+
assert_eq!(commands.len(), 1);
|
|
2438
|
+
assert_matches!(
|
|
2439
|
+
commands[0].command_type(),
|
|
2440
|
+
CommandType::CompleteWorkflowExecution
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
});
|
|
2445
|
+
|
|
2446
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2447
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
|
|
2448
|
+
worker.register_activity(
|
|
2449
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
2450
|
+
move |_ctx: ActContext, _: ()| async move {
|
|
2451
|
+
if replay {
|
|
2452
|
+
panic!("Should not be invoked on replay");
|
|
2453
|
+
}
|
|
2454
|
+
if completes_ok {
|
|
2455
|
+
Ok("hi")
|
|
2456
|
+
} else {
|
|
2457
|
+
Err(anyhow!("Oh no I failed!").into())
|
|
2458
|
+
}
|
|
2459
|
+
},
|
|
2460
|
+
);
|
|
2461
|
+
worker.run().await.unwrap();
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
async fn two_la_wf(ctx: WfContext) -> WorkflowResult<()> {
|
|
2465
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2466
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2467
|
+
input: ().as_json_payload().unwrap(),
|
|
2468
|
+
..Default::default()
|
|
2469
|
+
})
|
|
2470
|
+
.await;
|
|
2471
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2472
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2473
|
+
input: ().as_json_payload().unwrap(),
|
|
2474
|
+
..Default::default()
|
|
2475
|
+
})
|
|
2476
|
+
.await;
|
|
2477
|
+
Ok(().into())
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
async fn two_la_wf_parallel(ctx: WfContext) -> WorkflowResult<()> {
|
|
2481
|
+
tokio::join!(
|
|
2482
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2483
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2484
|
+
input: ().as_json_payload().unwrap(),
|
|
2485
|
+
..Default::default()
|
|
2486
|
+
}),
|
|
2487
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2488
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2489
|
+
input: ().as_json_payload().unwrap(),
|
|
2490
|
+
..Default::default()
|
|
2491
|
+
})
|
|
2492
|
+
);
|
|
2493
|
+
Ok(().into())
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
#[rstest]
|
|
2497
|
+
#[tokio::test]
|
|
2498
|
+
async fn two_sequential_las(
|
|
2499
|
+
#[values(true, false)] replay: bool,
|
|
2500
|
+
#[values(true, false)] parallel: bool,
|
|
2501
|
+
) {
|
|
2502
|
+
let t = canned_histories::two_local_activities_one_wft(parallel);
|
|
2503
|
+
let mut mock_cfg = if replay {
|
|
2504
|
+
MockPollCfg::from_resps(t, [ResponseType::AllHistory])
|
|
2505
|
+
} else {
|
|
2506
|
+
MockPollCfg::from_hist_builder(t)
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2509
|
+
let mut aai = ActivationAssertionsInterceptor::default();
|
|
2510
|
+
let first_act_ts_seconds: &'static _ = Box::leak(Box::new(AtomicI64::new(-1)));
|
|
2511
|
+
aai.then(|a| {
|
|
2512
|
+
first_act_ts_seconds.store(a.timestamp.as_ref().unwrap().seconds, Ordering::Relaxed)
|
|
2513
|
+
});
|
|
2514
|
+
// Verify LAs advance time (they take 1s as defined in the canned history)
|
|
2515
|
+
aai.then(move |a| {
|
|
2516
|
+
if !parallel {
|
|
2517
|
+
assert_matches!(
|
|
2518
|
+
a.jobs.as_slice(),
|
|
2519
|
+
[WorkflowActivationJob {
|
|
2520
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
|
|
2521
|
+
}] => assert_eq!(ra.seq, 1)
|
|
2522
|
+
);
|
|
2523
|
+
} else {
|
|
2524
|
+
assert_matches!(
|
|
2525
|
+
a.jobs.as_slice(),
|
|
2526
|
+
[WorkflowActivationJob {
|
|
2527
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
|
|
2528
|
+
}, WorkflowActivationJob {
|
|
2529
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(ra2))
|
|
2530
|
+
}] => {assert_eq!(ra.seq, 1); assert_eq!(ra2.seq, 2)}
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
if replay {
|
|
2534
|
+
assert!(
|
|
2535
|
+
a.timestamp.as_ref().unwrap().seconds
|
|
2536
|
+
> first_act_ts_seconds.load(Ordering::Relaxed)
|
|
2537
|
+
)
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
if !parallel {
|
|
2541
|
+
aai.then(move |a| {
|
|
2542
|
+
assert_matches!(
|
|
2543
|
+
a.jobs.as_slice(),
|
|
2544
|
+
[WorkflowActivationJob {
|
|
2545
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
|
|
2546
|
+
}] => assert_eq!(ra.seq, 2)
|
|
2547
|
+
);
|
|
2548
|
+
if replay {
|
|
2549
|
+
assert!(
|
|
2550
|
+
a.timestamp.as_ref().unwrap().seconds
|
|
2551
|
+
>= first_act_ts_seconds.load(Ordering::Relaxed) + 2
|
|
2552
|
+
)
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2558
|
+
asserts.then(move |wft| {
|
|
2559
|
+
let commands = &wft.commands;
|
|
2560
|
+
if !replay {
|
|
2561
|
+
assert_eq!(commands.len(), 3);
|
|
2562
|
+
assert_eq!(commands[0].command_type(), CommandType::RecordMarker);
|
|
2563
|
+
assert_eq!(commands[1].command_type(), CommandType::RecordMarker);
|
|
2564
|
+
assert_matches!(
|
|
2565
|
+
commands[2].command_type(),
|
|
2566
|
+
CommandType::CompleteWorkflowExecution
|
|
2567
|
+
);
|
|
2568
|
+
} else {
|
|
2569
|
+
assert_eq!(commands.len(), 1);
|
|
2570
|
+
assert_matches!(
|
|
2571
|
+
commands[0].command_type(),
|
|
2572
|
+
CommandType::CompleteWorkflowExecution
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2579
|
+
worker.set_worker_interceptor(aai);
|
|
2580
|
+
if parallel {
|
|
2581
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf_parallel);
|
|
2582
|
+
} else {
|
|
2583
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, two_la_wf);
|
|
2584
|
+
}
|
|
2585
|
+
worker.register_activity(
|
|
2586
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
2587
|
+
move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
|
|
2588
|
+
);
|
|
2589
|
+
worker.run().await.unwrap();
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
async fn la_timer_la(ctx: WfContext) -> WorkflowResult<()> {
|
|
2593
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2594
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2595
|
+
input: ().as_json_payload().unwrap(),
|
|
2596
|
+
..Default::default()
|
|
2597
|
+
})
|
|
2598
|
+
.await;
|
|
2599
|
+
ctx.timer(Duration::from_secs(5)).await;
|
|
2600
|
+
ctx.local_activity(LocalActivityOptions {
|
|
2601
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2602
|
+
input: ().as_json_payload().unwrap(),
|
|
2603
|
+
..Default::default()
|
|
2604
|
+
})
|
|
2605
|
+
.await;
|
|
2606
|
+
Ok(().into())
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
#[rstest]
|
|
2610
|
+
#[case::incremental(false)]
|
|
2611
|
+
#[case::replay(true)]
|
|
2612
|
+
#[tokio::test]
|
|
2613
|
+
async fn las_separated_by_timer(#[case] replay: bool) {
|
|
2614
|
+
let mut t = TestHistoryBuilder::default();
|
|
2615
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2616
|
+
t.add_full_wf_task();
|
|
2617
|
+
t.add_local_activity_result_marker(1, "1", b"hi".into());
|
|
2618
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
2619
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
2620
|
+
t.add_full_wf_task();
|
|
2621
|
+
t.add_local_activity_result_marker(2, "2", b"hi2".into());
|
|
2622
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2623
|
+
let mut mock_cfg = if replay {
|
|
2624
|
+
MockPollCfg::from_resps(t, [ResponseType::AllHistory])
|
|
2625
|
+
} else {
|
|
2626
|
+
MockPollCfg::from_hist_builder(t)
|
|
2627
|
+
};
|
|
2628
|
+
|
|
2629
|
+
let mut aai = ActivationAssertionsInterceptor::default();
|
|
2630
|
+
aai.skip_one()
|
|
2631
|
+
.then(|a| {
|
|
2632
|
+
assert_matches!(
|
|
2633
|
+
a.jobs.as_slice(),
|
|
2634
|
+
[WorkflowActivationJob {
|
|
2635
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(ra))
|
|
2636
|
+
}] => assert_eq!(ra.seq, 1)
|
|
2637
|
+
);
|
|
2638
|
+
})
|
|
2639
|
+
.then(|a| {
|
|
2640
|
+
assert_matches!(
|
|
2641
|
+
a.jobs.as_slice(),
|
|
2642
|
+
[WorkflowActivationJob {
|
|
2643
|
+
variant: Some(workflow_activation_job::Variant::FireTimer(_))
|
|
2644
|
+
}]
|
|
2645
|
+
);
|
|
2646
|
+
});
|
|
2647
|
+
|
|
2648
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2649
|
+
if replay {
|
|
2650
|
+
asserts.then(|wft| {
|
|
2651
|
+
assert_eq!(wft.commands.len(), 1);
|
|
2652
|
+
assert_eq!(
|
|
2653
|
+
wft.commands[0].command_type,
|
|
2654
|
+
CommandType::CompleteWorkflowExecution as i32
|
|
2655
|
+
);
|
|
2656
|
+
});
|
|
2657
|
+
} else {
|
|
2658
|
+
asserts
|
|
2659
|
+
.then(|wft| {
|
|
2660
|
+
let commands = &wft.commands;
|
|
2661
|
+
assert_eq!(commands.len(), 2);
|
|
2662
|
+
assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
|
|
2663
|
+
assert_eq!(commands[1].command_type, CommandType::StartTimer as i32);
|
|
2664
|
+
})
|
|
2665
|
+
.then(|wft| {
|
|
2666
|
+
let commands = &wft.commands;
|
|
2667
|
+
assert_eq!(commands.len(), 2);
|
|
2668
|
+
assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
|
|
2669
|
+
assert_eq!(
|
|
2670
|
+
commands[1].command_type,
|
|
2671
|
+
CommandType::CompleteWorkflowExecution as i32
|
|
2672
|
+
);
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
});
|
|
2676
|
+
|
|
2677
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2678
|
+
worker.set_worker_interceptor(aai);
|
|
2679
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_timer_la);
|
|
2680
|
+
worker.register_activity(
|
|
2681
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
2682
|
+
move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
|
|
2683
|
+
);
|
|
2684
|
+
worker.run().await.unwrap();
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
#[tokio::test]
|
|
2688
|
+
async fn one_la_heartbeating_wft_failure_still_executes() {
|
|
2689
|
+
let mut t = TestHistoryBuilder::default();
|
|
2690
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2691
|
+
// Heartbeats
|
|
2692
|
+
t.add_full_wf_task();
|
|
2693
|
+
// fails a wft for some reason
|
|
2694
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2695
|
+
t.add_workflow_task_failed_with_failure(
|
|
2696
|
+
WorkflowTaskFailedCause::NonDeterministicError,
|
|
2697
|
+
Default::default(),
|
|
2698
|
+
);
|
|
2699
|
+
t.add_workflow_task_scheduled_and_started();
|
|
2700
|
+
|
|
2701
|
+
let mut mock_cfg = MockPollCfg::from_hist_builder(t);
|
|
2702
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2703
|
+
asserts.then(move |wft| {
|
|
2704
|
+
assert_eq!(wft.commands.len(), 2);
|
|
2705
|
+
assert_eq!(wft.commands[0].command_type(), CommandType::RecordMarker);
|
|
2706
|
+
assert_matches!(
|
|
2707
|
+
wft.commands[1].command_type(),
|
|
2708
|
+
CommandType::CompleteWorkflowExecution
|
|
2709
|
+
);
|
|
2710
|
+
});
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2714
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, la_wf);
|
|
2715
|
+
worker.register_activity(
|
|
2716
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
2717
|
+
move |_ctx: ActContext, _: ()| async move { Ok("Resolved") },
|
|
2718
|
+
);
|
|
2719
|
+
worker.run().await.unwrap();
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
#[rstest]
|
|
2723
|
+
#[tokio::test]
|
|
2724
|
+
async fn immediate_cancel(
|
|
2725
|
+
#[values(
|
|
2726
|
+
ActivityCancellationType::WaitCancellationCompleted,
|
|
2727
|
+
ActivityCancellationType::TryCancel,
|
|
2728
|
+
ActivityCancellationType::Abandon
|
|
2729
|
+
)]
|
|
2730
|
+
cancel_type: ActivityCancellationType,
|
|
2731
|
+
) {
|
|
2732
|
+
let mut t = TestHistoryBuilder::default();
|
|
2733
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2734
|
+
t.add_full_wf_task();
|
|
2735
|
+
t.add_workflow_execution_completed();
|
|
2736
|
+
|
|
2737
|
+
let mut mock_cfg = MockPollCfg::from_hist_builder(t);
|
|
2738
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2739
|
+
asserts.then(|wft| {
|
|
2740
|
+
assert_eq!(wft.commands.len(), 2);
|
|
2741
|
+
// We record the cancel marker
|
|
2742
|
+
assert_eq!(wft.commands[0].command_type(), CommandType::RecordMarker);
|
|
2743
|
+
assert_matches!(
|
|
2744
|
+
wft.commands[1].command_type(),
|
|
2745
|
+
CommandType::CompleteWorkflowExecution
|
|
2746
|
+
);
|
|
2747
|
+
});
|
|
2748
|
+
});
|
|
2749
|
+
|
|
2750
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2751
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
|
|
2752
|
+
let la = ctx.local_activity(LocalActivityOptions {
|
|
2753
|
+
cancel_type,
|
|
2754
|
+
..Default::default()
|
|
2755
|
+
});
|
|
2756
|
+
la.cancel(&ctx);
|
|
2757
|
+
la.await;
|
|
2758
|
+
Ok(().into())
|
|
2759
|
+
});
|
|
2760
|
+
// Explicitly don't register an activity, since we shouldn't need to run one.
|
|
2761
|
+
worker.run().await.unwrap();
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
#[rstest]
|
|
2765
|
+
#[case::incremental(false)]
|
|
2766
|
+
#[case::replay(true)]
|
|
2767
|
+
#[tokio::test]
|
|
2768
|
+
async fn cancel_after_act_starts_canned(
|
|
2769
|
+
#[case] replay: bool,
|
|
2770
|
+
#[values(
|
|
2771
|
+
ActivityCancellationType::WaitCancellationCompleted,
|
|
2772
|
+
ActivityCancellationType::TryCancel,
|
|
2773
|
+
ActivityCancellationType::Abandon
|
|
2774
|
+
)]
|
|
2775
|
+
cancel_type: ActivityCancellationType,
|
|
2776
|
+
) {
|
|
2777
|
+
let mut t = TestHistoryBuilder::default();
|
|
2778
|
+
t.add_wfe_started_with_wft_timeout(Duration::from_millis(100));
|
|
2779
|
+
t.add_full_wf_task();
|
|
2780
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
2781
|
+
t.add_timer_fired(timer_started_event_id, "1".to_string());
|
|
2782
|
+
t.add_full_wf_task();
|
|
2783
|
+
// This extra workflow task serves to prevent looking ahead and pre-resolving during
|
|
2784
|
+
// wait-cancel.
|
|
2785
|
+
// TODO: including this on non wait-cancel seems to cause double-send of
|
|
2786
|
+
// marker recorded cmd
|
|
2787
|
+
if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
|
|
2788
|
+
t.add_full_wf_task();
|
|
2789
|
+
}
|
|
2790
|
+
if cancel_type != ActivityCancellationType::WaitCancellationCompleted {
|
|
2791
|
+
// With non-wait cancels, the cancel is immediate
|
|
2792
|
+
t.add_local_activity_cancel_marker(1, "1");
|
|
2793
|
+
}
|
|
2794
|
+
let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
|
|
2795
|
+
if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
|
|
2796
|
+
// With wait cancels, the cancel marker is not recorded until activity reports.
|
|
2797
|
+
t.add_local_activity_cancel_marker(1, "1");
|
|
2798
|
+
}
|
|
2799
|
+
t.add_timer_fired(timer_started_event_id, "2".to_string());
|
|
2800
|
+
t.add_full_wf_task();
|
|
2801
|
+
t.add_workflow_execution_completed();
|
|
2802
|
+
|
|
2803
|
+
let mut mock_cfg = if replay {
|
|
2804
|
+
MockPollCfg::from_resps(t, [ResponseType::AllHistory])
|
|
2805
|
+
} else {
|
|
2806
|
+
MockPollCfg::from_hist_builder(t)
|
|
2807
|
+
};
|
|
2808
|
+
let allow_cancel_barr = CancellationToken::new();
|
|
2809
|
+
let allow_cancel_barr_clone = allow_cancel_barr.clone();
|
|
2810
|
+
|
|
2811
|
+
if !replay {
|
|
2812
|
+
mock_cfg.completion_asserts_from_expectations(|mut asserts| {
|
|
2813
|
+
asserts
|
|
2814
|
+
.then(move |wft| {
|
|
2815
|
+
assert_eq!(wft.commands.len(), 1);
|
|
2816
|
+
assert_eq!(wft.commands[0].command_type, CommandType::StartTimer as i32);
|
|
2817
|
+
})
|
|
2818
|
+
.then(move |wft| {
|
|
2819
|
+
let commands = &wft.commands;
|
|
2820
|
+
if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
|
|
2821
|
+
assert_eq!(commands.len(), 1);
|
|
2822
|
+
assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
|
|
2823
|
+
} else {
|
|
2824
|
+
// Try-cancel/abandon immediately recordsmarker (when not replaying)
|
|
2825
|
+
assert_eq!(commands.len(), 2);
|
|
2826
|
+
assert_eq!(commands[0].command_type, CommandType::RecordMarker as i32);
|
|
2827
|
+
assert_eq!(commands[1].command_type, CommandType::StartTimer as i32);
|
|
2828
|
+
}
|
|
2829
|
+
// Allow the wait-cancel to actually cancel
|
|
2830
|
+
allow_cancel_barr.cancel();
|
|
2831
|
+
})
|
|
2832
|
+
.then(move |wft| {
|
|
2833
|
+
let commands = &wft.commands;
|
|
2834
|
+
if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
|
|
2835
|
+
assert_eq!(commands[0].command_type, CommandType::StartTimer as i32);
|
|
2836
|
+
assert_eq!(commands[1].command_type, CommandType::RecordMarker as i32);
|
|
2837
|
+
} else {
|
|
2838
|
+
assert_eq!(
|
|
2839
|
+
commands[0].command_type,
|
|
2840
|
+
CommandType::CompleteWorkflowExecution as i32
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
let mut worker = build_fake_sdk(mock_cfg);
|
|
2848
|
+
worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
|
|
2849
|
+
let la = ctx.local_activity(LocalActivityOptions {
|
|
2850
|
+
cancel_type,
|
|
2851
|
+
input: ().as_json_payload().unwrap(),
|
|
2852
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
2853
|
+
..Default::default()
|
|
2854
|
+
});
|
|
2855
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
2856
|
+
la.cancel(&ctx);
|
|
2857
|
+
// This extra timer is here to ensure the presence of another WF task doesn't mess up
|
|
2858
|
+
// resolving the LA with cancel on replay
|
|
2859
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
2860
|
+
let resolution = la.await;
|
|
2861
|
+
assert!(resolution.cancelled());
|
|
2862
|
+
let rfail = resolution.unwrap_failure();
|
|
2863
|
+
assert_matches!(
|
|
2864
|
+
rfail.failure_info,
|
|
2865
|
+
Some(FailureInfo::ActivityFailureInfo(_))
|
|
2866
|
+
);
|
|
2867
|
+
assert_matches!(
|
|
2868
|
+
rfail.cause.unwrap().failure_info,
|
|
2869
|
+
Some(FailureInfo::CanceledFailureInfo(_))
|
|
2870
|
+
);
|
|
2871
|
+
Ok(().into())
|
|
2872
|
+
});
|
|
2873
|
+
worker.register_activity(DEFAULT_ACTIVITY_TYPE, move |ctx: ActContext, _: ()| {
|
|
2874
|
+
let allow_cancel_barr_clone = allow_cancel_barr_clone.clone();
|
|
2875
|
+
async move {
|
|
2876
|
+
if cancel_type == ActivityCancellationType::WaitCancellationCompleted {
|
|
2877
|
+
ctx.cancelled().await;
|
|
2878
|
+
}
|
|
2879
|
+
allow_cancel_barr_clone.cancelled().await;
|
|
2880
|
+
Result::<(), _>::Err(ActivityError::cancelled())
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
worker.run().await.unwrap();
|
|
2884
|
+
}
|