@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
@@ -1,51 +1,65 @@
1
1
  use crate::{
2
+ abstractions::{dbg_panic, MeteredSemaphore, OwnedMeteredSemPermit},
2
3
  pollers::{self, Poller},
3
4
  worker::client::WorkerClient,
4
5
  };
5
6
  use futures::{prelude::stream::FuturesUnordered, StreamExt};
7
+ use futures_util::{future::BoxFuture, FutureExt};
8
+ use governor::{Quota, RateLimiter};
6
9
  use std::{
7
10
  fmt::Debug,
8
11
  future::Future,
9
12
  sync::{
10
- atomic::{AtomicUsize, Ordering},
13
+ atomic::{AtomicBool, AtomicUsize, Ordering},
11
14
  Arc,
12
15
  },
16
+ time::Duration,
13
17
  };
14
- use temporal_sdk_core_protos::temporal::api::workflowservice::v1::{
15
- PollActivityTaskQueueResponse, PollWorkflowTaskQueueResponse,
18
+ use temporal_sdk_core_protos::temporal::api::{
19
+ taskqueue::v1::TaskQueue,
20
+ workflowservice::v1::{PollActivityTaskQueueResponse, PollWorkflowTaskQueueResponse},
16
21
  };
17
22
  use tokio::{
18
23
  sync::{
19
- mpsc::{channel, Receiver},
20
- Mutex, Semaphore,
24
+ broadcast,
25
+ mpsc::{unbounded_channel, UnboundedReceiver},
26
+ Mutex,
21
27
  },
22
28
  task::JoinHandle,
23
29
  };
24
30
  use tokio_util::sync::CancellationToken;
25
31
 
26
32
  pub struct LongPollBuffer<T> {
27
- buffered_polls: Mutex<Receiver<pollers::Result<T>>>,
33
+ buffered_polls: Mutex<UnboundedReceiver<pollers::Result<(T, OwnedMeteredSemPermit)>>>,
28
34
  shutdown: CancellationToken,
29
- /// This semaphore exists to ensure that we only poll server as many times as core actually
30
- /// *asked* it to be polled - otherwise we might spin and buffer polls constantly. This also
31
- /// means unit tests can continue to function in a predictable manner when calling mocks.
32
- polls_requested: Arc<Semaphore>,
33
35
  join_handles: FuturesUnordered<JoinHandle<()>>,
34
- /// Called every time the number of pollers is changed
35
- num_pollers_changed: Option<Box<dyn Fn(usize) + Send + Sync>>,
36
- active_pollers: Arc<AtomicUsize>,
36
+ /// Pollers won't actually start polling until initialized & value is sent
37
+ starter: broadcast::Sender<()>,
38
+ did_start: AtomicBool,
37
39
  }
38
40
 
39
- struct ActiveCounter<'a>(&'a AtomicUsize);
40
- impl<'a> ActiveCounter<'a> {
41
- fn new(a: &'a AtomicUsize) -> Self {
42
- a.fetch_add(1, Ordering::Relaxed);
43
- Self(a)
41
+ struct ActiveCounter<'a, F: Fn(usize)>(&'a AtomicUsize, Option<F>);
42
+ impl<'a, F> ActiveCounter<'a, F>
43
+ where
44
+ F: Fn(usize),
45
+ {
46
+ fn new(a: &'a AtomicUsize, change_fn: Option<F>) -> Self {
47
+ let v = a.fetch_add(1, Ordering::Relaxed) + 1;
48
+ if let Some(cfn) = change_fn.as_ref() {
49
+ cfn(v);
50
+ }
51
+ Self(a, change_fn)
44
52
  }
45
53
  }
46
- impl Drop for ActiveCounter<'_> {
54
+ impl<F> Drop for ActiveCounter<'_, F>
55
+ where
56
+ F: Fn(usize),
57
+ {
47
58
  fn drop(&mut self) {
48
- self.0.fetch_sub(1, Ordering::Relaxed);
59
+ let v = self.0.fetch_sub(1, Ordering::Relaxed) - 1;
60
+ if let Some(cfn) = self.1.as_ref() {
61
+ cfn(v)
62
+ }
49
63
  }
50
64
  }
51
65
 
@@ -53,42 +67,67 @@ impl<T> LongPollBuffer<T>
53
67
  where
54
68
  T: Send + Debug + 'static,
55
69
  {
56
- pub fn new<FT>(
70
+ pub(crate) fn new<FT, DelayFut>(
57
71
  poll_fn: impl Fn() -> FT + Send + Sync + 'static,
72
+ poll_semaphore: Arc<MeteredSemaphore>,
58
73
  max_pollers: usize,
59
- buffer_size: usize,
60
74
  shutdown: CancellationToken,
75
+ num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
76
+ pre_permit_delay: Option<impl Fn() -> DelayFut + Send + Sync + 'static>,
61
77
  ) -> Self
62
78
  where
63
79
  FT: Future<Output = pollers::Result<T>> + Send,
80
+ DelayFut: Future<Output = ()> + Send,
64
81
  {
65
- let (tx, rx) = channel(buffer_size);
66
- let polls_requested = Arc::new(Semaphore::new(0));
82
+ let (tx, rx) = unbounded_channel();
83
+ let (starter, wait_for_start) = broadcast::channel(1);
67
84
  let active_pollers = Arc::new(AtomicUsize::new(0));
68
85
  let join_handles = FuturesUnordered::new();
69
86
  let pf = Arc::new(poll_fn);
87
+ let nph = num_pollers_handler.map(Arc::new);
88
+ let pre_permit_delay = pre_permit_delay.map(Arc::new);
70
89
  for _ in 0..max_pollers {
71
90
  let tx = tx.clone();
72
91
  let pf = pf.clone();
73
92
  let shutdown = shutdown.clone();
74
- let polls_requested = polls_requested.clone();
75
93
  let ap = active_pollers.clone();
94
+ let poll_semaphore = poll_semaphore.clone();
95
+ let nph = nph.clone();
96
+ let pre_permit_delay = pre_permit_delay.clone();
97
+ let mut wait_for_start = wait_for_start.resubscribe();
76
98
  let jh = tokio::spawn(async move {
99
+ tokio::select! {
100
+ _ = wait_for_start.recv() => (),
101
+ _ = shutdown.cancelled() => return,
102
+ }
103
+ drop(wait_for_start);
104
+
105
+ let nph = nph.as_ref().map(|a| a.as_ref());
77
106
  loop {
78
107
  if shutdown.is_cancelled() {
79
108
  break;
80
109
  }
81
- let sp = tokio::select! {
82
- sp = polls_requested.acquire() => sp.expect("Polls semaphore not dropped"),
83
- _ = shutdown.cancelled() => continue,
110
+ if let Some(ref ppd) = pre_permit_delay {
111
+ tokio::select! {
112
+ _ = ppd() => (),
113
+ _ = shutdown.cancelled() => break,
114
+ }
115
+ }
116
+ let permit = tokio::select! {
117
+ p = poll_semaphore.acquire_owned() => p,
118
+ _ = shutdown.cancelled() => break,
119
+ };
120
+ let permit = if let Ok(p) = permit {
121
+ p
122
+ } else {
123
+ break;
84
124
  };
85
- let _active_guard = ActiveCounter::new(ap.as_ref());
125
+ let _active_guard = ActiveCounter::new(ap.as_ref(), nph);
86
126
  let r = tokio::select! {
87
127
  r = pf() => r,
88
- _ = shutdown.cancelled() => continue,
128
+ _ = shutdown.cancelled() => break,
89
129
  };
90
- sp.forget();
91
- let _ = tx.send(r).await;
130
+ let _ = tx.send(r.map(|r| (r, permit)));
92
131
  }
93
132
  });
94
133
  join_handles.push(jh);
@@ -96,50 +135,29 @@ where
96
135
  Self {
97
136
  buffered_polls: Mutex::new(rx),
98
137
  shutdown,
99
- polls_requested,
100
138
  join_handles,
101
- num_pollers_changed: None,
102
- active_pollers,
139
+ starter,
140
+ did_start: AtomicBool::new(false),
103
141
  }
104
142
  }
105
-
106
- /// Set a function that will be called every time the number of pollers changes.
107
- /// TODO: Currently a bit weird, will make more sense once we implement dynamic poller scaling.
108
- pub fn set_num_pollers_handler(&mut self, handler: impl Fn(usize) + Send + Sync + 'static) {
109
- self.num_pollers_changed = Some(Box::new(handler));
110
- }
111
143
  }
112
144
 
113
145
  #[async_trait::async_trait]
114
- impl<T> Poller<T> for LongPollBuffer<T>
146
+ impl<T> Poller<(T, OwnedMeteredSemPermit)> for LongPollBuffer<T>
115
147
  where
116
148
  T: Send + Sync + Debug + 'static,
117
149
  {
118
- /// Poll the buffer. Adds one permit to the polling pool - the point of this being that the
119
- /// buffer may support many concurrent pollers, but there is no reason to have them poll unless
120
- /// enough polls have actually been requested. Calling this function adds a permit that any
121
- /// concurrent poller may fulfill.
122
- ///
123
- /// EX: If this function is only ever called serially and always `await`ed, there will be no
124
- /// concurrent polling. If it is called many times and the futures are awaited concurrently,
125
- /// then polling will happen concurrently.
150
+ /// Poll for the next item from this poller
126
151
  ///
127
- /// Returns `None` if the poll buffer has been shut down
152
+ /// Returns `None` if the poller has been shut down
128
153
  #[instrument(name = "long_poll", level = "trace", skip(self))]
129
- async fn poll(&self) -> Option<pollers::Result<T>> {
130
- self.polls_requested.add_permits(1);
131
- if let Some(fun) = self.num_pollers_changed.as_ref() {
132
- fun(self.active_pollers.load(Ordering::Relaxed));
154
+ async fn poll(&self) -> Option<pollers::Result<(T, OwnedMeteredSemPermit)>> {
155
+ if !self.did_start.fetch_or(true, Ordering::Relaxed) {
156
+ let _ = self.starter.send(());
133
157
  }
134
158
 
135
159
  let mut locked = self.buffered_polls.lock().await;
136
- let res = (*locked).recv().await;
137
-
138
- if let Some(fun) = self.num_pollers_changed.as_ref() {
139
- fun(self.active_pollers.load(Ordering::Relaxed));
140
- }
141
-
142
- res
160
+ (*locked).recv().await
143
161
  }
144
162
 
145
163
  fn notify_shutdown(&self) {
@@ -148,7 +166,13 @@ where
148
166
 
149
167
  async fn shutdown(mut self) {
150
168
  self.notify_shutdown();
151
- while self.join_handles.next().await.is_some() {}
169
+ while let Some(jh) = self.join_handles.next().await {
170
+ if let Err(e) = jh {
171
+ if !e.is_cancelled() {
172
+ dbg_panic!("Poller task did not terminate cleanly: {:?}", e);
173
+ }
174
+ }
175
+ }
152
176
  }
153
177
 
154
178
  async fn shutdown_box(self: Box<Self>) {
@@ -165,8 +189,10 @@ pub struct WorkflowTaskPoller {
165
189
  }
166
190
 
167
191
  #[async_trait::async_trait]
168
- impl Poller<PollWorkflowTaskQueueResponse> for WorkflowTaskPoller {
169
- async fn poll(&self) -> Option<pollers::Result<PollWorkflowTaskQueueResponse>> {
192
+ impl Poller<(PollWorkflowTaskQueueResponse, OwnedMeteredSemPermit)> for WorkflowTaskPoller {
193
+ async fn poll(
194
+ &self,
195
+ ) -> Option<pollers::Result<(PollWorkflowTaskQueueResponse, OwnedMeteredSemPermit)>> {
170
196
  if let Some(sq) = self.sticky_poller.as_ref() {
171
197
  tokio::select! {
172
198
  r = self.normal_poller.poll() => r,
@@ -200,51 +226,106 @@ impl Poller<PollWorkflowTaskQueueResponse> for WorkflowTaskPoller {
200
226
  pub type PollWorkflowTaskBuffer = LongPollBuffer<PollWorkflowTaskQueueResponse>;
201
227
  pub(crate) fn new_workflow_task_buffer(
202
228
  client: Arc<dyn WorkerClient>,
203
- task_queue: String,
204
- is_sticky: bool,
229
+ task_queue: TaskQueue,
205
230
  concurrent_pollers: usize,
206
- buffer_size: usize,
231
+ semaphore: Arc<MeteredSemaphore>,
207
232
  shutdown: CancellationToken,
233
+ num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
208
234
  ) -> PollWorkflowTaskBuffer {
209
235
  LongPollBuffer::new(
210
236
  move || {
211
237
  let client = client.clone();
212
238
  let task_queue = task_queue.clone();
213
- async move { client.poll_workflow_task(task_queue, is_sticky).await }
239
+ async move { client.poll_workflow_task(task_queue).await }
214
240
  },
241
+ semaphore,
215
242
  concurrent_pollers,
216
- buffer_size,
217
243
  shutdown,
244
+ num_pollers_handler,
245
+ None::<fn() -> BoxFuture<'static, ()>>,
218
246
  )
219
247
  }
220
248
 
221
249
  pub type PollActivityTaskBuffer = LongPollBuffer<PollActivityTaskQueueResponse>;
250
+ #[allow(clippy::too_many_arguments)]
222
251
  pub(crate) fn new_activity_task_buffer(
223
252
  client: Arc<dyn WorkerClient>,
224
253
  task_queue: String,
225
254
  concurrent_pollers: usize,
226
- buffer_size: usize,
255
+ semaphore: Arc<MeteredSemaphore>,
227
256
  max_tps: Option<f64>,
228
257
  shutdown: CancellationToken,
258
+ num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
259
+ max_worker_acts_per_sec: Option<f64>,
229
260
  ) -> PollActivityTaskBuffer {
261
+ let rate_limiter = max_worker_acts_per_sec.and_then(|ps| {
262
+ Quota::with_period(Duration::from_secs_f64(ps.recip()))
263
+ .map(|q| Arc::new(RateLimiter::direct(q)))
264
+ });
230
265
  LongPollBuffer::new(
231
266
  move || {
232
267
  let client = client.clone();
233
268
  let task_queue = task_queue.clone();
234
269
  async move { client.poll_activity_task(task_queue, max_tps).await }
235
270
  },
271
+ semaphore,
236
272
  concurrent_pollers,
237
- buffer_size,
238
273
  shutdown,
274
+ num_pollers_handler,
275
+ rate_limiter.map(|rl| {
276
+ move || {
277
+ let rl = rl.clone();
278
+ async move { rl.until_ready().await }.boxed()
279
+ }
280
+ }),
239
281
  )
240
282
  }
241
283
 
284
+ #[cfg(test)]
285
+ #[derive(derive_more::Constructor)]
286
+ pub(crate) struct MockPermittedPollBuffer<PT> {
287
+ sem: Arc<MeteredSemaphore>,
288
+ inner: PT,
289
+ }
290
+
291
+ #[cfg(test)]
292
+ #[async_trait::async_trait]
293
+ impl<T, PT> Poller<(T, OwnedMeteredSemPermit)> for MockPermittedPollBuffer<PT>
294
+ where
295
+ T: Send + Sync + 'static,
296
+ PT: Poller<T> + Send + Sync + 'static,
297
+ {
298
+ async fn poll(&self) -> Option<pollers::Result<(T, OwnedMeteredSemPermit)>> {
299
+ let p = self
300
+ .sem
301
+ .acquire_owned()
302
+ .await
303
+ .expect("Semaphore in poller not closed!");
304
+ self.inner.poll().await.map(|r| r.map(|r| (r, p)))
305
+ }
306
+
307
+ fn notify_shutdown(&self) {
308
+ self.inner.notify_shutdown();
309
+ }
310
+
311
+ async fn shutdown(self) {
312
+ self.inner.shutdown().await;
313
+ }
314
+
315
+ async fn shutdown_box(self: Box<Self>) {
316
+ self.inner.shutdown().await;
317
+ }
318
+ }
319
+
242
320
  #[cfg(test)]
243
321
  mod tests {
244
322
  use super::*;
245
- use crate::worker::client::mocks::mock_manual_workflow_client;
323
+ use crate::{
324
+ telemetry::metrics::MetricsContext, worker::client::mocks::mock_manual_workflow_client,
325
+ };
246
326
  use futures::FutureExt;
247
327
  use std::time::Duration;
328
+ use temporal_sdk_core_protos::temporal::api::enums::v1::TaskQueueKind;
248
329
  use tokio::{select, sync::mpsc::channel};
249
330
 
250
331
  #[tokio::test]
@@ -253,7 +334,7 @@ mod tests {
253
334
  mock_client
254
335
  .expect_poll_workflow_task()
255
336
  .times(2)
256
- .returning(move |_, _| {
337
+ .returning(move |_| {
257
338
  async {
258
339
  tokio::time::sleep(Duration::from_millis(100)).await;
259
340
  Ok(Default::default())
@@ -263,11 +344,19 @@ mod tests {
263
344
 
264
345
  let pb = new_workflow_task_buffer(
265
346
  Arc::new(mock_client),
266
- "someq".to_string(),
267
- false,
268
- 1,
347
+ TaskQueue {
348
+ name: "sometq".to_string(),
349
+ kind: TaskQueueKind::Normal as i32,
350
+ normal_name: "".to_string(),
351
+ },
269
352
  1,
353
+ Arc::new(MeteredSemaphore::new(
354
+ 10,
355
+ MetricsContext::no_op(),
356
+ |_, _| {},
357
+ )),
270
358
  CancellationToken::new(),
359
+ None::<fn(usize)>,
271
360
  );
272
361
 
273
362
  // Poll a bunch of times, "interrupting" it each time, we should only actually have polled
@@ -337,10 +337,7 @@ impl Default for LACloseTimeouts {
337
337
  }
338
338
 
339
339
  impl ValidScheduleLA {
340
- pub fn from_schedule_la(
341
- v: ScheduleLocalActivity,
342
- wf_exe_timeout: Option<Duration>,
343
- ) -> Result<Self, anyhow::Error> {
340
+ pub fn from_schedule_la(v: ScheduleLocalActivity) -> Result<Self, anyhow::Error> {
344
341
  let original_schedule_time = v
345
342
  .original_schedule_time
346
343
  .map(|x| {
@@ -354,9 +351,7 @@ impl ValidScheduleLA {
354
351
  x.try_into()
355
352
  .map_err(|_| anyhow!("Could not convert schedule_to_close_timeout"))
356
353
  })
357
- .transpose()?
358
- // Default to execution timeout if unset
359
- .or(wf_exe_timeout);
354
+ .transpose()?;
360
355
  let mut schedule_to_start_timeout = v
361
356
  .schedule_to_start_timeout
362
357
  .map(|x| {
@@ -392,7 +387,8 @@ impl ValidScheduleLA {
392
387
  }
393
388
  (None, None) => {
394
389
  return Err(anyhow!(
395
- "One of schedule_to_close or start_to_close timeouts must be set"
390
+ "One or both of schedule_to_close or start_to_close timeouts must be set for \
391
+ local activities"
396
392
  ))
397
393
  }
398
394
  };
@@ -89,7 +89,7 @@ pub(crate) fn mock_client_from_histories(historator: Historator) -> impl WorkerC
89
89
  let hist_allow_tx = historator.replay_done_tx.clone();
90
90
  let historator = Arc::new(TokioMutex::new(historator));
91
91
 
92
- mg.expect_poll_workflow_task().returning(move |_, _| {
92
+ mg.expect_poll_workflow_task().returning(move |_| {
93
93
  let historator = historator.clone();
94
94
  async move {
95
95
  let mut hlock = historator.lock().await;
@@ -87,6 +87,7 @@ struct Instruments {
87
87
  sticky_cache_hit: Counter<u64>,
88
88
  sticky_cache_miss: Counter<u64>,
89
89
  sticky_cache_size: Histogram<u64>,
90
+ sticky_cache_evictions: Counter<u64>,
90
91
  }
91
92
 
92
93
  impl MetricsContext {
@@ -288,6 +289,13 @@ impl MetricsContext {
288
289
  .sticky_cache_size
289
290
  .record(&self.ctx, size, &self.kvs);
290
291
  }
292
+
293
+ /// Count a workflow being evicted from the cache
294
+ pub(crate) fn cache_eviction(&self) {
295
+ self.instruments
296
+ .sticky_cache_evictions
297
+ .add(&self.ctx, 1, &self.kvs);
298
+ }
291
299
  }
292
300
 
293
301
  impl Instruments {
@@ -327,6 +335,7 @@ impl Instruments {
327
335
  sticky_cache_hit: meter.counter("sticky_cache_hit"),
328
336
  sticky_cache_miss: meter.counter("sticky_cache_miss"),
329
337
  sticky_cache_size: meter.histogram(STICKY_CACHE_SIZE_NAME),
338
+ sticky_cache_evictions: meter.counter("sticky_cache_total_forced_eviction"),
330
339
  }
331
340
  }
332
341
  }
@@ -297,6 +297,9 @@ pub fn telemetry_init(opts: TelemetryOptions) -> Result<TelemetryInstance, anyho
297
297
  .with(forward_layer)
298
298
  .with(export_layer);
299
299
 
300
+ #[cfg(feature = "tokio-console")]
301
+ let reg = reg.with(console_subscriber::spawn());
302
+
300
303
  tx.send(TelemetryInstance::new(
301
304
  Arc::new(reg),
302
305
  logs_out,
@@ -1,7 +1,7 @@
1
1
  pub(crate) use temporal_sdk_core_test_utils::canned_histories;
2
2
 
3
3
  use crate::{
4
- pollers::{BoxedActPoller, BoxedPoller, BoxedWFPoller, MockManualPoller, MockPoller},
4
+ pollers::{BoxedPoller, MockManualPoller, MockPoller},
5
5
  protosext::ValidPollWFTQResponse,
6
6
  replay::TestHistoryBuilder,
7
7
  sticky_q_name_for_worker,
@@ -10,7 +10,7 @@ use crate::{
10
10
  client::{
11
11
  mocks::mock_workflow_client, MockWorkerClient, WorkerClient, WorkflowTaskCompletion,
12
12
  },
13
- new_wft_poller,
13
+ TaskPollers,
14
14
  },
15
15
  TaskToken, Worker, WorkerConfig, WorkerConfigBuilder,
16
16
  };
@@ -53,7 +53,6 @@ use temporal_sdk_core_protos::{
53
53
  use temporal_sdk_core_test_utils::TestWorker;
54
54
  use tokio::sync::{mpsc::unbounded_channel, Notify};
55
55
  use tokio_stream::wrappers::UnboundedReceiverStream;
56
- use tokio_util::sync::CancellationToken;
57
56
 
58
57
  pub const TEST_Q: &str = "q";
59
58
 
@@ -148,11 +147,12 @@ pub(crate) fn mock_worker(mocks: MocksHolder) -> Worker {
148
147
  mocks.inputs.config,
149
148
  sticky_q,
150
149
  mocks.client,
151
- mocks.inputs.wft_stream,
152
- act_poller,
150
+ TaskPollers::Mocked {
151
+ wft_stream: mocks.inputs.wft_stream,
152
+ act_poller,
153
+ },
153
154
  MetricsContext::no_op(),
154
155
  None,
155
- CancellationToken::new(),
156
156
  )
157
157
  }
158
158
 
@@ -187,7 +187,7 @@ impl MocksHolder {
187
187
  pub fn worker_cfg(&mut self, mutator: impl FnOnce(&mut WorkerConfig)) {
188
188
  mutator(&mut self.inputs.config);
189
189
  }
190
- pub fn set_act_poller(&mut self, poller: BoxedActPoller) {
190
+ pub fn set_act_poller(&mut self, poller: BoxedPoller<PollActivityTaskQueueResponse>) {
191
191
  self.inputs.act_poller = Some(poller);
192
192
  }
193
193
  /// Can be used for tests that need to avoid auto-shutdown due to running out of mock responses
@@ -199,13 +199,13 @@ impl MocksHolder {
199
199
 
200
200
  pub struct MockWorkerInputs {
201
201
  pub wft_stream: BoxStream<'static, Result<ValidPollWFTQResponse, tonic::Status>>,
202
- pub act_poller: Option<BoxedActPoller>,
202
+ pub act_poller: Option<BoxedPoller<PollActivityTaskQueueResponse>>,
203
203
  pub config: WorkerConfig,
204
204
  }
205
205
 
206
206
  impl Default for MockWorkerInputs {
207
207
  fn default() -> Self {
208
- Self::new_from_poller(Box::from(mock_poller()))
208
+ Self::new(stream::empty().boxed())
209
209
  }
210
210
  }
211
211
 
@@ -219,13 +219,6 @@ impl MockWorkerInputs {
219
219
  config: test_worker_cfg().build().unwrap(),
220
220
  }
221
221
  }
222
- pub fn new_from_poller(wf_poller: BoxedWFPoller) -> Self {
223
- Self {
224
- wft_stream: new_wft_poller(wf_poller, MetricsContext::no_op()).boxed(),
225
- act_poller: None,
226
- config: test_worker_cfg().build().unwrap(),
227
- }
228
- }
229
222
  }
230
223
 
231
224
  impl MocksHolder {
@@ -1,23 +1,11 @@
1
- use crate::{
2
- abstractions::MeteredSemaphore, pollers::BoxedActPoller, worker::activities::PermittedTqResp,
3
- MetricsContext,
4
- };
1
+ use crate::{pollers::BoxedActPoller, worker::activities::PermittedTqResp, MetricsContext};
5
2
  use futures::{stream, Stream};
6
- use governor::{
7
- clock::DefaultClock,
8
- middleware::NoOpMiddleware,
9
- state::{InMemoryState, NotKeyed},
10
- RateLimiter,
11
- };
12
- use std::sync::Arc;
13
3
  use temporal_sdk_core_protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse;
14
4
  use tokio::select;
15
5
  use tokio_util::sync::CancellationToken;
16
6
 
17
7
  struct StreamState {
18
8
  poller: BoxedActPoller,
19
- semaphore: Arc<MeteredSemaphore>,
20
- rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
21
9
  metrics: MetricsContext,
22
10
  shutdown_token: CancellationToken,
23
11
  poller_was_shutdown: bool,
@@ -25,15 +13,11 @@ struct StreamState {
25
13
 
26
14
  pub(crate) fn new_activity_task_poller(
27
15
  poller: BoxedActPoller,
28
- semaphore: Arc<MeteredSemaphore>,
29
- rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
30
16
  metrics: MetricsContext,
31
17
  shutdown_token: CancellationToken,
32
18
  ) -> impl Stream<Item = Result<PermittedTqResp, tonic::Status>> {
33
19
  let state = StreamState {
34
20
  poller,
35
- semaphore,
36
- rate_limiter,
37
21
  metrics,
38
22
  shutdown_token,
39
23
  poller_was_shutdown: false,
@@ -41,21 +25,12 @@ pub(crate) fn new_activity_task_poller(
41
25
  stream::unfold(state, |mut state| async move {
42
26
  loop {
43
27
  let poll = async {
44
- let permit = state
45
- .semaphore
46
- .acquire_owned()
47
- .await
48
- .expect("outstanding activity semaphore not closed");
49
- if !state.poller_was_shutdown {
50
- if let Some(ref rl) = state.rate_limiter {
51
- rl.until_ready().await;
52
- }
53
- }
54
28
  loop {
55
29
  return match state.poller.poll().await {
56
- Some(Ok(resp)) => {
30
+ Some(Ok((resp, permit))) => {
57
31
  if resp == PollActivityTaskQueueResponse::default() {
58
- // We get the default proto in the event that the long poll times out.
32
+ // We get the default proto in the event that the long poll times
33
+ // out.
59
34
  debug!("Poll activity task timeout");
60
35
  state.metrics.act_poll_timeout();
61
36
  continue;
@@ -66,8 +41,8 @@ pub(crate) fn new_activity_task_poller(
66
41
  warn!(error=?e, "Error while polling for activity tasks");
67
42
  Some(Err(e))
68
43
  }
69
- // If poller returns None, it's dead, thus we also return None to terminate this
70
- // stream.
44
+ // If poller returns None, it's dead, thus we also return None to terminate
45
+ // this stream.
71
46
  None => None,
72
47
  };
73
48
  }