@temporalio/core-bridge 1.10.2 → 1.11.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.
- package/Cargo.lock +563 -676
- package/Cargo.toml +3 -3
- package/lib/index.d.ts +16 -5
- package/lib/index.js.map +1 -1
- package/lib/worker-tuner.d.ts +57 -0
- package/lib/worker-tuner.js +3 -0
- package/lib/worker-tuner.js.map +1 -0
- 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/.github/workflows/heavy.yml +6 -11
- package/sdk-core/.github/workflows/per-pr.yml +23 -41
- package/sdk-core/Cargo.toml +5 -5
- package/sdk-core/README.md +2 -0
- package/sdk-core/client/Cargo.toml +4 -2
- package/sdk-core/client/src/lib.rs +60 -17
- package/sdk-core/client/src/metrics.rs +1 -1
- package/sdk-core/client/src/proxy.rs +17 -12
- package/sdk-core/client/src/raw.rs +218 -69
- package/sdk-core/client/src/retry.rs +19 -9
- package/sdk-core/core/Cargo.toml +12 -12
- package/sdk-core/core/src/abstractions.rs +3 -3
- package/sdk-core/core/src/core_tests/activity_tasks.rs +2 -1
- package/sdk-core/core/src/core_tests/determinism.rs +1 -1
- package/sdk-core/core/src/core_tests/local_activities.rs +73 -10
- package/sdk-core/core/src/core_tests/queries.rs +2 -1
- package/sdk-core/core/src/core_tests/updates.rs +162 -4
- package/sdk-core/core/src/core_tests/workers.rs +38 -2
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +158 -27
- package/sdk-core/core/src/internal_flags.rs +17 -7
- package/sdk-core/core/src/lib.rs +9 -3
- package/sdk-core/core/src/pollers/poll_buffer.rs +1 -10
- package/sdk-core/core/src/protosext/mod.rs +0 -1
- package/sdk-core/core/src/protosext/protocol_messages.rs +105 -16
- package/sdk-core/core/src/retry_logic.rs +22 -2
- package/sdk-core/core/src/telemetry/otel.rs +44 -12
- package/sdk-core/core/src/test_help/mod.rs +65 -12
- package/sdk-core/core/src/worker/activities/local_activities.rs +1 -4
- package/sdk-core/core/src/worker/activities.rs +3 -4
- package/sdk-core/core/src/worker/client/mocks.rs +7 -6
- package/sdk-core/core/src/worker/client.rs +11 -2
- package/sdk-core/core/src/worker/mod.rs +49 -24
- package/sdk-core/core/src/worker/tuner/resource_based.rs +48 -48
- package/sdk-core/core/src/worker/tuner.rs +124 -4
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +1 -1
- package/sdk-core/core/src/worker/workflow/history_update.rs +11 -2
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +18 -3
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +4 -4
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -0
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +3 -1
- package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +38 -28
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +4 -2
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +95 -71
- package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +1 -1
- package/sdk-core/core/src/worker/workflow/managed_run.rs +214 -14
- package/sdk-core/core/src/worker/workflow/mod.rs +49 -36
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +1 -2
- package/sdk-core/core-api/src/errors.rs +13 -7
- package/sdk-core/core-api/src/lib.rs +9 -1
- package/sdk-core/sdk/Cargo.toml +1 -1
- package/sdk-core/sdk/src/activity_context.rs +3 -4
- package/sdk-core/sdk/src/lib.rs +96 -49
- package/sdk-core/sdk/src/workflow_context/options.rs +8 -4
- package/sdk-core/sdk/src/workflow_context.rs +53 -49
- package/sdk-core/sdk/src/workflow_future.rs +10 -4
- package/sdk-core/sdk-core-protos/Cargo.toml +4 -3
- package/sdk-core/sdk-core-protos/build.rs +2 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/build.yaml +18 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/LICENSE +21 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/Makefile +59 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +25 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.gen.yaml +14 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +8 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +9 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +520 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +263 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +173 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +164 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +36 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +22 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +50 -8
- package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +3 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +132 -54
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +177 -81
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +13 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +8 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +10 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +6 -6
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +96 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +49 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -7
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +55 -24
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +7 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +21 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +64 -45
- package/sdk-core/sdk-core-protos/src/history_builder.rs +8 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +40 -10
- package/sdk-core/test-utils/src/canned_histories.rs +1 -1
- package/sdk-core/tests/fuzzy_workflow.rs +4 -2
- package/sdk-core/tests/heavy_tests.rs +3 -3
- package/sdk-core/tests/integ_tests/activity_functions.rs +2 -2
- package/sdk-core/tests/integ_tests/client_tests.rs +234 -6
- package/sdk-core/tests/integ_tests/update_tests.rs +180 -47
- package/sdk-core/tests/integ_tests/worker_tests.rs +32 -0
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +47 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +10 -10
- package/sdk-core/tests/main.rs +1 -0
- package/src/conversions.rs +84 -11
- package/src/runtime.rs +5 -17
- package/src/worker.rs +27 -6
- package/ts/index.ts +24 -5
- package/ts/worker-tuner.ts +71 -0
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
use
|
|
2
|
-
use
|
|
3
|
-
use
|
|
1
|
+
use assert_matches::assert_matches;
|
|
2
|
+
use futures_util::{future::BoxFuture, FutureExt};
|
|
3
|
+
use std::{
|
|
4
|
+
collections::HashMap,
|
|
5
|
+
convert::Infallible,
|
|
6
|
+
env,
|
|
7
|
+
task::{Context, Poll},
|
|
8
|
+
time::Duration,
|
|
9
|
+
};
|
|
10
|
+
use temporal_client::{Namespace, RetryConfig, WorkflowClientTrait, WorkflowService};
|
|
11
|
+
use temporal_sdk_core_protos::temporal::api::{
|
|
12
|
+
cloud::cloudservice::v1::GetNamespaceRequest, workflowservice::v1::DescribeNamespaceRequest,
|
|
13
|
+
};
|
|
4
14
|
use temporal_sdk_core_test_utils::{get_integ_server_options, CoreWfStarter, NAMESPACE};
|
|
15
|
+
use tokio::{
|
|
16
|
+
net::TcpListener,
|
|
17
|
+
sync::{mpsc::UnboundedSender, oneshot},
|
|
18
|
+
};
|
|
19
|
+
use tonic::{
|
|
20
|
+
body::BoxBody, codegen::Service, server::NamedService, transport::Server, Code, Request,
|
|
21
|
+
};
|
|
22
|
+
use tracing::info;
|
|
5
23
|
|
|
6
24
|
#[tokio::test]
|
|
7
25
|
async fn can_use_retry_client() {
|
|
@@ -17,9 +35,8 @@ async fn can_use_retry_client() {
|
|
|
17
35
|
#[tokio::test]
|
|
18
36
|
async fn can_use_retry_raw_client() {
|
|
19
37
|
let opts = get_integ_server_options();
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
retry_client
|
|
38
|
+
let mut client = opts.connect_no_namespace(None).await.unwrap();
|
|
39
|
+
client
|
|
23
40
|
.describe_namespace(DescribeNamespaceRequest {
|
|
24
41
|
namespace: NAMESPACE.to_string(),
|
|
25
42
|
..Default::default()
|
|
@@ -34,3 +51,214 @@ async fn calls_get_system_info() {
|
|
|
34
51
|
let raw_client = opts.connect_no_namespace(None).await.unwrap();
|
|
35
52
|
assert!(raw_client.get_client().capabilities().is_some());
|
|
36
53
|
}
|
|
54
|
+
|
|
55
|
+
#[tokio::test]
|
|
56
|
+
async fn per_call_timeout_respected_whole_client() {
|
|
57
|
+
let opts = get_integ_server_options();
|
|
58
|
+
let mut raw_client = opts.connect_no_namespace(None).await.unwrap();
|
|
59
|
+
let mut hm = HashMap::new();
|
|
60
|
+
hm.insert("grpc-timeout".to_string(), "0S".to_string());
|
|
61
|
+
raw_client.get_client().set_headers(hm);
|
|
62
|
+
let err = raw_client
|
|
63
|
+
.describe_namespace(DescribeNamespaceRequest {
|
|
64
|
+
namespace: NAMESPACE.to_string(),
|
|
65
|
+
..Default::default()
|
|
66
|
+
})
|
|
67
|
+
.await
|
|
68
|
+
.unwrap_err();
|
|
69
|
+
assert_matches!(err.code(), Code::DeadlineExceeded | Code::Cancelled);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[tokio::test]
|
|
73
|
+
async fn per_call_timeout_respected_one_call() {
|
|
74
|
+
let opts = get_integ_server_options();
|
|
75
|
+
let mut client = opts.connect_no_namespace(None).await.unwrap();
|
|
76
|
+
|
|
77
|
+
let mut req = Request::new(DescribeNamespaceRequest {
|
|
78
|
+
namespace: NAMESPACE.to_string(),
|
|
79
|
+
..Default::default()
|
|
80
|
+
});
|
|
81
|
+
req.set_timeout(Duration::from_millis(0));
|
|
82
|
+
let res = client.describe_namespace(req).await;
|
|
83
|
+
assert_matches!(
|
|
84
|
+
res.unwrap_err().code(),
|
|
85
|
+
Code::DeadlineExceeded | Code::Cancelled
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[derive(Clone)]
|
|
90
|
+
struct GenericService {
|
|
91
|
+
header_to_parse: &'static str,
|
|
92
|
+
header_tx: UnboundedSender<String>,
|
|
93
|
+
}
|
|
94
|
+
impl Service<tonic::codegen::http::Request<BoxBody>> for GenericService {
|
|
95
|
+
type Response = tonic::codegen::http::Response<BoxBody>;
|
|
96
|
+
type Error = Infallible;
|
|
97
|
+
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
|
98
|
+
|
|
99
|
+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
100
|
+
Poll::Ready(Ok(()))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn call(&mut self, req: tonic::codegen::http::Request<BoxBody>) -> Self::Future {
|
|
104
|
+
self.header_tx
|
|
105
|
+
.send(
|
|
106
|
+
String::from_utf8_lossy(
|
|
107
|
+
req.headers()
|
|
108
|
+
.get(self.header_to_parse)
|
|
109
|
+
.map(|hv| hv.as_bytes())
|
|
110
|
+
.unwrap_or_default(),
|
|
111
|
+
)
|
|
112
|
+
.to_string(),
|
|
113
|
+
)
|
|
114
|
+
.unwrap();
|
|
115
|
+
async move { Ok(Self::Response::new(tonic::codegen::empty_body())) }.boxed()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
impl NamedService for GenericService {
|
|
119
|
+
const NAME: &'static str = "temporal.api.workflowservice.v1.WorkflowService";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[tokio::test]
|
|
123
|
+
async fn timeouts_respected_one_call_fake_server() {
|
|
124
|
+
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
|
|
125
|
+
let (header_tx, mut header_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
126
|
+
|
|
127
|
+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
128
|
+
let addr = listener.local_addr().unwrap();
|
|
129
|
+
|
|
130
|
+
let server_handle = tokio::spawn(async move {
|
|
131
|
+
Server::builder()
|
|
132
|
+
.add_service(GenericService {
|
|
133
|
+
header_to_parse: "grpc-timeout",
|
|
134
|
+
header_tx,
|
|
135
|
+
})
|
|
136
|
+
.serve_with_incoming_shutdown(
|
|
137
|
+
tokio_stream::wrappers::TcpListenerStream::new(listener),
|
|
138
|
+
async {
|
|
139
|
+
shutdown_rx.await.ok();
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
.await
|
|
143
|
+
.unwrap();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
let mut opts = get_integ_server_options();
|
|
147
|
+
let uri = format!("http://localhost:{}", addr.port()).parse().unwrap();
|
|
148
|
+
opts.target_url = uri;
|
|
149
|
+
opts.skip_get_system_info = true;
|
|
150
|
+
opts.retry_config = RetryConfig {
|
|
151
|
+
max_retries: 1,
|
|
152
|
+
..Default::default()
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
let mut client = opts.connect_no_namespace(None).await.unwrap();
|
|
156
|
+
|
|
157
|
+
macro_rules! call_client {
|
|
158
|
+
($client:ident, $trx:ident, $client_fn:ident) => {
|
|
159
|
+
let mut req = Request::new(Default::default());
|
|
160
|
+
req.set_timeout(Duration::from_millis(100));
|
|
161
|
+
// Response is always error b/c empty body
|
|
162
|
+
let _ = $client.$client_fn(req).await;
|
|
163
|
+
let timeout = $trx.recv().await.unwrap();
|
|
164
|
+
assert_eq!("100000u", timeout);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
call_client!(client, header_rx, get_workflow_execution_history);
|
|
169
|
+
call_client!(client, header_rx, update_workflow_execution);
|
|
170
|
+
call_client!(client, header_rx, poll_workflow_execution_update);
|
|
171
|
+
|
|
172
|
+
// Shutdown the server
|
|
173
|
+
shutdown_tx.send(()).unwrap();
|
|
174
|
+
server_handle.await.unwrap();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[tokio::test]
|
|
178
|
+
async fn namespace_header_attached_to_relevant_calls() {
|
|
179
|
+
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
|
|
180
|
+
let (header_tx, mut header_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
181
|
+
|
|
182
|
+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
183
|
+
let addr = listener.local_addr().unwrap();
|
|
184
|
+
|
|
185
|
+
let server_handle = tokio::spawn(async move {
|
|
186
|
+
Server::builder()
|
|
187
|
+
.add_service(GenericService {
|
|
188
|
+
header_to_parse: "Temporal-Namespace",
|
|
189
|
+
header_tx,
|
|
190
|
+
})
|
|
191
|
+
.serve_with_incoming_shutdown(
|
|
192
|
+
tokio_stream::wrappers::TcpListenerStream::new(listener),
|
|
193
|
+
async {
|
|
194
|
+
shutdown_rx.await.ok();
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
.await
|
|
198
|
+
.unwrap();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
let mut opts = get_integ_server_options();
|
|
202
|
+
let uri = format!("http://localhost:{}", addr.port()).parse().unwrap();
|
|
203
|
+
opts.target_url = uri;
|
|
204
|
+
opts.skip_get_system_info = true;
|
|
205
|
+
opts.retry_config = RetryConfig {
|
|
206
|
+
max_retries: 1,
|
|
207
|
+
..Default::default()
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
let namespace = "namespace";
|
|
211
|
+
let client = opts.connect(namespace, None).await.unwrap();
|
|
212
|
+
|
|
213
|
+
let _ = client
|
|
214
|
+
.get_workflow_execution_history("hi".to_string(), None, vec![])
|
|
215
|
+
.await;
|
|
216
|
+
let val = header_rx.recv().await.unwrap();
|
|
217
|
+
assert_eq!(namespace, val);
|
|
218
|
+
let _ = client.list_namespaces().await;
|
|
219
|
+
let val = header_rx.recv().await.unwrap();
|
|
220
|
+
// List namespaces is not namespace-specific
|
|
221
|
+
assert_eq!("", val);
|
|
222
|
+
let _ = client
|
|
223
|
+
.describe_namespace(Namespace::Name("Other".to_string()))
|
|
224
|
+
.await;
|
|
225
|
+
let val = header_rx.recv().await.unwrap();
|
|
226
|
+
assert_eq!("Other", val);
|
|
227
|
+
|
|
228
|
+
// Shutdown the server
|
|
229
|
+
shutdown_tx.send(()).unwrap();
|
|
230
|
+
server_handle.await.unwrap();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[tokio::test]
|
|
234
|
+
async fn cloud_ops_test() {
|
|
235
|
+
let api_key = match env::var("TEMPORAL_CLIENT_CLOUD_API_KEY") {
|
|
236
|
+
Ok(k) => k,
|
|
237
|
+
Err(_) => {
|
|
238
|
+
// skip test
|
|
239
|
+
info!("Skipped cloud operations client test");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
let api_version =
|
|
244
|
+
env::var("TEMPORAL_CLIENT_CLOUD_API_VERSION").expect("version env var must exist");
|
|
245
|
+
let namespace =
|
|
246
|
+
env::var("TEMPORAL_CLIENT_CLOUD_NAMESPACE").expect("namespace env var must exist");
|
|
247
|
+
let mut opts = get_integ_server_options();
|
|
248
|
+
opts.target_url = "saas-api.tmprl.cloud:443".parse().unwrap();
|
|
249
|
+
opts.api_key = Some(api_key);
|
|
250
|
+
opts.headers = Some({
|
|
251
|
+
let mut hm = HashMap::new();
|
|
252
|
+
hm.insert("temporal-cloud-api-version".to_string(), api_version);
|
|
253
|
+
hm
|
|
254
|
+
});
|
|
255
|
+
let mut client = opts.connect_no_namespace(None).await.unwrap().into_inner();
|
|
256
|
+
let cloud_client = client.cloud_svc_mut();
|
|
257
|
+
let res = cloud_client
|
|
258
|
+
.get_namespace(GetNamespaceRequest {
|
|
259
|
+
namespace: namespace.clone(),
|
|
260
|
+
})
|
|
261
|
+
.await
|
|
262
|
+
.unwrap();
|
|
263
|
+
assert_eq!(res.into_inner().namespace.unwrap().namespace, namespace);
|
|
264
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
use anyhow::
|
|
1
|
+
use anyhow::anyhow;
|
|
2
2
|
use assert_matches::assert_matches;
|
|
3
3
|
use futures_util::{future, future::join_all, StreamExt};
|
|
4
4
|
use once_cell::sync::Lazy;
|
|
5
5
|
use std::{
|
|
6
|
-
sync::
|
|
6
|
+
sync::{
|
|
7
|
+
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
8
|
+
Arc,
|
|
9
|
+
},
|
|
7
10
|
time::Duration,
|
|
8
11
|
};
|
|
9
|
-
use temporal_client::WorkflowClientTrait;
|
|
12
|
+
use temporal_client::{Client, RetryClient, WorkflowClientTrait, WorkflowService};
|
|
10
13
|
use temporal_sdk::{ActContext, ActivityOptions, LocalActivityOptions, UpdateContext, WfContext};
|
|
11
14
|
use temporal_sdk_core::replay::HistoryForReplay;
|
|
12
15
|
use temporal_sdk_core_api::Worker;
|
|
@@ -23,29 +26,159 @@ use temporal_sdk_core_protos::{
|
|
|
23
26
|
ActivityTaskCompletion, AsJsonPayloadExt, IntoPayloadsExt,
|
|
24
27
|
},
|
|
25
28
|
temporal::api::{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
update::v1::WaitPolicy,
|
|
29
|
+
common::v1::WorkflowExecution,
|
|
30
|
+
enums::v1::{EventType, ResetReapplyType, UpdateWorkflowExecutionLifecycleStage},
|
|
31
|
+
update::{self, v1::WaitPolicy},
|
|
32
|
+
workflowservice::v1::ResetWorkflowExecutionRequest,
|
|
29
33
|
},
|
|
30
34
|
};
|
|
31
35
|
use temporal_sdk_core_test_utils::{
|
|
32
|
-
init_core_and_create_wf, init_core_replay_preloaded,
|
|
33
|
-
WorkerTestHelpers,
|
|
36
|
+
drain_pollers_and_shutdown, init_core_and_create_wf, init_core_replay_preloaded,
|
|
37
|
+
start_timer_cmd, CoreWfStarter, WorkerTestHelpers,
|
|
34
38
|
};
|
|
35
39
|
use tokio::{join, sync::Barrier};
|
|
40
|
+
use uuid::Uuid;
|
|
41
|
+
|
|
42
|
+
#[derive(Clone, Copy)]
|
|
43
|
+
enum FailUpdate {
|
|
44
|
+
Yes,
|
|
45
|
+
No,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[derive(Clone, Copy)]
|
|
49
|
+
enum CompleteWorkflow {
|
|
50
|
+
Yes,
|
|
51
|
+
No,
|
|
52
|
+
}
|
|
36
53
|
|
|
37
54
|
#[rstest::rstest]
|
|
38
55
|
#[tokio::test]
|
|
39
|
-
async fn update_workflow(#[values(
|
|
56
|
+
async fn update_workflow(#[values(FailUpdate::Yes, FailUpdate::No)] will_fail: FailUpdate) {
|
|
40
57
|
let mut starter = init_core_and_create_wf("update_workflow").await;
|
|
41
58
|
let core = starter.get_worker().await;
|
|
42
59
|
let client = starter.get_client().await;
|
|
43
|
-
let workflow_id = starter.get_task_queue()
|
|
44
|
-
|
|
60
|
+
let workflow_id = starter.get_task_queue();
|
|
45
61
|
let update_id = "some_update";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
send_and_handle_update(
|
|
63
|
+
workflow_id,
|
|
64
|
+
update_id,
|
|
65
|
+
will_fail,
|
|
66
|
+
CompleteWorkflow::Yes,
|
|
67
|
+
core.as_ref(),
|
|
68
|
+
client.as_ref(),
|
|
69
|
+
)
|
|
70
|
+
.await;
|
|
71
|
+
|
|
72
|
+
// Make sure replay works
|
|
73
|
+
let history = client
|
|
74
|
+
.get_workflow_execution_history(workflow_id.to_string(), None, vec![])
|
|
75
|
+
.await
|
|
76
|
+
.unwrap()
|
|
77
|
+
.history
|
|
78
|
+
.unwrap();
|
|
79
|
+
let with_id = HistoryForReplay::new(history, workflow_id.to_string());
|
|
80
|
+
let replay_worker = init_core_replay_preloaded(workflow_id, [with_id]);
|
|
81
|
+
handle_update(will_fail, CompleteWorkflow::Yes, replay_worker.as_ref()).await;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[rstest::rstest]
|
|
85
|
+
#[tokio::test]
|
|
86
|
+
async fn reapplied_updates_due_to_reset() {
|
|
87
|
+
let mut starter = init_core_and_create_wf("update_workflow").await;
|
|
88
|
+
let core = starter.get_worker().await;
|
|
89
|
+
let client = starter.get_client().await;
|
|
90
|
+
let workflow_id = starter.get_task_queue();
|
|
91
|
+
let pre_reset_run_id = send_and_handle_update(
|
|
92
|
+
workflow_id,
|
|
93
|
+
"first-update",
|
|
94
|
+
FailUpdate::No,
|
|
95
|
+
CompleteWorkflow::Yes,
|
|
96
|
+
core.as_ref(),
|
|
97
|
+
client.as_ref(),
|
|
98
|
+
)
|
|
99
|
+
.await;
|
|
100
|
+
|
|
101
|
+
// Reset to before the update was accepted
|
|
102
|
+
let workflow_task_finish_event_id = 4;
|
|
103
|
+
|
|
104
|
+
let mut client_mut = client.clone();
|
|
105
|
+
let reset_response = WorkflowService::reset_workflow_execution(
|
|
106
|
+
Arc::make_mut(&mut client_mut),
|
|
107
|
+
ResetWorkflowExecutionRequest {
|
|
108
|
+
namespace: client.namespace().into(),
|
|
109
|
+
workflow_execution: Some(WorkflowExecution {
|
|
110
|
+
workflow_id: workflow_id.into(),
|
|
111
|
+
run_id: pre_reset_run_id.clone(),
|
|
112
|
+
}),
|
|
113
|
+
workflow_task_finish_event_id,
|
|
114
|
+
reset_reapply_type: ResetReapplyType::AllEligible as i32,
|
|
115
|
+
request_id: Uuid::new_v4().to_string(),
|
|
116
|
+
..Default::default()
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
.await
|
|
120
|
+
.unwrap()
|
|
121
|
+
.into_inner();
|
|
122
|
+
|
|
123
|
+
// Accept and complete the reapplied update
|
|
124
|
+
handle_update(FailUpdate::No, CompleteWorkflow::No, core.as_ref()).await;
|
|
125
|
+
|
|
126
|
+
// Send a second update and complete the workflow
|
|
127
|
+
let post_reset_run_id = send_and_handle_update(
|
|
128
|
+
workflow_id,
|
|
129
|
+
"second-update",
|
|
130
|
+
FailUpdate::No,
|
|
131
|
+
CompleteWorkflow::Yes,
|
|
132
|
+
core.as_ref(),
|
|
133
|
+
client.as_ref(),
|
|
134
|
+
)
|
|
135
|
+
.await;
|
|
136
|
+
|
|
137
|
+
assert_eq!(post_reset_run_id, reset_response.run_id);
|
|
138
|
+
|
|
139
|
+
// Make sure replay works
|
|
140
|
+
let history = client
|
|
141
|
+
.get_workflow_execution_history(workflow_id.to_string(), Some(post_reset_run_id), vec![])
|
|
142
|
+
.await
|
|
143
|
+
.unwrap()
|
|
144
|
+
.history
|
|
145
|
+
.unwrap();
|
|
146
|
+
let with_id = HistoryForReplay::new(history, workflow_id.to_string());
|
|
147
|
+
|
|
148
|
+
let replay_worker = init_core_replay_preloaded(workflow_id, [with_id]);
|
|
149
|
+
// We now recapitulate the actions that the worker took on first execution above, pretending
|
|
150
|
+
// that we always followed the post-reset history.
|
|
151
|
+
// First, we handled the post-reset reapplied update and did not complete the workflow.
|
|
152
|
+
handle_update(FailUpdate::No, CompleteWorkflow::No, replay_worker.as_ref()).await;
|
|
153
|
+
// Then the client sent a second update; we handled it and completed the workflow.
|
|
154
|
+
handle_update(
|
|
155
|
+
FailUpdate::No,
|
|
156
|
+
CompleteWorkflow::Yes,
|
|
157
|
+
replay_worker.as_ref(),
|
|
158
|
+
)
|
|
159
|
+
.await;
|
|
160
|
+
|
|
161
|
+
// This is a replay worker and there is a remaining activation containing a RemoveFromCache job.
|
|
162
|
+
let act = replay_worker.poll_workflow_activation().await.unwrap();
|
|
163
|
+
replay_worker
|
|
164
|
+
.complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id))
|
|
165
|
+
.await
|
|
166
|
+
.unwrap();
|
|
167
|
+
drain_pollers_and_shutdown(&replay_worker).await;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Start a workflow, send an update, accept the update, complete the update, complete the workflow.
|
|
171
|
+
async fn send_and_handle_update(
|
|
172
|
+
workflow_id: &str,
|
|
173
|
+
update_id: &str,
|
|
174
|
+
fail_update: FailUpdate,
|
|
175
|
+
complete_workflow: CompleteWorkflow,
|
|
176
|
+
core: &dyn Worker,
|
|
177
|
+
client: &RetryClient<Client>,
|
|
178
|
+
) -> String {
|
|
179
|
+
// Complete first task with no commands
|
|
180
|
+
let act = core.poll_workflow_activation().await.unwrap();
|
|
181
|
+
core.complete_workflow_activation(WorkflowActivationCompletion::empty(act.run_id.clone()))
|
|
49
182
|
.await
|
|
50
183
|
.unwrap();
|
|
51
184
|
|
|
@@ -54,7 +187,7 @@ async fn update_workflow(#[values(true, false)] will_fail: bool) {
|
|
|
54
187
|
client
|
|
55
188
|
.update_workflow_execution(
|
|
56
189
|
workflow_id.to_string(),
|
|
57
|
-
|
|
190
|
+
act.run_id.to_string(),
|
|
58
191
|
update_id.to_string(),
|
|
59
192
|
WaitPolicy {
|
|
60
193
|
lifecycle_stage: UpdateWorkflowExecutionLifecycleStage::Completed as i32,
|
|
@@ -65,40 +198,38 @@ async fn update_workflow(#[values(true, false)] will_fail: bool) {
|
|
|
65
198
|
.unwrap()
|
|
66
199
|
};
|
|
67
200
|
|
|
68
|
-
|
|
201
|
+
// Accept update, complete update and complete workflow
|
|
202
|
+
let processing_task = handle_update(fail_update, complete_workflow, core);
|
|
69
203
|
let (ur, _) = join!(update_task, processing_task);
|
|
70
204
|
|
|
71
205
|
let v = ur.outcome.unwrap().value.unwrap();
|
|
72
|
-
|
|
73
|
-
assert_matches!(v, update::v1::outcome::Value::Failure(_))
|
|
74
|
-
|
|
75
|
-
assert_matches!(v, update::v1::outcome::Value::Success(_));
|
|
206
|
+
match fail_update {
|
|
207
|
+
FailUpdate::Yes => assert_matches!(v, update::v1::outcome::Value::Failure(_)),
|
|
208
|
+
FailUpdate::No => assert_matches!(v, update::v1::outcome::Value::Success(_)),
|
|
76
209
|
}
|
|
77
|
-
|
|
78
|
-
// Make sure replay works
|
|
79
|
-
let history = client
|
|
80
|
-
.get_workflow_execution_history(workflow_id.clone(), None, vec![])
|
|
81
|
-
.await
|
|
82
|
-
.unwrap()
|
|
83
|
-
.history
|
|
84
|
-
.unwrap();
|
|
85
|
-
let with_id = HistoryForReplay::new(history, workflow_id.clone());
|
|
86
|
-
let replay_worker = init_core_replay_preloaded(&workflow_id, [with_id]);
|
|
87
|
-
_do_update_workflow(will_fail, replay_worker.as_ref()).await;
|
|
210
|
+
act.run_id
|
|
88
211
|
}
|
|
89
212
|
|
|
90
|
-
|
|
91
|
-
|
|
213
|
+
// Accept and then complete update. If `FailUpdate::Yes` then complete the update as a failure; if
|
|
214
|
+
// `CompleteWorkflow::Yes` then additionally complete the workflow. Timers are created when further
|
|
215
|
+
// activations are required (i.e., on accepting-but-not-completing the update, and on completing the
|
|
216
|
+
// update if not also completing the workflow).
|
|
217
|
+
async fn handle_update(
|
|
218
|
+
fail_update: FailUpdate,
|
|
219
|
+
complete_workflow: CompleteWorkflow,
|
|
220
|
+
core: &dyn Worker,
|
|
221
|
+
) {
|
|
222
|
+
let act = core.poll_workflow_activation().await.unwrap();
|
|
92
223
|
// On replay, the first activation has update & start workflow, but on first execution, it does
|
|
93
224
|
// not - can happen if update is waiting on some condition.
|
|
94
225
|
let pid = assert_matches!(
|
|
95
|
-
&
|
|
226
|
+
&act.jobs[0],
|
|
96
227
|
WorkflowActivationJob {
|
|
97
228
|
variant: Some(workflow_activation_job::Variant::DoUpdate(d)),
|
|
98
229
|
} => &d.protocol_instance_id
|
|
99
230
|
);
|
|
100
231
|
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
101
|
-
|
|
232
|
+
act.run_id,
|
|
102
233
|
vec![
|
|
103
234
|
UpdateResponse {
|
|
104
235
|
protocol_instance_id: pid.to_string(),
|
|
@@ -111,26 +242,28 @@ async fn _do_update_workflow(will_fail: bool, core: &dyn Worker) {
|
|
|
111
242
|
.await
|
|
112
243
|
.unwrap();
|
|
113
244
|
|
|
114
|
-
|
|
115
|
-
|
|
245
|
+
// Timer fires
|
|
246
|
+
let act = core.poll_workflow_activation().await.unwrap();
|
|
247
|
+
let update_response = match fail_update {
|
|
248
|
+
FailUpdate::Yes => UpdateResponse {
|
|
116
249
|
protocol_instance_id: pid.to_string(),
|
|
117
250
|
response: Some(update_response::Response::Rejected(
|
|
118
251
|
"uh oh spaghettios!".into(),
|
|
119
252
|
)),
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
UpdateResponse {
|
|
253
|
+
},
|
|
254
|
+
FailUpdate::No => UpdateResponse {
|
|
123
255
|
protocol_instance_id: pid.to_string(),
|
|
124
256
|
response: Some(update_response::Response::Completed("done!".into())),
|
|
125
|
-
}
|
|
257
|
+
},
|
|
126
258
|
};
|
|
127
|
-
let res = core.poll_workflow_activation().await.unwrap();
|
|
128
|
-
// Timer fires
|
|
129
259
|
core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
|
|
130
|
-
|
|
260
|
+
act.run_id,
|
|
131
261
|
vec![
|
|
132
|
-
|
|
133
|
-
|
|
262
|
+
update_response.into(),
|
|
263
|
+
match complete_workflow {
|
|
264
|
+
CompleteWorkflow::Yes => CompleteWorkflowExecution { result: None }.into(),
|
|
265
|
+
CompleteWorkflow::No => start_timer_cmd(1, Duration::from_millis(1)),
|
|
266
|
+
},
|
|
134
267
|
],
|
|
135
268
|
))
|
|
136
269
|
.await
|
|
@@ -843,7 +976,7 @@ async fn worker_restarted_in_middle_of_update() {
|
|
|
843
976
|
BARR.wait().await;
|
|
844
977
|
if !ACT_RAN.fetch_or(true, Ordering::Relaxed) {
|
|
845
978
|
// On first run fail the task so we'll get retried on the new worker
|
|
846
|
-
|
|
979
|
+
return Err(anyhow!("Fail first time").into());
|
|
847
980
|
}
|
|
848
981
|
Ok(echo_me)
|
|
849
982
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
use assert_matches::assert_matches;
|
|
2
|
+
use temporal_sdk_core::{init_worker, CoreRuntime};
|
|
3
|
+
use temporal_sdk_core_api::{errors::WorkerValidationError, worker::WorkerConfigBuilder, Worker};
|
|
4
|
+
use temporal_sdk_core_test_utils::{get_integ_server_options, get_integ_telem_options};
|
|
5
|
+
|
|
6
|
+
#[tokio::test]
|
|
7
|
+
async fn worker_validation_fails_on_nonexistent_namespace() {
|
|
8
|
+
let opts = get_integ_server_options();
|
|
9
|
+
let runtime = CoreRuntime::new_assume_tokio(get_integ_telem_options()).unwrap();
|
|
10
|
+
let retrying_client = opts
|
|
11
|
+
.connect_no_namespace(runtime.telemetry().get_temporal_metric_meter())
|
|
12
|
+
.await
|
|
13
|
+
.unwrap();
|
|
14
|
+
|
|
15
|
+
let worker = init_worker(
|
|
16
|
+
&runtime,
|
|
17
|
+
WorkerConfigBuilder::default()
|
|
18
|
+
.namespace("i_dont_exist")
|
|
19
|
+
.task_queue("Wheee!")
|
|
20
|
+
.worker_build_id("blah")
|
|
21
|
+
.build()
|
|
22
|
+
.unwrap(),
|
|
23
|
+
retrying_client,
|
|
24
|
+
)
|
|
25
|
+
.unwrap();
|
|
26
|
+
|
|
27
|
+
let res = worker.validate().await;
|
|
28
|
+
assert_matches!(
|
|
29
|
+
res,
|
|
30
|
+
Err(WorkerValidationError::NamespaceDescribeError { .. })
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -8,8 +8,8 @@ use std::{
|
|
|
8
8
|
};
|
|
9
9
|
use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions};
|
|
10
10
|
use temporal_sdk::{
|
|
11
|
-
ActContext, ActExitValue,
|
|
12
|
-
|
|
11
|
+
ActContext, ActExitValue, ActivityError, ActivityOptions, CancellableFuture, WfContext,
|
|
12
|
+
WorkflowResult,
|
|
13
13
|
};
|
|
14
14
|
use temporal_sdk_core_protos::{
|
|
15
15
|
coresdk::{
|
|
@@ -787,6 +787,50 @@ async fn activity_cancelled_after_heartbeat_times_out() {
|
|
|
787
787
|
.unwrap();
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
+
#[tokio::test]
|
|
791
|
+
async fn one_activity_abandon_cancelled_before_started() {
|
|
792
|
+
let wf_name = "one_activity_abandon_cancelled_before_started";
|
|
793
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
794
|
+
let mut worker = starter.worker().await;
|
|
795
|
+
let client = starter.get_client().await;
|
|
796
|
+
worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
|
|
797
|
+
let act_fut = ctx.activity(ActivityOptions {
|
|
798
|
+
activity_type: "echo_activity".to_string(),
|
|
799
|
+
start_to_close_timeout: Some(Duration::from_secs(5)),
|
|
800
|
+
input: "hi!".as_json_payload().expect("serializes fine"),
|
|
801
|
+
cancellation_type: ActivityCancellationType::Abandon,
|
|
802
|
+
..Default::default()
|
|
803
|
+
});
|
|
804
|
+
act_fut.cancel(&ctx);
|
|
805
|
+
act_fut.await;
|
|
806
|
+
Ok(().into())
|
|
807
|
+
});
|
|
808
|
+
worker.register_activity(
|
|
809
|
+
"echo_activity",
|
|
810
|
+
|_ctx: ActContext, echo_me: String| async move {
|
|
811
|
+
sleep(Duration::from_secs(2)).await;
|
|
812
|
+
Ok(echo_me)
|
|
813
|
+
},
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
let run_id = worker
|
|
817
|
+
.submit_wf(
|
|
818
|
+
wf_name.to_owned(),
|
|
819
|
+
wf_name.to_owned(),
|
|
820
|
+
vec![],
|
|
821
|
+
WorkflowOptions::default(),
|
|
822
|
+
)
|
|
823
|
+
.await
|
|
824
|
+
.unwrap();
|
|
825
|
+
worker.run_until_done().await.unwrap();
|
|
826
|
+
let handle = client.get_untyped_workflow_handle(wf_name, run_id);
|
|
827
|
+
let res = handle
|
|
828
|
+
.get_workflow_result(Default::default())
|
|
829
|
+
.await
|
|
830
|
+
.unwrap();
|
|
831
|
+
assert_matches!(res, WorkflowExecutionResult::Succeeded(_));
|
|
832
|
+
}
|
|
833
|
+
|
|
790
834
|
#[tokio::test]
|
|
791
835
|
async fn one_activity_abandon_cancelled_after_complete() {
|
|
792
836
|
let wf_name = "one_activity_abandon_cancelled_after_complete";
|
|
@@ -946,7 +990,7 @@ async fn graceful_shutdown() {
|
|
|
946
990
|
// just wait to be cancelled
|
|
947
991
|
ctx.cancelled().await;
|
|
948
992
|
ACTS_DONE.add_permits(1);
|
|
949
|
-
Result::<(), _>::Err(
|
|
993
|
+
Result::<(), _>::Err(ActivityError::cancelled())
|
|
950
994
|
});
|
|
951
995
|
|
|
952
996
|
worker
|