@temporalio/core-bridge 0.21.0 → 0.23.0
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 +139 -127
- package/Cargo.toml +2 -1
- package/index.d.ts +93 -14
- package/package.json +3 -3
- package/releases/aarch64-apple-darwin/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/.buildkite/docker/docker-compose.yaml +5 -4
- package/sdk-core/bridge-ffi/src/lib.rs +1 -1
- package/sdk-core/bridge-ffi/src/wrappers.rs +60 -34
- package/sdk-core/client/Cargo.toml +2 -0
- package/sdk-core/client/src/lib.rs +76 -16
- package/sdk-core/client/src/raw.rs +10 -2
- package/sdk-core/client/src/retry.rs +12 -1
- package/sdk-core/client/src/workflow_handle/mod.rs +183 -0
- package/sdk-core/core/benches/workflow_replay.rs +1 -7
- package/sdk-core/core/src/abstractions.rs +10 -3
- package/sdk-core/core/src/core_tests/child_workflows.rs +7 -9
- package/sdk-core/core/src/core_tests/determinism.rs +8 -19
- package/sdk-core/core/src/core_tests/local_activities.rs +22 -32
- package/sdk-core/core/src/core_tests/queries.rs +181 -20
- package/sdk-core/core/src/core_tests/workers.rs +4 -34
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +197 -41
- package/sdk-core/core/src/lib.rs +2 -1
- package/sdk-core/core/src/pending_activations.rs +11 -0
- package/sdk-core/core/src/protosext/mod.rs +1 -3
- package/sdk-core/core/src/retry_logic.rs +73 -16
- package/sdk-core/core/src/telemetry/mod.rs +168 -110
- package/sdk-core/core/src/test_help/mod.rs +57 -7
- package/sdk-core/core/src/worker/activities/local_activities.rs +6 -12
- package/sdk-core/core/src/worker/mod.rs +64 -15
- package/sdk-core/core/src/workflow/machines/mod.rs +1 -1
- package/sdk-core/core/src/workflow/machines/timer_state_machine.rs +2 -2
- package/sdk-core/core/src/workflow/machines/workflow_machines.rs +14 -3
- package/sdk-core/core/src/workflow/mod.rs +5 -2
- package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +47 -2
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +16 -2
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +202 -101
- package/sdk-core/core-api/src/worker.rs +9 -0
- package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +27 -5
- package/sdk-core/sdk/Cargo.toml +1 -0
- package/sdk-core/sdk/src/activity_context.rs +223 -0
- package/sdk-core/sdk/src/interceptors.rs +8 -2
- package/sdk-core/sdk/src/lib.rs +168 -123
- package/sdk-core/sdk-core-protos/src/history_info.rs +3 -7
- package/sdk-core/sdk-core-protos/src/lib.rs +13 -0
- package/sdk-core/test-utils/Cargo.toml +1 -0
- package/sdk-core/test-utils/src/histfetch.rs +1 -1
- package/sdk-core/test-utils/src/lib.rs +88 -44
- package/sdk-core/tests/integ_tests/client_tests.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +11 -4
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +0 -1
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +0 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +33 -17
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +10 -1
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +0 -1
- package/sdk-core/tests/integ_tests/workflow_tests.rs +71 -3
- package/sdk-core/tests/load_tests.rs +80 -6
- package/sdk-core/tests/main.rs +5 -2
- package/src/conversions.rs +104 -33
- package/src/lib.rs +70 -6
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
|
@@ -11,8 +11,13 @@ use std::{
|
|
|
11
11
|
use temporal_sdk_core_api::Worker as WorkerTrait;
|
|
12
12
|
use temporal_sdk_core_protos::{
|
|
13
13
|
coresdk::{
|
|
14
|
-
workflow_activation::{
|
|
15
|
-
|
|
14
|
+
workflow_activation::{
|
|
15
|
+
remove_from_cache::EvictionReason, workflow_activation_job, WorkflowActivationJob,
|
|
16
|
+
},
|
|
17
|
+
workflow_commands::{
|
|
18
|
+
ActivityCancellationType, CompleteWorkflowExecution, ContinueAsNewWorkflowExecution,
|
|
19
|
+
QueryResult, QuerySuccess, RequestCancelActivity,
|
|
20
|
+
},
|
|
16
21
|
workflow_completion::WorkflowActivationCompletion,
|
|
17
22
|
},
|
|
18
23
|
temporal::api::{
|
|
@@ -26,7 +31,7 @@ use temporal_sdk_core_protos::{
|
|
|
26
31
|
},
|
|
27
32
|
},
|
|
28
33
|
};
|
|
29
|
-
use temporal_sdk_core_test_utils::start_timer_cmd;
|
|
34
|
+
use temporal_sdk_core_test_utils::{schedule_activity_cmd, start_timer_cmd};
|
|
30
35
|
|
|
31
36
|
#[rstest::rstest]
|
|
32
37
|
#[case::with_history(true)]
|
|
@@ -81,6 +86,10 @@ async fn legacy_query(#[case] include_history: bool) {
|
|
|
81
86
|
};
|
|
82
87
|
let clear_eviction = || async {
|
|
83
88
|
let t = worker.poll_workflow_activation().await.unwrap();
|
|
89
|
+
assert_matches!(
|
|
90
|
+
t.jobs[0].variant,
|
|
91
|
+
Some(workflow_activation_job::Variant::RemoveFromCache(_))
|
|
92
|
+
);
|
|
84
93
|
worker
|
|
85
94
|
.complete_workflow_activation(WorkflowActivationCompletion::empty(t.run_id))
|
|
86
95
|
.await
|
|
@@ -156,7 +165,12 @@ async fn new_queries(#[case] num_queries: usize) {
|
|
|
156
165
|
let tasks = VecDeque::from(vec![
|
|
157
166
|
hist_to_poll_resp(&t, wfid.to_owned(), 1.into(), TEST_Q.to_string()),
|
|
158
167
|
{
|
|
159
|
-
let mut pr = hist_to_poll_resp(
|
|
168
|
+
let mut pr = hist_to_poll_resp(
|
|
169
|
+
&t,
|
|
170
|
+
wfid.to_owned(),
|
|
171
|
+
ResponseType::OneTask(2),
|
|
172
|
+
TEST_Q.to_string(),
|
|
173
|
+
);
|
|
160
174
|
pr.queries = HashMap::new();
|
|
161
175
|
for i in 1..=num_queries {
|
|
162
176
|
pr.queries.insert(
|
|
@@ -383,20 +397,50 @@ async fn legacy_query_after_complete(#[values(false, true)] full_history: bool)
|
|
|
383
397
|
core.shutdown().await;
|
|
384
398
|
}
|
|
385
399
|
|
|
400
|
+
enum QueryHists {
|
|
401
|
+
Empty,
|
|
402
|
+
Full,
|
|
403
|
+
Partial,
|
|
404
|
+
}
|
|
405
|
+
#[rstest::rstest]
|
|
386
406
|
#[tokio::test]
|
|
387
|
-
async fn query_cache_miss_causes_page_fetch_dont_reply_wft_too_early(
|
|
407
|
+
async fn query_cache_miss_causes_page_fetch_dont_reply_wft_too_early(
|
|
408
|
+
#[values(QueryHists::Empty, QueryHists::Full, QueryHists::Partial)] hist_type: QueryHists,
|
|
409
|
+
) {
|
|
388
410
|
let wfid = "fake_wf_id";
|
|
389
411
|
let query_resp = "response";
|
|
390
412
|
let t = canned_histories::single_timer("1");
|
|
391
413
|
let full_hist = t.get_full_history_info().unwrap();
|
|
392
414
|
let tasks = VecDeque::from(vec![{
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
415
|
+
let mut pr = match hist_type {
|
|
416
|
+
QueryHists::Empty => {
|
|
417
|
+
// Create a no-history poll response. This happens to be easiest to do by just ripping
|
|
418
|
+
// out the history after making a normal one.
|
|
419
|
+
let mut pr = hist_to_poll_resp(
|
|
420
|
+
&t,
|
|
421
|
+
wfid.to_owned(),
|
|
422
|
+
ResponseType::AllHistory,
|
|
423
|
+
TEST_Q.to_string(),
|
|
424
|
+
);
|
|
425
|
+
pr.history = Some(Default::default());
|
|
426
|
+
pr
|
|
427
|
+
}
|
|
428
|
+
QueryHists::Full => hist_to_poll_resp(
|
|
429
|
+
&t,
|
|
430
|
+
wfid.to_owned(),
|
|
431
|
+
ResponseType::AllHistory,
|
|
432
|
+
TEST_Q.to_string(),
|
|
433
|
+
),
|
|
434
|
+
QueryHists::Partial => {
|
|
435
|
+
// Create a partial task
|
|
436
|
+
hist_to_poll_resp(
|
|
437
|
+
&t,
|
|
438
|
+
wfid.to_owned(),
|
|
439
|
+
ResponseType::OneTask(2),
|
|
440
|
+
TEST_Q.to_string(),
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
};
|
|
400
444
|
pr.queries = HashMap::new();
|
|
401
445
|
pr.queries.insert(
|
|
402
446
|
"the-query".to_string(),
|
|
@@ -409,14 +453,16 @@ async fn query_cache_miss_causes_page_fetch_dont_reply_wft_too_early() {
|
|
|
409
453
|
pr
|
|
410
454
|
}]);
|
|
411
455
|
let mut mock_client = mock_workflow_client();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
456
|
+
if !matches!(hist_type, QueryHists::Full) {
|
|
457
|
+
mock_client
|
|
458
|
+
.expect_get_workflow_execution_history()
|
|
459
|
+
.returning(move |_, _, _| {
|
|
460
|
+
Ok(GetWorkflowExecutionHistoryResponse {
|
|
461
|
+
history: Some(full_hist.clone().into()),
|
|
462
|
+
..Default::default()
|
|
463
|
+
})
|
|
464
|
+
});
|
|
465
|
+
}
|
|
420
466
|
mock_client
|
|
421
467
|
.expect_complete_workflow_task()
|
|
422
468
|
.times(1)
|
|
@@ -487,3 +533,118 @@ async fn query_cache_miss_causes_page_fetch_dont_reply_wft_too_early() {
|
|
|
487
533
|
|
|
488
534
|
core.shutdown().await;
|
|
489
535
|
}
|
|
536
|
+
|
|
537
|
+
#[tokio::test]
|
|
538
|
+
async fn query_replay_with_continue_as_new_doesnt_reply_empty_command() {
|
|
539
|
+
let wfid = "fake_wf_id";
|
|
540
|
+
let t = canned_histories::single_timer("1");
|
|
541
|
+
let query_with_hist_task = {
|
|
542
|
+
let mut pr = hist_to_poll_resp(
|
|
543
|
+
&t,
|
|
544
|
+
wfid.to_owned(),
|
|
545
|
+
ResponseType::ToTaskNum(1),
|
|
546
|
+
TEST_Q.to_string(),
|
|
547
|
+
);
|
|
548
|
+
pr.queries = HashMap::new();
|
|
549
|
+
pr.queries.insert(
|
|
550
|
+
"the-query".to_string(),
|
|
551
|
+
WorkflowQuery {
|
|
552
|
+
query_type: "query-type".to_string(),
|
|
553
|
+
query_args: Some(b"hi".into()),
|
|
554
|
+
header: None,
|
|
555
|
+
},
|
|
556
|
+
);
|
|
557
|
+
pr
|
|
558
|
+
};
|
|
559
|
+
let tasks = VecDeque::from(vec![query_with_hist_task]);
|
|
560
|
+
let mut mock_client = mock_workflow_client();
|
|
561
|
+
mock_client
|
|
562
|
+
.expect_complete_workflow_task()
|
|
563
|
+
.times(1)
|
|
564
|
+
.returning(|resp| {
|
|
565
|
+
// Verify both the complete command and the query response are sent
|
|
566
|
+
assert_eq!(resp.commands.len(), 1);
|
|
567
|
+
assert_eq!(resp.query_responses.len(), 1);
|
|
568
|
+
Ok(RespondWorkflowTaskCompletedResponse::default())
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
let mut mock = MocksHolder::from_client_with_responses(mock_client, tasks, vec![]);
|
|
572
|
+
mock.worker_cfg(|wc| wc.max_cached_workflows = 10);
|
|
573
|
+
let core = mock_worker(mock);
|
|
574
|
+
|
|
575
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
576
|
+
// Scheduling and immediately canceling an activity produces another activation which is
|
|
577
|
+
// important in this repro
|
|
578
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
579
|
+
task.run_id,
|
|
580
|
+
vec![
|
|
581
|
+
schedule_activity_cmd(
|
|
582
|
+
0,
|
|
583
|
+
"whatever",
|
|
584
|
+
"act-id",
|
|
585
|
+
ActivityCancellationType::TryCancel,
|
|
586
|
+
Duration::from_secs(60),
|
|
587
|
+
Duration::from_secs(60),
|
|
588
|
+
),
|
|
589
|
+
RequestCancelActivity { seq: 0 }.into(),
|
|
590
|
+
ContinueAsNewWorkflowExecution {
|
|
591
|
+
..Default::default()
|
|
592
|
+
}
|
|
593
|
+
.into(),
|
|
594
|
+
],
|
|
595
|
+
))
|
|
596
|
+
.await
|
|
597
|
+
.unwrap();
|
|
598
|
+
|
|
599
|
+
// Activity unblocked
|
|
600
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
601
|
+
assert_matches!(
|
|
602
|
+
task.jobs.as_slice(),
|
|
603
|
+
[WorkflowActivationJob {
|
|
604
|
+
variant: Some(workflow_activation_job::Variant::ResolveActivity(_)),
|
|
605
|
+
}]
|
|
606
|
+
);
|
|
607
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
|
|
608
|
+
.await
|
|
609
|
+
.unwrap();
|
|
610
|
+
|
|
611
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
612
|
+
let query = assert_matches!(
|
|
613
|
+
task.jobs.as_slice(),
|
|
614
|
+
[WorkflowActivationJob {
|
|
615
|
+
variant: Some(workflow_activation_job::Variant::QueryWorkflow(q)),
|
|
616
|
+
}] => q
|
|
617
|
+
);
|
|
618
|
+
// Throw an evict in there. Repro required a pending eviction during complete.
|
|
619
|
+
core.request_wf_eviction(&task.run_id, "I said so", EvictionReason::LangRequested);
|
|
620
|
+
|
|
621
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
622
|
+
task.run_id,
|
|
623
|
+
QueryResult {
|
|
624
|
+
query_id: query.query_id.clone(),
|
|
625
|
+
variant: Some(
|
|
626
|
+
QuerySuccess {
|
|
627
|
+
response: Some("whatever".into()),
|
|
628
|
+
}
|
|
629
|
+
.into(),
|
|
630
|
+
),
|
|
631
|
+
}
|
|
632
|
+
.into(),
|
|
633
|
+
))
|
|
634
|
+
.await
|
|
635
|
+
.unwrap();
|
|
636
|
+
|
|
637
|
+
// Need to complete the eviction to finally send commands and finish
|
|
638
|
+
let task = core.poll_workflow_activation().await.unwrap();
|
|
639
|
+
assert_matches!(
|
|
640
|
+
task.jobs.as_slice(),
|
|
641
|
+
[WorkflowActivationJob {
|
|
642
|
+
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_))
|
|
643
|
+
}]
|
|
644
|
+
);
|
|
645
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
|
|
646
|
+
.await
|
|
647
|
+
.unwrap();
|
|
648
|
+
|
|
649
|
+
core.shutdown().await;
|
|
650
|
+
}
|
|
@@ -20,38 +20,6 @@ use temporal_sdk_core_protos::{
|
|
|
20
20
|
use temporal_sdk_core_test_utils::start_timer_cmd;
|
|
21
21
|
use tokio::sync::{watch, Barrier};
|
|
22
22
|
|
|
23
|
-
#[tokio::test]
|
|
24
|
-
async fn multi_workers() {
|
|
25
|
-
// TODO: Turn this into a test with multiple independent workers
|
|
26
|
-
// Make histories for 5 different workflows on 5 different task queues
|
|
27
|
-
// let hists = (0..5).into_iter().map(|i| {
|
|
28
|
-
// let wf_id = format!("fake-wf-{}", i);
|
|
29
|
-
// let hist = canned_histories::single_timer("1");
|
|
30
|
-
// FakeWfResponses {
|
|
31
|
-
// wf_id,
|
|
32
|
-
// hist,
|
|
33
|
-
// response_batches: vec![1.into(), 2.into()],
|
|
34
|
-
// task_q: format!("q-{}", i),
|
|
35
|
-
// }
|
|
36
|
-
// });
|
|
37
|
-
// let mock = build_multihist_mock_sg(hists, false, None);
|
|
38
|
-
//
|
|
39
|
-
// let core = &mock_worker(mock);
|
|
40
|
-
//
|
|
41
|
-
// for i in 0..5 {
|
|
42
|
-
// let tq = format!("q-{}", i);
|
|
43
|
-
// let res = core.poll_workflow_activation().await.unwrap();
|
|
44
|
-
// assert_matches!(
|
|
45
|
-
// res.jobs[0].variant,
|
|
46
|
-
// Some(workflow_activation_job::Variant::StartWorkflow(_))
|
|
47
|
-
// );
|
|
48
|
-
// core.complete_workflow_activation(WorkflowActivationCompletion::empty(res.run_id))
|
|
49
|
-
// .await
|
|
50
|
-
// .unwrap();
|
|
51
|
-
// }
|
|
52
|
-
// core.shutdown().await;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
23
|
#[tokio::test]
|
|
56
24
|
async fn after_shutdown_of_worker_get_shutdown_err() {
|
|
57
25
|
let t = canned_histories::single_timer("1");
|
|
@@ -195,7 +163,7 @@ async fn can_shutdown_local_act_only_worker_when_act_polling() {
|
|
|
195
163
|
}
|
|
196
164
|
|
|
197
165
|
#[tokio::test]
|
|
198
|
-
async fn
|
|
166
|
+
async fn complete_with_task_not_found_during_shutdown() {
|
|
199
167
|
let t = canned_histories::single_timer("1");
|
|
200
168
|
let mut mock = mock_workflow_client();
|
|
201
169
|
mock.expect_complete_workflow_task()
|
|
@@ -236,5 +204,7 @@ async fn complete_with_task_not_found_during_shutdwn() {
|
|
|
236
204
|
complete_order.borrow_mut().push(1);
|
|
237
205
|
};
|
|
238
206
|
tokio::join!(shutdown_fut, poll_fut, complete_fut);
|
|
239
|
-
|
|
207
|
+
// Shutdown will currently complete first before the actual eviction reply since the
|
|
208
|
+
// workflow task is marked complete as soon as we get not found back from the server.
|
|
209
|
+
assert_eq!(&complete_order.into_inner(), &[1, 3, 2])
|
|
240
210
|
}
|
|
@@ -71,7 +71,7 @@ fn single_activity_failure_setup(hist_batches: &'static [usize]) -> Worker {
|
|
|
71
71
|
#[case::incremental(single_timer_setup(&[1, 2]), NonSticky)]
|
|
72
72
|
#[case::replay(single_timer_setup(&[2]), NonSticky)]
|
|
73
73
|
#[case::incremental_evict(single_timer_setup(&[1, 2]), AfterEveryReply)]
|
|
74
|
-
#[case::replay_evict(single_timer_setup(&[2
|
|
74
|
+
#[case::replay_evict(single_timer_setup(&[2]), AfterEveryReply)]
|
|
75
75
|
#[tokio::test]
|
|
76
76
|
async fn single_timer(#[case] worker: Worker, #[case] evict: WorkflowCachingPolicy) {
|
|
77
77
|
poll_and_reply(
|
|
@@ -1114,14 +1114,9 @@ async fn complete_after_eviction() {
|
|
|
1114
1114
|
let eviction_activation = core.poll_workflow_activation().await.unwrap();
|
|
1115
1115
|
assert_matches!(
|
|
1116
1116
|
eviction_activation.jobs.as_slice(),
|
|
1117
|
-
[
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
},
|
|
1121
|
-
WorkflowActivationJob {
|
|
1122
|
-
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
1123
|
-
}
|
|
1124
|
-
]
|
|
1117
|
+
[WorkflowActivationJob {
|
|
1118
|
+
variant: Some(workflow_activation_job::Variant::FireTimer(_)),
|
|
1119
|
+
},]
|
|
1125
1120
|
);
|
|
1126
1121
|
// Complete the activation containing the eviction, the way we normally would have
|
|
1127
1122
|
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
@@ -1130,6 +1125,13 @@ async fn complete_after_eviction() {
|
|
|
1130
1125
|
))
|
|
1131
1126
|
.await
|
|
1132
1127
|
.unwrap();
|
|
1128
|
+
let eviction = core.poll_workflow_activation().await.unwrap();
|
|
1129
|
+
assert_matches!(
|
|
1130
|
+
eviction.jobs.as_slice(),
|
|
1131
|
+
[WorkflowActivationJob {
|
|
1132
|
+
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
1133
|
+
}]
|
|
1134
|
+
);
|
|
1133
1135
|
core.shutdown().await;
|
|
1134
1136
|
}
|
|
1135
1137
|
|
|
@@ -1260,38 +1262,6 @@ async fn buffered_work_drained_on_shutdown() {
|
|
|
1260
1262
|
});
|
|
1261
1263
|
}
|
|
1262
1264
|
|
|
1263
|
-
#[tokio::test]
|
|
1264
|
-
async fn buffering_tasks_doesnt_count_toward_outstanding_max() {
|
|
1265
|
-
let wfid = "fake_wf_id";
|
|
1266
|
-
let t = canned_histories::single_timer("1");
|
|
1267
|
-
let mock = mock_workflow_client();
|
|
1268
|
-
let mut tasks = VecDeque::new();
|
|
1269
|
-
// A way bigger task list than allowed outstanding tasks
|
|
1270
|
-
tasks.extend(
|
|
1271
|
-
std::iter::repeat(hist_to_poll_resp(
|
|
1272
|
-
&t,
|
|
1273
|
-
wfid.to_owned(),
|
|
1274
|
-
2.into(),
|
|
1275
|
-
TEST_Q.to_string(),
|
|
1276
|
-
))
|
|
1277
|
-
.take(20),
|
|
1278
|
-
);
|
|
1279
|
-
let mut mock = MocksHolder::from_client_with_responses(mock, tasks, []);
|
|
1280
|
-
mock.worker_cfg(|wc| {
|
|
1281
|
-
wc.max_cached_workflows = 10;
|
|
1282
|
-
wc.max_outstanding_workflow_tasks = 5;
|
|
1283
|
-
});
|
|
1284
|
-
let core = mock_worker(mock);
|
|
1285
|
-
// Poll for first WFT
|
|
1286
|
-
core.poll_workflow_activation().await.unwrap();
|
|
1287
|
-
// This will error out when the mock runs out of responses. Otherwise it would hang when we
|
|
1288
|
-
// hit the max
|
|
1289
|
-
assert_matches!(
|
|
1290
|
-
core.poll_workflow_activation().await.unwrap_err(),
|
|
1291
|
-
PollWfError::TonicError(_)
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
1265
|
#[tokio::test]
|
|
1296
1266
|
async fn fail_wft_then_recover() {
|
|
1297
1267
|
let t = canned_histories::long_sequential_timers(1);
|
|
@@ -1737,3 +1707,189 @@ async fn evict_missing_wf_during_poll_doesnt_eat_permit() {
|
|
|
1737
1707
|
|
|
1738
1708
|
core.shutdown().await;
|
|
1739
1709
|
}
|
|
1710
|
+
|
|
1711
|
+
#[tokio::test]
|
|
1712
|
+
async fn poll_faster_than_complete_wont_overflow_cache() {
|
|
1713
|
+
// Make workflow tasks for 5 different runs
|
|
1714
|
+
let tasks: Vec<_> = (1..=5)
|
|
1715
|
+
.map(|i| {
|
|
1716
|
+
hist_to_poll_resp(
|
|
1717
|
+
// New hist each time for new run ids
|
|
1718
|
+
&canned_histories::single_timer("1"),
|
|
1719
|
+
format!("wf-{}", i),
|
|
1720
|
+
ResponseType::ToTaskNum(1),
|
|
1721
|
+
TEST_Q.to_string(),
|
|
1722
|
+
)
|
|
1723
|
+
})
|
|
1724
|
+
.collect();
|
|
1725
|
+
let mut mock = mock_workflow_client();
|
|
1726
|
+
mock.expect_complete_workflow_task()
|
|
1727
|
+
.times(3)
|
|
1728
|
+
.returning(|_| Ok(Default::default()));
|
|
1729
|
+
let mut mock = MocksHolder::from_client_with_responses(mock, tasks, []);
|
|
1730
|
+
mock.worker_cfg(|wc| {
|
|
1731
|
+
wc.max_cached_workflows = 3;
|
|
1732
|
+
wc.max_outstanding_workflow_tasks = 3;
|
|
1733
|
+
});
|
|
1734
|
+
let core = mock_worker(mock);
|
|
1735
|
+
// Poll 4 times, completing once, such that max tasks are never exceeded
|
|
1736
|
+
let p1 = core.poll_workflow_activation().await.unwrap();
|
|
1737
|
+
let p2 = core.poll_workflow_activation().await.unwrap();
|
|
1738
|
+
let p3 = core.poll_workflow_activation().await.unwrap();
|
|
1739
|
+
for (i, p_res) in [&p1, &p2, &p3].into_iter().enumerate() {
|
|
1740
|
+
assert_matches!(
|
|
1741
|
+
&p_res.jobs[0].variant,
|
|
1742
|
+
Some(workflow_activation_job::Variant::StartWorkflow(sw))
|
|
1743
|
+
if sw.workflow_id == format!("wf-{}", i + 1)
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
// Complete first task to free a wft slot. Cache size is at 3
|
|
1747
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1748
|
+
p1.run_id,
|
|
1749
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
1750
|
+
))
|
|
1751
|
+
.await
|
|
1752
|
+
.unwrap();
|
|
1753
|
+
// Now we're at cache limit. We will poll for a task, discover it is for a new run, issue
|
|
1754
|
+
// an eviction, and buffer the new run task. However, the run we're trying to evict has pending
|
|
1755
|
+
// activations! Thus, we must complete them first before this poll will unblock, and then it
|
|
1756
|
+
// will unblock with the eviciton.
|
|
1757
|
+
let p4 = async {
|
|
1758
|
+
let p4 = core.poll_workflow_activation().await.unwrap();
|
|
1759
|
+
assert_matches!(
|
|
1760
|
+
&p4.jobs.as_slice(),
|
|
1761
|
+
[WorkflowActivationJob {
|
|
1762
|
+
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
1763
|
+
}]
|
|
1764
|
+
);
|
|
1765
|
+
p4
|
|
1766
|
+
};
|
|
1767
|
+
let p2_pending_completer = async {
|
|
1768
|
+
// Sleep needed because otherwise the complete unblocks waiting for the cache to free a slot
|
|
1769
|
+
// before we have a chance to actually... wait for it.
|
|
1770
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
1771
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1772
|
+
p2.run_id,
|
|
1773
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
1774
|
+
))
|
|
1775
|
+
.await
|
|
1776
|
+
.unwrap();
|
|
1777
|
+
};
|
|
1778
|
+
let (p4, _) = tokio::join!(p4, p2_pending_completer);
|
|
1779
|
+
assert_eq!(core.cached_workflows(), 3);
|
|
1780
|
+
|
|
1781
|
+
// This poll should also block until the eviction is actually completed
|
|
1782
|
+
let blocking_poll = async {
|
|
1783
|
+
let res = core.poll_workflow_activation().await.unwrap();
|
|
1784
|
+
assert_matches!(
|
|
1785
|
+
&res.jobs[0].variant,
|
|
1786
|
+
Some(workflow_activation_job::Variant::StartWorkflow(sw))
|
|
1787
|
+
if sw.workflow_id == format!("wf-{}", 4)
|
|
1788
|
+
);
|
|
1789
|
+
res
|
|
1790
|
+
};
|
|
1791
|
+
let complete_evict = async {
|
|
1792
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::empty(p4.run_id))
|
|
1793
|
+
.await
|
|
1794
|
+
.unwrap();
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
let (_p5, _) = tokio::join!(blocking_poll, complete_evict);
|
|
1798
|
+
assert_eq!(core.cached_workflows(), 3);
|
|
1799
|
+
// The next poll will get an buffer a task for a new run, and generate an eviction for p3 but
|
|
1800
|
+
// that eviction cannot be obtained until we complete the existing outstanding task.
|
|
1801
|
+
let p6 = async {
|
|
1802
|
+
let p6 = core.poll_workflow_activation().await.unwrap();
|
|
1803
|
+
assert_matches!(
|
|
1804
|
+
p6.jobs.as_slice(),
|
|
1805
|
+
[WorkflowActivationJob {
|
|
1806
|
+
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
1807
|
+
}]
|
|
1808
|
+
);
|
|
1809
|
+
p6
|
|
1810
|
+
};
|
|
1811
|
+
let completer = async {
|
|
1812
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1813
|
+
p3.run_id,
|
|
1814
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
1815
|
+
))
|
|
1816
|
+
.await
|
|
1817
|
+
.unwrap();
|
|
1818
|
+
};
|
|
1819
|
+
let (p6, _) = tokio::join!(p6, completer);
|
|
1820
|
+
let complete_evict = async {
|
|
1821
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::empty(p6.run_id))
|
|
1822
|
+
.await
|
|
1823
|
+
.unwrap();
|
|
1824
|
+
};
|
|
1825
|
+
let blocking_poll = async {
|
|
1826
|
+
// This poll will also block until the last eviction goes through, and when it does it'll
|
|
1827
|
+
// produce the final start workflow task
|
|
1828
|
+
let res = core.poll_workflow_activation().await.unwrap();
|
|
1829
|
+
assert_matches!(
|
|
1830
|
+
&res.jobs[0].variant,
|
|
1831
|
+
Some(workflow_activation_job::Variant::StartWorkflow(sw))
|
|
1832
|
+
if sw.workflow_id == "wf-5"
|
|
1833
|
+
);
|
|
1834
|
+
};
|
|
1835
|
+
|
|
1836
|
+
tokio::join!(blocking_poll, complete_evict);
|
|
1837
|
+
// p5 outstanding and final poll outstanding -- hence one permit available
|
|
1838
|
+
assert_eq!(core.available_wft_permits(), 1);
|
|
1839
|
+
assert_eq!(core.cached_workflows(), 3);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
#[tokio::test]
|
|
1843
|
+
async fn eviction_waits_until_replay_finished() {
|
|
1844
|
+
let wfid = "fake_wf_id";
|
|
1845
|
+
let t = canned_histories::long_sequential_timers(3);
|
|
1846
|
+
let mock = mock_workflow_client();
|
|
1847
|
+
let mock = single_hist_mock_sg(wfid, t, &[3], mock, true);
|
|
1848
|
+
let core = mock_worker(mock);
|
|
1849
|
+
|
|
1850
|
+
let activation = core.poll_workflow_activation().await.unwrap();
|
|
1851
|
+
// Immediately request eviction after getting start workflow
|
|
1852
|
+
core.request_workflow_eviction(&activation.run_id);
|
|
1853
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1854
|
+
activation.run_id,
|
|
1855
|
+
start_timer_cmd(1, Duration::from_secs(1)),
|
|
1856
|
+
))
|
|
1857
|
+
.await
|
|
1858
|
+
.unwrap();
|
|
1859
|
+
let t1_fired = core.poll_workflow_activation().await.unwrap();
|
|
1860
|
+
assert_matches!(
|
|
1861
|
+
t1_fired.jobs.as_slice(),
|
|
1862
|
+
[WorkflowActivationJob {
|
|
1863
|
+
variant: Some(workflow_activation_job::Variant::FireTimer(_)),
|
|
1864
|
+
}]
|
|
1865
|
+
);
|
|
1866
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
|
|
1867
|
+
t1_fired.run_id,
|
|
1868
|
+
start_timer_cmd(2, Duration::from_secs(1)),
|
|
1869
|
+
))
|
|
1870
|
+
.await
|
|
1871
|
+
.unwrap();
|
|
1872
|
+
let t2_fired = core.poll_workflow_activation().await.unwrap();
|
|
1873
|
+
assert_matches!(
|
|
1874
|
+
t2_fired.jobs.as_slice(),
|
|
1875
|
+
[WorkflowActivationJob {
|
|
1876
|
+
variant: Some(workflow_activation_job::Variant::FireTimer(_)),
|
|
1877
|
+
}]
|
|
1878
|
+
);
|
|
1879
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
1880
|
+
t2_fired.run_id,
|
|
1881
|
+
vec![CompleteWorkflowExecution { result: None }.into()],
|
|
1882
|
+
))
|
|
1883
|
+
.await
|
|
1884
|
+
.unwrap();
|
|
1885
|
+
// The first two WFTs were replay, and now that we've caught up, the eviction will be sent
|
|
1886
|
+
let eviction = core.poll_workflow_activation().await.unwrap();
|
|
1887
|
+
assert_matches!(
|
|
1888
|
+
eviction.jobs.as_slice(),
|
|
1889
|
+
[WorkflowActivationJob {
|
|
1890
|
+
variant: Some(workflow_activation_job::Variant::RemoveFromCache(_)),
|
|
1891
|
+
}]
|
|
1892
|
+
);
|
|
1893
|
+
|
|
1894
|
+
core.shutdown().await;
|
|
1895
|
+
}
|
package/sdk-core/core/src/lib.rs
CHANGED
|
@@ -34,7 +34,8 @@ pub use pollers::{
|
|
|
34
34
|
TlsConfig, WorkflowClientTrait,
|
|
35
35
|
};
|
|
36
36
|
pub use telemetry::{
|
|
37
|
-
fetch_global_buffered_logs, telemetry_init,
|
|
37
|
+
fetch_global_buffered_logs, telemetry_init, Logger, MetricsExporter, OtelCollectorOptions,
|
|
38
|
+
TelemetryOptions, TelemetryOptionsBuilder, TraceExporter,
|
|
38
39
|
};
|
|
39
40
|
pub use temporal_sdk_core_api as api;
|
|
40
41
|
pub use temporal_sdk_core_protos as protos;
|
|
@@ -22,6 +22,7 @@ struct PaInner {
|
|
|
22
22
|
queue: VecDeque<ActivationKey>,
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
#[derive(Debug)]
|
|
25
26
|
pub struct PendingActInfo {
|
|
26
27
|
pub needs_eviction: Option<RemoveFromCache>,
|
|
27
28
|
pub run_id: String,
|
|
@@ -41,6 +42,7 @@ impl PendingActivations {
|
|
|
41
42
|
inner.queue.push_back(key);
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
|
|
44
46
|
pub fn notify_needs_eviction(&self, run_id: &str, message: String, reason: EvictionReason) {
|
|
45
47
|
let mut inner = self.inner.write();
|
|
46
48
|
|
|
@@ -106,6 +108,15 @@ impl PendingActivations {
|
|
|
106
108
|
inner.activations.remove(k);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
111
|
+
|
|
112
|
+
/// Returns true if any pending activation contains an eviction
|
|
113
|
+
pub fn is_some_eviction(&self) -> bool {
|
|
114
|
+
self.inner
|
|
115
|
+
.read()
|
|
116
|
+
.activations
|
|
117
|
+
.values()
|
|
118
|
+
.any(|act| act.needs_eviction.is_some())
|
|
119
|
+
}
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
#[cfg(test)]
|
|
@@ -359,9 +359,7 @@ impl ValidScheduleLA {
|
|
|
359
359
|
))
|
|
360
360
|
}
|
|
361
361
|
};
|
|
362
|
-
let retry_policy = v
|
|
363
|
-
.retry_policy
|
|
364
|
-
.ok_or_else(|| anyhow!("Retry policy must be defined!"))?;
|
|
362
|
+
let retry_policy = v.retry_policy.unwrap_or_default();
|
|
365
363
|
let local_retry_threshold = v
|
|
366
364
|
.local_retry_threshold
|
|
367
365
|
.clone()
|