@temporalio/core-bridge 0.23.0 → 1.0.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.
Files changed (135) hide show
  1. package/Cargo.lock +118 -15
  2. package/Cargo.toml +2 -1
  3. package/LICENSE.md +1 -1
  4. package/README.md +1 -1
  5. package/index.d.ts +47 -18
  6. package/package.json +7 -7
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/releases/x86_64-apple-darwin/index.node +0 -0
  10. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  11. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  12. package/sdk-core/.buildkite/docker/docker-compose.yaml +4 -2
  13. package/sdk-core/ARCHITECTURE.md +9 -7
  14. package/sdk-core/README.md +5 -1
  15. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -3
  17. package/sdk-core/client/src/lib.rs +26 -8
  18. package/sdk-core/client/src/raw.rs +166 -54
  19. package/sdk-core/client/src/retry.rs +9 -4
  20. package/sdk-core/client/src/workflow_handle/mod.rs +4 -2
  21. package/sdk-core/core/Cargo.toml +2 -0
  22. package/sdk-core/core/src/abstractions.rs +137 -16
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +258 -63
  24. package/sdk-core/core/src/core_tests/child_workflows.rs +1 -2
  25. package/sdk-core/core/src/core_tests/determinism.rs +2 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +8 -7
  27. package/sdk-core/core/src/core_tests/queries.rs +146 -60
  28. package/sdk-core/core/src/core_tests/replay_flag.rs +1 -1
  29. package/sdk-core/core/src/core_tests/workers.rs +39 -23
  30. package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +387 -280
  32. package/sdk-core/core/src/lib.rs +6 -4
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +16 -10
  34. package/sdk-core/core/src/protosext/mod.rs +6 -6
  35. package/sdk-core/core/src/retry_logic.rs +1 -1
  36. package/sdk-core/core/src/telemetry/metrics.rs +21 -7
  37. package/sdk-core/core/src/telemetry/mod.rs +18 -4
  38. package/sdk-core/core/src/test_help/mod.rs +341 -109
  39. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +18 -9
  40. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -16
  41. package/sdk-core/core/src/worker/activities.rs +156 -29
  42. package/sdk-core/core/src/worker/client.rs +1 -0
  43. package/sdk-core/core/src/worker/mod.rs +132 -659
  44. package/sdk-core/core/src/{workflow → worker/workflow}/bridge.rs +1 -1
  45. package/sdk-core/core/src/{workflow → worker/workflow}/driven_workflow.rs +1 -1
  46. package/sdk-core/core/src/{workflow → worker/workflow}/history_update.rs +16 -2
  47. package/sdk-core/core/src/{workflow → worker/workflow}/machines/activity_state_machine.rs +39 -4
  48. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_external_state_machine.rs +5 -2
  49. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_workflow_state_machine.rs +1 -1
  50. package/sdk-core/core/src/{workflow → worker/workflow}/machines/child_workflow_state_machine.rs +2 -4
  51. package/sdk-core/core/src/{workflow → worker/workflow}/machines/complete_workflow_state_machine.rs +0 -0
  52. package/sdk-core/core/src/{workflow → worker/workflow}/machines/continue_as_new_workflow_state_machine.rs +1 -1
  53. package/sdk-core/core/src/{workflow → worker/workflow}/machines/fail_workflow_state_machine.rs +0 -0
  54. package/sdk-core/core/src/{workflow → worker/workflow}/machines/local_activity_state_machine.rs +2 -5
  55. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mod.rs +1 -1
  56. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
  57. package/sdk-core/core/src/{workflow → worker/workflow}/machines/patch_state_machine.rs +1 -1
  58. package/sdk-core/core/src/{workflow → worker/workflow}/machines/side_effect_state_machine.rs +0 -0
  59. package/sdk-core/core/src/{workflow → worker/workflow}/machines/signal_external_state_machine.rs +4 -2
  60. package/sdk-core/core/src/{workflow → worker/workflow}/machines/timer_state_machine.rs +1 -2
  61. package/sdk-core/core/src/{workflow → worker/workflow}/machines/transition_coverage.rs +1 -1
  62. package/sdk-core/core/src/{workflow → worker/workflow}/machines/upsert_search_attributes_state_machine.rs +5 -7
  63. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines/local_acts.rs +2 -2
  64. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines.rs +40 -16
  65. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_task_state_machine.rs +0 -0
  66. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
  67. package/sdk-core/core/src/worker/workflow/managed_run.rs +627 -0
  68. package/sdk-core/core/src/worker/workflow/mod.rs +1115 -0
  69. package/sdk-core/core/src/worker/workflow/run_cache.rs +143 -0
  70. package/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
  71. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +936 -0
  72. package/sdk-core/core-api/src/errors.rs +3 -10
  73. package/sdk-core/core-api/src/lib.rs +2 -1
  74. package/sdk-core/core-api/src/worker.rs +26 -2
  75. package/sdk-core/etc/dynamic-config.yaml +2 -0
  76. package/sdk-core/integ-with-otel.sh +1 -1
  77. package/sdk-core/protos/api_upstream/Makefile +4 -4
  78. package/sdk-core/protos/api_upstream/api-linter.yaml +2 -0
  79. package/sdk-core/protos/api_upstream/buf.yaml +8 -9
  80. package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
  81. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -1
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
  83. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
  84. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +3 -1
  85. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +3 -0
  87. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +32 -4
  88. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +69 -19
  89. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +13 -0
  90. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +163 -0
  91. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +97 -0
  92. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
  93. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +25 -0
  94. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +180 -3
  95. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +53 -3
  96. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +2 -2
  97. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +6 -5
  98. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -1
  99. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +2 -1
  100. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +0 -64
  101. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -1
  102. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +11 -8
  103. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +30 -25
  104. package/sdk-core/sdk/src/activity_context.rs +12 -5
  105. package/sdk-core/sdk/src/app_data.rs +37 -0
  106. package/sdk-core/sdk/src/lib.rs +76 -43
  107. package/sdk-core/sdk/src/workflow_context/options.rs +8 -6
  108. package/sdk-core/sdk/src/workflow_context.rs +14 -19
  109. package/sdk-core/sdk/src/workflow_future.rs +11 -6
  110. package/sdk-core/sdk-core-protos/src/history_builder.rs +19 -5
  111. package/sdk-core/sdk-core-protos/src/history_info.rs +11 -6
  112. package/sdk-core/sdk-core-protos/src/lib.rs +74 -176
  113. package/sdk-core/test-utils/src/lib.rs +85 -72
  114. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -9
  115. package/sdk-core/tests/integ_tests/polling_tests.rs +12 -0
  116. package/sdk-core/tests/integ_tests/queries_tests.rs +39 -22
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +49 -4
  118. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  120. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +74 -13
  121. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +19 -0
  122. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  123. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests.rs +10 -23
  125. package/sdk-core/tests/load_tests.rs +8 -3
  126. package/sdk-core/tests/main.rs +2 -1
  127. package/src/conversions.rs +47 -39
  128. package/src/errors.rs +10 -21
  129. package/src/lib.rs +342 -325
  130. package/sdk-core/core/src/pending_activations.rs +0 -173
  131. package/sdk-core/core/src/worker/wft_delivery.rs +0 -81
  132. package/sdk-core/core/src/workflow/mod.rs +0 -478
  133. package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +0 -194
  134. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +0 -418
  135. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +0 -989
