@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
@@ -7,20 +7,12 @@ extern crate tracing;
7
7
  pub mod canned_histories;
8
8
 
9
9
  use crate::stream::TryStreamExt;
10
- use futures::{stream, stream::FuturesUnordered, StreamExt};
10
+ use futures::{future, stream, stream::FuturesUnordered, StreamExt};
11
11
  use parking_lot::Mutex;
12
12
  use prost::Message;
13
13
  use rand::{distributions::Standard, Rng};
14
14
  use std::{
15
- convert::TryFrom,
16
- env,
17
- future::Future,
18
- net::SocketAddr,
19
- path::PathBuf,
20
- sync::{
21
- atomic::{AtomicUsize, Ordering},
22
- Arc,
23
- },
15
+ convert::TryFrom, env, future::Future, net::SocketAddr, path::PathBuf, sync::Arc,
24
16
  time::Duration,
25
17
  };
26
18
  use temporal_client::{
@@ -35,14 +27,13 @@ use temporal_sdk_core::{
35
27
  use temporal_sdk_core_api::Worker as CoreWorker;
36
28
  use temporal_sdk_core_protos::{
37
29
  coresdk::{
38
- common::Payload,
39
30
  workflow_commands::{
40
31
  workflow_command, ActivityCancellationType, CompleteWorkflowExecution,
41
32
  ScheduleActivity, StartTimer,
42
33
  },
43
34
  workflow_completion::WorkflowActivationCompletion,
44
35
  },
45
- temporal::api::history::v1::History,
36
+ temporal::api::{common::v1::Payload, history::v1::History},
46
37
  };
47
38
  use tokio::sync::OnceCell;
48
39
  use url::Url;
@@ -69,9 +60,11 @@ pub fn init_core_replay_preloaded(
69
60
  test_name: &str,
70
61
  history: &History,
71
62
  ) -> (Arc<dyn CoreWorker>, String) {
63
+ telemetry_init(&get_integ_telem_options()).expect("Telemetry inits cleanly");
72
64
  let worker_cfg = WorkerConfigBuilder::default()
73
65
  .namespace(NAMESPACE)
74
66
  .task_queue(test_name)
67
+ .worker_build_id("test_bin_id")
75
68
  .build()
76
69
  .expect("Configuration options construct properly");
77
70
  let worker = init_replay_worker(worker_cfg, history).expect("Replay worker must init properly");
@@ -92,7 +85,7 @@ pub struct CoreWfStarter {
92
85
  /// Used for both the task queue and workflow id
93
86
  task_queue_name: String,
94
87
  telemetry_options: TelemetryOptions,
95
- worker_config: WorkerConfig,
88
+ pub worker_config: WorkerConfig,
96
89
  wft_timeout: Option<Duration>,
97
90
  initted_worker: OnceCell<InitializedWorker>,
98
91
  }
@@ -116,6 +109,7 @@ impl CoreWfStarter {
116
109
  worker_config: WorkerConfigBuilder::default()
117
110
  .namespace(NAMESPACE)
118
111
  .task_queue(task_queue)
112
+ .worker_build_id("test_build_id")
119
113
  .max_cached_workflows(1000_usize)
120
114
  .build()
121
115
  .unwrap(),
@@ -257,28 +251,29 @@ impl CoreWfStarter {
257
251
  /// Provides conveniences for running integ tests with the SDK (against real server or mocks)
258
252
  pub struct TestWorker {
259
253
  inner: Worker,
260
- pub orig_core_worker: Arc<dyn CoreWorker>,
254
+ pub core_worker: Arc<dyn CoreWorker>,
261
255
  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.
268
- incomplete_workflows: Arc<AtomicUsize>,
256
+ pub started_workflows: Mutex<Vec<WorkflowExecutionInfo>>,
257
+ /// If set true (default), and a client is available, we will fetch workflow results to
258
+ /// determine when they have all completed.
259
+ pub fetch_results: bool,
260
+ iceptor: Option<TestWorkerCompletionIceptor>,
269
261
  }
270
262
  impl TestWorker {
271
263
  /// Create a new test worker
272
264
  pub fn new(core_worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
273
- let ct = Arc::new(AtomicUsize::new(0));
274
265
  let inner = Worker::new_from_core(core_worker.clone(), task_queue);
266
+ let iceptor = TestWorkerCompletionIceptor::new(
267
+ TestWorkerShutdownCond::NoAutoShutdown,
268
+ Arc::new(inner.shutdown_handle()),
269
+ );
275
270
  Self {
276
271
  inner,
277
- orig_core_worker: core_worker,
272
+ core_worker,
278
273
  client: None,
279
- auto_shutdown: true,
280
274
  started_workflows: Mutex::new(vec![]),
281
- incomplete_workflows: ct,
275
+ fetch_results: true,
276
+ iceptor: Some(iceptor),
282
277
  }
283
278
  }
284
279
 
@@ -340,70 +335,87 @@ impl TestWorker {
340
335
 
341
336
  /// Runs until all expected workflows have completed
342
337
  pub async fn run_until_done(&mut self) -> Result<(), anyhow::Error> {
343
- self.run_until_done_intercepted(Option::<TestWorkerInterceptor>::None)
338
+ self.run_until_done_intercepted(Option::<TestWorkerCompletionIceptor>::None)
344
339
  .await
345
340
  }
346
341
 
347
- /// See [Self::run_until_done], except calls the provided callback just before performing core
348
- /// shutdown.
342
+ /// See [Self::run_until_done], but allows configuration of some low-level interception.
349
343
  pub async fn run_until_done_intercepted(
350
344
  &mut self,
351
345
  interceptor: Option<impl WorkerInterceptor + 'static>,
352
346
  ) -> Result<(), anyhow::Error> {
353
- let iceptor = TestWorkerInterceptor {
354
- incomplete_workflows: self.incomplete_workflows.clone(),
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>),
362
- };
363
- self.inner.set_worker_interceptor(Box::new(iceptor));
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();
347
+ let mut iceptor = self.iceptor.take().unwrap();
348
+ // Automatically use results-based complete detection if we have a client
349
+ if self.fetch_results {
350
+ if let Some(c) = self.client.clone() {
351
+ iceptor.condition = TestWorkerShutdownCond::GetResults(
352
+ std::mem::take(&mut self.started_workflows.lock()),
353
+ c,
354
+ );
379
355
  }
380
- Ok::<_, anyhow::Error>(())
381
- };
382
- tokio::try_join!(self.inner.run(), workflows_complete_fut)?;
356
+ }
357
+ iceptor.next = interceptor.map(|i| Box::new(i) as Box<dyn WorkerInterceptor>);
358
+ let get_results_waiter = iceptor.wait_all_wfs();
359
+ self.inner.set_worker_interceptor(Box::new(iceptor));
360
+ tokio::try_join!(self.inner.run(), get_results_waiter)?;
383
361
  Ok(())
384
362
  }
385
363
  }
386
364
 
365
+ pub enum TestWorkerShutdownCond {
366
+ GetResults(Vec<WorkflowExecutionInfo>, Arc<RetryClient<Client>>),
367
+ NoAutoShutdown,
368
+ }
387
369
  /// Implements calling the shutdown handle when the expected number of test workflows has completed
388
- struct TestWorkerInterceptor {
389
- incomplete_workflows: Arc<AtomicUsize>,
390
- shutdown_handle: Option<Box<dyn Fn()>>,
370
+ pub struct TestWorkerCompletionIceptor {
371
+ condition: TestWorkerShutdownCond,
372
+ shutdown_handle: Arc<dyn Fn()>,
373
+ every_activation: Option<Box<dyn Fn(&WorkflowActivationCompletion)>>,
391
374
  next: Option<Box<dyn WorkerInterceptor>>,
392
375
  }
376
+ impl TestWorkerCompletionIceptor {
377
+ pub fn new(condition: TestWorkerShutdownCond, shutdown_handle: Arc<dyn Fn()>) -> Self {
378
+ Self {
379
+ condition,
380
+ shutdown_handle,
381
+ every_activation: None,
382
+ next: None,
383
+ }
384
+ }
393
385
 
386
+ fn wait_all_wfs(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> + 'static {
387
+ if let TestWorkerShutdownCond::GetResults(ref mut wfs, ref client) = self.condition {
388
+ let wfs = std::mem::take(wfs);
389
+ let shutdown_h = self.shutdown_handle.clone();
390
+ let client = (**client).clone();
391
+ let stream = stream::iter(
392
+ wfs.into_iter()
393
+ .map(move |info| info.bind_untyped(client.clone())),
394
+ )
395
+ .map(Ok);
396
+ future::Either::Left(async move {
397
+ stream
398
+ .try_for_each_concurrent(None, |wh| async move {
399
+ wh.get_workflow_result(Default::default()).await?;
400
+ Ok::<_, anyhow::Error>(())
401
+ })
402
+ .await?;
403
+ shutdown_h();
404
+ Ok(())
405
+ })
406
+ } else {
407
+ future::Either::Right(future::ready(Ok(())))
408
+ }
409
+ }
410
+ }
394
411
  #[async_trait::async_trait(?Send)]
395
- impl WorkerInterceptor for TestWorkerInterceptor {
412
+ impl WorkerInterceptor for TestWorkerCompletionIceptor {
396
413
  async fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
414
+ if let Some(func) = self.every_activation.as_ref() {
415
+ func(completion);
416
+ }
397
417
  if completion.has_execution_ending() {
398
418
  info!("Workflow {} says it's finishing", &completion.run_id);
399
- let prev = self.incomplete_workflows.fetch_sub(1, Ordering::SeqCst);
400
- if prev <= 1 {
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
- }
406
- }
407
419
  }
408
420
  if let Some(n) = self.next.as_ref() {
409
421
  n.on_workflow_activation_completion(completion).await;
@@ -425,7 +437,6 @@ pub fn get_integ_server_options() -> ClientOptions {
425
437
  let url = Url::try_from(&*temporal_server_address).unwrap();
426
438
  ClientOptionsBuilder::default()
427
439
  .identity("integ_tester".to_string())
428
- .worker_binary_id("fakebinaryid".to_string())
429
440
  .target_url(url)
430
441
  .client_name("temporal-core".to_string())
431
442
  .client_version("0.1.0".to_string())
@@ -439,10 +450,12 @@ pub fn get_integ_telem_options() -> TelemetryOptions {
439
450
  .ok()
440
451
  .map(|x| x.parse::<Url>().unwrap())
441
452
  {
442
- ob.tracing(TraceExporter::Otel(OtelCollectorOptions {
453
+ let opts = OtelCollectorOptions {
443
454
  url,
444
455
  headers: Default::default(),
445
- }));
456
+ };
457
+ ob.tracing(TraceExporter::Otel(opts.clone()));
458
+ ob.metrics(MetricsExporter::Otel(opts));
446
459
  }
447
460
  if let Some(addr) = env::var(PROM_ENABLE_ENV_VAR)
448
461
  .ok()
@@ -1,15 +1,17 @@
1
1
  use assert_matches::assert_matches;
2
2
  use std::time::Duration;
3
- use temporal_sdk_core_protos::coresdk::{
4
- activity_result::{
5
- self, activity_resolution as act_res, ActivityExecutionResult, ActivityResolution,
3
+ use temporal_sdk_core_protos::{
4
+ coresdk::{
5
+ activity_result::{
6
+ self, activity_resolution as act_res, ActivityExecutionResult, ActivityResolution,
7
+ },
8
+ activity_task::activity_task,
9
+ workflow_activation::{workflow_activation_job, ResolveActivity, WorkflowActivationJob},
10
+ workflow_commands::{ActivityCancellationType, ScheduleActivity},
11
+ workflow_completion::WorkflowActivationCompletion,
12
+ ActivityHeartbeat, ActivityTaskCompletion, IntoCompletion,
6
13
  },
7
- activity_task::activity_task,
8
- common::{Payload, RetryPolicy},
9
- workflow_activation::{workflow_activation_job, ResolveActivity, WorkflowActivationJob},
10
- workflow_commands::{ActivityCancellationType, ScheduleActivity},
11
- workflow_completion::WorkflowActivationCompletion,
12
- ActivityHeartbeat, ActivityTaskCompletion, IntoCompletion,
14
+ temporal::api::common::v1::{Payload, RetryPolicy},
13
15
  };
14
16
  use temporal_sdk_core_test_utils::{
15
17
  init_core_and_create_wf, schedule_activity_cmd, WorkerTestHelpers,
@@ -3,6 +3,7 @@ use futures::future::join_all;
3
3
  use std::time::Duration;
4
4
  use temporal_client::WorkflowOptions;
5
5
  use temporal_sdk::{WfContext, WorkflowResult};
6
+ use temporal_sdk_core_api::errors::PollWfError;
6
7
  use temporal_sdk_core_protos::coresdk::{
7
8
  activity_task::activity_task as act_task,
8
9
  workflow_activation::{workflow_activation_job, FireTimer, WorkflowActivationJob},
@@ -131,3 +132,14 @@ async fn can_paginate_long_history() {
131
132
  .unwrap();
132
133
  worker.run_until_done().await.unwrap();
133
134
  }
135
+
136
+ #[tokio::test]
137
+ async fn poll_of_nonexistent_namespace_is_fatal() {
138
+ let mut starter = CoreWfStarter::new("whatever_yo");
139
+ starter.worker_config.namespace = "I do not exist".to_string();
140
+ let worker = starter.get_worker().await;
141
+ assert_matches!(
142
+ worker.poll_workflow_activation().await,
143
+ Err(PollWfError::TonicError(_))
144
+ );
145
+ }
@@ -112,7 +112,7 @@ async fn simple_query_legacy() {
112
112
  async fn query_after_execution_complete(#[case] do_evict: bool) {
113
113
  let query_resp = b"response";
114
114
  let mut starter =
115
- init_core_and_create_wf(&format!("after_done_query_evict-{}", do_evict)).await;
115
+ init_core_and_create_wf(&format!("query_after_execution_complete-{}", do_evict)).await;
116
116
  let core = &starter.get_worker().await;
117
117
  let workflow_id = &starter.get_task_queue().to_string();
118
118
 
@@ -156,19 +156,19 @@ async fn query_after_execution_complete(#[case] do_evict: bool) {
156
156
  .unwrap();
157
157
  continue;
158
158
  }
159
- assert_matches!(
159
+ if matches!(
160
160
  task.jobs.as_slice(),
161
161
  [WorkflowActivationJob {
162
162
  variant: Some(workflow_activation_job::Variant::StartWorkflow(_)),
163
163
  }]
164
- );
165
- let run_id = task.run_id.clone();
166
- core.complete_timer(&task.run_id, 1, Duration::from_millis(500))
167
- .await;
168
- let task = core.poll_workflow_activation().await.unwrap();
169
- core.complete_execution(&task.run_id).await;
164
+ ) {
165
+ core.complete_timer(&task.run_id, 1, Duration::from_millis(500))
166
+ .await;
167
+ } else {
168
+ core.complete_execution(&task.run_id).await;
169
+ }
170
170
  if !go_until_query {
171
- break run_id;
171
+ break task.run_id;
172
172
  }
173
173
  }
174
174
  };
@@ -205,6 +205,7 @@ async fn query_after_execution_complete(#[case] do_evict: bool) {
205
205
  query_futs.push(do_workflow(true).map(|_| ()).boxed());
206
206
  }
207
207
  while query_futs.next().await.is_some() {}
208
+ core.shutdown().await;
208
209
  }
209
210
 
210
211
  #[ignore]
@@ -339,20 +340,21 @@ async fn fail_legacy_query() {
339
340
  let core = starter.get_worker().await;
340
341
  let workflow_id = starter.get_task_queue().to_string();
341
342
  let task = core.poll_workflow_activation().await.unwrap();
343
+ let t1_resp = vec![
344
+ StartTimer {
345
+ seq: 1,
346
+ start_to_fire_timeout: Some(Duration::from_millis(500).into()),
347
+ }
348
+ .into(),
349
+ StartTimer {
350
+ seq: 2,
351
+ start_to_fire_timeout: Some(Duration::from_secs(3).into()),
352
+ }
353
+ .into(),
354
+ ];
342
355
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
343
356
  task.run_id.clone(),
344
- vec![
345
- StartTimer {
346
- seq: 1,
347
- start_to_fire_timeout: Some(Duration::from_millis(500).into()),
348
- }
349
- .into(),
350
- StartTimer {
351
- seq: 2,
352
- start_to_fire_timeout: Some(Duration::from_secs(3).into()),
353
- }
354
- .into(),
355
- ],
357
+ t1_resp.clone(),
356
358
  ))
357
359
  .await
358
360
  .unwrap();
@@ -410,7 +412,22 @@ async fn fail_legacy_query() {
410
412
  ))
411
413
  .await
412
414
  .unwrap();
413
- // Finish the workflow
415
+ // Finish the workflow (handling cache removal)
416
+ let task = core.poll_workflow_activation().await.unwrap();
417
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
418
+ .await
419
+ .unwrap();
420
+ let task = core.poll_workflow_activation().await.unwrap();
421
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
422
+ task.run_id,
423
+ t1_resp.clone(),
424
+ ))
425
+ .await
426
+ .unwrap();
427
+ let task = core.poll_workflow_activation().await.unwrap();
428
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(task.run_id))
429
+ .await
430
+ .unwrap();
414
431
  let task = core.poll_workflow_activation().await.unwrap();
415
432
  core.complete_execution(&task.run_id).await;
416
433
  };
@@ -1,7 +1,7 @@
1
1
  use assert_matches::assert_matches;
2
2
  use std::time::Duration;
3
3
  use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions};
4
- use temporal_sdk::{ActContext, ActivityOptions, WfContext, WorkflowResult};
4
+ use temporal_sdk::{ActContext, ActivityOptions, CancellableFuture, WfContext, WorkflowResult};
5
5
  use temporal_sdk_core_protos::{
6
6
  coresdk::{
7
7
  activity_result::{
@@ -9,7 +9,6 @@ use temporal_sdk_core_protos::{
9
9
  ActivityResolution,
10
10
  },
11
11
  activity_task::activity_task as act_task,
12
- common::Payload,
13
12
  workflow_activation::{
14
13
  workflow_activation_job, FireTimer, ResolveActivity, WorkflowActivationJob,
15
14
  },
@@ -18,7 +17,7 @@ use temporal_sdk_core_protos::{
18
17
  ActivityHeartbeat, ActivityTaskCompletion, AsJsonPayloadExt, IntoCompletion,
19
18
  },
20
19
  temporal::api::{
21
- common::v1::{ActivityType, Payloads},
20
+ common::v1::{ActivityType, Payload, Payloads},
22
21
  enums::v1::RetryState,
23
22
  failure::v1::{failure::FailureInfo, ActivityFailureInfo, Failure},
24
23
  },
@@ -663,7 +662,7 @@ async fn async_activity_completion_workflow() {
663
662
  .complete_activity_task(
664
663
  task.task_token.into(),
665
664
  Some(Payloads {
666
- payloads: vec![response_payload.clone().into()],
665
+ payloads: vec![response_payload.clone()],
667
666
  }),
668
667
  )
669
668
  .await
@@ -748,3 +747,49 @@ async fn activity_cancelled_after_heartbeat_times_out() {
748
747
  .await
749
748
  .unwrap();
750
749
  }
750
+
751
+ #[tokio::test]
752
+ async fn one_activity_abandon_cancelled_after_complete() {
753
+ let wf_name = "one_activity_abandon_cancelled_after_complete";
754
+ let mut starter = CoreWfStarter::new(wf_name);
755
+ let mut worker = starter.worker().await;
756
+ let client = starter.get_client().await;
757
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
758
+ let act_fut = ctx.activity(ActivityOptions {
759
+ activity_type: "echo_activity".to_string(),
760
+ start_to_close_timeout: Some(Duration::from_secs(5)),
761
+ input: "hi!".as_json_payload().expect("serializes fine"),
762
+ cancellation_type: ActivityCancellationType::Abandon,
763
+ ..Default::default()
764
+ });
765
+ ctx.timer(Duration::from_secs(1)).await;
766
+ act_fut.cancel(&ctx);
767
+ ctx.timer(Duration::from_secs(3)).await;
768
+ act_fut.await;
769
+ Ok(().into())
770
+ });
771
+ worker.register_activity(
772
+ "echo_activity",
773
+ |_ctx: ActContext, echo_me: String| async move {
774
+ sleep(Duration::from_secs(2)).await;
775
+ Ok(echo_me)
776
+ },
777
+ );
778
+
779
+ let run_id = worker
780
+ .submit_wf(
781
+ wf_name.to_owned(),
782
+ wf_name.to_owned(),
783
+ vec![],
784
+ WorkflowOptions::default(),
785
+ )
786
+ .await
787
+ .unwrap();
788
+ worker.run_until_done().await.unwrap();
789
+ let handle = client.get_untyped_workflow_handle(wf_name, run_id);
790
+ let res = handle
791
+ .get_workflow_result(Default::default())
792
+ .await
793
+ .unwrap();
794
+ assert_matches!(res, WorkflowExecutionResult::Succeeded(_));
795
+ }
@@ -0,0 +1,61 @@
1
+ use assert_matches::assert_matches;
2
+ use std::time::Duration;
3
+ use temporal_client::{WfClientExt, WorkflowExecutionResult, WorkflowOptions};
4
+ use temporal_sdk::{ActContext, ActivityOptions, WfContext, WorkflowResult};
5
+ use temporal_sdk_core_protos::coresdk::AsJsonPayloadExt;
6
+ use temporal_sdk_core_test_utils::CoreWfStarter;
7
+
8
+ const TEST_APPDATA_MESSAGE: &str = "custom app data, yay";
9
+
10
+ struct Data {
11
+ message: String,
12
+ }
13
+
14
+ pub async fn appdata_activity_wf(ctx: WfContext) -> WorkflowResult<()> {
15
+ ctx.activity(ActivityOptions {
16
+ activity_type: "echo_activity".to_string(),
17
+ start_to_close_timeout: Some(Duration::from_secs(5)),
18
+ input: "hi!".as_json_payload().expect("serializes fine"),
19
+ ..Default::default()
20
+ })
21
+ .await;
22
+ Ok(().into())
23
+ }
24
+
25
+ #[tokio::test]
26
+ async fn appdata_access_in_activities_and_workflows() {
27
+ let wf_name = "appdata_activity";
28
+ let mut starter = CoreWfStarter::new(wf_name);
29
+ let mut worker = starter.worker().await;
30
+ worker.inner_mut().insert_app_data(Data {
31
+ message: TEST_APPDATA_MESSAGE.to_owned(),
32
+ });
33
+
34
+ let client = starter.get_client().await;
35
+ worker.register_wf(wf_name.to_owned(), appdata_activity_wf);
36
+ worker.register_activity(
37
+ "echo_activity",
38
+ |ctx: ActContext, echo_me: String| async move {
39
+ let data = ctx.app_data::<Data>().expect("appdata exists. qed");
40
+ assert_eq!(data.message, TEST_APPDATA_MESSAGE.to_owned());
41
+ Ok(echo_me)
42
+ },
43
+ );
44
+
45
+ let run_id = worker
46
+ .submit_wf(
47
+ wf_name.to_owned(),
48
+ wf_name.to_owned(),
49
+ vec![],
50
+ WorkflowOptions::default(),
51
+ )
52
+ .await
53
+ .unwrap();
54
+ worker.run_until_done().await.unwrap();
55
+ let handle = client.get_untyped_workflow_handle(wf_name, run_id);
56
+ let res = handle
57
+ .get_workflow_result(Default::default())
58
+ .await
59
+ .unwrap();
60
+ assert_matches!(res, WorkflowExecutionResult::Succeeded(_));
61
+ }
@@ -39,7 +39,7 @@ async fn cancel_during_timer() {
39
39
  tokio::time::sleep(Duration::from_millis(500)).await;
40
40
  // Cancel the workflow externally
41
41
  client
42
- .cancel_workflow_execution(wf_name.to_string(), None)
42
+ .cancel_workflow_execution(wf_name.to_string(), None, "Dieee".to_string())
43
43
  .await
44
44
  .unwrap();
45
45
  };
@@ -6,9 +6,12 @@ use temporal_sdk::{
6
6
  interceptors::WorkerInterceptor, ActContext, ActivityCancelledError, CancellableFuture,
7
7
  LocalActivityOptions, WfContext, WorkflowResult,
8
8
  };
9
- use temporal_sdk_core_protos::coresdk::{
10
- common::RetryPolicy, workflow_commands::ActivityCancellationType,
11
- workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
9
+ use temporal_sdk_core_protos::{
10
+ coresdk::{
11
+ workflow_commands::ActivityCancellationType,
12
+ workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
13
+ },
14
+ temporal::api::common::v1::RetryPolicy,
12
15
  };
13
16
  use temporal_sdk_core_test_utils::CoreWfStarter;
14
17
  use tokio_util::sync::CancellationToken;
@@ -494,12 +497,15 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
494
497
  worker.run_until_done().await.unwrap();
495
498
  }
496
499
 
500
+ #[rstest::rstest]
497
501
  #[tokio::test]
498
- async fn eviction_wont_make_local_act_get_dropped() {
499
- let wf_name = "eviction_wont_make_local_act_get_dropped";
500
- let mut starter = CoreWfStarter::new(wf_name);
502
+ async fn eviction_wont_make_local_act_get_dropped(#[values(true, false)] short_wft_timeout: bool) {
503
+ let wf_name = format!(
504
+ "eviction_wont_make_local_act_get_dropped_{}",
505
+ short_wft_timeout
506
+ );
507
+ let mut starter = CoreWfStarter::new(&wf_name);
501
508
  starter.max_cached_workflows(0);
502
- starter.wft_timeout(Duration::from_secs(1));
503
509
  let mut worker = starter.worker().await;
504
510
  worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
505
511
  worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
@@ -507,13 +513,16 @@ async fn eviction_wont_make_local_act_get_dropped() {
507
513
  Ok(str)
508
514
  });
509
515
 
516
+ let opts = if short_wft_timeout {
517
+ WorkflowOptions {
518
+ task_timeout: Some(Duration::from_secs(1)),
519
+ ..Default::default()
520
+ }
521
+ } else {
522
+ Default::default()
523
+ };
510
524
  worker
511
- .submit_wf(
512
- wf_name.to_owned(),
513
- wf_name.to_owned(),
514
- vec![],
515
- WorkflowOptions::default(),
516
- )
525
+ .submit_wf(wf_name.to_owned(), wf_name.to_owned(), vec![], opts)
517
526
  .await
518
527
  .unwrap();
519
528
  worker.run_until_done().await.unwrap();
@@ -571,3 +580,55 @@ async fn timer_backoff_concurrent_with_non_timer_backoff() {
571
580
  .unwrap();
572
581
  worker.run_until_done().await.unwrap();
573
582
  }
583
+
584
+ #[tokio::test]
585
+ async fn repro_nondeterminism_with_timer_bug() {
586
+ let wf_name = "repro_nondeterminism_with_timer_bug";
587
+ let mut starter = CoreWfStarter::new(wf_name);
588
+ let mut worker = starter.worker().await;
589
+
590
+ worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
591
+ let t1 = ctx.timer(Duration::from_secs(30));
592
+ let r1 = ctx.local_activity(LocalActivityOptions {
593
+ activity_type: "delay".to_string(),
594
+ input: "hi".as_json_payload().expect("serializes fine"),
595
+ retry_policy: RetryPolicy {
596
+ initial_interval: Some(Duration::from_micros(15).into()),
597
+ backoff_coefficient: 1_000.,
598
+ maximum_interval: Some(Duration::from_millis(1500).into()),
599
+ maximum_attempts: 4,
600
+ non_retryable_error_types: vec![],
601
+ },
602
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
603
+ ..Default::default()
604
+ });
605
+ tokio::pin!(t1);
606
+ tokio::select! {
607
+ _ = &mut t1 => {},
608
+ _ = r1 => {
609
+ t1.cancel(&ctx);
610
+ },
611
+ };
612
+ ctx.timer(Duration::from_secs(1)).await;
613
+ Ok(().into())
614
+ });
615
+ worker.register_activity("delay", |_: ActContext, _: String| async {
616
+ tokio::time::sleep(Duration::from_secs(2)).await;
617
+ Ok(())
618
+ });
619
+
620
+ let run_id = worker
621
+ .submit_wf(
622
+ wf_name.to_owned(),
623
+ wf_name.to_owned(),
624
+ vec![],
625
+ WorkflowOptions::default(),
626
+ )
627
+ .await
628
+ .unwrap();
629
+ worker.run_until_done().await.unwrap();
630
+ starter
631
+ .fetch_history_and_replay(wf_name, run_id, worker.inner_mut())
632
+ .await
633
+ .unwrap();
634
+ }