@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.
- package/Cargo.lock +90 -157
- package/Cargo.toml +1 -0
- package/index.d.ts +11 -27
- package/package.json +3 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.buildkite/docker/Dockerfile +1 -1
- package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
- package/sdk-core/.cargo/config.toml +1 -0
- package/sdk-core/CODEOWNERS +1 -1
- package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +119 -86
- package/sdk-core/bridge-ffi/src/lib.rs +311 -315
- package/sdk-core/bridge-ffi/src/wrappers.rs +108 -113
- package/sdk-core/client/Cargo.toml +13 -9
- package/sdk-core/client/LICENSE.txt +23 -0
- package/sdk-core/client/src/lib.rs +286 -174
- package/sdk-core/client/src/metrics.rs +86 -12
- package/sdk-core/client/src/raw.rs +566 -0
- package/sdk-core/client/src/retry.rs +137 -99
- package/sdk-core/core/Cargo.toml +15 -10
- package/sdk-core/core/LICENSE.txt +23 -0
- package/sdk-core/core/benches/workflow_replay.rs +79 -0
- package/sdk-core/core/src/abstractions.rs +38 -0
- package/sdk-core/core/src/core_tests/activity_tasks.rs +108 -182
- package/sdk-core/core/src/core_tests/child_workflows.rs +16 -11
- package/sdk-core/core/src/core_tests/determinism.rs +24 -12
- package/sdk-core/core/src/core_tests/local_activities.rs +53 -27
- package/sdk-core/core/src/core_tests/mod.rs +30 -43
- package/sdk-core/core/src/core_tests/queries.rs +82 -81
- package/sdk-core/core/src/core_tests/workers.rs +111 -296
- package/sdk-core/core/src/core_tests/workflow_cancels.rs +4 -4
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +257 -242
- package/sdk-core/core/src/lib.rs +73 -318
- package/sdk-core/core/src/pollers/mod.rs +4 -6
- package/sdk-core/core/src/pollers/poll_buffer.rs +20 -14
- package/sdk-core/core/src/protosext/mod.rs +7 -10
- package/sdk-core/core/src/replay/mod.rs +11 -150
- package/sdk-core/core/src/telemetry/metrics.rs +35 -2
- package/sdk-core/core/src/telemetry/mod.rs +49 -16
- package/sdk-core/core/src/telemetry/prometheus_server.rs +14 -35
- package/sdk-core/core/src/test_help/mod.rs +104 -170
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +57 -34
- package/sdk-core/core/src/worker/activities/local_activities.rs +95 -23
- package/sdk-core/core/src/worker/activities.rs +23 -16
- package/sdk-core/core/src/worker/client/mocks.rs +86 -0
- package/sdk-core/core/src/worker/client.rs +209 -0
- package/sdk-core/core/src/worker/mod.rs +207 -108
- package/sdk-core/core/src/workflow/driven_workflow.rs +21 -6
- package/sdk-core/core/src/workflow/history_update.rs +107 -24
- package/sdk-core/core/src/workflow/machines/activity_state_machine.rs +2 -3
- package/sdk-core/core/src/workflow/machines/child_workflow_state_machine.rs +2 -3
- package/sdk-core/core/src/workflow/machines/mod.rs +20 -17
- package/sdk-core/core/src/workflow/machines/signal_external_state_machine.rs +56 -19
- package/sdk-core/core/src/workflow/machines/transition_coverage.rs +5 -0
- package/sdk-core/core/src/workflow/machines/upsert_search_attributes_state_machine.rs +230 -22
- package/sdk-core/core/src/workflow/machines/workflow_machines.rs +81 -115
- package/sdk-core/core/src/workflow/machines/workflow_task_state_machine.rs +4 -4
- package/sdk-core/core/src/workflow/mod.rs +13 -1
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +70 -11
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +65 -41
- package/sdk-core/core-api/Cargo.toml +9 -1
- package/sdk-core/core-api/LICENSE.txt +23 -0
- package/sdk-core/core-api/src/errors.rs +7 -38
- package/sdk-core/core-api/src/lib.rs +44 -52
- package/sdk-core/core-api/src/worker.rs +10 -2
- package/sdk-core/etc/deps.svg +127 -96
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -7
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +10 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +6 -1
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +6 -0
- package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +6 -0
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +2 -1
- package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +3 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +12 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +25 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -0
- package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +19 -35
- package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -6
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +53 -11
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +14 -7
- package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -5
- package/sdk-core/sdk/Cargo.toml +16 -2
- package/sdk-core/sdk/LICENSE.txt +23 -0
- package/sdk-core/sdk/src/interceptors.rs +11 -0
- package/sdk-core/sdk/src/lib.rs +139 -151
- package/sdk-core/sdk/src/workflow_context/options.rs +86 -1
- package/sdk-core/sdk/src/workflow_context.rs +36 -17
- package/sdk-core/sdk/src/workflow_future.rs +19 -25
- package/sdk-core/sdk-core-protos/Cargo.toml +1 -1
- package/sdk-core/sdk-core-protos/build.rs +1 -0
- package/sdk-core/sdk-core-protos/src/history_info.rs +17 -4
- package/sdk-core/sdk-core-protos/src/lib.rs +251 -47
- package/sdk-core/test-utils/Cargo.toml +3 -1
- package/sdk-core/test-utils/src/canned_histories.rs +27 -0
- package/sdk-core/test-utils/src/histfetch.rs +3 -3
- package/sdk-core/test-utils/src/lib.rs +223 -68
- package/sdk-core/tests/integ_tests/client_tests.rs +27 -4
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +93 -14
- package/sdk-core/tests/integ_tests/polling_tests.rs +18 -12
- package/sdk-core/tests/integ_tests/queries_tests.rs +50 -53
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +117 -103
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +8 -1
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +10 -5
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +7 -1
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +32 -9
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +7 -1
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +76 -15
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +19 -3
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +39 -42
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +84 -0
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +30 -8
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +21 -6
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +26 -16
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +66 -0
- package/sdk-core/tests/integ_tests/workflow_tests.rs +78 -74
- package/sdk-core/tests/load_tests.rs +9 -6
- package/sdk-core/tests/main.rs +43 -10
- package/src/conversions.rs +7 -12
- package/src/lib.rs +322 -357
- package/sdk-core/client/src/mocks.rs +0 -167
- package/sdk-core/core/src/worker/dispatcher.rs +0 -171
- 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
|
-
|
|
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
|
|
26
|
+
use temporal_client::{Client, RetryClient, WorkflowClientTrait, WorkflowOptions};
|
|
27
|
+
use temporal_sdk::{interceptors::WorkerInterceptor, IntoActivityFunc, Worker, WorkflowFunction};
|
|
15
28
|
use temporal_sdk_core::{
|
|
16
|
-
|
|
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::
|
|
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
|
|
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) ->
|
|
56
|
+
pub async fn init_core_and_create_wf(test_name: &str) -> CoreWfStarter {
|
|
43
57
|
let mut starter = CoreWfStarter::new(test_name);
|
|
44
|
-
let
|
|
58
|
+
let _ = starter.get_worker().await;
|
|
45
59
|
starter.start_wf().await;
|
|
46
|
-
|
|
60
|
+
starter
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
/// Create a
|
|
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(
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
91
|
+
telemetry_options: TelemetryOptions,
|
|
77
92
|
worker_config: WorkerConfig,
|
|
78
93
|
wft_timeout: Option<Duration>,
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
+
initted_worker: OnceCell::new(),
|
|
105
121
|
}
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
pub async fn worker(&mut self) ->
|
|
109
|
-
|
|
110
|
-
self.
|
|
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
|
-
|
|
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.
|
|
135
|
+
self.get_worker().await.shutdown().await;
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
pub async fn
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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())
|
|
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.
|
|
139
|
-
|
|
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
|
-
"
|
|
142
|
-
Tests must call `
|
|
157
|
+
"Worker must be initted before starting a workflow.\
|
|
158
|
+
Tests must call `get_worker` first.",
|
|
143
159
|
)
|
|
144
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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 (
|
|
176
|
-
|
|
177
|
-
|
|
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() ->
|
|
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
|
-
|
|
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
|
|
312
|
-
async fn complete_execution(&self,
|
|
313
|
-
async fn complete_timer(&self,
|
|
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>
|
|
474
|
+
impl<T> WorkerTestHelpers for T
|
|
318
475
|
where
|
|
319
|
-
T:
|
|
476
|
+
T: CoreWorker + ?Sized,
|
|
320
477
|
{
|
|
321
|
-
async fn complete_execution(&self,
|
|
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,
|
|
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
|
|
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
|
|
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("
|
|
8
|
-
let retry_client = core.
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
41
|
+
let task = core.poll_activity_task().await.unwrap();
|
|
39
42
|
assert_matches!(
|
|
40
43
|
task.variant,
|
|
41
|
-
Some(
|
|
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(
|
|
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(&
|
|
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
|
}
|