@temporalio/core-bridge 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/client/src/lib.rs +52 -2
- package/sdk-core/client/src/retry.rs +35 -12
- package/sdk-core/core/src/core_tests/activity_tasks.rs +70 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +179 -9
- package/sdk-core/core/src/core_tests/workers.rs +33 -1
- package/sdk-core/core/src/lib.rs +13 -4
- package/sdk-core/core/src/protosext/mod.rs +44 -7
- package/sdk-core/core/src/test_help/mod.rs +19 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +2 -2
- package/sdk-core/core/src/worker/client.rs +9 -1
- package/sdk-core/core/src/worker/mod.rs +11 -4
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +5 -6
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +1 -1
- package/sdk-core/core/src/worker/workflow/managed_run.rs +27 -8
- package/sdk-core/core/src/worker/workflow/mod.rs +44 -17
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +14 -12
- package/sdk-core/sdk/src/lib.rs +36 -9
- package/sdk-core/sdk-core-protos/src/history_builder.rs +1 -1
- package/sdk-core/test-utils/src/lib.rs +22 -3
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/core-bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Temporal.io SDK Core<>Node bridge",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@opentelemetry/api": "^1.1.0",
|
|
23
|
-
"@temporalio/internal-non-workflow-common": "^1.0.
|
|
23
|
+
"@temporalio/internal-non-workflow-common": "^1.0.1",
|
|
24
24
|
"arg": "^5.0.2",
|
|
25
25
|
"cargo-cp-artifact": "^0.1.6",
|
|
26
26
|
"which": "^2.0.2"
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "a1dae539e72b6b088b400998d7bef482e8ed52f1"
|
|
47
47
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -12,7 +12,7 @@ mod raw;
|
|
|
12
12
|
mod retry;
|
|
13
13
|
mod workflow_handle;
|
|
14
14
|
|
|
15
|
-
pub use crate::retry::{CallType, RetryClient};
|
|
15
|
+
pub use crate::retry::{CallType, RetryClient, RETRYABLE_ERROR_CODES};
|
|
16
16
|
pub use raw::WorkflowService;
|
|
17
17
|
pub use workflow_handle::{WorkflowExecutionInfo, WorkflowExecutionResult};
|
|
18
18
|
|
|
@@ -609,6 +609,20 @@ pub trait WorkflowClientTrait {
|
|
|
609
609
|
payloads: Option<Payloads>,
|
|
610
610
|
) -> Result<SignalWorkflowExecutionResponse>;
|
|
611
611
|
|
|
612
|
+
/// Send signal and start workflow transcationally
|
|
613
|
+
//#TODO maybe lift the Signal type from sdk::workflow_context::options
|
|
614
|
+
#[allow(clippy::too_many_arguments)]
|
|
615
|
+
async fn signal_with_start_workflow_execution(
|
|
616
|
+
&self,
|
|
617
|
+
input: Option<Payloads>,
|
|
618
|
+
task_queue: String,
|
|
619
|
+
workflow_id: String,
|
|
620
|
+
workflow_type: String,
|
|
621
|
+
options: WorkflowOptions,
|
|
622
|
+
signal_name: String,
|
|
623
|
+
signal_input: Option<Payloads>,
|
|
624
|
+
) -> Result<SignalWithStartWorkflowExecutionResponse>;
|
|
625
|
+
|
|
612
626
|
/// Request a query of a certain workflow instance
|
|
613
627
|
async fn query_workflow_execution(
|
|
614
628
|
&self,
|
|
@@ -697,7 +711,7 @@ impl WorkflowClientTrait for Client {
|
|
|
697
711
|
}),
|
|
698
712
|
task_queue: Some(TaskQueue {
|
|
699
713
|
name: task_queue,
|
|
700
|
-
kind:
|
|
714
|
+
kind: TaskQueueKind::Unspecified as i32,
|
|
701
715
|
}),
|
|
702
716
|
request_id,
|
|
703
717
|
workflow_task_timeout: options.task_timeout.map(Into::into),
|
|
@@ -928,6 +942,42 @@ impl WorkflowClientTrait for Client {
|
|
|
928
942
|
.into_inner())
|
|
929
943
|
}
|
|
930
944
|
|
|
945
|
+
async fn signal_with_start_workflow_execution(
|
|
946
|
+
&self,
|
|
947
|
+
input: Option<Payloads>,
|
|
948
|
+
task_queue: String,
|
|
949
|
+
workflow_id: String,
|
|
950
|
+
workflow_type: String,
|
|
951
|
+
options: WorkflowOptions,
|
|
952
|
+
signal_name: String,
|
|
953
|
+
signal_input: Option<Payloads>,
|
|
954
|
+
) -> Result<SignalWithStartWorkflowExecutionResponse> {
|
|
955
|
+
let request_id = Uuid::new_v4().to_string();
|
|
956
|
+
Ok(self
|
|
957
|
+
.wf_svc()
|
|
958
|
+
.signal_with_start_workflow_execution(SignalWithStartWorkflowExecutionRequest {
|
|
959
|
+
namespace: self.namespace.clone(),
|
|
960
|
+
workflow_id,
|
|
961
|
+
workflow_type: Some(WorkflowType {
|
|
962
|
+
name: workflow_type,
|
|
963
|
+
}),
|
|
964
|
+
task_queue: Some(TaskQueue {
|
|
965
|
+
name: task_queue,
|
|
966
|
+
kind: TaskQueueKind::Normal as i32,
|
|
967
|
+
}),
|
|
968
|
+
request_id,
|
|
969
|
+
input,
|
|
970
|
+
signal_name,
|
|
971
|
+
signal_input,
|
|
972
|
+
identity: self.inner.options.identity.clone(),
|
|
973
|
+
workflow_task_timeout: options.task_timeout.map(Into::into),
|
|
974
|
+
search_attributes: options.search_attributes.map(Into::into),
|
|
975
|
+
..Default::default()
|
|
976
|
+
})
|
|
977
|
+
.await?
|
|
978
|
+
.into_inner())
|
|
979
|
+
}
|
|
980
|
+
|
|
931
981
|
async fn query_workflow_execution(
|
|
932
982
|
&self,
|
|
933
983
|
workflow_id: String,
|
|
@@ -120,9 +120,9 @@ impl TonicErrorHandler {
|
|
|
120
120
|
fn new(cfg: RetryConfig, call_type: CallType, call_name: &'static str) -> Self {
|
|
121
121
|
Self {
|
|
122
122
|
max_retries: cfg.max_retries,
|
|
123
|
-
backoff: cfg.into(),
|
|
124
123
|
call_type,
|
|
125
124
|
call_name,
|
|
125
|
+
backoff: cfg.into(),
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -346,6 +346,29 @@ where
|
|
|
346
346
|
)
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
async fn signal_with_start_workflow_execution(
|
|
350
|
+
&self,
|
|
351
|
+
input: Option<Payloads>,
|
|
352
|
+
task_queue: String,
|
|
353
|
+
workflow_id: String,
|
|
354
|
+
workflow_type: String,
|
|
355
|
+
options: WorkflowOptions,
|
|
356
|
+
signal_name: String,
|
|
357
|
+
signal_input: Option<Payloads>,
|
|
358
|
+
) -> Result<SignalWithStartWorkflowExecutionResponse> {
|
|
359
|
+
retry_call!(
|
|
360
|
+
self,
|
|
361
|
+
signal_with_start_workflow_execution,
|
|
362
|
+
input.clone(),
|
|
363
|
+
task_queue.clone(),
|
|
364
|
+
workflow_id.clone(),
|
|
365
|
+
workflow_type.clone(),
|
|
366
|
+
options.clone(),
|
|
367
|
+
signal_name.clone(),
|
|
368
|
+
signal_input.clone()
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
349
372
|
async fn query_workflow_execution(
|
|
350
373
|
&self,
|
|
351
374
|
workflow_id: String,
|
|
@@ -443,6 +466,17 @@ where
|
|
|
443
466
|
}
|
|
444
467
|
}
|
|
445
468
|
|
|
469
|
+
impl<C> RawClientLikeUser for RetryClient<C>
|
|
470
|
+
where
|
|
471
|
+
C: RawClientLikeUser,
|
|
472
|
+
{
|
|
473
|
+
type RawClientT = C::RawClientT;
|
|
474
|
+
|
|
475
|
+
fn wf_svc(&self) -> Self::RawClientT {
|
|
476
|
+
self.client.wf_svc()
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
446
480
|
#[cfg(test)]
|
|
447
481
|
mod tests {
|
|
448
482
|
use super::*;
|
|
@@ -597,14 +631,3 @@ mod tests {
|
|
|
597
631
|
}
|
|
598
632
|
}
|
|
599
633
|
}
|
|
600
|
-
|
|
601
|
-
impl<C> RawClientLikeUser for RetryClient<C>
|
|
602
|
-
where
|
|
603
|
-
C: RawClientLikeUser,
|
|
604
|
-
{
|
|
605
|
-
type RawClientT = C::RawClientT;
|
|
606
|
-
|
|
607
|
-
fn wf_svc(&self) -> Self::RawClientT {
|
|
608
|
-
self.client.wf_svc()
|
|
609
|
-
}
|
|
610
|
-
}
|
|
@@ -22,10 +22,13 @@ use std::{
|
|
|
22
22
|
};
|
|
23
23
|
use temporal_client::WorkflowOptions;
|
|
24
24
|
use temporal_sdk::{ActivityOptions, WfContext};
|
|
25
|
-
use temporal_sdk_core_api::Worker as WorkerTrait;
|
|
25
|
+
use temporal_sdk_core_api::{errors::CompleteActivityError, Worker as WorkerTrait};
|
|
26
26
|
use temporal_sdk_core_protos::{
|
|
27
27
|
coresdk::{
|
|
28
|
-
activity_result::{
|
|
28
|
+
activity_result::{
|
|
29
|
+
activity_execution_result, activity_resolution, ActivityExecutionResult,
|
|
30
|
+
ActivityResolution, Success,
|
|
31
|
+
},
|
|
29
32
|
activity_task::{activity_task, ActivityTask},
|
|
30
33
|
workflow_activation::{workflow_activation_job, ResolveActivity, WorkflowActivationJob},
|
|
31
34
|
workflow_commands::{
|
|
@@ -801,3 +804,68 @@ async fn activity_tasks_from_completion_reserve_slots() {
|
|
|
801
804
|
let run_fut = async { worker.run_until_done().await.unwrap() };
|
|
802
805
|
tokio::join!(run_fut, act_completer);
|
|
803
806
|
}
|
|
807
|
+
|
|
808
|
+
#[tokio::test]
|
|
809
|
+
async fn retryable_net_error_exhaustion_is_nonfatal() {
|
|
810
|
+
let mut mock_client = mock_workflow_client();
|
|
811
|
+
mock_client
|
|
812
|
+
.expect_complete_activity_task()
|
|
813
|
+
.times(1)
|
|
814
|
+
.returning(|_, _| Err(tonic::Status::internal("retryable error")));
|
|
815
|
+
|
|
816
|
+
let core = mock_worker(MocksHolder::from_client_with_activities(
|
|
817
|
+
mock_client,
|
|
818
|
+
[PollActivityTaskQueueResponse {
|
|
819
|
+
task_token: vec![1],
|
|
820
|
+
activity_id: "act1".to_string(),
|
|
821
|
+
heartbeat_timeout: Some(Duration::from_secs(10).into()),
|
|
822
|
+
..Default::default()
|
|
823
|
+
}
|
|
824
|
+
.into()],
|
|
825
|
+
));
|
|
826
|
+
|
|
827
|
+
let act = core.poll_activity_task().await.unwrap();
|
|
828
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
829
|
+
task_token: act.task_token,
|
|
830
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
831
|
+
})
|
|
832
|
+
.await
|
|
833
|
+
.unwrap();
|
|
834
|
+
core.shutdown().await;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
#[tokio::test]
|
|
838
|
+
async fn cant_complete_activity_with_unset_result_payload() {
|
|
839
|
+
let mut mock_client = mock_workflow_client();
|
|
840
|
+
mock_client
|
|
841
|
+
.expect_poll_activity_task()
|
|
842
|
+
.returning(move |_, _| {
|
|
843
|
+
Ok(PollActivityTaskQueueResponse {
|
|
844
|
+
task_token: vec![1],
|
|
845
|
+
..Default::default()
|
|
846
|
+
})
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
let cfg = WorkerConfigBuilder::default()
|
|
850
|
+
.namespace("enchi")
|
|
851
|
+
.task_queue("cat")
|
|
852
|
+
.worker_build_id("enchi_loves_salmon")
|
|
853
|
+
.build()
|
|
854
|
+
.unwrap();
|
|
855
|
+
let worker = Worker::new_test(cfg, mock_client);
|
|
856
|
+
let t = worker.poll_activity_task().await.unwrap();
|
|
857
|
+
let res = worker
|
|
858
|
+
.complete_activity_task(ActivityTaskCompletion {
|
|
859
|
+
task_token: t.task_token,
|
|
860
|
+
result: Some(ActivityExecutionResult {
|
|
861
|
+
status: Some(activity_execution_result::Status::Completed(Success {
|
|
862
|
+
result: None,
|
|
863
|
+
})),
|
|
864
|
+
}),
|
|
865
|
+
})
|
|
866
|
+
.await;
|
|
867
|
+
assert_matches!(
|
|
868
|
+
res,
|
|
869
|
+
Err(CompleteActivityError::MalformedActivityCompletion { .. })
|
|
870
|
+
)
|
|
871
|
+
}
|
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
replay::{default_wes_attribs, TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE},
|
|
3
|
-
test_help::{
|
|
3
|
+
test_help::{
|
|
4
|
+
hist_to_poll_resp, mock_sdk, mock_sdk_cfg, mock_worker, single_hist_mock_sg, MockPollCfg,
|
|
5
|
+
ResponseType, TEST_Q,
|
|
6
|
+
},
|
|
4
7
|
worker::client::mocks::mock_workflow_client,
|
|
5
8
|
};
|
|
6
9
|
use anyhow::anyhow;
|
|
7
|
-
use futures::future::join_all;
|
|
10
|
+
use futures::{future::join_all, FutureExt};
|
|
8
11
|
use std::{
|
|
9
|
-
|
|
12
|
+
collections::HashMap,
|
|
13
|
+
sync::{
|
|
14
|
+
atomic::{AtomicUsize, Ordering},
|
|
15
|
+
Arc,
|
|
16
|
+
},
|
|
10
17
|
time::Duration,
|
|
11
18
|
};
|
|
12
19
|
use temporal_client::WorkflowOptions;
|
|
13
20
|
use temporal_sdk::{ActContext, LocalActivityOptions, WfContext, WorkflowResult};
|
|
21
|
+
use temporal_sdk_core_api::Worker;
|
|
14
22
|
use temporal_sdk_core_protos::{
|
|
15
|
-
coresdk::
|
|
16
|
-
|
|
23
|
+
coresdk::{
|
|
24
|
+
activity_result::ActivityExecutionResult,
|
|
25
|
+
workflow_activation::{workflow_activation_job, WorkflowActivationJob},
|
|
26
|
+
workflow_commands::{ActivityCancellationType, QueryResult, QuerySuccess},
|
|
27
|
+
workflow_completion::WorkflowActivationCompletion,
|
|
28
|
+
ActivityTaskCompletion, AsJsonPayloadExt,
|
|
29
|
+
},
|
|
30
|
+
temporal::api::{
|
|
31
|
+
common::v1::RetryPolicy, enums::v1::EventType, failure::v1::Failure,
|
|
32
|
+
query::v1::WorkflowQuery,
|
|
33
|
+
},
|
|
17
34
|
};
|
|
35
|
+
use temporal_sdk_core_test_utils::{schedule_local_activity_cmd, WorkerTestHelpers};
|
|
18
36
|
use tokio::sync::Barrier;
|
|
19
37
|
|
|
20
38
|
async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
|
|
@@ -117,10 +135,7 @@ async fn local_act_many_concurrent() {
|
|
|
117
135
|
let mut worker = mock_sdk(mh);
|
|
118
136
|
|
|
119
137
|
worker.register_wf(DEFAULT_WORKFLOW_TYPE.to_owned(), local_act_fanout_wf);
|
|
120
|
-
worker.register_activity(
|
|
121
|
-
"echo",
|
|
122
|
-
|_ctx: ActContext, str: String| async move { Ok(str) },
|
|
123
|
-
);
|
|
138
|
+
worker.register_activity("echo", echo);
|
|
124
139
|
worker
|
|
125
140
|
.submit_wf(
|
|
126
141
|
wf_id.to_owned(),
|
|
@@ -343,3 +358,158 @@ async fn local_act_retry_long_backoff_uses_timer() {
|
|
|
343
358
|
.unwrap();
|
|
344
359
|
worker.run_until_done().await.unwrap();
|
|
345
360
|
}
|
|
361
|
+
|
|
362
|
+
#[tokio::test]
|
|
363
|
+
async fn local_act_null_result() {
|
|
364
|
+
let mut t = TestHistoryBuilder::default();
|
|
365
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
366
|
+
t.add_full_wf_task();
|
|
367
|
+
t.add_local_activity_marker(1, "1", None, None, None);
|
|
368
|
+
t.add_workflow_execution_completed();
|
|
369
|
+
|
|
370
|
+
let wf_id = "fakeid";
|
|
371
|
+
let mock = mock_workflow_client();
|
|
372
|
+
let mh = MockPollCfg::from_resp_batches(wf_id, t, [ResponseType::AllHistory], mock);
|
|
373
|
+
let mut worker = mock_sdk_cfg(mh, |w| w.max_cached_workflows = 1);
|
|
374
|
+
|
|
375
|
+
worker.register_wf(
|
|
376
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
377
|
+
|ctx: WfContext| async move {
|
|
378
|
+
ctx.local_activity(LocalActivityOptions {
|
|
379
|
+
activity_type: "nullres".to_string(),
|
|
380
|
+
input: "hi".as_json_payload().expect("serializes fine"),
|
|
381
|
+
..Default::default()
|
|
382
|
+
})
|
|
383
|
+
.await;
|
|
384
|
+
Ok(().into())
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
worker.register_activity("nullres", |_ctx: ActContext, _: String| async { Ok(()) });
|
|
388
|
+
worker
|
|
389
|
+
.submit_wf(
|
|
390
|
+
wf_id.to_owned(),
|
|
391
|
+
DEFAULT_WORKFLOW_TYPE.to_owned(),
|
|
392
|
+
vec![],
|
|
393
|
+
WorkflowOptions::default(),
|
|
394
|
+
)
|
|
395
|
+
.await
|
|
396
|
+
.unwrap();
|
|
397
|
+
worker.run_until_done().await.unwrap();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
#[tokio::test]
|
|
401
|
+
async fn query_during_wft_heartbeat_doesnt_accidentally_fail_to_continue_heartbeat() {
|
|
402
|
+
crate::telemetry::test_telem_console();
|
|
403
|
+
let wfid = "fake_wf_id";
|
|
404
|
+
let mut t = TestHistoryBuilder::default();
|
|
405
|
+
let mut wes_short_wft_timeout = default_wes_attribs();
|
|
406
|
+
wes_short_wft_timeout.workflow_task_timeout = Some(Duration::from_millis(200).into());
|
|
407
|
+
t.add(
|
|
408
|
+
EventType::WorkflowExecutionStarted,
|
|
409
|
+
wes_short_wft_timeout.into(),
|
|
410
|
+
);
|
|
411
|
+
t.add_full_wf_task();
|
|
412
|
+
// get query here
|
|
413
|
+
t.add_full_wf_task();
|
|
414
|
+
t.add_local_activity_marker(1, "1", None, None, None);
|
|
415
|
+
t.add_workflow_execution_completed();
|
|
416
|
+
|
|
417
|
+
let query_with_hist_task = {
|
|
418
|
+
let mut pr = hist_to_poll_resp(&t, wfid, ResponseType::ToTaskNum(1), TEST_Q);
|
|
419
|
+
pr.queries = HashMap::new();
|
|
420
|
+
pr.queries.insert(
|
|
421
|
+
"the-query".to_string(),
|
|
422
|
+
WorkflowQuery {
|
|
423
|
+
query_type: "query-type".to_string(),
|
|
424
|
+
query_args: Some(b"hi".into()),
|
|
425
|
+
header: None,
|
|
426
|
+
},
|
|
427
|
+
);
|
|
428
|
+
pr
|
|
429
|
+
};
|
|
430
|
+
let after_la_resolved = Arc::new(Barrier::new(2));
|
|
431
|
+
let poll_barr = after_la_resolved.clone();
|
|
432
|
+
let tasks = [
|
|
433
|
+
query_with_hist_task,
|
|
434
|
+
hist_to_poll_resp(
|
|
435
|
+
&t,
|
|
436
|
+
wfid,
|
|
437
|
+
ResponseType::UntilResolved(
|
|
438
|
+
async move {
|
|
439
|
+
poll_barr.wait().await;
|
|
440
|
+
}
|
|
441
|
+
.boxed(),
|
|
442
|
+
3,
|
|
443
|
+
),
|
|
444
|
+
TEST_Q,
|
|
445
|
+
),
|
|
446
|
+
];
|
|
447
|
+
let mock = mock_workflow_client();
|
|
448
|
+
let mut mock = single_hist_mock_sg(wfid, t, tasks, mock, true);
|
|
449
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
|
|
450
|
+
let core = mock_worker(mock);
|
|
451
|
+
|
|
452
|
+
let barrier = Barrier::new(2);
|
|
453
|
+
|
|
454
|
+
let wf_fut = async {
|
|
455
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
456
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
457
|
+
task.run_id,
|
|
458
|
+
schedule_local_activity_cmd(
|
|
459
|
+
1,
|
|
460
|
+
"act-id",
|
|
461
|
+
ActivityCancellationType::TryCancel,
|
|
462
|
+
Duration::from_secs(60),
|
|
463
|
+
),
|
|
464
|
+
))
|
|
465
|
+
.await
|
|
466
|
+
.unwrap();
|
|
467
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
468
|
+
// Get query, and complete it
|
|
469
|
+
let query = assert_matches!(
|
|
470
|
+
task.jobs.as_slice(),
|
|
471
|
+
[WorkflowActivationJob {
|
|
472
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(q)),
|
|
473
|
+
}] => q
|
|
474
|
+
);
|
|
475
|
+
// Now complete the LA
|
|
476
|
+
barrier.wait().await;
|
|
477
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
478
|
+
task.run_id,
|
|
479
|
+
QueryResult {
|
|
480
|
+
query_id: query.query_id.clone(),
|
|
481
|
+
variant: Some(
|
|
482
|
+
QuerySuccess {
|
|
483
|
+
response: Some("whatever".into()),
|
|
484
|
+
}
|
|
485
|
+
.into(),
|
|
486
|
+
),
|
|
487
|
+
}
|
|
488
|
+
.into(),
|
|
489
|
+
))
|
|
490
|
+
.await
|
|
491
|
+
.unwrap();
|
|
492
|
+
// Activation with it resolving:
|
|
493
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
494
|
+
assert_matches!(
|
|
495
|
+
task.jobs.as_slice(),
|
|
496
|
+
[WorkflowActivationJob {
|
|
497
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
|
|
498
|
+
}]
|
|
499
|
+
);
|
|
500
|
+
core.complete_execution(&task.run_id).await;
|
|
501
|
+
};
|
|
502
|
+
let act_fut = async {
|
|
503
|
+
let act_task = core.poll_activity_task().await.unwrap();
|
|
504
|
+
barrier.wait().await;
|
|
505
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
506
|
+
task_token: act_task.task_token,
|
|
507
|
+
result: Some(ActivityExecutionResult::ok(vec![1].into())),
|
|
508
|
+
})
|
|
509
|
+
.await
|
|
510
|
+
.unwrap();
|
|
511
|
+
after_la_resolved.wait().await;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
tokio::join!(wf_fut, act_fut);
|
|
515
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use crate::{
|
|
2
2
|
test_help::{
|
|
3
3
|
build_fake_worker, build_mock_pollers, canned_histories, mock_manual_poller, mock_worker,
|
|
4
|
-
MockPollCfg, MockWorkerInputs, MocksHolder,
|
|
4
|
+
MockPollCfg, MockWorkerInputs, MocksHolder, ResponseType,
|
|
5
5
|
},
|
|
6
6
|
worker::client::mocks::mock_workflow_client,
|
|
7
7
|
PollActivityError, PollWfError,
|
|
@@ -224,3 +224,35 @@ async fn complete_eviction_after_shutdown_doesnt_panic() {
|
|
|
224
224
|
.await
|
|
225
225
|
.unwrap();
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
#[tokio::test]
|
|
229
|
+
async fn worker_does_not_panic_on_retry_exhaustion_of_nonfatal_net_err() {
|
|
230
|
+
let t = canned_histories::single_timer("1");
|
|
231
|
+
let mut mock = mock_workflow_client();
|
|
232
|
+
// Return a failure that counts as retryable, and hence we want to be swallowed
|
|
233
|
+
mock.expect_complete_workflow_task()
|
|
234
|
+
.times(1)
|
|
235
|
+
.returning(|_| Err(tonic::Status::internal("Some retryable error")));
|
|
236
|
+
let mut mh =
|
|
237
|
+
MockPollCfg::from_resp_batches("fakeid", t, [1.into(), ResponseType::AllHistory], mock);
|
|
238
|
+
mh.enforce_correct_number_of_polls = false;
|
|
239
|
+
let mut mock = build_mock_pollers(mh);
|
|
240
|
+
mock.worker_cfg(|w| w.max_cached_workflows = 1);
|
|
241
|
+
let core = mock_worker(mock);
|
|
242
|
+
|
|
243
|
+
let res = core.poll_workflow_activation().await.unwrap();
|
|
244
|
+
assert_eq!(res.jobs.len(), 1);
|
|
245
|
+
// This should not return a fatal error
|
|
246
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
247
|
+
res.run_id,
|
|
248
|
+
vec![start_timer_cmd(1, Duration::from_secs(1))],
|
|
249
|
+
))
|
|
250
|
+
.await
|
|
251
|
+
.unwrap();
|
|
252
|
+
// We should see an eviction
|
|
253
|
+
let res = core.poll_workflow_activation().await.unwrap();
|
|
254
|
+
assert_matches!(
|
|
255
|
+
res.jobs[0].variant,
|
|
256
|
+
Some(workflow_activation_job::Variant::RemoveFromCache(_))
|
|
257
|
+
);
|
|
258
|
+
}
|
package/sdk-core/core/src/lib.rs
CHANGED
|
@@ -62,7 +62,17 @@ lazy_static::lazy_static! {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/// Initialize a worker bound to a task queue
|
|
65
|
+
/// Initialize a worker bound to a task queue.
|
|
66
|
+
///
|
|
67
|
+
/// Lang implementations should pass in a a [temporal_client::ConfiguredClient] directly (or a
|
|
68
|
+
/// [RetryClient] wrapping one). When they do so, this function will always overwrite the client
|
|
69
|
+
/// retry configuration, force the client to use the namespace defined in the worker config, and set
|
|
70
|
+
/// the client identity appropriately. IE: Use [ClientOptions::connect_no_namespace], not
|
|
71
|
+
/// [ClientOptions::connect].
|
|
72
|
+
///
|
|
73
|
+
/// It is also possible to pass in a [WorkflowClientTrait] implementor, but this largely exists to
|
|
74
|
+
/// support testing and mocking. Lang impls should not operate that way, as it may result in
|
|
75
|
+
/// improper retry behavior for a worker.
|
|
66
76
|
pub fn init_worker<CT>(worker_config: WorkerConfig, client: CT) -> Worker
|
|
67
77
|
where
|
|
68
78
|
CT: Into<AnyClient>,
|
|
@@ -76,19 +86,18 @@ where
|
|
|
76
86
|
if let Some(ref id_override) = worker_config.client_identity_override {
|
|
77
87
|
client.options_mut().identity = id_override.clone();
|
|
78
88
|
}
|
|
79
|
-
let retry_client = RetryClient::new(client,
|
|
89
|
+
let retry_client = RetryClient::new(client, Default::default());
|
|
80
90
|
Arc::new(retry_client)
|
|
81
91
|
}
|
|
82
92
|
};
|
|
83
|
-
let c_opts = client.get_options().clone();
|
|
84
93
|
if client.namespace() != worker_config.namespace {
|
|
85
94
|
panic!("Passed in client is not bound to the same namespace as the worker");
|
|
86
95
|
}
|
|
96
|
+
let sticky_q = sticky_q_name_for_worker(&client.get_options().identity, &worker_config);
|
|
87
97
|
let client_bag = Arc::new(WorkerClientBag::new(
|
|
88
98
|
Box::new(client),
|
|
89
99
|
worker_config.namespace.clone(),
|
|
90
100
|
));
|
|
91
|
-
let sticky_q = sticky_q_name_for_worker(&c_opts.identity, &worker_config);
|
|
92
101
|
let metrics = MetricsContext::top_level(worker_config.namespace.clone())
|
|
93
102
|
.with_task_q(worker_config.task_queue.clone());
|
|
94
103
|
Worker::new(worker_config, sticky_q, client_bag, metrics)
|
|
@@ -6,7 +6,7 @@ use anyhow::anyhow;
|
|
|
6
6
|
use std::{
|
|
7
7
|
collections::HashMap,
|
|
8
8
|
convert::TryFrom,
|
|
9
|
-
fmt::{Display, Formatter},
|
|
9
|
+
fmt::{Debug, Display, Formatter},
|
|
10
10
|
time::{Duration, SystemTime},
|
|
11
11
|
};
|
|
12
12
|
use temporal_sdk_core_protos::{
|
|
@@ -39,7 +39,7 @@ use temporal_sdk_core_protos::{
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/// A validated version of a [PollWorkflowTaskQueueResponse]
|
|
42
|
-
#[derive(
|
|
42
|
+
#[derive(Clone, PartialEq)]
|
|
43
43
|
#[allow(clippy::manual_non_exhaustive)] // Clippy doesn't understand it's only for *in* this crate
|
|
44
44
|
pub struct ValidPollWFTQResponse {
|
|
45
45
|
pub task_token: TaskToken,
|
|
@@ -61,6 +61,28 @@ pub struct ValidPollWFTQResponse {
|
|
|
61
61
|
_cant_construct_me: (),
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
impl Debug for ValidPollWFTQResponse {
|
|
65
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
66
|
+
write!(
|
|
67
|
+
f,
|
|
68
|
+
"ValidWFT {{ task_token: {}, task_queue: {}, workflow_execution: {:?}, \
|
|
69
|
+
workflow_type: {}, attempt: {}, previous_started_event_id: {}, started_event_id {}, \
|
|
70
|
+
history_length: {}, first_evt_in_hist_id: {:?}, legacy_query: {:?}, queries: {:?} }}",
|
|
71
|
+
self.task_token,
|
|
72
|
+
self.task_queue,
|
|
73
|
+
self.workflow_execution,
|
|
74
|
+
self.workflow_type,
|
|
75
|
+
self.attempt,
|
|
76
|
+
self.previous_started_event_id,
|
|
77
|
+
self.started_event_id,
|
|
78
|
+
self.history.events.len(),
|
|
79
|
+
self.history.events.get(0).map(|e| e.event_id),
|
|
80
|
+
self.legacy_query,
|
|
81
|
+
self.query_requests
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
impl TryFrom<PollWorkflowTaskQueueResponse> for ValidPollWFTQResponse {
|
|
65
87
|
/// We return the poll response itself if it was invalid
|
|
66
88
|
type Error = PollWorkflowTaskQueueResponse;
|
|
@@ -204,11 +226,10 @@ impl HistoryEventExt for HistoryEvent {
|
|
|
204
226
|
)) if marker_name == LOCAL_ACTIVITY_MARKER_NAME => {
|
|
205
227
|
let (data, ok_res) = extract_local_activity_marker_details(&mut details);
|
|
206
228
|
let data = data?;
|
|
207
|
-
let result =
|
|
208
|
-
Ok(r)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
Err(fail)
|
|
229
|
+
let result = match (ok_res, failure) {
|
|
230
|
+
(Some(r), None) => Ok(r),
|
|
231
|
+
(None | Some(_), Some(f)) => Err(f),
|
|
232
|
+
(None, None) => Ok(Default::default()),
|
|
212
233
|
};
|
|
213
234
|
Some(CompleteLocalActivityData {
|
|
214
235
|
marker_dat: data,
|
|
@@ -228,6 +249,22 @@ pub(crate) struct CompleteLocalActivityData {
|
|
|
228
249
|
pub result: Result<Payload, Failure>,
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
pub(crate) fn validate_activity_completion(
|
|
253
|
+
status: &activity_execution_result::Status,
|
|
254
|
+
) -> Result<(), CompleteActivityError> {
|
|
255
|
+
match status {
|
|
256
|
+
Status::Completed(c) if c.result.is_none() => {
|
|
257
|
+
Err(CompleteActivityError::MalformedActivityCompletion {
|
|
258
|
+
reason: "Activity completions must contain a `result` payload \
|
|
259
|
+
(which may be empty)"
|
|
260
|
+
.to_string(),
|
|
261
|
+
completion: None,
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
_ => Ok(()),
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
231
268
|
impl TryFrom<activity_execution_result::Status> for LocalActivityExecutionResult {
|
|
232
269
|
type Error = CompleteActivityError;
|
|
233
270
|
|