@@ -1,13 +1,19 @@
1
1
  //! This module contains very generic helpers that can be used codebase-wide
2
2
 
3
3
  use crate::MetricsContext;
4
- use tokio::sync::{AcquireError, Semaphore, SemaphorePermit};
4
+ use futures::{stream, Stream, StreamExt};
5
+ use std::{
6
+ fmt::{Debug, Formatter},
7
+ future::Future,
8
+ sync::Arc,
9
+ };
10
+ use tokio::sync::{AcquireError, Notify, OwnedSemaphorePermit, Semaphore, TryAcquireError};
5
11
 
6
12
  /// Wraps a [Semaphore] with a function call that is fed the available permits any time a permit is
7
13
  /// acquired or restored through the provided methods
14
+ #[derive(Clone)]
8
15
  pub(crate) struct MeteredSemaphore {
9
- pub sem: Semaphore,
10
- max_permits: usize,
16
+ sem: Arc<Semaphore>,
11
17
  metrics_ctx: MetricsContext,
12
18
  record_fn: fn(&MetricsContext, usize),
13
19
  }
@@ -19,27 +25,142 @@ impl MeteredSemaphore {
19
25
  record_fn: fn(&MetricsContext, usize),
20
26
  ) -> Self {
21
27
  Self {
22
- sem: Semaphore::new(inital_permits),
23
- max_permits: inital_permits,
28
+ sem: Arc::new(Semaphore::new(inital_permits)),
24
29
  metrics_ctx,
25
30
  record_fn,
26
31
  }
27
32
  }
28
33
 
