@temporalio/core-bridge 0.19.2 → 0.20.2

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 (125) hide show
  1. package/Cargo.lock +90 -157
  2. package/Cargo.toml +1 -0
  3. package/index.d.ts +11 -27
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.buildkite/docker/Dockerfile +1 -1
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.cargo/config.toml +1 -0
  13. package/sdk-core/CODEOWNERS +1 -1
  14. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +119 -86
  15. package/sdk-core/bridge-ffi/src/lib.rs +311 -315
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +108 -113
  17. package/sdk-core/client/Cargo.toml +13 -9
  18. package/sdk-core/client/LICENSE.txt +23 -0
  19. package/sdk-core/client/src/lib.rs +286 -174
  20. package/sdk-core/client/src/metrics.rs +86 -12
  21. package/sdk-core/client/src/raw.rs +566 -0
  22. package/sdk-core/client/src/retry.rs +137 -99
  23. package/sdk-core/core/Cargo.toml +15 -10
  24. package/sdk-core/core/LICENSE.txt +23 -0
  25. package/sdk-core/core/benches/workflow_replay.rs +79 -0
  26. package/sdk-core/core/src/abstractions.rs +38 -0
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +108 -182
  28. package/sdk-core/core/src/core_tests/child_workflows.rs +16 -11
  29. package/sdk-core/core/src/core_tests/determinism.rs +24 -12
  30. package/sdk-core/core/src/core_tests/local_activities.rs +53 -27
  31. package/sdk-core/core/src/core_tests/mod.rs +30 -43
  32. package/sdk-core/core/src/core_tests/queries.rs +82 -81
  33. package/sdk-core/core/src/core_tests/workers.rs +111 -296
  34. package/sdk-core/core/src/core_tests/workflow_cancels.rs +4 -4
  35. package/sdk-core/core/src/core_tests/workflow_tasks.rs +257 -242
  36. package/sdk-core/core/src/lib.rs +73 -318
  37. package/sdk-core/core/src/pollers/mod.rs +4 -6
  38. package/sdk-core/core/src/pollers/poll_buffer.rs +20 -14
  39. package/sdk-core/core/src/protosext/mod.rs +7 -10
  40. package/sdk-core/core/src/replay/mod.rs +11 -150
  41. package/sdk-core/core/src/telemetry/metrics.rs +35 -2
  42. package/sdk-core/core/src/telemetry/mod.rs +49 -16
  43. package/sdk-core/core/src/telemetry/prometheus_server.rs +14 -35
  44. package/sdk-core/core/src/test_help/mod.rs +104 -170
  45. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +57 -34
  46. package/sdk-core/core/src/worker/activities/local_activities.rs +95 -23
  47. package/sdk-core/core/src/worker/activities.rs +23 -16
  48. package/sdk-core/core/src/worker/client/mocks.rs +86 -0
  49. package/sdk-core/core/src/worker/client.rs +209 -0
  50. package/sdk-core/core/src/worker/mod.rs +207 -108
  51. package/sdk-core/core/src/workflow/driven_workflow.rs +21 -6
  52. package/sdk-core/core/src/workflow/history_update.rs +107 -24
  53. package/sdk-core/core/src/workflow/machines/activity_state_machine.rs +2 -3
  54. package/sdk-core/core/src/workflow/machines/child_workflow_state_machine.rs +2 -3
  55. package/sdk-core/core/src/workflow/machines/mod.rs +20 -17
  56. package/sdk-core/core/src/workflow/machines/signal_external_state_machine.rs +56 -19
  57. package/sdk-core/core/src/workflow/machines/transition_coverage.rs +5 -0
  58. package/sdk-core/core/src/workflow/machines/upsert_search_attributes_state_machine.rs +230 -22
  59. package/sdk-core/core/src/workflow/machines/workflow_machines.rs +81 -115
  60. package/sdk-core/core/src/workflow/machines/workflow_task_state_machine.rs +4 -4
  61. package/sdk-core/core/src/workflow/mod.rs +13 -1
  62. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +70 -11
  63. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +65 -41
  64. package/sdk-core/core-api/Cargo.toml +9 -1
  65. package/sdk-core/core-api/LICENSE.txt +23 -0
  66. package/sdk-core/core-api/src/errors.rs +7 -38
  67. package/sdk-core/core-api/src/lib.rs +44 -52
  68. package/sdk-core/core-api/src/worker.rs +10 -2
  69. package/sdk-core/etc/deps.svg +127 -96
  70. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -7
  71. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +10 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +6 -1
  73. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +6 -0
  74. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +6 -0
  75. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +2 -1
  76. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +3 -0
  77. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +12 -0
  78. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +25 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -0
  80. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +19 -35
  81. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -6
  82. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +53 -11
  83. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +14 -7
  84. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -5
  85. package/sdk-core/sdk/Cargo.toml +16 -2
  86. package/sdk-core/sdk/LICENSE.txt +23 -0
  87. package/sdk-core/sdk/src/interceptors.rs +11 -0
  88. package/sdk-core/sdk/src/lib.rs +139 -151
  89. package/sdk-core/sdk/src/workflow_context/options.rs +86 -1
  90. package/sdk-core/sdk/src/workflow_context.rs +36 -17
  91. package/sdk-core/sdk/src/workflow_future.rs +19 -25
  92. package/sdk-core/sdk-core-protos/Cargo.toml +1 -1
  93. package/sdk-core/sdk-core-protos/build.rs +1 -0
  94. package/sdk-core/sdk-core-protos/src/history_info.rs +17 -4
  95. package/sdk-core/sdk-core-protos/src/lib.rs +251 -47
  96. package/sdk-core/test-utils/Cargo.toml +3 -1
  97. package/sdk-core/test-utils/src/canned_histories.rs +27 -0
  98. package/sdk-core/test-utils/src/histfetch.rs +3 -3
  99. package/sdk-core/test-utils/src/lib.rs +223 -68
  100. package/sdk-core/tests/integ_tests/client_tests.rs +27 -4
  101. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +93 -14
  102. package/sdk-core/tests/integ_tests/polling_tests.rs +18 -12
  103. package/sdk-core/tests/integ_tests/queries_tests.rs +50 -53
  104. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +117 -103
  105. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +8 -1
  106. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +10 -5
  107. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +7 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +32 -9
  109. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +7 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +76 -15
  111. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +19 -3
  112. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +39 -42
  113. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +84 -0
  114. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +30 -8
  115. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +21 -6
  116. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +26 -16
  117. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +66 -0
  118. package/sdk-core/tests/integ_tests/workflow_tests.rs +78 -74
  119. package/sdk-core/tests/load_tests.rs +9 -6
  120. package/sdk-core/tests/main.rs +43 -10
  121. package/src/conversions.rs +7 -12
  122. package/src/lib.rs +322 -357
  123. package/sdk-core/client/src/mocks.rs +0 -167
  124. package/sdk-core/core/src/worker/dispatcher.rs +0 -171
  125. package/sdk-core/protos/local/temporal/sdk/core/bridge/service.proto +0 -61
