@temporalio/core-bridge 0.20.1 → 0.20.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/package.json +2 -2
- 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/core/src/core_tests/workflow_tasks.rs +44 -1
- package/sdk-core/core/src/worker/activities/local_activities.rs +58 -6
- package/sdk-core/core/src/worker/mod.rs +14 -3
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +1 -1
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +22 -8
- package/sdk-core/sdk/src/lib.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +26 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/core-bridge",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Temporal.io SDK Core<>Node bridge",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "ce4a3b017cdc327fb9f2d3812e3278304f6514b4"
|
|
47
47
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -35,7 +35,7 @@ use temporal_sdk_core_protos::{
|
|
|
35
35
|
temporal::api::{
|
|
36
36
|
enums::v1::{EventType, WorkflowTaskFailedCause},
|
|
37
37
|
failure::v1::Failure,
|
|
38
|
-
history::v1::{history_event, TimerFiredEventAttributes},
|
|
38
|
+
history::v1::{history_event, History, TimerFiredEventAttributes},
|
|
39
39
|
workflowservice::v1::{
|
|
40
40
|
GetWorkflowExecutionHistoryResponse, RespondWorkflowTaskCompletedResponse,
|
|
41
41
|
},
|
|
@@ -1694,3 +1694,46 @@ async fn tasks_from_completion_are_delivered() {
|
|
|
1694
1694
|
.unwrap();
|
|
1695
1695
|
core.shutdown().await;
|
|
1696
1696
|
}
|
|
1697
|
+
|
|
1698
|
+
#[tokio::test]
|
|
1699
|
+
async fn evict_missing_wf_during_poll_doesnt_eat_permit() {
|
|
1700
|
+
let wfid = "fake_wf_id";
|
|
1701
|
+
let mut t = TestHistoryBuilder::default();
|
|
1702
|
+
t.add_by_type(EventType::WorkflowExecutionStarted);
|
|
1703
|
+
t.add_full_wf_task();
|
|
1704
|
+
t.add_we_signaled("sig", vec![]);
|
|
1705
|
+
t.add_full_wf_task();
|
|
1706
|
+
t.add_workflow_execution_completed();
|
|
1707
|
+
|
|
1708
|
+
let tasks = [hist_to_poll_resp(
|
|
1709
|
+
&t,
|
|
1710
|
+
wfid.to_owned(),
|
|
1711
|
+
// Use a partial task so that we'll fetch history
|
|
1712
|
+
ResponseType::OneTask(2),
|
|
1713
|
+
TEST_Q.to_string(),
|
|
1714
|
+
)];
|
|
1715
|
+
let mut mock = mock_workflow_client();
|
|
1716
|
+
mock.expect_get_workflow_execution_history()
|
|
1717
|
+
.times(1)
|
|
1718
|
+
.returning(move |_, _, _| {
|
|
1719
|
+
Ok(GetWorkflowExecutionHistoryResponse {
|
|
1720
|
+
// Empty history so we error applying it (no jobs)
|
|
1721
|
+
history: Some(History { events: vec![] }),
|
|
1722
|
+
raw_history: vec![],
|
|
1723
|
+
next_page_token: vec![],
|
|
1724
|
+
archived: false,
|
|
1725
|
+
})
|
|
1726
|
+
});
|
|
1727
|
+
let mut mock = MocksHolder::from_client_with_responses(mock, tasks, []);
|
|
1728
|
+
mock.worker_cfg(|wc| {
|
|
1729
|
+
wc.max_cached_workflows = 1;
|
|
1730
|
+
wc.max_outstanding_workflow_tasks = 1;
|
|
1731
|
+
});
|
|
1732
|
+
let core = mock_worker(mock);
|
|
1733
|
+
|
|
1734
|
+
// Should error because mock is out of work
|
|
1735
|
+
assert_matches!(core.poll_workflow_activation().await, Err(_));
|
|
1736
|
+
assert_eq!(core.available_wft_permits(), 1);
|
|
1737
|
+
|
|
1738
|
+
core.shutdown().await;
|
|
1739
|
+
}
|
|
@@ -14,7 +14,10 @@ use temporal_sdk_core_protos::{
|
|
|
14
14
|
activity_task::{activity_task, ActivityCancelReason, ActivityTask, Cancel, Start},
|
|
15
15
|
common::WorkflowExecution,
|
|
16
16
|
},
|
|
17
|
-
temporal::api::
|
|
17
|
+
temporal::api::{
|
|
18
|
+
enums::v1::TimeoutType,
|
|
19
|
+
failure::v1::{failure::FailureInfo, ApplicationFailureInfo},
|
|
20
|
+
},
|
|
18
21
|
};
|
|
19
22
|
use tokio::{
|
|
20
23
|
sync::{
|
|
@@ -420,10 +423,13 @@ impl LocalActivityManager {
|
|
|
420
423
|
LocalActivityExecutionResult::Failed(f) => {
|
|
421
424
|
if let Some(backoff_dur) = info.la_info.schedule_cmd.retry_policy.should_retry(
|
|
422
425
|
info.attempt as usize,
|
|
423
|
-
&f.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
426
|
+
f.failure.as_ref().map_or("", |f| match &f.failure_info {
|
|
427
|
+
Some(FailureInfo::ApplicationFailureInfo(ApplicationFailureInfo {
|
|
428
|
+
r#type,
|
|
429
|
+
..
|
|
430
|
+
})) => r#type.as_str(),
|
|
431
|
+
_ => "",
|
|
432
|
+
}),
|
|
427
433
|
) {
|
|
428
434
|
let will_use_timer =
|
|
429
435
|
backoff_dur > info.la_info.schedule_cmd.local_retry_threshold;
|
|
@@ -637,7 +643,9 @@ impl Drop for TimeoutBag {
|
|
|
637
643
|
mod tests {
|
|
638
644
|
use super::*;
|
|
639
645
|
use crate::protosext::LACloseTimeouts;
|
|
640
|
-
use temporal_sdk_core_protos::
|
|
646
|
+
use temporal_sdk_core_protos::{
|
|
647
|
+
coresdk::common::RetryPolicy, temporal::api::failure::v1::Failure,
|
|
648
|
+
};
|
|
641
649
|
use tokio::{sync::mpsc::error::TryRecvError, task::yield_now};
|
|
642
650
|
|
|
643
651
|
impl DispatchOrTimeoutLA {
|
|
@@ -785,6 +793,50 @@ mod tests {
|
|
|
785
793
|
)
|
|
786
794
|
}
|
|
787
795
|
|
|
796
|
+
#[tokio::test]
|
|
797
|
+
async fn respects_non_retryable_error_types() {
|
|
798
|
+
let lam = LocalActivityManager::test(1);
|
|
799
|
+
lam.enqueue([NewLocalAct {
|
|
800
|
+
schedule_cmd: ValidScheduleLA {
|
|
801
|
+
seq: 1,
|
|
802
|
+
activity_id: "1".to_string(),
|
|
803
|
+
attempt: 1,
|
|
804
|
+
retry_policy: RetryPolicy {
|
|
805
|
+
initial_interval: Some(Duration::from_secs(1).into()),
|
|
806
|
+
backoff_coefficient: 10.0,
|
|
807
|
+
maximum_interval: Some(Duration::from_secs(10).into()),
|
|
808
|
+
maximum_attempts: 10,
|
|
809
|
+
non_retryable_error_types: vec!["TestError".to_string()],
|
|
810
|
+
},
|
|
811
|
+
local_retry_threshold: Duration::from_secs(5),
|
|
812
|
+
..Default::default()
|
|
813
|
+
},
|
|
814
|
+
workflow_type: "".to_string(),
|
|
815
|
+
workflow_exec_info: Default::default(),
|
|
816
|
+
schedule_time: SystemTime::now(),
|
|
817
|
+
}
|
|
818
|
+
.into()]);
|
|
819
|
+
|
|
820
|
+
let next = lam.next_pending().await.unwrap().unwrap();
|
|
821
|
+
let tt = TaskToken(next.task_token);
|
|
822
|
+
let res = lam.complete(
|
|
823
|
+
&tt,
|
|
824
|
+
&LocalActivityExecutionResult::Failed(ActFail {
|
|
825
|
+
failure: Some(Failure {
|
|
826
|
+
failure_info: Some(FailureInfo::ApplicationFailureInfo(
|
|
827
|
+
ApplicationFailureInfo {
|
|
828
|
+
r#type: "TestError".to_string(),
|
|
829
|
+
non_retryable: false,
|
|
830
|
+
..Default::default()
|
|
831
|
+
},
|
|
832
|
+
)),
|
|
833
|
+
..Default::default()
|
|
834
|
+
}),
|
|
835
|
+
}),
|
|
836
|
+
);
|
|
837
|
+
assert_matches!(res, LACompleteAction::Report(_));
|
|
838
|
+
}
|
|
839
|
+
|
|
788
840
|
#[tokio::test]
|
|
789
841
|
async fn can_cancel_during_local_backoff() {
|
|
790
842
|
let lam = LocalActivityManager::test(1);
|
|
@@ -65,6 +65,7 @@ use tracing_futures::Instrument;
|
|
|
65
65
|
|
|
66
66
|
#[cfg(test)]
|
|
67
67
|
use crate::worker::client::WorkerClient;
|
|
68
|
+
use crate::workflow::workflow_tasks::EvictionRequestResult;
|
|
68
69
|
|
|
69
70
|
/// A worker polls on a certain task queue
|
|
70
71
|
pub struct Worker {
|
|
@@ -530,13 +531,18 @@ impl Worker {
|
|
|
530
531
|
self.workflows_semaphore.add_permit();
|
|
531
532
|
}
|
|
532
533
|
|
|
534
|
+
/// Request a workflow eviction. Returns true if we actually queued up a new eviction request.
|
|
533
535
|
pub(crate) fn request_wf_eviction(
|
|
534
536
|
&self,
|
|
535
537
|
run_id: &str,
|
|
536
538
|
message: impl Into<String>,
|
|
537
539
|
reason: EvictionReason,
|
|
538
|
-
) {
|
|
539
|
-
self.wft_manager.request_eviction(run_id, message, reason)
|
|
540
|
+
) -> bool {
|
|
541
|
+
match self.wft_manager.request_eviction(run_id, message, reason) {
|
|
542
|
+
EvictionRequestResult::EvictionIssued(_) => true,
|
|
543
|
+
EvictionRequestResult::NotFound => false,
|
|
544
|
+
EvictionRequestResult::EvictionAlreadyOutstanding => false,
|
|
545
|
+
}
|
|
540
546
|
}
|
|
541
547
|
|
|
542
548
|
/// Sets a function to be called at the end of each activation completion
|
|
@@ -675,11 +681,16 @@ impl Worker {
|
|
|
675
681
|
}
|
|
676
682
|
NewWfTaskOutcome::Evict(e) => {
|
|
677
683
|
warn!(error=?e, run_id=%we.run_id, "Error while applying poll response to workflow");
|
|
678
|
-
self.request_wf_eviction(
|
|
684
|
+
let did_issue_eviction = self.request_wf_eviction(
|
|
679
685
|
&we.run_id,
|
|
680
686
|
format!("Error while applying poll response to workflow: {:?}", e),
|
|
681
687
|
e.evict_reason(),
|
|
682
688
|
);
|
|
689
|
+
// If we didn't actually need to issue an eviction, then return the WFT permit.
|
|
690
|
+
// EX: The workflow we tried to evict wasn't in the cache.
|
|
691
|
+
if !did_issue_eviction {
|
|
692
|
+
self.return_workflow_task_permit();
|
|
693
|
+
}
|
|
683
694
|
None
|
|
684
695
|
}
|
|
685
696
|
})
|
|
@@ -8,11 +8,11 @@ use crate::{
|
|
|
8
8
|
};
|
|
9
9
|
use futures::future::{BoxFuture, FutureExt};
|
|
10
10
|
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|
11
|
-
use std::sync::Arc;
|
|
12
11
|
use std::{
|
|
13
12
|
collections::HashMap,
|
|
14
13
|
fmt::Debug,
|
|
15
14
|
ops::{Deref, DerefMut},
|
|
15
|
+
sync::Arc,
|
|
16
16
|
};
|
|
17
17
|
use temporal_sdk_core_protos::coresdk::workflow_activation::WorkflowActivation;
|
|
18
18
|
|
|
@@ -150,6 +150,13 @@ pub(crate) enum ActivationAction {
|
|
|
150
150
|
RespondLegacyQuery { result: QueryResult },
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
154
|
+
pub(crate) enum EvictionRequestResult {
|
|
155
|
+
EvictionIssued(Option<u32>),
|
|
156
|
+
NotFound,
|
|
157
|
+
EvictionAlreadyOutstanding,
|
|
158
|
+
}
|
|
159
|
+
|
|
153
160
|
macro_rules! machine_mut {
|
|
154
161
|
($myself:ident, $run_id:ident, $clos:expr) => {{
|
|
155
162
|
$myself
|
|
@@ -247,7 +254,7 @@ impl WorkflowTaskManager {
|
|
|
247
254
|
run_id: &str,
|
|
248
255
|
message: impl Into<String>,
|
|
249
256
|
reason: EvictionReason,
|
|
250
|
-
) ->
|
|
257
|
+
) -> EvictionRequestResult {
|
|
251
258
|
if self.workflow_machines.exists(run_id) {
|
|
252
259
|
if !self.activation_has_eviction(run_id) {
|
|
253
260
|
let message = message.into();
|
|
@@ -256,13 +263,17 @@ impl WorkflowTaskManager {
|
|
|
256
263
|
self.pending_activations
|
|
257
264
|
.notify_needs_eviction(run_id, message, reason);
|
|
258
265
|
self.pending_activations_notifier.notify_waiters();
|
|
266
|
+
EvictionRequestResult::EvictionIssued(
|
|
267
|
+
self.workflow_machines
|
|
268
|
+
.get_task(run_id)
|
|
269
|
+
.map(|wt| wt.info.attempt),
|
|
270
|
+
)
|
|
271
|
+
} else {
|
|
272
|
+
EvictionRequestResult::EvictionAlreadyOutstanding
|
|
259
273
|
}
|
|
260
|
-
self.workflow_machines
|
|
261
|
-
.get_task(run_id)
|
|
262
|
-
.map(|wt| wt.info.attempt)
|
|
263
274
|
} else {
|
|
264
275
|
warn!(%run_id, "Eviction requested for unknown run");
|
|
265
|
-
|
|
276
|
+
EvictionRequestResult::NotFound
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
|
|
@@ -304,9 +315,11 @@ impl WorkflowTaskManager {
|
|
|
304
315
|
return NewWfTaskOutcome::TaskBuffered;
|
|
305
316
|
};
|
|
306
317
|
|
|
318
|
+
let start_event_id = work.history.events.first().map(|e| e.event_id);
|
|
307
319
|
debug!(
|
|
308
320
|
task_token = %&work.task_token,
|
|
309
321
|
history_length = %work.history.events.len(),
|
|
322
|
+
start_event_id = ?start_event_id,
|
|
310
323
|
attempt = %work.attempt,
|
|
311
324
|
run_id = %work.workflow_execution.run_id,
|
|
312
325
|
"Applying new workflow task from server"
|
|
@@ -559,9 +572,10 @@ impl WorkflowTaskManager {
|
|
|
559
572
|
FailedActivationOutcome::ReportLegacyQueryFailure(tt)
|
|
560
573
|
} else {
|
|
561
574
|
// Blow up any cached data associated with the workflow
|
|
562
|
-
let should_report = self
|
|
563
|
-
|
|
564
|
-
|
|
575
|
+
let should_report = match self.request_eviction(run_id, failstr, reason) {
|
|
576
|
+
EvictionRequestResult::EvictionIssued(Some(attempt)) => attempt <= 1,
|
|
577
|
+
_ => false,
|
|
578
|
+
};
|
|
565
579
|
if should_report {
|
|
566
580
|
FailedActivationOutcome::Report(tt)
|
|
567
581
|
} else {
|
package/sdk-core/sdk/src/lib.rs
CHANGED
|
@@ -389,7 +389,7 @@ impl ActivityHalf {
|
|
|
389
389
|
tokio::spawn(ACT_CANCEL_TOK.scope(ct, async move {
|
|
390
390
|
let mut inputs = start.input;
|
|
391
391
|
let arg = inputs.pop().unwrap_or_default();
|
|
392
|
-
let output = (
|
|
392
|
+
let output = (act_fn.act_func)(arg).await;
|
|
393
393
|
let result = match output {
|
|
394
394
|
Ok(res) => ActivityExecutionResult::ok(res),
|
|
395
395
|
Err(err) => match err.downcast::<ActivityCancelledError>() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
use std::time::Duration;
|
|
2
|
-
use temporal_client::
|
|
2
|
+
use temporal_client::WorkflowOptions;
|
|
3
3
|
use temporal_sdk::{WfContext, WfExitValue, WorkflowResult};
|
|
4
4
|
use temporal_sdk_core_protos::coresdk::workflow_commands::ContinueAsNewWorkflowExecution;
|
|
5
5
|
use temporal_sdk_core_test_utils::CoreWfStarter;
|
|
@@ -33,13 +33,31 @@ async fn continue_as_new_happy_path() {
|
|
|
33
33
|
)
|
|
34
34
|
.await
|
|
35
35
|
.unwrap();
|
|
36
|
+
// The four additional runs
|
|
37
|
+
worker.incr_expected_run_count(4);
|
|
36
38
|
worker.run_until_done().await.unwrap();
|
|
39
|
+
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
#[tokio::test]
|
|
42
|
+
async fn continue_as_new_multiple_concurrent() {
|
|
43
|
+
let wf_name = "continue_as_new_multiple_concurrent";
|
|
44
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
45
|
+
starter.max_cached_workflows(3).max_wft(3);
|
|
46
|
+
let mut worker = starter.worker().await;
|
|
47
|
+
worker.register_wf(wf_name.to_string(), continue_as_new_wf);
|
|
48
|
+
|
|
49
|
+
let wf_names = (1..=20).map(|i| format!("{}-{}", wf_name, i));
|
|
50
|
+
for name in wf_names.clone() {
|
|
51
|
+
worker
|
|
52
|
+
.submit_wf(
|
|
53
|
+
name.to_string(),
|
|
54
|
+
wf_name.to_string(),
|
|
55
|
+
vec![[1].into()],
|
|
56
|
+
WorkflowOptions::default(),
|
|
57
|
+
)
|
|
58
|
+
.await
|
|
59
|
+
.unwrap();
|
|
60
|
+
}
|
|
61
|
+
worker.incr_expected_run_count(20 * 4);
|
|
62
|
+
worker.run_until_done().await.unwrap();
|
|
45
63
|
}
|