29
- pub async fn acquire(&self) -> Result<SemaphorePermit<'_>, AcquireError> {
30
- let res = self.sem.acquire().await;
34
+ pub fn available_permits(&self) -> usize {
35
+ self.sem.available_permits()
36
+ }
37
+
38
+ pub async fn acquire_owned(&self) -> Result<OwnedMeteredSemPermit, AcquireError> {
39
+ let res = self.sem.clone().acquire_owned().await?;
40
+ self.record();
41
+ Ok(OwnedMeteredSemPermit {
42
+ inner: res,
43
+ record_fn: self.record_drop_owned(),
44
+ })
45
+ }
46
+
47
+ pub fn try_acquire_owned(&self) -> Result<OwnedMeteredSemPermit, TryAcquireError> {
48
+ let res = self.sem.clone().try_acquire_owned()?;
49
+ self.record();
50
+ Ok(OwnedMeteredSemPermit {
51
+ inner: res,
52
+ record_fn: self.record_drop_owned(),
53
+ })
54
+ }
55
+
56
+ fn record(&self) {
31
57
  (self.record_fn)(&self.metrics_ctx, self.sem.available_permits());
32
- res
33
58
  }
34
59
 
35
- /// Adds just one permit. Will not add if already at the initial/max capacity.
36
- pub fn add_permit(&self) {
37
- if self.sem.available_permits() < self.max_permits {
38
- self.sem.add_permits(1);
39
- (self.record_fn)(&self.metrics_ctx, self.sem.available_permits());
40
- } else if cfg!(debug_assertions) {
41
- // Panic only during debug mode if this happens
42
- panic!("Tried to add permit to a semaphore that already was at capacity!");
60
+ fn record_drop_owned(&self) -> Box<dyn Fn() + Send + Sync> {
61
+ let rcf = self.record_fn;
62
+ let mets = self.metrics_ctx.clone();
63
+ let sem = self.sem.clone();
64
+ Box::new(move || rcf(&mets, sem.available_permits() + 1))
65
+ }
66
+ }
67
+
68
+ /// Wraps an [OwnedSemaphorePermit] to update metrics when it's dropped
69
+ pub(crate) struct OwnedMeteredSemPermit {
70
+ inner: OwnedSemaphorePermit,
71
+ record_fn: Box<dyn Fn() + Send + Sync>,
72
+ }
73
+ impl Drop for OwnedMeteredSemPermit {
74
+ fn drop(&mut self) {
75
+ (self.record_fn)()
76
+ }
77
+ }
78
+ impl Debug for OwnedMeteredSemPermit {
79
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80
+ self.inner.fmt(f)
81
+ }
82
+ }
83
+
84
+ /// From the input stream, create a new stream which only pulls from the input stream when allowed.
85
+ /// When allowed is determined by the passed in `proceeder` which must return a future every time
86
+ /// it's called. The input stream is only pulled from when that future resolves.
87
+ pub(crate) fn stream_when_allowed<S, F, FF>(
88
+ input: S,
89
+ proceeder: FF,
90
+ ) -> impl Stream<Item = (S::Item, F::Output)>
91
+ where
92
+ S: Stream + Send + 'static,
93
+ F: Future,
94
+ FF: FnMut() -> F,
95
+ {
96
+ let acceptable_notify = Arc::new(Notify::new());
97
+ acceptable_notify.notify_one();
98
+ let stream = stream::unfold(
99
+ (proceeder, input.boxed()),
100
+ |(mut proceeder, mut input)| async {
101
+ let v = proceeder().await;
102
+ input.next().await.map(|i| ((i, v), (proceeder, input)))
103
+ },
104
+ );
105
+ stream
106
+ }
107
+
108
+ macro_rules! dbg_panic {
109
+ ($($arg:tt)*) => {
110
+ error!($($arg)*);
111
+ debug_assert!(true, $($arg)*);
112
+ };
113
+ }
114
+ pub(crate) use dbg_panic;
115
+
116
+ #[cfg(test)]
117
+ mod tests {
118
+ use super::*;
119
+ use futures::pin_mut;
120
+ use std::{cell::RefCell, task::Poll};
121
+ use tokio::sync::mpsc::unbounded_channel;
122
+
123
+ // This is fine. Test only / guaranteed to happen serially.
124
+ #[allow(clippy::await_holding_refcell_ref)]
125
+ #[test]
126
+ fn stream_when_allowed_works() {
127
+ let inputs = stream::iter([1, 2, 3]);
128
+ let (allow_tx, allow_rx) = unbounded_channel();
129
+ let allow_rx = RefCell::new(allow_rx);
130
+ let when_allowed = stream_when_allowed(inputs, || async {
131
+ allow_rx.borrow_mut().recv().await.unwrap()
132
+ });
133
+
134
+ let waker = futures::task::noop_waker_ref();
135
+ let mut cx = std::task::Context::from_waker(waker);
136
+ pin_mut!(when_allowed);
137
+
138
+ allow_tx.send(()).unwrap();
139
+ assert_eq!(
140
+ when_allowed.poll_next_unpin(&mut cx),
141
+ Poll::Ready(Some((1, ())))
142
+ );
143
+ // Now, it won't be ready
144
+ for _ in 1..10 {
145
+ assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Pending);
146
+ }
147
+ allow_tx.send(()).unwrap();
148
+ assert_eq!(
149
+ when_allowed.poll_next_unpin(&mut cx),
150
+ Poll::Ready(Some((2, ())))
151
+ );
152
+ for _ in 1..10 {
153
+ assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Pending);
154
+ }
155
+ allow_tx.send(()).unwrap();
156
+ assert_eq!(
157
+ when_allowed.poll_next_unpin(&mut cx),
158
+ Poll::Ready(Some((3, ())))
159
+ );
160
+ for _ in 1..10 {
161
+ assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Pending);
43
162
  }
