@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.
Files changed (90) hide show
  1. package/Cargo.lock +245 -247
  2. package/Cargo.toml +1 -1
  3. package/lib/errors.d.ts +9 -0
  4. package/lib/errors.js +13 -0
  5. package/lib/errors.js.map +1 -1
  6. package/lib/index.d.ts +19 -3
  7. package/lib/index.js.map +1 -1
  8. package/package.json +3 -3
  9. package/releases/aarch64-apple-darwin/index.node +0 -0
  10. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  11. package/releases/x86_64-apple-darwin/index.node +0 -0
  12. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  13. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  14. package/sdk-core/.github/workflows/heavy.yml +1 -1
  15. package/sdk-core/.github/workflows/semgrep.yml +25 -0
  16. package/sdk-core/README.md +2 -0
  17. package/sdk-core/cargo-tokio-console.sh +5 -0
  18. package/sdk-core/client/src/lib.rs +6 -41
  19. package/sdk-core/client/src/raw.rs +9 -0
  20. package/sdk-core/client/src/retry.rs +0 -16
  21. package/sdk-core/core/Cargo.toml +9 -5
  22. package/sdk-core/core/src/abstractions.rs +7 -75
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +16 -8
  24. package/sdk-core/core/src/core_tests/local_activities.rs +97 -5
  25. package/sdk-core/core/src/core_tests/mod.rs +1 -1
  26. package/sdk-core/core/src/core_tests/workers.rs +16 -16
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +247 -28
  28. package/sdk-core/core/src/lib.rs +2 -3
  29. package/sdk-core/core/src/pollers/mod.rs +30 -3
  30. package/sdk-core/core/src/pollers/poll_buffer.rs +166 -77
  31. package/sdk-core/core/src/protosext/mod.rs +4 -8
  32. package/sdk-core/core/src/replay/mod.rs +1 -1
  33. package/sdk-core/core/src/telemetry/metrics.rs +9 -0
  34. package/sdk-core/core/src/telemetry/mod.rs +3 -0
  35. package/sdk-core/core/src/test_help/mod.rs +9 -16
  36. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +6 -31
  37. package/sdk-core/core/src/worker/activities/local_activities.rs +214 -110
  38. package/sdk-core/core/src/worker/activities.rs +72 -47
  39. package/sdk-core/core/src/worker/client/mocks.rs +1 -1
  40. package/sdk-core/core/src/worker/client.rs +45 -32
  41. package/sdk-core/core/src/worker/mod.rs +170 -122
  42. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +0 -4
  43. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +9 -2
  44. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +9 -2
  45. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -3
  46. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +74 -22
  47. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +3 -2
  48. package/sdk-core/core/src/worker/workflow/managed_run.rs +16 -3
  49. package/sdk-core/core/src/worker/workflow/mod.rs +13 -22
  50. package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -0
  51. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +4 -7
  52. package/sdk-core/core/src/worker/workflow/wft_poller.rs +38 -8
  53. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +1 -0
  54. package/sdk-core/core-api/src/worker.rs +43 -2
  55. package/sdk-core/protos/api_upstream/Makefile +1 -1
  56. package/sdk-core/protos/api_upstream/buf.yaml +1 -6
  57. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +12 -0
  58. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -0
  59. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
  60. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +1 -0
  61. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  62. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +9 -0
  63. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +19 -0
  64. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +5 -0
  65. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +36 -4
  66. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +24 -7
  67. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +4 -0
  68. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +76 -44
  69. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +23 -1
  70. package/sdk-core/protos/google/rpc/status.proto +52 -0
  71. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +16 -0
  72. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  73. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +6 -0
  74. package/sdk-core/sdk/src/lib.rs +31 -10
  75. package/sdk-core/sdk/src/workflow_future.rs +7 -5
  76. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -0
  77. package/sdk-core/sdk-core-protos/src/history_info.rs +1 -0
  78. package/sdk-core/sdk-core-protos/src/lib.rs +82 -73
  79. package/sdk-core/test-utils/Cargo.toml +1 -1
  80. package/sdk-core/test-utils/src/lib.rs +50 -37
  81. package/sdk-core/tests/integ_tests/metrics_tests.rs +143 -10
  82. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +26 -15
  83. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -1
  84. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +2 -2
  85. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +5 -1
  86. package/sdk-core/tests/integ_tests/workflow_tests.rs +1 -0
  87. package/src/conversions.rs +9 -2
  88. package/src/runtime.rs +5 -7
  89. package/ts/errors.ts +15 -0
  90. 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, WorkflowResult,
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!("Oh nooooo")
977
- },
1068
+ panic!()
1069
+ }),
978
1070
  );
979
1071
  worker.register_activity(
980
1072
  "echo",
@@ -64,7 +64,7 @@ async fn shutdown_interrupts_both_polls() {
64
64
  mock_client
65
65
  .expect_poll_workflow_task()
66
66
  .times(1)
67
- .returning(move |_, _| {
67
+ .returning(move |_| {
68
68
  async move {
69
69
  BARR.wait().await;
70
70
  sleep(Duration::from_secs(1)).await;
@@ -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, mock_manual_poller, mock_worker,
5
- MockPollCfg, MockWorkerInputs, MocksHolder, ResponseType, WorkerExt,
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 futures::FutureExt;
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::RespondWorkflowTaskCompletedResponse,
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
- mock_poller.expect_poll().returning(move || {
93
- let mut rx = rx.clone();
94
- async move {
95
- // Don't resolve polls until worker shuts down
96
- rx.changed().await.unwrap();
97
- // We don't want to return a real response here because it would get buffered and
98
- // then we'd have real work to do to be able to finish shutdown.
99
- Some(Ok(Default::default()))
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::new_from_poller(Box::new(mock_poller));
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().await, 1);
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().await, 2);
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().await, 1);
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
- .expect_poll_workflow_task()
2007
- .returning(move |_, _| {
2008
- let t = canned_histories::single_timer("1");
2009
- let poll_resp = hist_to_poll_resp(&t, wfid.to_owned(), 2.into()).resp;
2010
- async move {
2011
- task_barr.wait().await;
2012
- Ok(poll_resp.clone())
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(1_usize)
2023
- .max_cached_workflows(10_usize)
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
- .expect_poll_workflow_task()
2084
- .returning(move |_, _| {
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 can_cmd = c.commands.pop().unwrap().attributes.unwrap();
2091
- if let Attributes::ContinueAsNewWorkflowExecutionCommandAttributes(a) = can_cmd {
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
+ }
@@ -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 = BoxedPoller<PollActivityTaskQueueResponse>;
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! {