@@ -1,6 +1,9 @@
1
1
  //! This crate contains testing functionality that can be useful when building SDKs against Core,
2
2
  //! or even when testing workflows written in SDKs that use Core.
3
3
 
4
+ #[macro_use]
5
+ extern crate tracing;
6
+
4
7
  pub mod canned_histories;
5
8
 
6
9
  use futures::{stream::FuturesUnordered, StreamExt};
@@ -8,18 +11,28 @@ use log::LevelFilter;
8
11
  use prost::Message;
9
12
  use rand::{distributions::Standard, Rng};
10
13
  use std::{
11
- convert::TryFrom, env, future::Future, net::SocketAddr, path::PathBuf, sync::Arc,
14
+ cell::RefCell,
15
+ convert::TryFrom,
16
+ env,
17
+ future::Future,
18
+ net::SocketAddr,
19
+ path::PathBuf,
20
+ sync::{
21
+ atomic::{AtomicUsize, Ordering},
22
+ Arc,
23
+ },
12
24
  time::Duration,
13
25
  };
14
- use temporal_sdk::TestRustWorker;
26
+ use temporal_client::{Client, RetryClient, WorkflowClientTrait, WorkflowOptions};
27
+ use temporal_sdk::{interceptors::WorkerInterceptor, IntoActivityFunc, Worker, WorkflowFunction};
15
28
  use temporal_sdk_core::{
16
- replay::{init_core_replay, ReplayCore},
17
- CoreInitOptions, CoreInitOptionsBuilder, ServerGatewayOptions, ServerGatewayOptionsBuilder,
29
+ init_replay_worker, init_worker, telemetry_init, ClientOptions, ClientOptionsBuilder,
18
30
  TelemetryOptions, TelemetryOptionsBuilder, WorkerConfig, WorkerConfigBuilder,
19
31
  };
