@temporalio/core-bridge 0.20.2 → 0.22.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 (50) hide show
  1. package/Cargo.lock +137 -127
  2. package/index.d.ts +7 -2
  3. package/package.json +3 -3
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/x86_64-apple-darwin/index.node +0 -0
  6. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  7. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  8. package/sdk-core/.buildkite/docker/docker-compose.yaml +5 -4
  9. package/sdk-core/client/Cargo.toml +1 -0
  10. package/sdk-core/client/src/lib.rs +52 -9
  11. package/sdk-core/client/src/raw.rs +9 -1
  12. package/sdk-core/client/src/retry.rs +12 -1
  13. package/sdk-core/client/src/workflow_handle/mod.rs +183 -0
  14. package/sdk-core/core/src/abstractions.rs +10 -3
  15. package/sdk-core/core/src/core_tests/child_workflows.rs +7 -9
  16. package/sdk-core/core/src/core_tests/determinism.rs +8 -19
  17. package/sdk-core/core/src/core_tests/local_activities.rs +22 -32
  18. package/sdk-core/core/src/core_tests/queries.rs +272 -5
  19. package/sdk-core/core/src/core_tests/workers.rs +4 -34
  20. package/sdk-core/core/src/core_tests/workflow_tasks.rs +197 -41
  21. package/sdk-core/core/src/pending_activations.rs +11 -0
  22. package/sdk-core/core/src/telemetry/mod.rs +1 -1
  23. package/sdk-core/core/src/test_help/mod.rs +57 -7
  24. package/sdk-core/core/src/worker/mod.rs +64 -15
  25. package/sdk-core/core/src/workflow/machines/mod.rs +1 -1
  26. package/sdk-core/core/src/workflow/machines/timer_state_machine.rs +2 -2
  27. package/sdk-core/core/src/workflow/machines/workflow_machines.rs +14 -3
  28. package/sdk-core/core/src/workflow/mod.rs +5 -2
  29. package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +47 -2
  30. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +16 -2
  31. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +252 -125
  32. package/sdk-core/core-api/src/worker.rs +9 -0
  33. package/sdk-core/sdk/Cargo.toml +1 -0
  34. package/sdk-core/sdk/src/activity_context.rs +223 -0
  35. package/sdk-core/sdk/src/interceptors.rs +8 -2
  36. package/sdk-core/sdk/src/lib.rs +167 -122
  37. package/sdk-core/sdk-core-protos/src/history_info.rs +3 -7
  38. package/sdk-core/test-utils/Cargo.toml +1 -0
  39. package/sdk-core/test-utils/src/lib.rs +78 -37
  40. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +11 -4
  41. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +0 -1
  42. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +0 -3
  43. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +33 -17
  44. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +10 -1
  45. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +0 -1
  46. package/sdk-core/tests/integ_tests/workflow_tests.rs +71 -3
  47. package/sdk-core/tests/load_tests.rs +80 -6
  48. package/src/errors.rs +9 -2
  49. package/src/lib.rs +39 -16
  50. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
@@ -6,12 +6,13 @@ extern crate tracing;
6
6
 
7
7
  pub mod canned_histories;
8
8
 
9
- use futures::{stream::FuturesUnordered, StreamExt};
9
+ use crate::stream::TryStreamExt;
10
+ use futures::{stream, stream::FuturesUnordered, StreamExt};
10
11
  use log::LevelFilter;
12
+ use parking_lot::Mutex;
11
13
  use prost::Message;
12
14
  use rand::{distributions::Standard, Rng};
13
15
  use std::{
14
- cell::RefCell,
15
16
  convert::TryFrom,
16
17
  env,
17
18
  future::Future,
@@ -23,7 +24,9 @@ use std::{
23
24
  },
24
25
  time::Duration,
25
26
  };
26
- use temporal_client::{Client, RetryClient, WorkflowClientTrait, WorkflowOptions};
27
+ use temporal_client::{
28
+ Client, RetryClient, WorkflowClientTrait, WorkflowExecutionInfo, WorkflowOptions,
29
+ };
27
30
  use temporal_sdk::{interceptors::WorkerInterceptor, IntoActivityFunc, Worker, WorkflowFunction};
