@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporalio/core-bridge",
3
- "version": "0.20.1",
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": "a9fc2f32f9e1624758f334319e2135f25ff9ed24"
46
+ "gitHead": "ce4a3b017cdc327fb9f2d3812e3278304f6514b4"
47
47
  }
@@ -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::enums::v1::TimeoutType,
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.failure
424
- .as_ref()
425
- .map(|f| format!("{:?}", f))
426
- .unwrap_or_else(|| "".to_string()),
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::coresdk::common::RetryPolicy;
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
- ) -> Option<u32> {
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
- None
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
- .request_eviction(run_id, failstr, reason)
564
- .map_or(true, |attempt| attempt <= 1);
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 {
@@ -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 = (&act_fn.act_func)(arg).await;
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::{WorkflowClientTrait, WorkflowOptions};
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
- // Terminate the continued workflow
39
- starter
40
- .get_client()
41
- .await
42
- .terminate_workflow_execution(wf_name.to_owned(), None)
43
- .await
44
- .unwrap();
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
  }