20
- use temporal_sdk_core_api::Core;
32
+ use temporal_sdk_core_api::Worker as CoreWorker;
21
33
  use temporal_sdk_core_protos::{
22
34
  coresdk::{
35
+ common::Payload,
23
36
  workflow_commands::{
24
37
  workflow_command, ActivityCancellationType, CompleteWorkflowExecution,
25
38
  ScheduleActivity, StartTimer,
@@ -28,6 +41,7 @@ use temporal_sdk_core_protos::{
28
41
  },
29
42
  temporal::api::history::v1::History,
30
43
  };
44
+ use tokio::sync::OnceCell;
31
45
  use url::Url;
32
46
 
33
47
  pub const NAMESPACE: &str = "default";
@@ -37,27 +51,28 @@ const OTEL_URL_ENV_VAR: &str = "TEMPORAL_INTEG_OTEL_URL";
37
51
  /// If set, enable direct scraping of prom metrics on the specified port
38
52
  const PROM_ENABLE_ENV_VAR: &str = "TEMPORAL_INTEG_PROM_PORT";
39
53
 
40
- /// Create a core instance which will use the provided test name to base the task queue and wf id
54
+ /// Create a worker instance which will use the provided test name to base the task queue and wf id
41
55
  /// upon. Returns the instance and the task queue name (which is also the workflow id).
42
- pub async fn init_core_and_create_wf(test_name: &str) -> (Arc<dyn Core>, String) {
56
+ pub async fn init_core_and_create_wf(test_name: &str) -> CoreWfStarter {
43
57
  let mut starter = CoreWfStarter::new(test_name);
44
- let core = starter.get_core().await;
58
+ let _ = starter.get_worker().await;
45
59
  starter.start_wf().await;
46
- (core, starter.get_task_queue().to_string())
60
+ starter
47
61
  }
48
62
 
49
- /// Create a core replay instance preloaded with just one provided history. Returns the core impl
63
+ /// Create a worker replay instance preloaded with a provided history. Returns the worker impl
50
64
  /// and the task queue name as in [init_core_and_create_wf].
51
- pub fn init_core_replay_preloaded(test_name: &str, history: &History) -> (Arc<dyn Core>, String) {
52
- let replay_core = init_core_replay(get_integ_telem_options());
65
+ pub fn init_core_replay_preloaded(
66
+ test_name: &str,
67
+ history: &History,
68
+ ) -> (Arc<dyn CoreWorker>, String) {
53
69
  let worker_cfg = WorkerConfigBuilder::default()
70
+ .namespace(NAMESPACE)
54
71
  .task_queue(test_name)
55
72
  .build()
56
73
  .expect("Configuration options construct properly");
57
- replay_core
58
- .make_replay_worker(worker_cfg, history)
59
- .expect("Worker registration works");
60
- (Arc::new(replay_core), test_name.to_string())
74
+ let worker = init_replay_worker(worker_cfg, history).expect("Replay worker must init properly");
75
+ (Arc::new(worker), test_name.to_string())
61
76
  }
62
77
 
63
78
  /// Load history from a file containing the protobuf serialization of it
@@ -73,10 +88,14 @@ pub async fn history_from_proto_binary(path_from_root: &str) -> Result<History,
73
88
  pub struct CoreWfStarter {
74
89
  /// Used for both the task queue and workflow id
75
90
  task_queue_name: String,
76
- core_options: CoreInitOptions,
91
+ telemetry_options: TelemetryOptions,
77
92
  worker_config: WorkerConfig,
78
93
  wft_timeout: Option<Duration>,
79
- initted_core: Option<Arc<dyn Core>>,
94
+ initted_worker: OnceCell<InitializedWorker>,
95
+ }
96
+ struct InitializedWorker {
97
+ worker: Arc<dyn CoreWorker>,
98
+ client: Arc<RetryClient<Client>>,
80
99
  }
81
100
 
82
101
  impl CoreWfStarter {
@@ -90,65 +109,61 @@ impl CoreWfStarter {
90
109
  pub fn new_tq_name(task_queue: &str) -> Self {
91
110
  Self {
92
111
  task_queue_name: task_queue.to_owned(),
93
- core_options: CoreInitOptionsBuilder::default()
94
- .gateway_opts(get_integ_server_options())
95
- .telemetry_opts(get_integ_telem_options())
96
- .build()
97
- .unwrap(),
112
+ telemetry_options: get_integ_telem_options(),
98
113
  worker_config: WorkerConfigBuilder::default()
114
+ .namespace(NAMESPACE)
99
115
  .task_queue(task_queue)
100
116
  .max_cached_workflows(1000_usize)
101
117
  .build()
102
118
  .unwrap(),
103
119
  wft_timeout: None,
104
- initted_core: None,
120
+ initted_worker: OnceCell::new(),
105
121
  }
106
122
  }
107
123
 
108
- pub async fn worker(&mut self) -> TestRustWorker {
109
- TestRustWorker::new(
110
- self.get_core().await,
124
+ pub async fn worker(&mut self) -> TestWorker {
125
+ let mut w = TestWorker::new(
126
+ self.get_worker().await,
111
127
  self.worker_config.task_queue.clone(),
112
- self.wft_timeout,
113
- )
128
+ );
129
+ w.client = Some(self.get_client().await);
130
+
131
+ w
114
132
  }
115
133
 
116
134
  pub async fn shutdown(&mut self) {
117
- self.get_core().await.shutdown().await;
135
+ self.get_worker().await.shutdown().await;
118
136
  }
119
137
 
120
- pub async fn get_core(&mut self) -> Arc<dyn Core> {
121
- if self.initted_core.is_none() {
122
- let core = temporal_sdk_core::init(self.core_options.clone())
123
- .await
124
- .unwrap();
125
- // Register a worker for the task queue
126
- core.register_worker(self.worker_config.clone()).unwrap();
127
- self.initted_core = Some(Arc::new(core));
128
- }
129
- self.initted_core.as_ref().unwrap().clone()
138
+ pub async fn get_worker(&mut self) -> Arc<dyn CoreWorker> {
139
+ self.get_or_init().await.worker.clone()
140
+ }
141
+
142
+ pub async fn get_client(&mut self) -> Arc<RetryClient<Client>> {
143
+ self.get_or_init().await.client.clone()
130
144
  }
131
145
 
132
146
  /// Start the workflow defined by the builder and return run id
133
147
  pub async fn start_wf(&self) -> String {
134
- self.start_wf_with_id(self.task_queue_name.clone()).await
148
+ self.start_wf_with_id(self.task_queue_name.clone(), WorkflowOptions::default())
149
+ .await
135
150
  }
136
151
 
137
- pub async fn start_wf_with_id(&self, workflow_id: String) -> String {
138
- self.initted_core
139
- .as_ref()
152
+ pub async fn start_wf_with_id(&self, workflow_id: String, mut opts: WorkflowOptions) -> String {
153
+ opts.task_timeout = opts.task_timeout.or(self.wft_timeout);
154
+ self.initted_worker
155
+ .get()
140
156
  .expect(
141
- "Core must be initted before starting a workflow.\
142
- Tests must call `get_core` first.",
157
+ "Worker must be initted before starting a workflow.\
158
+ Tests must call `get_worker` first.",
143
159
  )
144
- .as_ref()
145
- .server_gateway()
160
+ .client
146
161
  .start_workflow(
147
162
  vec![],
148
163
  self.worker_config.task_queue.clone(),
149
164
  workflow_id,
150
165
  self.task_queue_name.clone(),
151
- self.wft_timeout,
166
+ opts,
152
167
  )
153
168
  .await
154
169
  .unwrap()
@@ -161,21 +176,20 @@ impl CoreWfStarter {
161
176
  &mut self,
162
177
  wf_id: impl Into<String>,
163
178
  run_id: impl Into<String>,
164
- worker: &mut TestRustWorker,
179
+ // TODO: Need not be passed in
180
+ worker: &mut Worker,
165
181
  ) -> Result<(), anyhow::Error> {
166
182
  // Fetch history and replay it
167
183
  let history = self
168
- .get_core()
184
+ .get_client()
169
185
  .await
170
- .server_gateway()
171
186
  .get_workflow_execution_history(wf_id.into(), Some(run_id.into()), vec![])
172
187
  .await?
173
188
  .history
174
189
  .expect("history field must be populated");
175
- let (replay_core, _) = init_core_replay_preloaded(worker.task_queue(), &history);
176
- let mut replay_worker = worker.swap_core(replay_core);
177
- replay_worker.incr_expected_run_count(1);
178
- replay_worker.run_until_done().await.unwrap();
190
+ let (replay_worker, _) = init_core_replay_preloaded(worker.task_queue(), &history);
191
+ worker.with_new_core_worker(replay_worker);
192
+ worker.run().await.unwrap();
179
193
  Ok(())
180
194
  }
181
195
 
@@ -216,16 +230,159 @@ impl CoreWfStarter {
216
230
  self.wft_timeout = Some(timeout);
217
231
  self
218
232
  }
233
+
234
+ async fn get_or_init(&mut self) -> &InitializedWorker {
235
+ self.initted_worker
236
+ .get_or_init(|| async {
237
+ telemetry_init(&self.telemetry_options).expect("Telemetry inits cleanly");
238
+ let client = Arc::new(
239
+ get_integ_server_options()
240
+ .connect(self.worker_config.namespace.clone(), None)
241
+ .await
242
+ .expect("Must connect"),
243
+ );
244
+ let worker = init_worker(self.worker_config.clone(), client.clone());
245
+ InitializedWorker {
246
+ worker: Arc::new(worker),
247
+ client,
248
+ }
249
+ })
250
+ .await
251
+ }
252
+ }
253
+
254
+ /// Provides conveniences for running integ tests with the SDK
255
+ pub struct TestWorker {
256
+ inner: Worker,
257
+ client: Option<Arc<dyn WorkflowClientTrait>>,
258
+ incomplete_workflows: Arc<AtomicUsize>,
259
+ }
260
+ impl TestWorker {
261
+ /// Create a new test worker
262
+ pub fn new(core_worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
263
+ 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));
270
+ Self {
271
+ inner,
272
+ client: None,
273
+ incomplete_workflows: ct,
274
+ }
275
+ }
276
+
277
+ pub fn inner_mut(&mut self) -> &mut Worker {
278
+ &mut self.inner
279
+ }
280
+
281
+ pub fn incr_expected_run_count(&self, amount: usize) {
282
+ self.incomplete_workflows
283
+ .fetch_add(amount, Ordering::AcqRel);
284
+ }
285
+
286
+ // TODO: Maybe trait-ify?
287
+ pub fn register_wf<F: Into<WorkflowFunction>>(
288
+ &mut self,
289
+ workflow_type: impl Into<String>,
290
+ wf_function: F,
291
+ ) {
292
+ self.inner.register_wf(workflow_type, wf_function)
293
+ }
294
+
295
+ pub fn register_activity<A, R>(
296
+ &mut self,
297
+ activity_type: impl Into<String>,
298
+ act_function: impl IntoActivityFunc<A, R>,
299
+ ) {
300
+ self.inner.register_activity(activity_type, act_function)
301
+ }
302
+
303
+ /// Create a workflow, asking the server to start it with the provided workflow ID and using the
304
+ /// provided workflow function.
305
+ ///
306
+ /// Increments the expected Workflow run count.
307
+ ///
308
+ /// Returns the run id of the started workflow
309
+ pub async fn submit_wf(
310
+ &self,
311
+ workflow_id: impl Into<String>,
312
+ workflow_type: impl Into<String>,
313
+ input: Vec<Payload>,
314
+ options: WorkflowOptions,
315
+ ) -> Result<String, anyhow::Error> {
316
+ self.incr_expected_run_count(1);
317
+ if let Some(c) = self.client.as_ref() {
318
+ let wfid = workflow_id.into();
319
+ let res = c
320
+ .start_workflow(
321
+ input,
322
+ self.inner.task_queue().to_string(),
323
+ wfid.clone(),
324
+ workflow_type.into(),
325
+ options,
326
+ )
327
+ .await?;
328
+ Ok(res.run_id)
329
+ } else {
330
+ Ok("fake_run_id".to_string())
331
+ }
332
+ }
333
+
334
+ /// Runs until all expected workflows have completed
335
+ pub async fn run_until_done(&mut self) -> Result<(), anyhow::Error> {
336
+ self.inner.run().await
337
+ }
338
+
339
+ /// See [Self::run_until_done], except calls the provided callback just before performing core
340
+ /// shutdown.
341
+ pub async fn run_until_done_shutdown_hook(
342
+ &mut self,
343
+ before_shutdown: impl FnOnce() + 'static,
344
+ ) -> 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 {
349
+ 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
+ }),
356
+ };
357
+ self.inner.set_worker_interceptor(Box::new(iceptor));
358
+ self.inner.run().await
359
+ }
360
+ }
361
+
362
+ struct WorkflowCompletionCountingInterceptor {
363
+ incomplete_workflows: Arc<AtomicUsize>,
364
+ shutdown_handle: Box<dyn Fn()>,
365
+ }
366
+ impl WorkerInterceptor for WorkflowCompletionCountingInterceptor {
367
+ fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
368
+ if completion.has_execution_ending() {
369
+ info!("Workflow {} says it's finishing", &completion.run_id);
370
+ let prev = self.incomplete_workflows.fetch_sub(1, Ordering::SeqCst);
371
+ if prev <= 1 {
372
+ // There are now zero, we just subtracted one
373
+ (self.shutdown_handle)()
374
+ }
375
+ }
376
+ }
219
377
  }
220
378
 
221
- pub fn get_integ_server_options() -> ServerGatewayOptions {
379
+ pub fn get_integ_server_options() -> ClientOptions {
222
380
  let temporal_server_address = match env::var("TEMPORAL_SERVICE_ADDRESS") {
223
381
  Ok(addr) => addr,
224
382
  Err(_) => "http://localhost:7233".to_owned(),
225
383
  };
226
384
  let url = Url::try_from(&*temporal_server_address).unwrap();
227
- ServerGatewayOptionsBuilder::default()
228
- .namespace(NAMESPACE.to_string())
385
+ ClientOptionsBuilder::default()
229
386
  .identity("integ_tester".to_string())
230
387
  .worker_binary_id("fakebinaryid".to_string())
231
388
  .target_url(url)
@@ -308,19 +465,18 @@ where
308
465
  }
309
466
 
310
467
  #[async_trait::async_trait]
311
- pub trait CoreTestHelpers {
312
- async fn complete_execution(&self, task_q: &str, run_id: &str);
313
- async fn complete_timer(&self, task_q: &str, run_id: &str, seq: u32, duration: Duration);
468
+ pub trait WorkerTestHelpers {
469
+ async fn complete_execution(&self, run_id: &str);
470
+ async fn complete_timer(&self, run_id: &str, seq: u32, duration: Duration);
314
471
  }
315
472
 
316
473
  #[async_trait::async_trait]
317
- impl<T> CoreTestHelpers for T
474
+ impl<T> WorkerTestHelpers for T
318
475
  where
319
- T: Core + ?Sized,
476
+ T: CoreWorker + ?Sized,
320
477
  {
321
- async fn complete_execution(&self, task_q: &str, run_id: &str) {
478
+ async fn complete_execution(&self, run_id: &str) {
322
479
  self.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
323
- task_q.to_string(),
324
480
  run_id.to_string(),
325
481
  vec![CompleteWorkflowExecution { result: None }.into()],
326
482
  ))
@@ -328,9 +484,8 @@ where
328
484
  .unwrap();
329
485
  }
330
486
 
331
- async fn complete_timer(&self, task_q: &str, run_id: &str, seq: u32, duration: Duration) {
487
+ async fn complete_timer(&self, run_id: &str, seq: u32, duration: Duration) {
332
488
  self.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
333
- task_q.to_string(),
334
489
  run_id.to_string(),
335
490
  vec![StartTimer {
336
491
  seq,
@@ -1,13 +1,36 @@
1
1
  use std::time::Duration;
2
- use temporal_sdk_core_test_utils::CoreWfStarter;
2
+ use temporal_client::{RetryClient, WorkflowClientTrait, WorkflowService};
3
+ use temporal_sdk_core_protos::temporal::api::workflowservice::v1::DescribeNamespaceRequest;
4
+ use temporal_sdk_core_test_utils::{get_integ_server_options, CoreWfStarter, NAMESPACE};
3
5
 
4
6
  #[tokio::test]
5
- async fn can_use_retry_gateway() {
7
+ async fn can_use_retry_client() {
6
8
  // Not terribly interesting by itself but can be useful for manually inspecting metrics etc
7
- let mut core = CoreWfStarter::new("retry_gateway");
8
- let retry_client = core.get_core().await.server_gateway();
9
+ let mut core = CoreWfStarter::new("retry_client");
10
+ let retry_client = core.get_client().await;
9
11
  for _ in 0..10 {
10
12
  retry_client.list_namespaces().await.unwrap();
11
13
  tokio::time::sleep(Duration::from_millis(10)).await;
12
14
  }
13
15
  }
16
+
17
+ #[tokio::test]
18
+ async fn can_use_retry_raw_client() {
19
+ let opts = get_integ_server_options();
20
+ let raw_client = opts.connect_no_namespace(None).await.unwrap();
21
+ let mut retry_client = RetryClient::new(raw_client, opts.retry_config);
22
+ retry_client
23
+ .describe_namespace(DescribeNamespaceRequest {
24
+ namespace: NAMESPACE.to_string(),
25
+ ..Default::default()
26
+ })
27
+ .await
28
+ .unwrap();
29
+ }
30
+
31
+ #[tokio::test]
32
+ async fn calls_get_system_info() {
33
+ let opts = get_integ_server_options();
34
+ let raw_client = opts.connect_no_namespace(None).await.unwrap();
35
+ assert!(raw_client.get_client().capabilities().is_some());
36
+ }
@@ -4,41 +4,44 @@ use temporal_sdk_core_protos::coresdk::{
4
4
  activity_result::{
5
5
  self, activity_resolution as act_res, ActivityExecutionResult, ActivityResolution,
6
6
  },
7
- activity_task::activity_task as act_task,
8
- common::Payload,
7
+ activity_task::activity_task,
8
+ common::{Payload, RetryPolicy},
9
9
  workflow_activation::{workflow_activation_job, ResolveActivity, WorkflowActivationJob},
10
- workflow_commands::ActivityCancellationType,
10
+ workflow_commands::{ActivityCancellationType, ScheduleActivity},
11
+ workflow_completion::WorkflowActivationCompletion,
11
12
  ActivityHeartbeat, ActivityTaskCompletion, IntoCompletion,
12
13
  };
13
14
  use temporal_sdk_core_test_utils::{
14
- init_core_and_create_wf, schedule_activity_cmd, CoreTestHelpers,
15
+ init_core_and_create_wf, schedule_activity_cmd, WorkerTestHelpers,
15
16
  };
16
17
  use tokio::time::sleep;
17
18
 
18
19
  #[tokio::test]
19
20
  async fn activity_heartbeat() {
20
- let (core, task_q) = init_core_and_create_wf("activity_heartbeat").await;
21
+ let mut starter = init_core_and_create_wf("activity_heartbeat").await;
22
+ let core = starter.get_worker().await;
23
+ let task_q = starter.get_task_queue();
21
24
  let activity_id = "act-1";
22
- let task = core.poll_workflow_activation(&task_q).await.unwrap();
25
+ let task = core.poll_workflow_activation().await.unwrap();
23
26
  // Complete workflow task and schedule activity
24
27
  core.complete_workflow_activation(
25
28
  schedule_activity_cmd(
26
29
  0,
27
- &task_q,
30
+ task_q,
28
31
  activity_id,
29
32
  ActivityCancellationType::TryCancel,
30
33
  Duration::from_secs(60),
31
34
  Duration::from_secs(1),
32
35
  )
33
- .into_completion(task_q.to_string(), task.run_id),
36
+ .into_completion(task.run_id),
34
37
  )
35
38
  .await
36
39
  .unwrap();
37
40
  // Poll activity and verify that it's been scheduled with correct parameters
38
- let task = core.poll_activity_task(&task_q).await.unwrap();
41
+ let task = core.poll_activity_task().await.unwrap();
39
42
  assert_matches!(
40
43
  task.variant,
41
- Some(act_task::Variant::Start(start_activity)) => {
44
+ Some(activity_task::Variant::Start(start_activity)) => {
42
45
  assert_eq!(start_activity.activity_type, "test_activity".to_string())
43
46
  }
44
47
  );
@@ -49,7 +52,6 @@ async fn activity_heartbeat() {
49
52
  sleep(Duration::from_millis(100)).await;
50
53
  core.record_activity_heartbeat(ActivityHeartbeat {
51
54
  task_token: task.task_token.clone(),
52
- task_queue: task_q.to_string(),
53
55
  details: vec![],
54
56
  });
55
57
  }
@@ -61,13 +63,12 @@ async fn activity_heartbeat() {
61
63
  // Complete activity successfully.
62
64
  core.complete_activity_task(ActivityTaskCompletion {
63
65
  task_token: task.task_token,
64
- task_queue: task_q.to_string(),
65
66
  result: Some(ActivityExecutionResult::ok(response_payload.clone())),
66
67
  })
67
68
  .await
68
69
  .unwrap();
69
70
  // Poll workflow task and verify that activity has succeeded.
70
- let task = core.poll_workflow_activation(&task_q).await.unwrap();
71
+ let task = core.poll_workflow_activation().await.unwrap();
71
72
  assert_matches!(
72
73
  task.jobs.as_slice(),
73
74
  [
@@ -83,5 +84,83 @@ async fn activity_heartbeat() {
83
84
  assert_eq!(r, &response_payload);
84
85
  }
85
86
  );
86
- core.complete_execution(&task_q, &task.run_id).await;
87
+ core.complete_execution(&task.run_id).await;
88
+ }
89
+
90
+ #[tokio::test]
91
+ async fn many_act_fails_with_heartbeats() {
92
+ let mut starter = init_core_and_create_wf("many_act_fails_with_heartbeats").await;
93
+ let core = starter.get_worker().await;
94
+ let activity_id = "act-1";
95
+ let task = core.poll_workflow_activation().await.unwrap();
96
+ // Complete workflow task and schedule activity
97
+ core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
98
+ task.run_id,
99
+ ScheduleActivity {
100
+ seq: 0,
101
+ activity_id: activity_id.to_string(),
102
+ activity_type: "test_act".to_string(),
103
+ task_queue: starter.get_task_queue().to_string(),
104
+ start_to_close_timeout: Some(Duration::from_secs(30).into()),
105
+ retry_policy: Some(RetryPolicy {
106
+ initial_interval: Some(Duration::from_millis(10).into()),
107
+ backoff_coefficient: 1.0,
108
+ maximum_attempts: 4,
109
+ ..Default::default()
110
+ }),
111
+ heartbeat_timeout: Some(Duration::from_secs(1).into()),
112
+ ..Default::default()
113
+ }
114
+ .into(),
115
+ ))
116
+ .await
117
+ .unwrap();
118
+
119
+ // Multiple times, poll for the activity, heartbeat, and then immediately fail
120
+ // Poll activity and verify that it's been scheduled with correct parameters
121
+ for i in 0u8..=3 {
122
+ let task = core.poll_activity_task().await.unwrap();
123
+ let start_t = assert_matches!(task.variant, Some(activity_task::Variant::Start(s)) => s);
124
+
125
+ core.record_activity_heartbeat(ActivityHeartbeat {
126
+ task_token: task.task_token.clone(),
127
+ details: vec![[i].into()],
128
+ });
129
+
130
+ let compl = if i == 3 {
131
+ // Verify last hb was recorded
132
+ assert_eq!(start_t.heartbeat_details, [[2].into()]);
133
+ ActivityTaskCompletion {
134
+ task_token: task.task_token,
135
+ result: Some(ActivityExecutionResult::ok("passed".into())),
136
+ }
137
+ } else {
138
+ if i != 0 {
139
+ assert_eq!(start_t.heartbeat_details, [[i - 1].into()]);
140
+ }
141
+ ActivityTaskCompletion {
142
+ task_token: task.task_token,
143
+ result: Some(ActivityExecutionResult::fail(format!("Die on {i}").into())),
144
+ }
145
+ };
146
+ core.complete_activity_task(compl).await.unwrap();
147
+ }
148
+ let task = core.poll_workflow_activation().await.unwrap();
149
+
150
+ assert_matches!(
151
+ task.jobs.as_slice(),
152
+ [WorkflowActivationJob {
153
+ variant: Some(workflow_activation_job::Variant::ResolveActivity(
154
+ ResolveActivity {
155
+ result: Some(ActivityResolution {
156
+ status: Some(act_res::Status::Completed(activity_result::Success { .. })),
157
+ ..
158
+ }),
159
+ ..
160
+ }
161
+ )),
162
+ },]
163
+ );
164
+ core.complete_execution(&task.run_id).await;
165
+ core.shutdown().await;
87
166
  }