163
+ allow_tx.send(()).unwrap();
164
+ assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Ready(None));
44
165
  }
45
166
  }
@@ -1,11 +1,12 @@
1
1
  use crate::{
2
- job_assert,
2
+ advance_fut, job_assert,
3
3
  test_help::{
4
- build_fake_worker, canned_histories, gen_assert_and_reply, mock_manual_poller, mock_poller,
5
- mock_worker, poll_and_reply, test_worker_cfg, MockWorker, MocksHolder,
4
+ build_fake_worker, build_mock_pollers, canned_histories, gen_assert_and_reply,
5
+ mock_manual_poller, mock_poller, mock_poller_from_resps, mock_worker, poll_and_reply,
6
+ single_hist_mock_sg, test_worker_cfg, MockPollCfg, MockWorkerInputs, MocksHolder,
7
+ ResponseType, WorkflowCachingPolicy, TEST_Q,
6
8
  },
7
9
  worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
8
- workflow::WorkflowCachingPolicy::NonSticky,
9
10
  ActivityHeartbeat, Worker, WorkerConfigBuilder,
10
11
  };
11
12
  use futures::FutureExt;
@@ -13,9 +14,14 @@ use std::{
13
14
  cell::RefCell,
14
15
  collections::{hash_map::Entry, HashMap, VecDeque},
15
16
  rc::Rc,
16
- sync::atomic::{AtomicUsize, Ordering},
17
+ sync::{
18
+ atomic::{AtomicUsize, Ordering},
19
+ Arc,
20
+ },
17
21
  time::Duration,
18
22
  };
23
+ use temporal_client::WorkflowOptions;
24
+ use temporal_sdk::{ActivityOptions, WfContext};
19
25
  use temporal_sdk_core_api::Worker as WorkerTrait;
20
26
  use temporal_sdk_core_protos::{
21
27
  coresdk::{
@@ -26,16 +32,22 @@ use temporal_sdk_core_protos::{
26
32
  ActivityCancellationType, CompleteWorkflowExecution, RequestCancelActivity,
27
33
  ScheduleActivity,
28
34
  },
35
+ workflow_completion::WorkflowActivationCompletion,
29
36
  ActivityTaskCompletion,
30
37
  },
31
- temporal::api::workflowservice::v1::{
32
- PollActivityTaskQueueResponse, RecordActivityTaskHeartbeatResponse,
33
- RespondActivityTaskCanceledResponse, RespondActivityTaskCompletedResponse,
34
- RespondActivityTaskFailedResponse,
38
+ temporal::api::{
39
+ command::v1::command::Attributes,
40
+ enums::v1::EventType,
41
+ workflowservice::v1::{
42
+ PollActivityTaskQueueResponse, RecordActivityTaskHeartbeatResponse,
43
+ RespondActivityTaskCanceledResponse, RespondActivityTaskCompletedResponse,
44
+ RespondActivityTaskFailedResponse, RespondWorkflowTaskCompletedResponse,
45
+ },
35
46
  },
47
+ TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
36
48
  };
37
- use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd};
38
- use tokio::{join, time::sleep};
49
+ use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd, TestWorker};
50
+ use tokio::{sync::Barrier, time::sleep};
39
51
 
40
52
  #[tokio::test]
