@temporalio/core-bridge 1.7.4 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +245 -247
- package/Cargo.toml +1 -1
- package/lib/errors.d.ts +9 -0
- package/lib/errors.js +13 -0
- package/lib/errors.js.map +1 -1
- package/lib/index.d.ts +19 -3
- package/lib/index.js.map +1 -1
- 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/.github/workflows/heavy.yml +1 -1
- package/sdk-core/.github/workflows/semgrep.yml +25 -0
- package/sdk-core/README.md +2 -0
- package/sdk-core/cargo-tokio-console.sh +5 -0
- package/sdk-core/client/src/lib.rs +6 -41
- package/sdk-core/client/src/raw.rs +9 -0
- package/sdk-core/client/src/retry.rs +0 -16
- package/sdk-core/core/Cargo.toml +9 -5
- package/sdk-core/core/src/abstractions.rs +7 -75
- package/sdk-core/core/src/core_tests/activity_tasks.rs +16 -8
- package/sdk-core/core/src/core_tests/local_activities.rs +97 -5
- package/sdk-core/core/src/core_tests/mod.rs +1 -1
- package/sdk-core/core/src/core_tests/workers.rs +16 -16
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +247 -28
- package/sdk-core/core/src/lib.rs +2 -3
- package/sdk-core/core/src/pollers/mod.rs +30 -3
- package/sdk-core/core/src/pollers/poll_buffer.rs +166 -77
- package/sdk-core/core/src/protosext/mod.rs +4 -8
- package/sdk-core/core/src/replay/mod.rs +1 -1
- package/sdk-core/core/src/telemetry/metrics.rs +9 -0
- package/sdk-core/core/src/telemetry/mod.rs +3 -0
- package/sdk-core/core/src/test_help/mod.rs +9 -16
- package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +6 -31
- package/sdk-core/core/src/worker/activities/local_activities.rs +214 -110
- package/sdk-core/core/src/worker/activities.rs +72 -47
- package/sdk-core/core/src/worker/client/mocks.rs +1 -1
- package/sdk-core/core/src/worker/client.rs +45 -32
- package/sdk-core/core/src/worker/mod.rs +170 -122
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +0 -4
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +9 -2
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +9 -2
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -3
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +74 -22
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +3 -2
- package/sdk-core/core/src/worker/workflow/managed_run.rs +16 -3
- package/sdk-core/core/src/worker/workflow/mod.rs +13 -22
- package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -0
- package/sdk-core/core/src/worker/workflow/wft_extraction.rs +4 -7
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +38 -8
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +1 -0
- package/sdk-core/core-api/src/worker.rs +43 -2
- package/sdk-core/protos/api_upstream/Makefile +1 -1
- package/sdk-core/protos/api_upstream/buf.yaml +1 -6
- package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +12 -0
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -0
- package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +1 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +9 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +19 -0
- package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +5 -0
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +36 -4
- package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +24 -7
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +4 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +76 -44
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +23 -1
- package/sdk-core/protos/google/rpc/status.proto +52 -0
- package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +16 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +6 -0
- package/sdk-core/sdk/src/lib.rs +31 -10
- package/sdk-core/sdk/src/workflow_future.rs +7 -5
- package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -0
- package/sdk-core/sdk-core-protos/src/history_info.rs +1 -0
- package/sdk-core/sdk-core-protos/src/lib.rs +82 -73
- package/sdk-core/test-utils/Cargo.toml +1 -1
- package/sdk-core/test-utils/src/lib.rs +50 -37
- package/sdk-core/tests/integ_tests/metrics_tests.rs +143 -10
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +26 -15
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +5 -1
- package/sdk-core/tests/integ_tests/workflow_tests.rs +1 -0
- package/src/conversions.rs +9 -2
- package/src/runtime.rs +5 -7
- package/ts/errors.ts +15 -0
- package/ts/index.ts +22 -4
|
@@ -21,7 +21,8 @@ use std::{
|
|
|
21
21
|
};
|
|
22
22
|
use temporal_client::WorkflowOptions;
|
|
23
23
|
use temporal_sdk::{
|
|
24
|
-
ActContext, ActivityCancelledError, LocalActivityOptions, WfContext,
|
|
24
|
+
ActContext, ActivityCancelledError, LocalActivityOptions, WfContext, WorkflowFunction,
|
|
25
|
+
WorkflowResult,
|
|
25
26
|
};
|
|
26
27
|
use temporal_sdk_core_api::{
|
|
27
28
|
errors::{PollActivityError, PollWfError},
|
|
@@ -48,7 +49,7 @@ use temporal_sdk_core_protos::{
|
|
|
48
49
|
use temporal_sdk_core_test_utils::{
|
|
49
50
|
schedule_local_activity_cmd, start_timer_cmd, WorkerTestHelpers,
|
|
50
51
|
};
|
|
51
|
-
use tokio::{join, sync::Barrier};
|
|
52
|
+
use tokio::{join, select, sync::Barrier};
|
|
52
53
|
|
|
53
54
|
async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
|
|
54
55
|
Ok(e)
|
|
@@ -883,6 +884,96 @@ async fn test_schedule_to_start_timeout_not_based_on_original_time(
|
|
|
883
884
|
worker.run_until_done().await.unwrap();
|
|
884
885
|
}
|
|
885
886
|
|
|
887
|
+
#[rstest::rstest]
|
|
888
|
+
#[tokio::test]
|
|
889
|
+
async fn start_to_close_timeout_allows_retries(#[values(true, false)] la_completes: bool) {
|
|
890
|
+
let mut t = TestHistoryBuilder::default();
|
|
891
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
892
|
+
t.add_full_wf_task();
|
|
893
|
+
if la_completes {
|
|
894
|
+
t.add_local_activity_marker(1, "1", Some("hi".into()), None, |_| {});
|
|
895
|
+
} else {
|
|
896
|
+
t.add_local_activity_marker(
|
|
897
|
+
1,
|
|
898
|
+
"1",
|
|
899
|
+
None,
|
|
900
|
+
Some(Failure::application_failure("la failed".to_string(), false)),
|
|
901
|
+
|_| {},
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
t.add_full_wf_task();
|
|
905
|
+
t.add_workflow_execution_completed();
|
|
906
|
+
|
|
907
|
+
let wf_id = "fakeid";
|
|
908
|
+
let mock = mock_workflow_client();
|
|
909
|
+
let mh = MockPollCfg::from_resp_batches(
|
|
910
|
+
wf_id,
|
|
911
|
+
t,
|
|
912
|
+
[ResponseType::ToTaskNum(1), ResponseType::AllHistory],
|
|
913
|
+
mock,
|
|
914
|
+
);
|
|
915
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
916
|
+
|
|
917
|
+
worker.register_wf(
|
|
918
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
919
|
+
move |ctx: WfContext| async move {
|
|
920
|
+
let la_res = ctx
|
|
921
|
+
.local_activity(LocalActivityOptions {
|
|
922
|
+
activity_type: DEFAULT_ACTIVITY_TYPE.to_string(),
|
|
923
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
924
|
+
retry_policy: RetryPolicy {
|
|
925
|
+
initial_interval: Some(prost_dur!(from_millis(20))),
|
|
926
|
+
backoff_coefficient: 1.0,
|
|
927
|
+
maximum_interval: None,
|
|
928
|
+
maximum_attempts: 5,
|
|
929
|
+
non_retryable_error_types: vec![],
|
|
930
|
+
},
|
|
931
|
+
start_to_close_timeout: Some(prost_dur!(from_millis(25))),
|
|
932
|
+
..Default::default()
|
|
933
|
+
})
|
|
934
|
+
.await;
|
|
935
|
+
if la_completes {
|
|
936
|
+
assert!(la_res.completed_ok());
|
|
937
|
+
} else {
|
|
938
|
+
assert_eq!(la_res.timed_out(), Some(TimeoutType::StartToClose));
|
|
939
|
+
}
|
|
940
|
+
Ok(().into())
|
|
941
|
+
},
|
|
942
|
+
);
|
|
943
|
+
let attempts: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
944
|
+
let cancels: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
|
|
945
|
+
worker.register_activity(
|
|
946
|
+
DEFAULT_ACTIVITY_TYPE,
|
|
947
|
+
move |ctx: ActContext, _: String| async move {
|
|
948
|
+
// Timeout the first 4 attempts, or all of them if we intend to fail
|
|
949
|
+
if attempts.fetch_add(1, Ordering::AcqRel) < 4 || !la_completes {
|
|
950
|
+
select! {
|
|
951
|
+
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
|
|
952
|
+
_ = ctx.cancelled() => {
|
|
953
|
+
cancels.fetch_add(1, Ordering::AcqRel);
|
|
954
|
+
return Err(anyhow!(ActivityCancelledError::default()));
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
Ok(())
|
|
959
|
+
},
|
|
960
|
+
);
|
|
961
|
+
worker
|
|
962
|
+
.submit_wf(
|
|
963
|
+
wf_id.to_owned(),
|
|
964
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
965
|
+
vec![],
|
|
966
|
+
WorkflowOptions::default(),
|
|
967
|
+
)
|
|
968
|
+
.await
|
|
969
|
+
.unwrap();
|
|
970
|
+
worker.run_until_done().await.unwrap();
|
|
971
|
+
// Activity should have been attempted all 5 times
|
|
972
|
+
assert_eq!(attempts.load(Ordering::Acquire), 5);
|
|
973
|
+
let num_cancels = if la_completes { 4 } else { 5 };
|
|
974
|
+
assert_eq!(cancels.load(Ordering::Acquire), num_cancels);
|
|
975
|
+
}
|
|
976
|
+
|
|
886
977
|
#[tokio::test]
|
|
887
978
|
async fn wft_failure_cancels_running_las() {
|
|
888
979
|
let mut t = TestHistoryBuilder::default();
|
|
@@ -964,17 +1055,18 @@ async fn resolved_las_not_recorded_if_wft_fails_many_times() {
|
|
|
964
1055
|
mh.num_expected_completions = Some(0.into());
|
|
965
1056
|
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
966
1057
|
|
|
1058
|
+
#[allow(unreachable_code)]
|
|
967
1059
|
worker.register_wf(
|
|
968
1060
|
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
969
|
-
|ctx: WfContext| async move {
|
|
1061
|
+
WorkflowFunction::new::<_, _, ()>(|ctx: WfContext| async move {
|
|
970
1062
|
ctx.local_activity(LocalActivityOptions {
|
|
971
1063
|
activity_type: "echo".to_string(),
|
|
972
1064
|
input: "hi".as_json_payload().expect("serializes fine"),
|
|
973
1065
|
..Default::default()
|
|
974
1066
|
})
|
|
975
1067
|
.await;
|
|
976
|
-
panic!(
|
|
977
|
-
},
|
|
1068
|
+
panic!()
|
|
1069
|
+
}),
|
|
978
1070
|
);
|
|
979
1071
|
worker.register_activity(
|
|
980
1072
|
"echo",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
prost_dur,
|
|
3
3
|
test_help::{
|
|
4
|
-
build_fake_worker, build_mock_pollers, canned_histories,
|
|
5
|
-
|
|
4
|
+
build_fake_worker, build_mock_pollers, canned_histories, mock_worker, MockPollCfg,
|
|
5
|
+
MockWorkerInputs, MocksHolder, ResponseType, WorkerExt,
|
|
6
6
|
},
|
|
7
7
|
worker::client::mocks::mock_workflow_client,
|
|
8
8
|
PollActivityError, PollWfError,
|
|
9
9
|
};
|
|
10
|
-
use
|
|
10
|
+
use futures_util::{stream, stream::StreamExt};
|
|
11
11
|
use std::{cell::RefCell, time::Duration};
|
|
12
12
|
use temporal_sdk_core_api::Worker;
|
|
13
13
|
use temporal_sdk_core_protos::{
|
|
@@ -16,7 +16,9 @@ use temporal_sdk_core_protos::{
|
|
|
16
16
|
workflow_commands::{workflow_command, CompleteWorkflowExecution, StartTimer},
|
|
17
17
|
workflow_completion::WorkflowActivationCompletion,
|
|
18
18
|
},
|
|
19
|
-
temporal::api::workflowservice::v1::
|
|
19
|
+
temporal::api::workflowservice::v1::{
|
|
20
|
+
PollWorkflowTaskQueueResponse, RespondWorkflowTaskCompletedResponse,
|
|
21
|
+
},
|
|
20
22
|
};
|
|
21
23
|
use temporal_sdk_core_test_utils::start_timer_cmd;
|
|
22
24
|
use tokio::sync::{watch, Barrier};
|
|
@@ -87,20 +89,18 @@ async fn shutdown_worker_can_complete_pending_activation() {
|
|
|
87
89
|
#[tokio::test]
|
|
88
90
|
async fn worker_shutdown_during_poll_doesnt_deadlock() {
|
|
89
91
|
let (tx, rx) = watch::channel(false);
|
|
90
|
-
let mut mock_poller = mock_manual_poller();
|
|
91
92
|
let rx = rx.clone();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.boxed()
|
|
93
|
+
let stream = stream::unfold(rx, |mut rx| async move {
|
|
94
|
+
// Don't resolve polls until worker shuts down
|
|
95
|
+
rx.changed().await.unwrap();
|
|
96
|
+
// We don't want to return a real response here because it would get buffered and
|
|
97
|
+
// then we'd have real work to do to be able to finish shutdown.
|
|
98
|
+
Some((
|
|
99
|
+
Ok(PollWorkflowTaskQueueResponse::default().try_into().unwrap()),
|
|
100
|
+
rx,
|
|
101
|
+
))
|
|
102
102
|
});
|
|
103
|
-
let mw = MockWorkerInputs::
|
|
103
|
+
let mw = MockWorkerInputs::new(stream.boxed());
|
|
104
104
|
let mut mock_client = mock_workflow_client();
|
|
105
105
|
mock_client
|
|
106
106
|
.expect_complete_workflow_task()
|
|
@@ -7,7 +7,7 @@ use crate::{
|
|
|
7
7
|
build_fake_worker, build_mock_pollers, build_multihist_mock_sg, canned_histories,
|
|
8
8
|
gen_assert_and_fail, gen_assert_and_reply, hist_to_poll_resp, mock_sdk, mock_sdk_cfg,
|
|
9
9
|
mock_worker, poll_and_reply, poll_and_reply_clears_outstanding_evicts, single_hist_mock_sg,
|
|
10
|
-
test_worker_cfg, FakeWfResponses, MockPollCfg, MocksHolder, ResponseType,
|
|
10
|
+
test_worker_cfg, FakeWfResponses, MockPollCfg, MocksHolder, ResponseType, WorkerExt,
|
|
11
11
|
WorkflowCachingPolicy::{self, AfterEveryReply, NonSticky},
|
|
12
12
|
},
|
|
13
13
|
worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
|
|
@@ -18,7 +18,8 @@ use rstest::{fixture, rstest};
|
|
|
18
18
|
use std::{
|
|
19
19
|
collections::{HashMap, HashSet, VecDeque},
|
|
20
20
|
sync::{
|
|
21
|
-
atomic::{AtomicU64, Ordering},
|
|
21
|
+
atomic::{AtomicU64, AtomicUsize, Ordering},
|
|
22
|
+
mpsc::sync_channel,
|
|
22
23
|
Arc,
|
|
23
24
|
},
|
|
24
25
|
time::Duration,
|
|
@@ -29,6 +30,7 @@ use temporal_sdk_core_api::{errors::PollWfError, Worker as WorkerTrait};
|
|
|
29
30
|
use temporal_sdk_core_protos::{
|
|
30
31
|
coresdk::{
|
|
31
32
|
activity_result::{self as ar, activity_resolution, ActivityResolution},
|
|
33
|
+
common::VersioningIntent,
|
|
32
34
|
workflow_activation::{
|
|
33
35
|
remove_from_cache::EvictionReason, workflow_activation_job, FireTimer, ResolveActivity,
|
|
34
36
|
StartWorkflow, UpdateRandomSeed, WorkflowActivationJob,
|
|
@@ -36,7 +38,7 @@ use temporal_sdk_core_protos::{
|
|
|
36
38
|
workflow_commands::{
|
|
37
39
|
ActivityCancellationType, CancelTimer, CompleteWorkflowExecution,
|
|
38
40
|
ContinueAsNewWorkflowExecution, FailWorkflowExecution, RequestCancelActivity,
|
|
39
|
-
ScheduleActivity, SetPatchMarker,
|
|
41
|
+
ScheduleActivity, SetPatchMarker, StartChildWorkflowExecution,
|
|
40
42
|
},
|
|
41
43
|
workflow_completion::WorkflowActivationCompletion,
|
|
42
44
|
},
|
|
@@ -60,6 +62,7 @@ use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd, WorkerTestHelp
|
|
|
60
62
|
use tokio::{
|
|
61
63
|
join,
|
|
62
64
|
sync::{Barrier, Semaphore},
|
|
65
|
+
time,
|
|
63
66
|
};
|
|
64
67
|
|
|
65
68
|
#[fixture(hist_batches = &[])]
|
|
@@ -1535,8 +1538,6 @@ async fn failing_wft_doesnt_eat_permit_forever() {
|
|
|
1535
1538
|
.unwrap();
|
|
1536
1539
|
}
|
|
1537
1540
|
assert_eq!(worker.outstanding_workflow_tasks().await, 0);
|
|
1538
|
-
// 1 permit is in use because the next task is buffered and has re-used the permit
|
|
1539
|
-
assert_eq!(worker.available_wft_permits().await, 1);
|
|
1540
1541
|
// We should be "out of work" because the mock service thinks we didn't complete the last task,
|
|
1541
1542
|
// which we didn't, because we don't spam failures. The real server would eventually time out
|
|
1542
1543
|
// the task. Mock doesn't understand that, so the WFT permit is released because eventually a
|
|
@@ -1545,7 +1546,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
|
|
|
1545
1546
|
outstanding_mock_tasks.unwrap().release_run(&run_id);
|
|
1546
1547
|
let activation = worker.poll_workflow_activation().await.unwrap();
|
|
1547
1548
|
// There should be no change in permits, since this just unbuffered the buffered task
|
|
1548
|
-
assert_eq!(worker.available_wft_permits()
|
|
1549
|
+
assert_eq!(worker.available_wft_permits(), 1);
|
|
1549
1550
|
worker
|
|
1550
1551
|
.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1551
1552
|
activation.run_id,
|
|
@@ -1554,7 +1555,7 @@ async fn failing_wft_doesnt_eat_permit_forever() {
|
|
|
1554
1555
|
.await
|
|
1555
1556
|
.unwrap();
|
|
1556
1557
|
worker.shutdown().await;
|
|
1557
|
-
assert_eq!(worker.available_wft_permits()
|
|
1558
|
+
assert_eq!(worker.available_wft_permits(), 2);
|
|
1558
1559
|
}
|
|
1559
1560
|
|
|
1560
1561
|
#[tokio::test]
|
|
@@ -1647,6 +1648,45 @@ async fn cache_miss_will_fetch_history() {
|
|
|
1647
1648
|
worker.shutdown().await;
|
|
1648
1649
|
}
|
|
1649
1650
|
|
|
1651
|
+
#[tokio::test]
|
|
1652
|
+
async fn history_byte_size_and_can_suggestion_in_activation() {
|
|
1653
|
+
let mut t = TestHistoryBuilder::default();
|
|
1654
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1655
|
+
t.add_full_wf_task();
|
|
1656
|
+
t.add_we_signaled("sig", vec![]);
|
|
1657
|
+
t.add_full_wf_task();
|
|
1658
|
+
t.add_workflow_execution_completed();
|
|
1659
|
+
t.modify_event(7, |he| {
|
|
1660
|
+
if let Some(history_event::Attributes::WorkflowTaskStartedEventAttributes(ref mut attrs)) =
|
|
1661
|
+
he.attributes
|
|
1662
|
+
{
|
|
1663
|
+
attrs.suggest_continue_as_new = true;
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
let mh = MockPollCfg::from_resp_batches(
|
|
1668
|
+
"fake_wf_id",
|
|
1669
|
+
t,
|
|
1670
|
+
[ResponseType::ToTaskNum(1), ResponseType::OneTask(2)],
|
|
1671
|
+
mock_workflow_client(),
|
|
1672
|
+
);
|
|
1673
|
+
let mut mock = build_mock_pollers(mh);
|
|
1674
|
+
mock.worker_cfg(|cfg| cfg.max_cached_workflows = 1);
|
|
1675
|
+
let worker = mock_worker(mock);
|
|
1676
|
+
|
|
1677
|
+
let activation = worker.poll_workflow_activation().await.unwrap();
|
|
1678
|
+
// Test builder always does num events * 10
|
|
1679
|
+
assert_eq!(activation.history_size_bytes, 30);
|
|
1680
|
+
assert!(!activation.continue_as_new_suggested);
|
|
1681
|
+
worker
|
|
1682
|
+
.complete_workflow_activation(WorkflowActivationCompletion::empty(activation.run_id))
|
|
1683
|
+
.await
|
|
1684
|
+
.unwrap();
|
|
1685
|
+
let activation = worker.poll_workflow_activation().await.unwrap();
|
|
1686
|
+
assert_eq!(activation.history_size_bytes, 70);
|
|
1687
|
+
assert!(activation.continue_as_new_suggested);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1650
1690
|
/// This test verifies that WFTs which come as replies to completing a WFT are properly delivered
|
|
1651
1691
|
/// via activation polling.
|
|
1652
1692
|
#[tokio::test]
|
|
@@ -1875,7 +1915,7 @@ async fn poll_faster_than_complete_wont_overflow_cache() {
|
|
|
1875
1915
|
|
|
1876
1916
|
join!(blocking_poll, complete_evict);
|
|
1877
1917
|
// p5 outstanding and final poll outstanding -- hence one permit available
|
|
1878
|
-
assert_eq!(core.available_wft_permits()
|
|
1918
|
+
assert_eq!(core.available_wft_permits(), 1);
|
|
1879
1919
|
assert_eq!(core.cached_workflows().await, 3);
|
|
1880
1920
|
}
|
|
1881
1921
|
|
|
@@ -2002,25 +2042,23 @@ async fn no_race_acquiring_permits() {
|
|
|
2002
2042
|
// We need to allow two polls to happen by triggering two processing events in the workflow
|
|
2003
2043
|
// stream, but then delivering the actual tasks after that
|
|
2004
2044
|
let task_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
|
|
2005
|
-
mock_client
|
|
2006
|
-
|
|
2007
|
-
.
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
.boxed()
|
|
2015
|
-
});
|
|
2045
|
+
mock_client.expect_poll_workflow_task().returning(move |_| {
|
|
2046
|
+
let t = canned_histories::single_timer("1");
|
|
2047
|
+
let poll_resp = hist_to_poll_resp(&t, wfid.to_owned(), 2.into()).resp;
|
|
2048
|
+
async move {
|
|
2049
|
+
task_barr.wait().await;
|
|
2050
|
+
Ok(poll_resp.clone())
|
|
2051
|
+
}
|
|
2052
|
+
.boxed()
|
|
2053
|
+
});
|
|
2016
2054
|
mock_client
|
|
2017
2055
|
.expect_complete_workflow_task()
|
|
2018
2056
|
.returning(|_| async move { Ok(Default::default()) }.boxed());
|
|
2019
2057
|
|
|
2020
2058
|
let worker = Worker::new_test(
|
|
2021
2059
|
test_worker_cfg()
|
|
2022
|
-
.max_outstanding_workflow_tasks(
|
|
2023
|
-
.max_cached_workflows(
|
|
2060
|
+
.max_outstanding_workflow_tasks(2_usize)
|
|
2061
|
+
.max_cached_workflows(0_usize)
|
|
2024
2062
|
.build()
|
|
2025
2063
|
.unwrap(),
|
|
2026
2064
|
mock_client,
|
|
@@ -2057,6 +2095,7 @@ async fn no_race_acquiring_permits() {
|
|
|
2057
2095
|
task_barr.wait().await;
|
|
2058
2096
|
};
|
|
2059
2097
|
join!(poll_1_f, poll_2_f, other_f);
|
|
2098
|
+
worker.drain_pollers_and_shutdown().await;
|
|
2060
2099
|
}
|
|
2061
2100
|
|
|
2062
2101
|
#[tokio::test]
|
|
@@ -2079,16 +2118,14 @@ async fn continue_as_new_preserves_some_values() {
|
|
|
2079
2118
|
t.add_full_wf_task();
|
|
2080
2119
|
t
|
|
2081
2120
|
};
|
|
2082
|
-
mock_client
|
|
2083
|
-
.
|
|
2084
|
-
|
|
2085
|
-
Ok(hist_to_poll_resp(&hist, wfid.to_owned(), ResponseType::AllHistory).resp)
|
|
2086
|
-
});
|
|
2121
|
+
mock_client.expect_poll_workflow_task().returning(move |_| {
|
|
2122
|
+
Ok(hist_to_poll_resp(&hist, wfid.to_owned(), ResponseType::AllHistory).resp)
|
|
2123
|
+
});
|
|
2087
2124
|
mock_client
|
|
2088
2125
|
.expect_complete_workflow_task()
|
|
2089
2126
|
.returning(move |mut c| {
|
|
2090
|
-
let
|
|
2091
|
-
if let Attributes::ContinueAsNewWorkflowExecutionCommandAttributes(a) =
|
|
2127
|
+
let cmd = c.commands.pop().unwrap().attributes.unwrap();
|
|
2128
|
+
if let Attributes::ContinueAsNewWorkflowExecutionCommandAttributes(a) = cmd {
|
|
2092
2129
|
assert_eq!(a.workflow_type.unwrap().name, "meow");
|
|
2093
2130
|
assert_eq!(a.memo, wes_attrs.memo);
|
|
2094
2131
|
assert_eq!(a.search_attributes, wes_attrs.search_attributes);
|
|
@@ -2588,3 +2625,185 @@ async fn history_length_with_fail_and_timeout(
|
|
|
2588
2625
|
.unwrap();
|
|
2589
2626
|
worker.run_until_done().await.unwrap();
|
|
2590
2627
|
}
|
|
2628
|
+
|
|
2629
|
+
#[tokio::test]
|
|
2630
|
+
async fn poller_wont_run_ahead_of_task_slots() {
|
|
2631
|
+
let popped_tasks = Arc::new(AtomicUsize::new(0));
|
|
2632
|
+
let ptc = popped_tasks.clone();
|
|
2633
|
+
let mut bunch_of_first_tasks = (1..50).map(move |i| {
|
|
2634
|
+
ptc.fetch_add(1, Ordering::Relaxed);
|
|
2635
|
+
hist_to_poll_resp(
|
|
2636
|
+
&canned_histories::single_timer(&format!("{i}")),
|
|
2637
|
+
format!("wf-{i}"),
|
|
2638
|
+
1.into(),
|
|
2639
|
+
)
|
|
2640
|
+
.resp
|
|
2641
|
+
});
|
|
2642
|
+
let mut mock_client = mock_workflow_client();
|
|
2643
|
+
mock_client
|
|
2644
|
+
.expect_poll_workflow_task()
|
|
2645
|
+
.returning(move |_| Ok(bunch_of_first_tasks.next().unwrap()));
|
|
2646
|
+
mock_client
|
|
2647
|
+
.expect_complete_workflow_task()
|
|
2648
|
+
.returning(|_| Ok(Default::default()));
|
|
2649
|
+
|
|
2650
|
+
let worker = Worker::new_test(
|
|
2651
|
+
test_worker_cfg()
|
|
2652
|
+
.max_cached_workflows(10_usize)
|
|
2653
|
+
.max_outstanding_workflow_tasks(10_usize)
|
|
2654
|
+
.max_concurrent_wft_polls(10_usize)
|
|
2655
|
+
.no_remote_activities(true)
|
|
2656
|
+
.build()
|
|
2657
|
+
.unwrap(),
|
|
2658
|
+
mock_client,
|
|
2659
|
+
);
|
|
2660
|
+
|
|
2661
|
+
// Should be able to get up to 10 tasks
|
|
2662
|
+
let mut tasks = vec![];
|
|
2663
|
+
for _ in 0..10 {
|
|
2664
|
+
tasks.push(worker.poll_workflow_activation().await.unwrap());
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
assert_eq!(worker.outstanding_workflow_tasks().await, 10);
|
|
2668
|
+
assert_eq!(worker.available_wft_permits(), 0);
|
|
2669
|
+
assert_eq!(worker.unused_wft_permits(), 0);
|
|
2670
|
+
|
|
2671
|
+
// This one should hang until we complete some tasks since we're at the limit
|
|
2672
|
+
let hung_poll = async {
|
|
2673
|
+
// This should end up getting shut down after the other routine finishes tasks
|
|
2674
|
+
assert_matches!(
|
|
2675
|
+
worker.poll_workflow_activation().await.unwrap_err(),
|
|
2676
|
+
PollWfError::ShutDown
|
|
2677
|
+
);
|
|
2678
|
+
};
|
|
2679
|
+
// Wait for a bit concurrently with above, verify no extra tasks got taken, shutdown
|
|
2680
|
+
let ender = async {
|
|
2681
|
+
time::sleep(Duration::from_millis(300)).await;
|
|
2682
|
+
// initiate shutdown, then complete open tasks
|
|
2683
|
+
worker.initiate_shutdown();
|
|
2684
|
+
for t in tasks {
|
|
2685
|
+
worker
|
|
2686
|
+
.complete_workflow_activation(WorkflowActivationCompletion::empty(t.run_id))
|
|
2687
|
+
.await
|
|
2688
|
+
.unwrap();
|
|
2689
|
+
}
|
|
2690
|
+
worker.shutdown().await;
|
|
2691
|
+
};
|
|
2692
|
+
join!(hung_poll, ender);
|
|
2693
|
+
// We shouldn't have got more than the 10 tasks from the poller -- verifying that the concurrent
|
|
2694
|
+
// polling is not exceeding the task limit
|
|
2695
|
+
assert_eq!(popped_tasks.load(Ordering::Relaxed), 10);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
#[tokio::test]
|
|
2699
|
+
async fn poller_wont_poll_until_lang_polls() {
|
|
2700
|
+
let mut mock_client = mock_workflow_client();
|
|
2701
|
+
let (tx, rx) = sync_channel(101);
|
|
2702
|
+
// Normally you'd just not set any expectations, but the problem is since we never poll
|
|
2703
|
+
// the WFT stream, we'll never join the tasks running the pollers and thus the error
|
|
2704
|
+
// gets printed but doesn't bubble up to the test. So we set this explicit expectation
|
|
2705
|
+
// in here to ensure it isn't called.
|
|
2706
|
+
mock_client.expect_poll_workflow_task().returning(move |_| {
|
|
2707
|
+
let _ = tx.send(());
|
|
2708
|
+
Ok(Default::default())
|
|
2709
|
+
});
|
|
2710
|
+
|
|
2711
|
+
let worker = Worker::new_test(
|
|
2712
|
+
test_worker_cfg()
|
|
2713
|
+
.no_remote_activities(true)
|
|
2714
|
+
.build()
|
|
2715
|
+
.unwrap(),
|
|
2716
|
+
mock_client,
|
|
2717
|
+
);
|
|
2718
|
+
|
|
2719
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
2720
|
+
|
|
2721
|
+
worker.drain_pollers_and_shutdown().await;
|
|
2722
|
+
// Nothing should've appeared here or we did poll
|
|
2723
|
+
assert!(rx.recv().is_err());
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
#[rstest]
|
|
2727
|
+
#[tokio::test]
|
|
2728
|
+
async fn use_compatible_version_flag(
|
|
2729
|
+
#[values(
|
|
2730
|
+
VersioningIntent::Unspecified,
|
|
2731
|
+
VersioningIntent::Compatible,
|
|
2732
|
+
VersioningIntent::Default
|
|
2733
|
+
)]
|
|
2734
|
+
intent: VersioningIntent,
|
|
2735
|
+
#[values(true, false)] different_tq: bool,
|
|
2736
|
+
#[values("activity", "child_wf", "continue_as_new")] command_type: &'static str,
|
|
2737
|
+
) {
|
|
2738
|
+
let wfid = "fake_wf_id";
|
|
2739
|
+
let mut mock_client = mock_workflow_client();
|
|
2740
|
+
let hist = {
|
|
2741
|
+
let mut t = TestHistoryBuilder::default();
|
|
2742
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
2743
|
+
t.add_full_wf_task();
|
|
2744
|
+
t
|
|
2745
|
+
};
|
|
2746
|
+
mock_client.expect_poll_workflow_task().returning(move |_| {
|
|
2747
|
+
Ok(hist_to_poll_resp(&hist, wfid.to_owned(), ResponseType::AllHistory).resp)
|
|
2748
|
+
});
|
|
2749
|
+
let compat_flag_expected = match intent {
|
|
2750
|
+
VersioningIntent::Unspecified => !different_tq,
|
|
2751
|
+
VersioningIntent::Compatible => true,
|
|
2752
|
+
VersioningIntent::Default => false,
|
|
2753
|
+
};
|
|
2754
|
+
mock_client
|
|
2755
|
+
.expect_complete_workflow_task()
|
|
2756
|
+
.returning(move |mut c| {
|
|
2757
|
+
let can_cmd = c.commands.pop().unwrap().attributes.unwrap();
|
|
2758
|
+
match can_cmd {
|
|
2759
|
+
Attributes::ContinueAsNewWorkflowExecutionCommandAttributes(a) => {
|
|
2760
|
+
assert_eq!(a.use_compatible_version, compat_flag_expected);
|
|
2761
|
+
}
|
|
2762
|
+
Attributes::ScheduleActivityTaskCommandAttributes(a) => {
|
|
2763
|
+
assert_eq!(a.use_compatible_version, compat_flag_expected);
|
|
2764
|
+
}
|
|
2765
|
+
Attributes::StartChildWorkflowExecutionCommandAttributes(a) => {
|
|
2766
|
+
assert_eq!(a.use_compatible_version, compat_flag_expected);
|
|
2767
|
+
}
|
|
2768
|
+
_ => panic!("invalid attributes type"),
|
|
2769
|
+
}
|
|
2770
|
+
Ok(Default::default())
|
|
2771
|
+
});
|
|
2772
|
+
|
|
2773
|
+
let worker = Worker::new_test(test_worker_cfg().build().unwrap(), mock_client);
|
|
2774
|
+
let r = worker.poll_workflow_activation().await.unwrap();
|
|
2775
|
+
let task_queue = if different_tq {
|
|
2776
|
+
"enchi cat!".to_string()
|
|
2777
|
+
} else {
|
|
2778
|
+
"".to_string()
|
|
2779
|
+
};
|
|
2780
|
+
let cmd = match command_type {
|
|
2781
|
+
"continue_as_new" => ContinueAsNewWorkflowExecution {
|
|
2782
|
+
workflow_type: "meow".to_string(),
|
|
2783
|
+
versioning_intent: intent as i32,
|
|
2784
|
+
task_queue,
|
|
2785
|
+
..Default::default()
|
|
2786
|
+
}
|
|
2787
|
+
.into(),
|
|
2788
|
+
"activity" => ScheduleActivity {
|
|
2789
|
+
seq: 1,
|
|
2790
|
+
activity_id: "1".to_string(),
|
|
2791
|
+
versioning_intent: intent as i32,
|
|
2792
|
+
task_queue,
|
|
2793
|
+
..default_act_sched()
|
|
2794
|
+
}
|
|
2795
|
+
.into(),
|
|
2796
|
+
"child_wf" => StartChildWorkflowExecution {
|
|
2797
|
+
seq: 1,
|
|
2798
|
+
versioning_intent: intent as i32,
|
|
2799
|
+
task_queue,
|
|
2800
|
+
..Default::default()
|
|
2801
|
+
}
|
|
2802
|
+
.into(),
|
|
2803
|
+
_ => panic!("invalid command type"),
|
|
2804
|
+
};
|
|
2805
|
+
worker
|
|
2806
|
+
.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(r.run_id, cmd))
|
|
2807
|
+
.await
|
|
2808
|
+
.unwrap();
|
|
2809
|
+
}
|
package/sdk-core/core/src/lib.rs
CHANGED
|
@@ -12,6 +12,7 @@ extern crate tracing;
|
|
|
12
12
|
extern crate core;
|
|
13
13
|
|
|
14
14
|
mod abstractions;
|
|
15
|
+
#[cfg(feature = "ephemeral-server")]
|
|
15
16
|
pub mod ephemeral_server;
|
|
16
17
|
mod internal_flags;
|
|
17
18
|
mod pollers;
|
|
@@ -82,7 +83,6 @@ where
|
|
|
82
83
|
let client = {
|
|
83
84
|
let ll = client.into().into_inner();
|
|
84
85
|
let mut client = Client::new(*ll, worker_config.namespace.clone());
|
|
85
|
-
client.set_worker_build_id(worker_config.worker_build_id.clone());
|
|
86
86
|
if let Some(ref id_override) = worker_config.client_identity_override {
|
|
87
87
|
client.options_mut().identity = id_override.clone();
|
|
88
88
|
}
|
|
@@ -147,9 +147,8 @@ pub(crate) fn sticky_q_name_for_worker(
|
|
|
147
147
|
) -> Option<String> {
|
|
148
148
|
if config.max_cached_workflows > 0 {
|
|
149
149
|
Some(format!(
|
|
150
|
-
"{}-{}
|
|
150
|
+
"{}-{}",
|
|
151
151
|
&process_identity,
|
|
152
|
-
&config.task_queue,
|
|
153
152
|
uuid::Uuid::new_v4().simple()
|
|
154
153
|
))
|
|
155
154
|
} else {
|
|
@@ -7,12 +7,16 @@ pub use temporal_client::{
|
|
|
7
7
|
Client, ClientOptions, ClientOptionsBuilder, ClientTlsConfig, RetryClient, RetryConfig,
|
|
8
8
|
TlsConfig, WorkflowClientTrait,
|
|
9
9
|
};
|
|
10
|
+
|
|
11
|
+
use crate::abstractions::OwnedMeteredSemPermit;
|
|
10
12
|
use temporal_sdk_core_protos::temporal::api::workflowservice::v1::{
|
|
11
13
|
PollActivityTaskQueueResponse, PollWorkflowTaskQueueResponse,
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
#[cfg(test)]
|
|
15
17
|
use futures::Future;
|
|
18
|
+
#[cfg(test)]
|
|
19
|
+
pub(crate) use poll_buffer::MockPermittedPollBuffer;
|
|
16
20
|
|
|
17
21
|
pub type Result<T, E = tonic::Status> = std::result::Result<T, E>;
|
|
18
22
|
|
|
@@ -31,9 +35,32 @@ where
|
|
|
31
35
|
/// Need a separate shutdown to be able to consume boxes :(
|
|
32
36
|
async fn shutdown_box(self: Box<Self>);
|
|
33
37
|
}
|
|
34
|
-
pub type BoxedPoller<T> = Box<dyn Poller<T> + Send + Sync + 'static>;
|
|
35
|
-
pub type BoxedWFPoller = BoxedPoller<PollWorkflowTaskQueueResponse>;
|
|
36
|
-
pub type BoxedActPoller =
|
|
38
|
+
pub(crate) type BoxedPoller<T> = Box<dyn Poller<T> + Send + Sync + 'static>;
|
|
39
|
+
pub(crate) type BoxedWFPoller = BoxedPoller<(PollWorkflowTaskQueueResponse, OwnedMeteredSemPermit)>;
|
|
40
|
+
pub(crate) type BoxedActPoller =
|
|
41
|
+
BoxedPoller<(PollActivityTaskQueueResponse, OwnedMeteredSemPermit)>;
|
|
42
|
+
|
|
43
|
+
#[async_trait::async_trait]
|
|
44
|
+
impl<T> Poller<T> for Box<dyn Poller<T> + Send + Sync>
|
|
45
|
+
where
|
|
46
|
+
T: Send + Sync + 'static,
|
|
47
|
+
{
|
|
48
|
+
async fn poll(&self) -> Option<Result<T>> {
|
|
49
|
+
Poller::poll(self.as_ref()).await
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn notify_shutdown(&self) {
|
|
53
|
+
Poller::notify_shutdown(self.as_ref())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async fn shutdown(self) {
|
|
57
|
+
Poller::shutdown(self).await
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fn shutdown_box(self: Box<Self>) {
|
|
61
|
+
Poller::shutdown_box(self).await
|
|
62
|
+
}
|
|
63
|
+
}
|
|
37
64
|
|
|
38
65
|
#[cfg(test)]
|
|
39
66
|
mockall::mock! {
|