28
31
  use temporal_sdk_core::{
29
32
  init_replay_worker, init_worker, telemetry_init, ClientOptions, ClientOptionsBuilder,
@@ -251,25 +254,30 @@ impl CoreWfStarter {
251
254
  }
252
255
  }
253
256
 
254
- /// Provides conveniences for running integ tests with the SDK
257
+ /// Provides conveniences for running integ tests with the SDK (against real server or mocks)
255
258
  pub struct TestWorker {
256
259
  inner: Worker,
257
- client: Option<Arc<dyn WorkflowClientTrait>>,
260
+ pub orig_core_worker: Arc<dyn CoreWorker>,
261
+ client: Option<Arc<RetryClient<Client>>>,
262
+ /// Defaults true, and if set, auto-shutdown the worker once all workflows started via
263
+ /// `submit_wf` have completed (as determined by fetching their result with following runs).
264
+ pub auto_shutdown: bool,
265
+ started_workflows: Mutex<Vec<WorkflowExecutionInfo>>,
266
+ /// Used only for mocked testing, where fetching results to determine if a WF is finished is too
267
+ /// annoying to make work.
258
268
  incomplete_workflows: Arc<AtomicUsize>,
259
269
  }
260
270
  impl TestWorker {
261
271
  /// Create a new test worker
262
272
  pub fn new(core_worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
263
273
  let ct = Arc::new(AtomicUsize::new(0));
264
- let mut inner = Worker::new_from_core(core_worker, task_queue);
265
- let iceptor = WorkflowCompletionCountingInterceptor {
266
- incomplete_workflows: ct.clone(),
267
- shutdown_handle: Box::new(inner.shutdown_handle()),
268
- };
269
- inner.set_worker_interceptor(Box::new(iceptor));
274
+ let inner = Worker::new_from_core(core_worker.clone(), task_queue);
270
275
  Self {
271
276
  inner,
277
+ orig_core_worker: core_worker,
272
278
  client: None,
279
+ auto_shutdown: true,
280
+ started_workflows: Mutex::new(vec![]),
273
281
  incomplete_workflows: ct,
274
282
  }
275
283
  }
@@ -278,11 +286,6 @@ impl TestWorker {
278
286
  &mut self.inner
279
287
  }
280
288
 
281
- pub fn incr_expected_run_count(&self, amount: usize) {
282
- self.incomplete_workflows
283
- .fetch_add(amount, Ordering::AcqRel);
284
- }
285
-
286
289
  // TODO: Maybe trait-ify?
287
290
  pub fn register_wf<F: Into<WorkflowFunction>>(
288
291
  &mut self,
@@ -313,7 +316,6 @@ impl TestWorker {
313
316
  input: Vec<Payload>,
314
317
  options: WorkflowOptions,
315
318
  ) -> Result<String, anyhow::Error> {
316
- self.incr_expected_run_count(1);
317
319
  if let Some(c) = self.client.as_ref() {
318
320
  let wfid = workflow_id.into();
319
321
  let res = c
@@ -325,6 +327,11 @@ impl TestWorker {
325
327
  options,
326
328
  )
327
329
  .await?;
330
+ self.started_workflows.lock().push(WorkflowExecutionInfo {
331
+ namespace: c.namespace().to_string(),
332
+ workflow_id: wfid,
333
+ run_id: Some(res.run_id.clone()),
334
+ });
328
335
  Ok(res.run_id)
329
336
  } else {
330
337
  Ok("fake_run_id".to_string())
@@ -333,46 +340,80 @@ impl TestWorker {
333
340
 
334
341
  /// Runs until all expected workflows have completed
335
342
  pub async fn run_until_done(&mut self) -> Result<(), anyhow::Error> {
336
- self.inner.run().await
343
+ self.run_until_done_intercepted(Option::<TestWorkerInterceptor>::None)
344
+ .await
337
345
  }
338
346
 
339
347
  /// See [Self::run_until_done], except calls the provided callback just before performing core
340
348
  /// shutdown.
341
- pub async fn run_until_done_shutdown_hook(
349
+ pub async fn run_until_done_intercepted(
342
350
  &mut self,
343
- before_shutdown: impl FnOnce() + 'static,
351
+ interceptor: Option<impl WorkerInterceptor + 'static>,
344
352
  ) -> Result<(), anyhow::Error> {
345
- // Replace shutdown interceptor with one that calls the before hook first
346
- let b4shut = RefCell::new(Some(before_shutdown));
347
- let shutdown_handle = self.inner.shutdown_handle();
348
- let iceptor = WorkflowCompletionCountingInterceptor {
353
+ let iceptor = TestWorkerInterceptor {
349
354
  incomplete_workflows: self.incomplete_workflows.clone(),
350
- shutdown_handle: Box::new(move || {
351
- if let Some(s) = b4shut.borrow_mut().take() {
352
- s();
353
- }
354
- shutdown_handle();
355
- }),
355
+ // Only use counting-based shutdown in the interceptor in mocked tests
356
+ shutdown_handle: if self.auto_shutdown && self.client.is_none() {
357
+ Some(Box::new(self.inner.shutdown_handle()))
358
+ } else {
359
+ None
360
+ },
361
+ next: interceptor.map(|i| Box::new(i) as Box<dyn WorkerInterceptor>),
356
362
  };
357
363
  self.inner.set_worker_interceptor(Box::new(iceptor));
358
- self.inner.run().await
364
+ let workflows_complete_fut = async {
365
+ if self.auto_shutdown && self.client.is_some() {
366
+ let wfs = std::mem::take(self.started_workflows.get_mut());
367
+ let client = self.client.clone().expect("Client must exist when running");
368
+ stream::iter(
369
+ wfs.into_iter()
370
+ .map(|info| info.bind_untyped((*client).clone())),
371
+ )
372
+ .map(Ok)
373
+ .try_for_each_concurrent(None, |wh| async move {
374
+ wh.get_workflow_result(Default::default()).await?;
375
+ Ok::<_, anyhow::Error>(())
376
+ })
377
+ .await?;
378
+ self.orig_core_worker.initiate_shutdown();
379
+ }
380
+ Ok::<_, anyhow::Error>(())
381
+ };
382
+ tokio::try_join!(self.inner.run(), workflows_complete_fut)?;
383
+ Ok(())
359
384
  }
360
385
  }
361
386
 
362
- struct WorkflowCompletionCountingInterceptor {
387
+ /// Implements calling the shutdown handle when the expected number of test workflows has completed
388
+ struct TestWorkerInterceptor {
363
389
  incomplete_workflows: Arc<AtomicUsize>,
364
- shutdown_handle: Box<dyn Fn()>,
390
+ shutdown_handle: Option<Box<dyn Fn()>>,
391
+ next: Option<Box<dyn WorkerInterceptor>>,
365
392
  }
366
- impl WorkerInterceptor for WorkflowCompletionCountingInterceptor {
367
- fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
393
+
394
+ #[async_trait::async_trait(?Send)]
395
+ impl WorkerInterceptor for TestWorkerInterceptor {
396
+ async fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
368
397
  if completion.has_execution_ending() {
369
398
  info!("Workflow {} says it's finishing", &completion.run_id);
370
399
  let prev = self.incomplete_workflows.fetch_sub(1, Ordering::SeqCst);
371
400
  if prev <= 1 {
372
- // There are now zero, we just subtracted one
373
- (self.shutdown_handle)()
401
+ if let Some(sh) = self.shutdown_handle.as_ref() {
402
+ info!("Test worker calling shutdown");
403
+ // There are now zero, we just subtracted one
404
+ sh()
405
+ }
374
406
  }
375
407
  }
408
+ if let Some(n) = self.next.as_ref() {
409
+ n.on_workflow_activation_completion(completion).await;
410
+ }
411
+ }
412
+
413
+ fn on_shutdown(&self, sdk_worker: &Worker) {
414
+ if let Some(n) = self.next.as_ref() {
415
+ n.on_shutdown(sdk_worker);
416
+ }
376
417
  }
377
418
  }
378
419
 
@@ -1,7 +1,7 @@
1
1
  use assert_matches::assert_matches;
2
2
  use std::time::Duration;
3
- use temporal_client::{WorkflowClientTrait, WorkflowOptions};
4
- use temporal_sdk::{ActivityOptions, WfContext, WorkflowResult};
3
+ use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions};
4
+ use temporal_sdk::{ActContext, ActivityOptions, WfContext, WorkflowResult};
5
5
  use temporal_sdk_core_protos::{
6
6
  coresdk::{
7
7
  activity_result::{
@@ -44,13 +44,14 @@ async fn one_activity() {
44
44
  let wf_name = "one_activity";
45
45
  let mut starter = CoreWfStarter::new(wf_name);
46
46
  let mut worker = starter.worker().await;
47
+ let client = starter.get_client().await;
47
48
  worker.register_wf(wf_name.to_owned(), one_activity_wf);
48
49
  worker.register_activity(
49
50
  "echo_activity",
50
- |echo_me: String| async move { Ok(echo_me) },
51
+ |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
51
52
  );
52
53
 
53
- worker
54
+ let run_id = worker
54
55
  .submit_wf(
55
56
  wf_name.to_owned(),
56
57
  wf_name.to_owned(),
@@ -60,6 +61,12 @@ async fn one_activity() {
60
61
  .await
61
62
  .unwrap();
62
63
  worker.run_until_done().await.unwrap();
64
+ let handle = client.get_untyped_workflow_handle(wf_name, run_id);
65
+ let res = handle
66
+ .get_workflow_result(Default::default())
67
+ .await
68
+ .unwrap();
69
+ assert_matches!(res, WorkflowExecutionResult::Succeeded(_));
63
70
  }
64
71
 
65
72
  #[tokio::test]
@@ -36,7 +36,6 @@ async fn child_workflow_happy_path() {
36
36
 
37
37
  worker.register_wf(PARENT_WF_TYPE.to_string(), parent_wf);
38
38
  worker.register_wf(CHILD_WF_TYPE.to_string(), child_wf);
39
- worker.incr_expected_run_count(1); // Expect another WF to be run as child
40
39
 
41
40
  worker
42
41
  .submit_wf(
@@ -33,8 +33,6 @@ async fn continue_as_new_happy_path() {
33
33
  )
34
34
  .await
35
35
  .unwrap();
36
- // The four additional runs
37
- worker.incr_expected_run_count(4);
38
36
  worker.run_until_done().await.unwrap();
39
37
  }
40
38
 
@@ -58,6 +56,5 @@ async fn continue_as_new_multiple_concurrent() {
58
56
  .await
59
57
  .unwrap();
60
58
  }
61
- worker.incr_expected_run_count(20 * 4);
62
59
  worker.run_until_done().await.unwrap();
63
60
  }
@@ -3,16 +3,17 @@ use futures::future::join_all;
3
3
  use std::time::Duration;
4
4
  use temporal_client::WorkflowOptions;
5
5
  use temporal_sdk::{
6
- act_cancelled, act_is_cancelled, ActivityCancelledError, CancellableFuture,
6
+ interceptors::WorkerInterceptor, ActContext, ActivityCancelledError, CancellableFuture,
7
7
  LocalActivityOptions, WfContext, WorkflowResult,
8
8
  };
9
9
  use temporal_sdk_core_protos::coresdk::{
10
- common::RetryPolicy, workflow_commands::ActivityCancellationType, AsJsonPayloadExt,
10
+ common::RetryPolicy, workflow_commands::ActivityCancellationType,
11
+ workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
11
12
  };
12
13
  use temporal_sdk_core_test_utils::CoreWfStarter;
13
14
  use tokio_util::sync::CancellationToken;
14
15
 
15
- pub async fn echo(e: String) -> anyhow::Result<String> {
16
+ pub async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
16
17
  Ok(e)
17
18
  }
18
19
 
@@ -119,7 +120,7 @@ async fn long_running_local_act_with_timer() {
119
120
  starter.wft_timeout(Duration::from_secs(1));
120
121
  let mut worker = starter.worker().await;
121
122
  worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
122
- worker.register_activity("echo_activity", |str: String| async {
123
+ worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
123
124
  tokio::time::sleep(Duration::from_secs(4)).await;
124
125
  Ok(str)
125
126
  });
@@ -199,7 +200,7 @@ async fn local_act_retry_timer_backoff() {
199
200
  assert!(res.failed());
200
201
  Ok(().into())
201
202
  });
202
- worker.register_activity("echo", |_: String| async {
203
+ worker.register_activity("echo", |_: ActContext, _: String| async {
203
204
  Result::<(), _>::Err(anyhow!("Oh no I failed!"))
204
205
  });
205
206
 
@@ -245,12 +246,12 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
245
246
  let manual_cancel = CancellationToken::new();
246
247
  let manual_cancel_act = manual_cancel.clone();
247
248
 
248
- worker.register_activity("echo", move |_: String| {
249
+ worker.register_activity("echo", move |ctx: ActContext, _: String| {
249
250
  let manual_cancel_act = manual_cancel_act.clone();
250
251
  async move {
251
252
  tokio::select! {
252
253
  _ = tokio::time::sleep(Duration::from_secs(10)) => {},
253
- _ = act_cancelled() => {
254
+ _ = ctx.cancelled() => {
254
255
  return Err(anyhow!(ActivityCancelledError::default()))
255
256
  }
256
257
  _ = manual_cancel_act.cancelled() => {}
@@ -269,11 +270,24 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
269
270
  .await
270
271
  .unwrap();
271
272
  worker
272
- .run_until_done_shutdown_hook(move || manual_cancel.cancel())
273
+ .run_until_done_intercepted(Some(LACancellerInterceptor {
274
+ token: manual_cancel,
275
+ }))
273
276
  .await
274
277
  .unwrap();
275
278
  }
276
279
 
280
+ struct LACancellerInterceptor {
281
+ token: CancellationToken,
282
+ }
283
+ #[async_trait::async_trait(?Send)]
284
+ impl WorkerInterceptor for LACancellerInterceptor {
285
+ async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {}
286
+ fn on_shutdown(&self, _: &temporal_sdk::Worker) {
287
+ self.token.cancel()
288
+ }
289
+ }
290
+
277
291
  #[rstest::rstest]
278
292
  #[case::while_running(None)]
279
293
  #[case::while_backing_off(Some(Duration::from_millis(1500)))]
@@ -329,11 +343,11 @@ async fn cancel_after_act_starts(
329
343
  let manual_cancel = CancellationToken::new();
330
344
  let manual_cancel_act = manual_cancel.clone();
331
345
 
332
- worker.register_activity("echo", move |_: String| {
346
+ worker.register_activity("echo", move |ctx: ActContext, _: String| {
333
347
  let manual_cancel_act = manual_cancel_act.clone();
334
348
  async move {
335
349
  if cancel_on_backoff.is_some() {
336
- if act_is_cancelled() {
350
+ if ctx.is_cancelled() {
337
351
  return Err(anyhow!(ActivityCancelledError::default()));
338
352
  }
339
353
  // Just fail constantly so we get stuck on the backoff timer
@@ -341,7 +355,7 @@ async fn cancel_after_act_starts(
341
355
  } else {
342
356
  tokio::select! {
343
357
  _ = tokio::time::sleep(Duration::from_secs(100)) => {},
344
- _ = act_cancelled() => {
358
+ _ = ctx.cancelled() => {
345
359
  return Err(anyhow!(ActivityCancelledError::default()))
346
360
  }
347
361
  _ = manual_cancel_act.cancelled() => {
@@ -363,7 +377,9 @@ async fn cancel_after_act_starts(
363
377
  .await
364
378
  .unwrap();
365
379
  worker
366
- .run_until_done_shutdown_hook(move || manual_cancel.cancel())
380
+ .run_until_done_intercepted(Some(LACancellerInterceptor {
381
+ token: manual_cancel,
382
+ }))
367
383
  .await
368
384
  .unwrap();
369
385
  }
@@ -406,10 +422,10 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
406
422
  assert!(res.timed_out());
407
423
  Ok(().into())
408
424
  });
409
- worker.register_activity("echo", |_: String| async {
425
+ worker.register_activity("echo", |ctx: ActContext, _: String| async move {
410
426
  tokio::select! {
411
427
  _ = tokio::time::sleep(Duration::from_secs(100)) => {},
412
- _ = act_cancelled() => {
428
+ _ = ctx.cancelled() => {
413
429
  return Err(anyhow!(ActivityCancelledError::default()))
414
430
  }
415
431
  };
@@ -462,7 +478,7 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
462
478
  assert!(res.timed_out());
463
479
  Ok(().into())
464
480
  });
465
- worker.register_activity("echo", |_: String| async {
481
+ worker.register_activity("echo", |_: ActContext, _: String| async {
466
482
  Result::<(), _>::Err(anyhow!("Oh no I failed!"))
467
483
  });
468
484
 
@@ -486,7 +502,7 @@ async fn eviction_wont_make_local_act_get_dropped() {
486
502
  starter.wft_timeout(Duration::from_secs(1));
487
503
  let mut worker = starter.worker().await;
488
504
  worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
489
- worker.register_activity("echo_activity", |str: String| async {
505
+ worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
490
506
  tokio::time::sleep(Duration::from_secs(4)).await;
491
507
  Ok(str)
492
508
  });
@@ -540,7 +556,7 @@ async fn timer_backoff_concurrent_with_non_timer_backoff() {
540
556
  assert!(r2.failed());
541
557
  Ok(().into())
542
558
  });
543
- worker.register_activity("echo", |_: String| async {
559
+ worker.register_activity("echo", |_: ActContext, _: String| async {
544
560
  Result::<(), _>::Err(anyhow!("Oh no I failed!"))
545
561
  });
546
562
 
@@ -1,6 +1,6 @@
1
1
  use futures::StreamExt;
2
2
  use std::{sync::Arc, time::Duration};
3
- use temporal_client::{WorkflowClientTrait, WorkflowOptions, WorkflowService};
3
+ use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions, WorkflowService};
4
4
  use temporal_sdk::WfContext;
5
5
  use temporal_sdk_core_protos::temporal::api::{
6
6
  common::v1::WorkflowExecution, workflowservice::v1::ResetWorkflowExecutionRequest,
@@ -15,6 +15,7 @@ async fn reset_workflow() {
15
15
  let wf_name = "reset_me_wf";
16
16
  let mut starter = CoreWfStarter::new(wf_name);
17
17
  let mut worker = starter.worker().await;
18
+ worker.auto_shutdown = false;
18
19
  let notify = Arc::new(Notify::new());
19
20
 
20
21
  let wf_notify = notify.clone();
@@ -77,6 +78,14 @@ async fn reset_workflow() {
77
78
  )
78
79
  .await
79
80
  .unwrap();
81
+
82
+ // Wait for the now-reset workflow to finish
83
+ client
84
+ .get_untyped_workflow_handle(wf_name.to_owned(), "")
85
+ .get_workflow_result(Default::default())
86
+ .await
87
+ .unwrap();
88
+ starter.shutdown().await;
80
89
  };
81
90
  let run_fut = worker.run_until_done();
82
91
  let (_, rr) = tokio::join!(resetter_fut, run_fut);
@@ -107,7 +107,6 @@ async fn sends_signal_to_child() {
107
107
  worker.register_wf("child_signaler", signals_child);
108
108
  worker.register_wf("child_receiver", signal_receiver);
109
109
 
110
- worker.incr_expected_run_count(1); // Expect another WF to be run as child
111
110
  worker
112
111
  .submit_wf(
113
112
  "sends-signal-to-child",
@@ -24,7 +24,9 @@ use std::{
24
24
  time::Duration,
25
25
  };
26
26
  use temporal_client::{WorkflowClientTrait, WorkflowOptions};
27
- use temporal_sdk::{WfContext, WorkflowResult};
27
+ use temporal_sdk::{
28
+ interceptors::WorkerInterceptor, ActContext, ActivityOptions, WfContext, WorkflowResult,
29
+ };
28
30
  use temporal_sdk_core_api::{errors::PollWfError, Worker};
29
31
  use temporal_sdk_core_protos::{
30
32
  coresdk::{
@@ -32,7 +34,7 @@ use temporal_sdk_core_protos::{
32
34
  workflow_activation::{workflow_activation_job, WorkflowActivation, WorkflowActivationJob},
33
35
  workflow_commands::{ActivityCancellationType, FailWorkflowExecution, StartTimer},
34
36
  workflow_completion::WorkflowActivationCompletion,
35
- ActivityTaskCompletion, IntoCompletion,
37
+ ActivityTaskCompletion, AsJsonPayloadExt, IntoCompletion,
36
38
  },
37
39
  temporal::api::failure::v1::Failure,
38
40
  };
@@ -128,7 +130,19 @@ async fn workflow_lru_cache_evictions() {
128
130
  .await
129
131
  .unwrap();
130
132
  }
131
- worker.run_until_done().await.unwrap();
133
+ struct CacheAsserter;
134
+ #[async_trait::async_trait(?Send)]
135
+ impl WorkerInterceptor for CacheAsserter {
136
+ async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {}
137
+ fn on_shutdown(&self, sdk_worker: &temporal_sdk::Worker) {
138
+ // 0 since the sdk worker force-evicts and drains everything on shutdown.
139
+ assert_eq!(sdk_worker.cached_workflows(), 0);
140
+ }
141
+ }
142
+ worker
143
+ .run_until_done_intercepted(Some(CacheAsserter))
144
+ .await
145
+ .unwrap();
132
146
  // The wf must have started more than # workflows times, since all but one must experience
133
147
  // an eviction
134
148
  assert!(RUN_CT.load(Ordering::SeqCst) > n_workflows);
@@ -515,3 +529,57 @@ async fn wft_timeout_doesnt_create_unsolvable_autocomplete() {
515
529
  let wf_task = poll_sched_act_poll().await;
516
530
  core.complete_execution(&wf_task.run_id).await;
517
531
  }
532
+
533
+ /// We had a bug related to polling being faster than completion causing issues with cache
534
+ /// overflow. This test intentionally makes completes slower than polls to evaluate that.
535
+ ///
536
+ /// It's expected that this test may generate some task timeouts.
537
+ #[tokio::test]
538
+ async fn slow_completes_with_small_cache() {
539
+ let wf_name = "slow_completes_with_small_cache";
540
+ let mut starter = CoreWfStarter::new(wf_name);
541
+ starter.max_wft(5).max_cached_workflows(5);
542
+ let mut worker = starter.worker().await;
543
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
544
+ for _ in 0..3 {
545
+ ctx.activity(ActivityOptions {
546
+ activity_type: "echo_activity".to_string(),
547
+ start_to_close_timeout: Some(Duration::from_secs(5)),
548
+ input: "hi!".as_json_payload().expect("serializes fine"),
549
+ ..Default::default()
550
+ })
551
+ .await;
552
+ ctx.timer(Duration::from_secs(1)).await;
553
+ }
554
+ Ok(().into())
555
+ });
556
+ worker.register_activity(
557
+ "echo_activity",
558
+ |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
559
+ );
560
+ for i in 0..20 {
561
+ worker
562
+ .submit_wf(
563
+ format!("{}_{}", wf_name, i),
564
+ wf_name.to_owned(),
565
+ vec![],
566
+ WorkflowOptions::default(),
567
+ )
568
+ .await
569
+ .unwrap();
570
+ }
571
+
572
+ struct SlowCompleter {}
573
+ #[async_trait::async_trait(?Send)]
574
+ impl WorkerInterceptor for SlowCompleter {
575
+ async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {
576
+ // They don't need to be much slower
577
+ tokio::time::sleep(Duration::from_millis(100)).await;
578
+ }
579
+ fn on_shutdown(&self, _: &temporal_sdk::Worker) {}
580
+ }
581
+ worker
582
+ .run_until_done_intercepted(Some(SlowCompleter {}))
583
+ .await
584
+ .unwrap();
585
+ }
@@ -1,18 +1,18 @@
1
1
  use assert_matches::assert_matches;
2
- use futures::future::join_all;
2
+ use futures::{future::join_all, sink, stream::FuturesUnordered, StreamExt};
3
3
  use std::time::{Duration, Instant};
4
- use temporal_client::WorkflowOptions;
5
- use temporal_sdk::{ActivityOptions, WfContext};
4
+ use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions};
5
+ use temporal_sdk::{ActContext, ActivityOptions, WfContext};
6
6
  use temporal_sdk_core_protos::coresdk::{
7
7
  activity_result::ActivityExecutionResult, activity_task::activity_task as act_task,
8
- workflow_commands::ActivityCancellationType, ActivityTaskCompletion,
8
+ workflow_commands::ActivityCancellationType, ActivityTaskCompletion, AsJsonPayloadExt,
9
9
  };
10
10
  use temporal_sdk_core_test_utils::CoreWfStarter;
11
11
 
12
- const CONCURRENCY: usize = 1000;
13
-
14
12
  #[tokio::test]
15
13
  async fn activity_load() {
14
+ const CONCURRENCY: usize = 1000;
15
+
16
16
  let mut starter = CoreWfStarter::new("activity_load");
17
17
  starter
18
18
  .max_wft(CONCURRENCY)
@@ -109,3 +109,77 @@ async fn activity_load() {
109
109
  };
110
110
  dbg!(running.elapsed());
111
111
  }
112
+
113
+ #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
114
+ async fn workflow_load() {
115
+ const SIGNAME: &str = "signame";
116
+ let wf_name = "workflow_load";
117
+ let mut starter = CoreWfStarter::new("workflow_load");
118
+ starter
119
+ .max_wft(5)
120
+ .max_cached_workflows(5)
121
+ .max_at_polls(10)
122
+ .max_at(100);
123
+ let mut worker = starter.worker().await;
124
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
125
+ let sigchan = ctx.make_signal_channel(SIGNAME).map(Ok);
126
+ let drained_fut = sigchan.forward(sink::drain());
127
+
128
+ let real_stuff = async move {
129
+ for _ in 0..20 {
130
+ ctx.activity(ActivityOptions {
131
+ activity_type: "echo_activity".to_string(),
132
+ start_to_close_timeout: Some(Duration::from_secs(5)),
133
+ input: "hi!".as_json_payload().expect("serializes fine"),
134
+ ..Default::default()
135
+ })
136
+ .await;
137
+ ctx.timer(Duration::from_secs(1)).await;
138
+ }
139
+ };
140
+ tokio::select! {
141
+ _ = drained_fut => {}
142
+ _ = real_stuff => {}
143
+ }
144
+
145
+ Ok(().into())
146
+ });
147
+ worker.register_activity(
148
+ "echo_activity",
149
+ |_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
150
+ );
151
+ let client = starter.get_client().await;
152
+
153
+ let mut workflow_handles = vec![];
154
+ for i in 0..200 {
155
+ let wfid = format!("{}_{}", wf_name, i);
156
+ let rid = worker
157
+ .submit_wf(
158
+ wfid.clone(),
159
+ wf_name.to_owned(),
160
+ vec![],
161
+ WorkflowOptions::default(),
162
+ )
163
+ .await
164
+ .unwrap();
165
+ workflow_handles.push(client.get_untyped_workflow_handle(wfid, rid));
166
+ }
167
+
168
+ let sig_sender = async {
169
+ loop {
170
+ let sends: FuturesUnordered<_> = (0..200)
171
+ .map(|i| {
172
+ client.signal_workflow_execution(
173
+ format!("{}_{}", wf_name, i),
174
+ "".to_string(),
175
+ SIGNAME.to_string(),
176
+ None,
177
+ )
178
+ })
179
+ .collect();
180
+ sends.map(|_| Ok(())).forward(sink::drain()).await.unwrap();
181
+ tokio::time::sleep(Duration::from_secs(2)).await;
182
+ }
183
+ };
184
+ tokio::select! { r1 = worker.run_until_done() => {r1.unwrap()}, _ = sig_sender => {}};
185
+ }