41
53
  async fn max_activities_respected() {
@@ -77,23 +89,18 @@ async fn max_activities_respected() {
77
89
  // We allow two outstanding activities, therefore first two polls should return right away
78
90
  let r1 = worker.poll_activity_task().await.unwrap();
79
91
  let _r2 = worker.poll_activity_task().await.unwrap();
80
- // Third should block until we complete one of the first two
81
- let last_finisher = AtomicUsize::new(0);
82
- tokio::join! {
83
- async {
84
- worker.complete_activity_task(ActivityTaskCompletion {
85
- task_token: r1.task_token,
86
- result: Some(ActivityExecutionResult::ok(vec![1].into()))
87
- }).await.unwrap();
88
- last_finisher.store(1, Ordering::SeqCst);
89
- },
90
- async {
91
- worker.poll_activity_task().await.unwrap();
92
- last_finisher.store(2, Ordering::SeqCst);
93
- }
94
- };
95
- // So that we know we blocked
96
- assert_eq!(last_finisher.load(Ordering::Acquire), 2);
92
+ // Third poll should block until we complete one of the first two. To ensure this, manually
93
+ // poll it a bunch to see it's not resolving.
94
+ let poll_fut = worker.poll_activity_task();
95
+ advance_fut!(poll_fut);
96
+ worker
97
+ .complete_activity_task(ActivityTaskCompletion {
98
+ task_token: r1.task_token,
99
+ result: Some(ActivityExecutionResult::ok(vec![1].into())),
100
+ })
101
+ .await
102
+ .unwrap();
103
+ poll_fut.await.unwrap();
97
104
  }
98
105
 
99
106
  #[tokio::test]
@@ -102,7 +109,7 @@ async fn activity_not_found_returns_ok() {
102
109
  // Mock won't even be called, since we weren't tracking activity
103
110
  mock_client.expect_complete_activity_task().times(0);
104
111
 
105
- let core = mock_worker(MocksHolder::from_client_with_responses(mock_client, [], []));
112
+ let core = mock_worker(MocksHolder::from_client_with_activities(mock_client, []));
106
113
 
107
114
  core.complete_activity_task(ActivityTaskCompletion {
108
115
  task_token: vec![1],
@@ -133,22 +140,23 @@ async fn heartbeats_report_cancels_only_once() {
133
140
  .times(1)
134
141
  .returning(|_, _| Ok(RespondActivityTaskCanceledResponse::default()));
135
142
 
136
- let core = mock_worker(MocksHolder::from_client_with_responses(
143
+ let core = mock_worker(MocksHolder::from_client_with_activities(
137
144
  mock_client,
138
- [],
139
145
  [
140
146
  PollActivityTaskQueueResponse {
141
147
  task_token: vec![1],
142
148
  activity_id: "act1".to_string(),
143
149
  heartbeat_timeout: Some(Duration::from_millis(1).into()),
144
150
  ..Default::default()
145
- },
151
+ }
152
+ .into(),
146
153
  PollActivityTaskQueueResponse {
147
154
  task_token: vec![2],
148
155
  activity_id: "act2".to_string(),
149
156
  heartbeat_timeout: Some(Duration::from_millis(1).into()),
150
157
  ..Default::default()
151
- },
158
+ }
159
+ .into(),
152
160
  ],
153
161
  ));
154
162
 
@@ -248,7 +256,7 @@ async fn activity_cancel_interrupts_poll() {
248
256
  .times(1)
249
257
  .returning(|_, _| async { Ok(RespondActivityTaskCompletedResponse::default()) }.boxed());
250
258
 
251
- let mw = MockWorker {
259
+ let mw = MockWorkerInputs {
252
260
  act_poller: Some(Box::from(mock_poller)),
253
261
  ..Default::default()
254
262
  };
@@ -300,7 +308,7 @@ async fn activity_poll_timeout_retries() {
300
308
  }))
301
309
  }
302
310
  });
303
- let mw = MockWorker {
311
+ let mw = MockWorkerInputs {
304
312
  act_poller: Some(Box::from(mock_act_poller)),
305
313
  ..Default::default()
306
314
  };
@@ -430,7 +438,7 @@ async fn activity_timeout_no_double_resolve() {
430
438
 
431
439
  poll_and_reply(
432
440
  &core,
433
- NonSticky,
441
+ WorkflowCachingPolicy::NonSticky,
434
442
  &[
435
443
  gen_assert_and_reply(
436
444
  &job_assert!(workflow_activation_job::Variant::StartWorkflow(_)),
@@ -490,41 +498,34 @@ async fn can_heartbeat_acts_during_shutdown() {
490
498
  .times(1)
491
499
  .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
492
500
 
493
- let core = mock_worker(MocksHolder::from_client_with_responses(
501
+ let core = mock_worker(MocksHolder::from_client_with_activities(
494
502
  mock_client,
495
- [],
496
503
  [PollActivityTaskQueueResponse {
497
504
  task_token: vec![1],
498
505
  activity_id: "act1".to_string(),
499
506
  heartbeat_timeout: Some(Duration::from_millis(1).into()),
500
507
  ..Default::default()
501
- }],
508
+ }
509
+ .into()],
502
510
  ));
503
511
 
504
512
  let act = core.poll_activity_task().await.unwrap();
505
- let complete_order = RefCell::new(vec![]);
506
- // Start shutdown before completing the activity
507
- let shutdown_fut = async {
508
- core.shutdown().await;
509
- complete_order.borrow_mut().push(1);
510
- };
511
- let complete_fut = async {
512
- core.record_activity_heartbeat(ActivityHeartbeat {
513
- task_token: act.task_token.clone(),
513
+ // Make sure shutdown has progressed before trying to record heartbeat / complete
514
+ let shutdown_fut = core.shutdown();
515
+ advance_fut!(shutdown_fut);
516
+ core.record_activity_heartbeat(ActivityHeartbeat {
517
+ task_token: act.task_token.clone(),
514
518
 
515
- details: vec![vec![1_u8, 2, 3].into()],
516
- });
517
- core.complete_activity_task(ActivityTaskCompletion {
518
- task_token: act.task_token,
519
+ details: vec![vec![1_u8, 2, 3].into()],
520
+ });
521
+ core.complete_activity_task(ActivityTaskCompletion {
522
+ task_token: act.task_token,
519
523
 
520
- result: Some(ActivityExecutionResult::ok(vec![1].into())),
521
- })
522
- .await
523
- .unwrap();
524
- complete_order.borrow_mut().push(2);
525
- };
526
- join!(shutdown_fut, complete_fut);
527
- assert_eq!(&complete_order.into_inner(), &[2, 1])
524
+ result: Some(ActivityExecutionResult::ok(vec![1].into())),
525
+ })
526
+ .await
527
+ .unwrap();
528
+ shutdown_fut.await;
528
529
  }
529
530
 
530
531
  /// Verifies that if a user has tried to record a heartbeat and then immediately after failed the
@@ -550,15 +551,15 @@ async fn complete_act_with_fail_flushes_heartbeat() {
550
551
  .times(1)
551
552
  .returning(|_, _| Ok(RespondActivityTaskFailedResponse::default()));
552
553
 
553
- let core = mock_worker(MocksHolder::from_client_with_responses(
554
+ let core = mock_worker(MocksHolder::from_client_with_activities(
554
555
  mock_client,
555
- [],
556
556
  [PollActivityTaskQueueResponse {
557
557
  task_token: vec![1],
558
558
  activity_id: "act1".to_string(),
559
559
  heartbeat_timeout: Some(Duration::from_secs(10).into()),
560
560
  ..Default::default()
561
- }],
561
+ }
562
+ .into()],
562
563
  ));
563
564
 
564
565
  let act = core.poll_activity_task().await.unwrap();
@@ -600,9 +601,203 @@ async fn max_tq_acts_set_passed_to_poll_properly() {
600
601
  .namespace("enchi")
601
602
  .task_queue("cat")
602
603
  .max_concurrent_at_polls(1_usize)
604
+ .worker_build_id("test_bin_id")
603
605
  .max_task_queue_activities_per_second(rate)
604
606
  .build()
605
607
  .unwrap();
606
608
  let worker = Worker::new_test(cfg, mock_client);
607
609
  worker.poll_activity_task().await.unwrap();
608
610
  }
611
+
612
+ /// This test verifies that activity tasks which come as replies to completing a WFT are properly
613
+ /// delivered via polling.
614
+ #[tokio::test]
615
+ async fn activity_tasks_from_completion_are_delivered() {
616
+ let wfid = "fake_wf_id";
617
+ let mut t = TestHistoryBuilder::default();
618
+ t.add_by_type(EventType::WorkflowExecutionStarted);
619
+ t.add_full_wf_task();
620
+ let schedid = t.add_activity_task_scheduled("act_id");
621
+ let startid = t.add_activity_task_started(schedid);
622
+ t.add_activity_task_completed(schedid, startid, b"hi".into());
623
+ t.add_full_wf_task();
624
+ t.add_workflow_execution_completed();
625
+
626
+ let mut mock = mock_workflow_client();
627
+ mock.expect_complete_workflow_task()
628
+ .times(1)
629
+ .returning(move |_| {
630
+ Ok(RespondWorkflowTaskCompletedResponse {
631
+ workflow_task: None,
632
+ activity_tasks: vec![PollActivityTaskQueueResponse {
633
+ task_token: vec![1],
634
+ activity_id: "act1".to_string(),
635
+ ..Default::default()
636
+ }],
637
+ })
638
+ });
639
+ mock.expect_complete_activity_task()
640
+ .times(1)
641
+ .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
642
+ let mut mock = single_hist_mock_sg(wfid, t, [1], mock, true);
643
+ let mut mock_poller = mock_manual_poller();
644
+ mock_poller
645
+ .expect_poll()
646
+ .returning(|| futures::future::pending().boxed());
647
+ mock.set_act_poller(Box::new(mock_poller));
648
+ mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
649
+ let core = mock_worker(mock);
650
+
651
+ let wf_task = core.poll_workflow_activation().await.unwrap();
652
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
653
+ wf_task.run_id,
654
+ ScheduleActivity {
655
+ seq: 1,
656
+ activity_id: "act_id".to_string(),
657
+ cancellation_type: ActivityCancellationType::TryCancel as i32,
658
+ ..Default::default()
659
+ }
660
+ .into(),
661
+ ))
662
+ .await
663
+ .unwrap();
664
+
665
+ // We should see the activity when we poll now
666
+ let act_task = core.poll_activity_task().await.unwrap();
667
+ assert_eq!(act_task.task_token, vec![1]);
668
+
669
+ core.complete_activity_task(ActivityTaskCompletion {
670
+ task_token: act_task.task_token.clone(),
671
+ result: Some(ActivityExecutionResult::ok("hi".into())),
672
+ })
673
+ .await
674
+ .unwrap();
675
+
676
+ core.shutdown().await;
677
+ }
678
+
679
+ #[tokio::test]
680
+ async fn activity_tasks_from_completion_reserve_slots() {
681
+ let wf_id = "fake_wf_id";
682
+ let mut t = TestHistoryBuilder::default();
683
+ t.add_by_type(EventType::WorkflowExecutionStarted);
684
+ t.add_full_wf_task();
685
+ let schedid = t.add_activity_task_scheduled("1");
686
+ let startid = t.add_activity_task_started(schedid);
687
+ t.add_activity_task_completed(schedid, startid, b"hi".into());
688
+ t.add_full_wf_task();
689
+ let schedid = t.add_activity_task_scheduled("2");
690
+ let startid = t.add_activity_task_started(schedid);
691
+ t.add_activity_task_completed(schedid, startid, b"hi".into());
692
+ t.add_full_wf_task();
693
+ t.add_workflow_execution_completed();
694
+
695
+ let mut mock = mock_workflow_client();
696
+ // Set up two tasks to be returned via normal activity polling
697
+ let act_tasks = VecDeque::from(vec![
698
+ PollActivityTaskQueueResponse {
699
+ task_token: vec![1],
700
+ activity_id: "act1".to_string(),
701
+ ..Default::default()
702
+ }
703
+ .into(),
704
+ PollActivityTaskQueueResponse {
705
+ task_token: vec![2],
706
+ activity_id: "act2".to_string(),
707
+ ..Default::default()
708
+ }
709
+ .into(),
710
+ ]);
711
+ mock.expect_complete_activity_task()
712
+ .times(2)
713
+ .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
714
+ let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
715
+ let mut mh = MockPollCfg::from_resp_batches(
716
+ wf_id,
717
+ t,
718
+ [
719
+ ResponseType::ToTaskNum(1),
720
+ // We don't want the second task to be delivered until *after* the activity tasks
721
+ // have been completed, so that the second activity schedule will have slots available
722
+ ResponseType::UntilResolved(
723
+ async {
724
+ barr.wait().await;
725
+ barr.wait().await;
726
+ }
727
+ .boxed(),
728
+ 2,
729
+ ),
730
+ ResponseType::AllHistory,
731
+ ],
732
+ mock,
733
+ );
734
+ mh.completion_asserts = Some(Box::new(|wftc| {
735
+ // Make sure when we see the completion with the schedule act command that it does
736
+ // not have the eager execution flag set the first time, and does the second.
737
+ if let Some(Attributes::ScheduleActivityTaskCommandAttributes(attrs)) =
738
+ wftc.commands.get(0).and_then(|cmd| cmd.attributes.as_ref())
739
+ {
740
+ if attrs.activity_id == "1" {
741
+ assert!(!attrs.request_eager_execution);
742
+ } else {
743
+ assert!(attrs.request_eager_execution);
744
+ }
745
+ }
746
+ }));
747
+ let mut mock = build_mock_pollers(mh);
748
+ mock.worker_cfg(|cfg| {
749
+ cfg.max_cached_workflows = 2;
750
+ cfg.max_outstanding_activities = 2;
751
+ });
752
+ mock.set_act_poller(mock_poller_from_resps(act_tasks));
753
+ let core = Arc::new(mock_worker(mock));
754
+ let mut worker = TestWorker::new(core.clone(), TEST_Q.to_string());
755
+
756
+ // First poll for activities twice, occupying both slots
757
+ let at1 = core.poll_activity_task().await.unwrap();
758
+ let at2 = core.poll_activity_task().await.unwrap();
759
+
760
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
761
+ ctx.activity(ActivityOptions {
762
+ activity_type: "act1".to_string(),
763
+ ..Default::default()
764
+ })
765
+ .await;
766
+ ctx.activity(ActivityOptions {
767
+ activity_type: "act2".to_string(),
768
+ ..Default::default()
769
+ })
770
+ .await;
771
+ Ok(().into())
772
+ });
773
+
774
+ worker
775
+ .submit_wf(
776
+ wf_id.to_owned(),
777
+ DEFAULT_WORKFLOW_TYPE,
778
+ vec![],
779
+ WorkflowOptions::default(),
780
+ )
781
+ .await
782
+ .unwrap();
783
+ let act_completer = async {
784
+ barr.wait().await;
785
+ core.complete_activity_task(ActivityTaskCompletion {
786
+ task_token: at1.task_token,
787
+ result: Some(ActivityExecutionResult::ok("hi".into())),
788
+ })
789
+ .await
790
+ .unwrap();
791
+ core.complete_activity_task(ActivityTaskCompletion {
792
+ task_token: at2.task_token,
793
+ result: Some(ActivityExecutionResult::ok("hi".into())),
794
+ })
795
+ .await
796
+ .unwrap();
797
+ barr.wait().await;
798
+ };
799
+ // This wf poll should *not* set the flag that it wants tasks back since both slots are
800
+ // occupied
801
+ let run_fut = async { worker.run_until_done().await.unwrap() };
802
+ tokio::join!(run_fut, act_completer);
803
+ }
@@ -1,8 +1,7 @@
1
1
  use crate::{
2
2
  replay::DEFAULT_WORKFLOW_TYPE,
3
3
  test_help::{canned_histories, mock_sdk, MockPollCfg, ResponseType},
4
- worker::client::mocks::mock_workflow_client,
5
- workflow::managed_wf::ManagedWFFunc,
4
+ worker::{client::mocks::mock_workflow_client, ManagedWFFunc},
6
5
  };
7
6
  use temporal_client::WorkflowOptions;
8
7
  use temporal_sdk::{ChildWorkflowOptions, Signal, WfContext, WorkflowFunction, WorkflowResult};
@@ -34,7 +34,7 @@ async fn test_panic_wf_task_rejected_properly() {
34
34
  let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 2], mock);
35
35
  // We should see one wft failure which has unspecified cause, since panics don't have a defined
36
36
  // type.
37
- mh.num_expected_fails = Some(1);
37
+ mh.num_expected_fails = 1;
38
38
  mh.expect_fail_wft_matcher =
39
39
  Box::new(|_, cause, _| matches!(cause, WorkflowTaskFailedCause::Unspecified));
40
40
  let mut worker = mock_sdk(mh);
@@ -71,7 +71,7 @@ async fn test_wf_task_rejected_properly_due_to_nondeterminism(#[case] use_cache:
71
71
  mock,
72
72
  );
73
73
  // We should see one wft failure which has nondeterminism cause
74
- mh.num_expected_fails = Some(1);
74
+ mh.num_expected_fails = 1;
75
75
  mh.expect_fail_wft_matcher =
76
76
  Box::new(|_, cause, _| matches!(cause, WorkflowTaskFailedCause::NonDeterministicError));
77
77
  let mut worker = mock_sdk_cfg(mh, |cfg| {
@@ -12,8 +12,8 @@ use std::{
12
12
  use temporal_client::WorkflowOptions;
13
13
  use temporal_sdk::{ActContext, LocalActivityOptions, WfContext, WorkflowResult};
14
14
  use temporal_sdk_core_protos::{
15
- coresdk::{common::RetryPolicy, AsJsonPayloadExt},
16
- temporal::api::{enums::v1::EventType, failure::v1::Failure},
15
+ coresdk::AsJsonPayloadExt,
16
+ temporal::api::{common::v1::RetryPolicy, enums::v1::EventType, failure::v1::Failure},
17
17
  };
18
18
  use tokio::sync::Barrier;
19
19
 
@@ -159,12 +159,13 @@ async fn local_act_heartbeat(#[case] shutdown_middle: bool) {
159
159
 
160
160
  let wf_id = "fakeid";
161
161
  let mock = mock_workflow_client();
162
- // Allow returning incomplete history more than once, as the wft timeout can be timing sensitive
163
- // and might poll an extra time
164
- let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 2, 2, 2], mock);
162
+ let mut mh = MockPollCfg::from_resp_batches(wf_id, t, [1, 2, 2, 2], mock);
165
163
  mh.enforce_correct_number_of_polls = false;
166
- let mut worker = mock_sdk_cfg(mh, |wc| wc.max_cached_workflows = 1);
167
- let core = worker.orig_core_worker.clone();
164
+ let mut worker = mock_sdk_cfg(mh, |wc| {
165
+ wc.max_cached_workflows = 1;
166
+ wc.max_outstanding_workflow_tasks = 1;
167
+ });
168
+ let core = worker.core_worker.clone();
168
169
 
169
170
  let shutdown_barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
170
171