@temporalio/core-bridge 1.12.2 → 1.12.3
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/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/.cargo/config.toml +1 -1
- package/sdk-core/client/src/callback_based.rs +123 -0
- package/sdk-core/client/src/lib.rs +96 -28
- package/sdk-core/client/src/metrics.rs +33 -5
- package/sdk-core/client/src/raw.rs +40 -1
- package/sdk-core/client/src/retry.rs +12 -3
- package/sdk-core/core/src/lib.rs +4 -2
- package/sdk-core/core/src/pollers/poll_buffer.rs +62 -14
- package/sdk-core/core/src/worker/client.rs +9 -5
- package/sdk-core/core/src/worker/heartbeat.rs +3 -1
- package/sdk-core/core-api/src/worker.rs +2 -2
- package/sdk-core/core-c-bridge/Cargo.toml +2 -0
- package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +105 -0
- package/sdk-core/core-c-bridge/src/client.rs +265 -8
- package/sdk-core/core-c-bridge/src/tests/context.rs +11 -0
- package/sdk-core/core-c-bridge/src/tests/mod.rs +179 -3
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +1 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +83 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +37 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/connectivityrule/v1/message.proto +64 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +3 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +10 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +1 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +644 -9
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +635 -21
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +60 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +84 -15
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +11 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +5 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/worker_config.proto +36 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +29 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +11 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +122 -4
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +41 -0
- package/sdk-core/sdk-core-protos/src/lib.rs +5 -1
- package/sdk-core/test-utils/Cargo.toml +1 -0
- package/sdk-core/test-utils/src/lib.rs +90 -3
- package/sdk-core/tests/cloud_tests.rs +11 -74
- package/sdk-core/tests/integ_tests/client_tests.rs +14 -10
- package/sdk-core/tests/integ_tests/worker_tests.rs +8 -2
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +13 -0
- package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +2 -108
- package/sdk-core/tests/main.rs +3 -0
- package/sdk-core/tests/shared_tests/mod.rs +43 -0
- package/sdk-core/tests/shared_tests/priority.rs +155 -0
- package/src/client.rs +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/core-bridge",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.3",
|
|
4
4
|
"description": "Temporal.io SDK Core<>Node bridge",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@grpc/grpc-js": "^1.12.4",
|
|
26
|
-
"@temporalio/common": "1.12.
|
|
26
|
+
"@temporalio/common": "1.12.3",
|
|
27
27
|
"arg": "^5.0.2",
|
|
28
28
|
"cargo-cp-artifact": "^0.1.8",
|
|
29
29
|
"which": "^4.0.0"
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "e25f1d5ddaf0b5c755457b1cc1dde7c6e089a63b"
|
|
60
60
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[env]
|
|
2
2
|
# This temporarily overrides the version of the CLI used for integration tests, locally and in CI
|
|
3
|
-
|
|
3
|
+
CLI_VERSION_OVERRIDE = "v1.4.1-cloud-v1-29-0-139-2.0"
|
|
4
4
|
|
|
5
5
|
[alias]
|
|
6
6
|
integ-test = ["test", "--features", "temporal-sdk-core-protos/serde_serialize", "--package", "temporal-sdk-core", "--test", "integ_runner", "--"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//! This module implements support for callback-based gRPC service that has a callback invoked for
|
|
2
|
+
//! every gRPC call instead of directly using the network.
|
|
3
|
+
|
|
4
|
+
use anyhow::anyhow;
|
|
5
|
+
use bytes::{BufMut, BytesMut};
|
|
6
|
+
use futures_util::future::BoxFuture;
|
|
7
|
+
use futures_util::stream;
|
|
8
|
+
use http::{HeaderMap, Request, Response};
|
|
9
|
+
use http_body_util::{BodyExt, StreamBody, combinators::BoxBody};
|
|
10
|
+
use hyper::body::{Bytes, Frame};
|
|
11
|
+
use std::{
|
|
12
|
+
sync::Arc,
|
|
13
|
+
task::{Context, Poll},
|
|
14
|
+
};
|
|
15
|
+
use tonic::{Status, metadata::GRPC_CONTENT_TYPE};
|
|
16
|
+
use tower::Service;
|
|
17
|
+
|
|
18
|
+
/// gRPC request for use by a callback.
|
|
19
|
+
pub struct GrpcRequest {
|
|
20
|
+
/// Fully qualified gRPC service name.
|
|
21
|
+
pub service: String,
|
|
22
|
+
/// RPC name.
|
|
23
|
+
pub rpc: String,
|
|
24
|
+
/// Request headers.
|
|
25
|
+
pub headers: HeaderMap,
|
|
26
|
+
/// Protobuf bytes of the request.
|
|
27
|
+
pub proto: Bytes,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Successful gRPC response returned by a callback.
|
|
31
|
+
pub struct GrpcSuccessResponse {
|
|
32
|
+
/// Response headers.
|
|
33
|
+
pub headers: HeaderMap,
|
|
34
|
+
|
|
35
|
+
/// Response proto bytes.
|
|
36
|
+
pub proto: Vec<u8>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// gRPC service that invokes the given callback on each call.
|
|
40
|
+
#[derive(Clone)]
|
|
41
|
+
pub struct CallbackBasedGrpcService {
|
|
42
|
+
/// Callback to invoke on each RPC call.
|
|
43
|
+
#[allow(clippy::type_complexity)] // Signature is not that complex
|
|
44
|
+
pub callback: Arc<
|
|
45
|
+
dyn Fn(GrpcRequest) -> BoxFuture<'static, Result<GrpcSuccessResponse, Status>>
|
|
46
|
+
+ Send
|
|
47
|
+
+ Sync,
|
|
48
|
+
>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl Service<Request<tonic::body::Body>> for CallbackBasedGrpcService {
|
|
52
|
+
type Response = http::Response<tonic::body::Body>;
|
|
53
|
+
type Error = anyhow::Error;
|
|
54
|
+
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
|
55
|
+
|
|
56
|
+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
57
|
+
Poll::Ready(Ok(()))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn call(&mut self, req: Request<tonic::body::Body>) -> Self::Future {
|
|
61
|
+
let callback = self.callback.clone();
|
|
62
|
+
|
|
63
|
+
Box::pin(async move {
|
|
64
|
+
// Build req
|
|
65
|
+
let (parts, body) = req.into_parts();
|
|
66
|
+
let mut path_parts = parts.uri.path().trim_start_matches('/').split('/');
|
|
67
|
+
let req_body = body.collect().await.map_err(|e| anyhow!(e))?.to_bytes();
|
|
68
|
+
// Body is flag saying whether compressed (we do not support that), then 32-bit length,
|
|
69
|
+
// then the actual proto.
|
|
70
|
+
if req_body.len() < 5 {
|
|
71
|
+
return Err(anyhow!("Too few request bytes: {}", req_body.len()));
|
|
72
|
+
} else if req_body[0] != 0 {
|
|
73
|
+
return Err(anyhow!("Compression not supported"));
|
|
74
|
+
}
|
|
75
|
+
let req_proto_len =
|
|
76
|
+
u32::from_be_bytes([req_body[1], req_body[2], req_body[3], req_body[4]]) as usize;
|
|
77
|
+
if req_body.len() < 5 + req_proto_len {
|
|
78
|
+
return Err(anyhow!(
|
|
79
|
+
"Expected request body length at least {}, got {}",
|
|
80
|
+
5 + req_proto_len,
|
|
81
|
+
req_body.len()
|
|
82
|
+
));
|
|
83
|
+
}
|
|
84
|
+
let req = GrpcRequest {
|
|
85
|
+
service: path_parts.next().unwrap_or_default().to_owned(),
|
|
86
|
+
rpc: path_parts.next().unwrap_or_default().to_owned(),
|
|
87
|
+
headers: parts.headers,
|
|
88
|
+
proto: req_body.slice(5..5 + req_proto_len),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Invoke and handle response
|
|
92
|
+
match (callback)(req).await {
|
|
93
|
+
Ok(success) => {
|
|
94
|
+
// Create body bytes which requires a flag saying whether compressed, then
|
|
95
|
+
// message len, then actual message. So we create a Bytes for those 5 prepend
|
|
96
|
+
// parts, then stream it alongside the user-provided Vec. This allows us to
|
|
97
|
+
// avoid copying the vec
|
|
98
|
+
let mut body_prepend = BytesMut::with_capacity(5);
|
|
99
|
+
body_prepend.put_u8(0); // 0 means no compression
|
|
100
|
+
body_prepend.put_u32(success.proto.len() as u32);
|
|
101
|
+
let stream = stream::iter(vec![
|
|
102
|
+
Ok::<_, Status>(Frame::data(Bytes::from(body_prepend))),
|
|
103
|
+
Ok::<_, Status>(Frame::data(Bytes::from(success.proto))),
|
|
104
|
+
]);
|
|
105
|
+
let stream_body = StreamBody::new(stream);
|
|
106
|
+
let full_body = BoxBody::new(stream_body).boxed();
|
|
107
|
+
|
|
108
|
+
// Build response appending headers
|
|
109
|
+
let mut resp_builder = Response::builder()
|
|
110
|
+
.status(200)
|
|
111
|
+
.header(http::header::CONTENT_TYPE, GRPC_CONTENT_TYPE);
|
|
112
|
+
for (key, value) in success.headers.iter() {
|
|
113
|
+
resp_builder = resp_builder.header(key, value);
|
|
114
|
+
}
|
|
115
|
+
Ok(resp_builder
|
|
116
|
+
.body(tonic::body::Body::new(full_body))
|
|
117
|
+
.map_err(|e| anyhow!(e))?)
|
|
118
|
+
}
|
|
119
|
+
Err(status) => Ok(status.into_http()),
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#[macro_use]
|
|
8
8
|
extern crate tracing;
|
|
9
9
|
|
|
10
|
+
pub mod callback_based;
|
|
10
11
|
mod metrics;
|
|
11
12
|
mod proxy;
|
|
12
13
|
mod raw;
|
|
@@ -35,7 +36,7 @@ pub use workflow_handle::{
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
use crate::{
|
|
38
|
-
metrics::{GrpcMetricSvc, MetricsContext},
|
|
39
|
+
metrics::{ChannelOrGrpcOverride, GrpcMetricSvc, MetricsContext},
|
|
39
40
|
raw::{AttachMetricLabels, sealed::RawClientLike},
|
|
40
41
|
sealed::WfHandleClient,
|
|
41
42
|
workflow_handle::UntypedWorkflowHandle,
|
|
@@ -89,6 +90,8 @@ static TEMPORAL_NAMESPACE_HEADER_KEY: &str = "temporal-namespace";
|
|
|
89
90
|
|
|
90
91
|
/// Key used to communicate when a GRPC message is too large
|
|
91
92
|
pub static MESSAGE_TOO_LARGE_KEY: &str = "message-too-large";
|
|
93
|
+
/// Key used to indicate a error was returned by the retryer because of the short-circuit predicate
|
|
94
|
+
pub static ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT: &str = "short-circuit";
|
|
92
95
|
|
|
93
96
|
/// The server times out polls after 60 seconds. Set our timeout to be slightly beyond that.
|
|
94
97
|
const LONG_POLL_TIMEOUT: Duration = Duration::from_secs(70);
|
|
@@ -432,34 +435,59 @@ impl ClientOptions {
|
|
|
432
435
|
metrics_meter: Option<TemporalMeter>,
|
|
433
436
|
) -> Result<RetryClient<ConfiguredClient<TemporalServiceClientWithMetrics>>, ClientInitError>
|
|
434
437
|
{
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
channel.connect().await?
|
|
455
|
-
};
|
|
456
|
-
let service = ServiceBuilder::new()
|
|
457
|
-
.layer_fn(move |channel| GrpcMetricSvc {
|
|
458
|
-
inner: channel,
|
|
438
|
+
self.connect_no_namespace_with_service_override(metrics_meter, None)
|
|
439
|
+
.await
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/// Attempt to establish a connection to the Temporal server and return a gRPC client which is
|
|
443
|
+
/// intercepted with retry, default headers functionality, and metrics if provided. If a
|
|
444
|
+
/// service_override is present, network-specific options are ignored and the callback is
|
|
445
|
+
/// invoked for each gRPC call.
|
|
446
|
+
///
|
|
447
|
+
/// See [RetryClient] for more
|
|
448
|
+
pub async fn connect_no_namespace_with_service_override(
|
|
449
|
+
&self,
|
|
450
|
+
metrics_meter: Option<TemporalMeter>,
|
|
451
|
+
service_override: Option<callback_based::CallbackBasedGrpcService>,
|
|
452
|
+
) -> Result<RetryClient<ConfiguredClient<TemporalServiceClientWithMetrics>>, ClientInitError>
|
|
453
|
+
{
|
|
454
|
+
let service = if let Some(service_override) = service_override {
|
|
455
|
+
GrpcMetricSvc {
|
|
456
|
+
inner: ChannelOrGrpcOverride::GrpcOverride(service_override),
|
|
459
457
|
metrics: metrics_meter.clone().map(MetricsContext::new),
|
|
460
458
|
disable_errcode_label: self.disable_error_code_metric_tags,
|
|
461
|
-
}
|
|
462
|
-
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
let channel = Channel::from_shared(self.target_url.to_string())?;
|
|
462
|
+
let channel = self.add_tls_to_channel(channel).await?;
|
|
463
|
+
let channel = if let Some(keep_alive) = self.keep_alive.as_ref() {
|
|
464
|
+
channel
|
|
465
|
+
.keep_alive_while_idle(true)
|
|
466
|
+
.http2_keep_alive_interval(keep_alive.interval)
|
|
467
|
+
.keep_alive_timeout(keep_alive.timeout)
|
|
468
|
+
} else {
|
|
469
|
+
channel
|
|
470
|
+
};
|
|
471
|
+
let channel = if let Some(origin) = self.override_origin.clone() {
|
|
472
|
+
channel.origin(origin)
|
|
473
|
+
} else {
|
|
474
|
+
channel
|
|
475
|
+
};
|
|
476
|
+
// If there is a proxy, we have to connect that way
|
|
477
|
+
let channel = if let Some(proxy) = self.http_connect_proxy.as_ref() {
|
|
478
|
+
proxy.connect_endpoint(&channel).await?
|
|
479
|
+
} else {
|
|
480
|
+
channel.connect().await?
|
|
481
|
+
};
|
|
482
|
+
ServiceBuilder::new()
|
|
483
|
+
.layer_fn(move |channel| GrpcMetricSvc {
|
|
484
|
+
inner: ChannelOrGrpcOverride::Channel(channel),
|
|
485
|
+
metrics: metrics_meter.clone().map(MetricsContext::new),
|
|
486
|
+
disable_errcode_label: self.disable_error_code_metric_tags,
|
|
487
|
+
})
|
|
488
|
+
.service(channel)
|
|
489
|
+
};
|
|
490
|
+
|
|
463
491
|
let headers = Arc::new(RwLock::new(ClientHeaders {
|
|
464
492
|
user_headers: self.headers.clone().unwrap_or_default(),
|
|
465
493
|
api_key: self.api_key.clone(),
|
|
@@ -1140,7 +1168,7 @@ pub struct WorkflowOptions {
|
|
|
1140
1168
|
/// The overall semantics of Priority are:
|
|
1141
1169
|
/// (more will be added here later)
|
|
1142
1170
|
/// 1. First, consider "priority_key": lower number goes first.
|
|
1143
|
-
#[derive(Debug, Clone, Default, PartialEq
|
|
1171
|
+
#[derive(Debug, Clone, Default, PartialEq)]
|
|
1144
1172
|
pub struct Priority {
|
|
1145
1173
|
/// Priority key is a positive integer from 1 to n, where smaller integers
|
|
1146
1174
|
/// correspond to higher priorities (tasks run sooner). In general, tasks in
|
|
@@ -1153,12 +1181,50 @@ pub struct Priority {
|
|
|
1153
1181
|
/// The default priority is (min+max)/2. With the default max of 5 and min of
|
|
1154
1182
|
/// 1, that comes out to 3.
|
|
1155
1183
|
pub priority_key: u32,
|
|
1184
|
+
|
|
1185
|
+
/// Fairness key is a short string that's used as a key for a fairness
|
|
1186
|
+
/// balancing mechanism. It may correspond to a tenant id, or to a fixed
|
|
1187
|
+
/// string like "high" or "low". The default is the empty string.
|
|
1188
|
+
///
|
|
1189
|
+
/// The fairness mechanism attempts to dispatch tasks for a given key in
|
|
1190
|
+
/// proportion to its weight. For example, using a thousand distinct tenant
|
|
1191
|
+
/// ids, each with a weight of 1.0 (the default) will result in each tenant
|
|
1192
|
+
/// getting a roughly equal share of task dispatch throughput.
|
|
1193
|
+
///
|
|
1194
|
+
/// (Note: this does not imply equal share of worker capacity! Fairness
|
|
1195
|
+
/// decisions are made based on queue statistics, not
|
|
1196
|
+
/// current worker load.)
|
|
1197
|
+
///
|
|
1198
|
+
/// As another example, using keys "high" and "low" with weight 9.0 and 1.0
|
|
1199
|
+
/// respectively will prefer dispatching "high" tasks over "low" tasks at a
|
|
1200
|
+
/// 9:1 ratio, while allowing either key to use all worker capacity if the
|
|
1201
|
+
/// other is not present.
|
|
1202
|
+
///
|
|
1203
|
+
/// All fairness mechanisms, including rate limits, are best-effort and
|
|
1204
|
+
/// probabilistic. The results may not match what a "perfect" algorithm with
|
|
1205
|
+
/// infinite resources would produce. The more unique keys are used, the less
|
|
1206
|
+
/// accurate the results will be.
|
|
1207
|
+
///
|
|
1208
|
+
/// Fairness keys are limited to 64 bytes.
|
|
1209
|
+
pub fairness_key: String,
|
|
1210
|
+
|
|
1211
|
+
/// Fairness weight for a task can come from multiple sources for
|
|
1212
|
+
/// flexibility. From highest to lowest precedence:
|
|
1213
|
+
/// 1. Weights for a small set of keys can be overridden in task queue
|
|
1214
|
+
/// configuration with an API.
|
|
1215
|
+
/// 2. It can be attached to the workflow/activity in this field.
|
|
1216
|
+
/// 3. The default weight of 1.0 will be used.
|
|
1217
|
+
///
|
|
1218
|
+
/// Weight values are clamped by the server to the range [0.001, 1000].
|
|
1219
|
+
pub fairness_weight: f32,
|
|
1156
1220
|
}
|
|
1157
1221
|
|
|
1158
1222
|
impl From<Priority> for common::v1::Priority {
|
|
1159
1223
|
fn from(priority: Priority) -> Self {
|
|
1160
1224
|
common::v1::Priority {
|
|
1161
1225
|
priority_key: priority.priority_key as i32,
|
|
1226
|
+
fairness_key: priority.fairness_key,
|
|
1227
|
+
fairness_weight: priority.fairness_weight,
|
|
1162
1228
|
}
|
|
1163
1229
|
}
|
|
1164
1230
|
}
|
|
@@ -1167,6 +1233,8 @@ impl From<common::v1::Priority> for Priority {
|
|
|
1167
1233
|
fn from(priority: common::v1::Priority) -> Self {
|
|
1168
1234
|
Self {
|
|
1169
1235
|
priority_key: priority.priority_key as u32,
|
|
1236
|
+
fairness_key: priority.fairness_key,
|
|
1237
|
+
fairness_weight: priority.fairness_weight,
|
|
1170
1238
|
}
|
|
1171
1239
|
}
|
|
1172
1240
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
use crate::{AttachMetricLabels, CallType, dbg_panic};
|
|
1
|
+
use crate::{AttachMetricLabels, CallType, callback_based, dbg_panic};
|
|
2
|
+
use futures_util::TryFutureExt;
|
|
3
|
+
use futures_util::future::Either;
|
|
2
4
|
use futures_util::{FutureExt, future::BoxFuture};
|
|
3
5
|
use std::{
|
|
6
|
+
fmt,
|
|
4
7
|
sync::Arc,
|
|
5
8
|
task::{Context, Poll},
|
|
6
9
|
time::{Duration, Instant},
|
|
@@ -205,19 +208,37 @@ fn code_as_screaming_snake(code: &Code) -> &'static str {
|
|
|
205
208
|
/// Implements metrics functionality for gRPC (really, any http) calls
|
|
206
209
|
#[derive(Debug, Clone)]
|
|
207
210
|
pub struct GrpcMetricSvc {
|
|
208
|
-
pub(crate) inner:
|
|
211
|
+
pub(crate) inner: ChannelOrGrpcOverride,
|
|
209
212
|
// If set to none, metrics are a no-op
|
|
210
213
|
pub(crate) metrics: Option<MetricsContext>,
|
|
211
214
|
pub(crate) disable_errcode_label: bool,
|
|
212
215
|
}
|
|
213
216
|
|
|
217
|
+
#[derive(Clone)]
|
|
218
|
+
pub(crate) enum ChannelOrGrpcOverride {
|
|
219
|
+
Channel(Channel),
|
|
220
|
+
GrpcOverride(callback_based::CallbackBasedGrpcService),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
impl fmt::Debug for ChannelOrGrpcOverride {
|
|
224
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
225
|
+
match self {
|
|
226
|
+
ChannelOrGrpcOverride::Channel(inner) => fmt::Debug::fmt(inner, f),
|
|
227
|
+
ChannelOrGrpcOverride::GrpcOverride(_) => f.write_str("<callback-based-grpc-service>"),
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
impl Service<http::Request<Body>> for GrpcMetricSvc {
|
|
215
233
|
type Response = http::Response<Body>;
|
|
216
|
-
type Error =
|
|
234
|
+
type Error = Box<dyn std::error::Error + Send + Sync>;
|
|
217
235
|
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
|
218
236
|
|
|
219
237
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
220
|
-
self.inner
|
|
238
|
+
match &mut self.inner {
|
|
239
|
+
ChannelOrGrpcOverride::Channel(inner) => inner.poll_ready(cx).map_err(Into::into),
|
|
240
|
+
ChannelOrGrpcOverride::GrpcOverride(inner) => inner.poll_ready(cx).map_err(Into::into),
|
|
241
|
+
}
|
|
221
242
|
}
|
|
222
243
|
|
|
223
244
|
fn call(&mut self, mut req: http::Request<Body>) -> Self::Future {
|
|
@@ -245,7 +266,14 @@ impl Service<http::Request<Body>> for GrpcMetricSvc {
|
|
|
245
266
|
metrics
|
|
246
267
|
})
|
|
247
268
|
});
|
|
248
|
-
let callfut = self.inner
|
|
269
|
+
let callfut = match &mut self.inner {
|
|
270
|
+
ChannelOrGrpcOverride::Channel(inner) => {
|
|
271
|
+
Either::Left(inner.call(req).map_err(Into::into))
|
|
272
|
+
}
|
|
273
|
+
ChannelOrGrpcOverride::GrpcOverride(inner) => {
|
|
274
|
+
Either::Right(inner.call(req).map_err(Into::into))
|
|
275
|
+
}
|
|
276
|
+
};
|
|
249
277
|
let errcode_label_disabled = self.disable_errcode_label;
|
|
250
278
|
async move {
|
|
251
279
|
let started = Instant::now();
|
|
@@ -1354,6 +1354,34 @@ proxier! {
|
|
|
1354
1354
|
r.extensions_mut().insert(labels);
|
|
1355
1355
|
}
|
|
1356
1356
|
);
|
|
1357
|
+
(
|
|
1358
|
+
update_task_queue_config,
|
|
1359
|
+
UpdateTaskQueueConfigRequest,
|
|
1360
|
+
UpdateTaskQueueConfigResponse,
|
|
1361
|
+
|r| {
|
|
1362
|
+
let mut labels = namespaced_request!(r);
|
|
1363
|
+
labels.task_q_str(r.get_ref().task_queue.clone());
|
|
1364
|
+
r.extensions_mut().insert(labels);
|
|
1365
|
+
}
|
|
1366
|
+
);
|
|
1367
|
+
(
|
|
1368
|
+
fetch_worker_config,
|
|
1369
|
+
FetchWorkerConfigRequest,
|
|
1370
|
+
FetchWorkerConfigResponse,
|
|
1371
|
+
|r| {
|
|
1372
|
+
let labels = namespaced_request!(r);
|
|
1373
|
+
r.extensions_mut().insert(labels);
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
(
|
|
1377
|
+
update_worker_config,
|
|
1378
|
+
UpdateWorkerConfigRequest,
|
|
1379
|
+
UpdateWorkerConfigResponse,
|
|
1380
|
+
|r| {
|
|
1381
|
+
let labels = namespaced_request!(r);
|
|
1382
|
+
r.extensions_mut().insert(labels);
|
|
1383
|
+
}
|
|
1384
|
+
);
|
|
1357
1385
|
}
|
|
1358
1386
|
|
|
1359
1387
|
proxier! {
|
|
@@ -1445,6 +1473,11 @@ proxier! {
|
|
|
1445
1473
|
(update_namespace_export_sink, cloudreq::UpdateNamespaceExportSinkRequest, cloudreq::UpdateNamespaceExportSinkResponse);
|
|
1446
1474
|
(delete_namespace_export_sink, cloudreq::DeleteNamespaceExportSinkRequest, cloudreq::DeleteNamespaceExportSinkResponse);
|
|
1447
1475
|
(validate_namespace_export_sink, cloudreq::ValidateNamespaceExportSinkRequest, cloudreq::ValidateNamespaceExportSinkResponse);
|
|
1476
|
+
(update_namespace_tags, cloudreq::UpdateNamespaceTagsRequest, cloudreq::UpdateNamespaceTagsResponse);
|
|
1477
|
+
(create_connectivity_rule, cloudreq::CreateConnectivityRuleRequest, cloudreq::CreateConnectivityRuleResponse);
|
|
1478
|
+
(get_connectivity_rule, cloudreq::GetConnectivityRuleRequest, cloudreq::GetConnectivityRuleResponse);
|
|
1479
|
+
(get_connectivity_rules, cloudreq::GetConnectivityRulesRequest, cloudreq::GetConnectivityRulesResponse);
|
|
1480
|
+
(delete_connectivity_rule, cloudreq::DeleteConnectivityRuleRequest, cloudreq::DeleteConnectivityRuleResponse);
|
|
1448
1481
|
}
|
|
1449
1482
|
|
|
1450
1483
|
proxier! {
|
|
@@ -1538,11 +1571,17 @@ mod tests {
|
|
|
1538
1571
|
})
|
|
1539
1572
|
.collect();
|
|
1540
1573
|
let no_underscores: HashSet<_> = impl_list.iter().map(|x| x.replace('_', "")).collect();
|
|
1574
|
+
let mut not_implemented = vec![];
|
|
1541
1575
|
for method in methods {
|
|
1542
1576
|
if !no_underscores.contains(&method.to_lowercase()) {
|
|
1543
|
-
|
|
1577
|
+
not_implemented.push(method);
|
|
1544
1578
|
}
|
|
1545
1579
|
}
|
|
1580
|
+
if !not_implemented.is_empty() {
|
|
1581
|
+
panic!(
|
|
1582
|
+
"The following RPC methods are not implemented by raw client: {not_implemented:?}"
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1546
1585
|
}
|
|
1547
1586
|
#[test]
|
|
1548
1587
|
fn verify_all_workflow_service_methods_implemented() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use crate::{
|
|
2
|
-
Client, IsWorkerTaskLongPoll, MESSAGE_TOO_LARGE_KEY,
|
|
3
|
-
Result, RetryConfig, raw::IsUserLongPoll,
|
|
2
|
+
Client, ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT, IsWorkerTaskLongPoll, MESSAGE_TOO_LARGE_KEY,
|
|
3
|
+
NamespacedClient, NoRetryOnMatching, Result, RetryConfig, raw::IsUserLongPoll,
|
|
4
4
|
};
|
|
5
5
|
use backoff::{Clock, SystemClock, backoff::Backoff, exponential::ExponentialBackoff};
|
|
6
6
|
use futures_retry::{ErrorHandler, FutureRetry, RetryPolicy};
|
|
@@ -214,6 +214,10 @@ where
|
|
|
214
214
|
if let Some(sc) = self.retry_short_circuit.as_ref()
|
|
215
215
|
&& (sc.predicate)(&e)
|
|
216
216
|
{
|
|
217
|
+
e.metadata_mut().insert(
|
|
218
|
+
ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT,
|
|
219
|
+
tonic::metadata::MetadataValue::from(0),
|
|
220
|
+
);
|
|
217
221
|
return RetryPolicy::ForwardError(e);
|
|
218
222
|
}
|
|
219
223
|
|
|
@@ -441,7 +445,12 @@ mod tests {
|
|
|
441
445
|
FixedClock(Instant::now()),
|
|
442
446
|
);
|
|
443
447
|
let result = err_handler.handle(1, Status::new(Code::ResourceExhausted, "leave me alone"));
|
|
444
|
-
assert_matches!(result, RetryPolicy::ForwardError(
|
|
448
|
+
let e = assert_matches!(result, RetryPolicy::ForwardError(e) => e);
|
|
449
|
+
assert!(
|
|
450
|
+
e.metadata()
|
|
451
|
+
.get(ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT)
|
|
452
|
+
.is_some()
|
|
453
|
+
);
|
|
445
454
|
}
|
|
446
455
|
|
|
447
456
|
#[tokio::test]
|
package/sdk-core/core/src/lib.rs
CHANGED
|
@@ -103,7 +103,9 @@ where
|
|
|
103
103
|
bail!("Client identity cannot be empty. Either lang or user should be setting this value");
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
let heartbeat_fn =
|
|
106
|
+
let heartbeat_fn = worker_config
|
|
107
|
+
.heartbeat_interval
|
|
108
|
+
.map(|_| Arc::new(OnceLock::new()));
|
|
107
109
|
|
|
108
110
|
let client_bag = Arc::new(WorkerClientBag::new(
|
|
109
111
|
client,
|
|
@@ -118,7 +120,7 @@ where
|
|
|
118
120
|
sticky_q,
|
|
119
121
|
client_bag,
|
|
120
122
|
Some(&runtime.telemetry),
|
|
121
|
-
|
|
123
|
+
heartbeat_fn,
|
|
122
124
|
))
|
|
123
125
|
}
|
|
124
126
|
|
|
@@ -18,7 +18,7 @@ use std::{
|
|
|
18
18
|
},
|
|
19
19
|
time::Duration,
|
|
20
20
|
};
|
|
21
|
-
use temporal_client::NoRetryOnMatching;
|
|
21
|
+
use temporal_client::{ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT, NoRetryOnMatching};
|
|
22
22
|
use temporal_sdk_core_api::worker::{
|
|
23
23
|
ActivitySlotKind, NexusSlotKind, PollerBehavior, SlotKind, WorkflowSlotKind,
|
|
24
24
|
};
|
|
@@ -538,20 +538,27 @@ impl PollScalerReportHandle {
|
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
Err(e) => {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
541
|
+
if matches!(self.behavior, PollerBehavior::Autoscaling { .. }) {
|
|
542
|
+
// We should only react to errors in autoscaling mode if we saw a scaling
|
|
543
|
+
// decision
|
|
544
|
+
if self.ever_saw_scaling_decision.load(Ordering::Relaxed) {
|
|
545
|
+
debug!("Got error from server while polling: {:?}", e);
|
|
546
|
+
if e.code() == Code::ResourceExhausted {
|
|
547
|
+
// Scale down significantly for resource exhaustion
|
|
548
|
+
self.change_target(usize::saturating_div, 2);
|
|
549
|
+
} else {
|
|
550
|
+
// Other codes that would normally have made us back off briefly can
|
|
551
|
+
// reclaim this poller
|
|
552
|
+
self.change_target(usize::saturating_sub, 1);
|
|
553
|
+
}
|
|
553
554
|
}
|
|
554
|
-
|
|
555
|
+
// Only propagate errors out if they weren't because of the short-circuiting
|
|
556
|
+
// logic. IE: We don't want to fail callers because we said we wanted to know
|
|
557
|
+
// about ResourceExhausted errors, but we haven't seen a scaling decision yet,
|
|
558
|
+
// so we're not reacting to errors, only propagating them.
|
|
559
|
+
return !e
|
|
560
|
+
.metadata()
|
|
561
|
+
.contains_key(ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT);
|
|
555
562
|
}
|
|
556
563
|
}
|
|
557
564
|
}
|
|
@@ -748,4 +755,45 @@ mod tests {
|
|
|
748
755
|
pb.poll().await.unwrap().unwrap();
|
|
749
756
|
pb.shutdown().await;
|
|
750
757
|
}
|
|
758
|
+
|
|
759
|
+
#[tokio::test]
|
|
760
|
+
async fn autoscale_wont_fail_caller_on_short_circuited_error() {
|
|
761
|
+
let mut mock_client = mock_manual_worker_client();
|
|
762
|
+
mock_client
|
|
763
|
+
.expect_poll_workflow_task()
|
|
764
|
+
.times(1)
|
|
765
|
+
.returning(move |_, _| {
|
|
766
|
+
async {
|
|
767
|
+
let mut st = tonic::Status::cancelled("whatever");
|
|
768
|
+
st.metadata_mut()
|
|
769
|
+
.insert(ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT, 1.into());
|
|
770
|
+
Err(st)
|
|
771
|
+
}
|
|
772
|
+
.boxed()
|
|
773
|
+
});
|
|
774
|
+
mock_client
|
|
775
|
+
.expect_poll_workflow_task()
|
|
776
|
+
.returning(move |_, _| async { Ok(Default::default()) }.boxed());
|
|
777
|
+
|
|
778
|
+
let pb = LongPollBuffer::new_workflow_task(
|
|
779
|
+
Arc::new(mock_client),
|
|
780
|
+
"sometq".to_string(),
|
|
781
|
+
None,
|
|
782
|
+
PollerBehavior::Autoscaling {
|
|
783
|
+
minimum: 1,
|
|
784
|
+
maximum: 1,
|
|
785
|
+
initial: 1,
|
|
786
|
+
},
|
|
787
|
+
fixed_size_permit_dealer(1),
|
|
788
|
+
CancellationToken::new(),
|
|
789
|
+
None::<fn(usize)>,
|
|
790
|
+
WorkflowTaskOptions {
|
|
791
|
+
wft_poller_shared: Some(Arc::new(WFTPollerShared::new(Some(1)))),
|
|
792
|
+
},
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
// Should not see error, unwraps should get empty response
|
|
796
|
+
pb.poll().await.unwrap().unwrap();
|
|
797
|
+
pb.shutdown().await;
|
|
798
|
+
}
|
|
751
799
|
}
|
|
@@ -50,7 +50,7 @@ pub(crate) struct WorkerClientBag {
|
|
|
50
50
|
namespace: String,
|
|
51
51
|
identity: String,
|
|
52
52
|
worker_versioning_strategy: WorkerVersioningStrategy,
|
|
53
|
-
heartbeat_data: Arc<OnceLock<HeartbeatFn
|
|
53
|
+
heartbeat_data: Option<Arc<OnceLock<HeartbeatFn>>>,
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
impl WorkerClientBag {
|
|
@@ -59,7 +59,7 @@ impl WorkerClientBag {
|
|
|
59
59
|
namespace: String,
|
|
60
60
|
identity: String,
|
|
61
61
|
worker_versioning_strategy: WorkerVersioningStrategy,
|
|
62
|
-
heartbeat_data: Arc<OnceLock<HeartbeatFn
|
|
62
|
+
heartbeat_data: Option<Arc<OnceLock<HeartbeatFn>>>,
|
|
63
63
|
) -> Self {
|
|
64
64
|
Self {
|
|
65
65
|
replaceable_client: RwLock::new(client),
|
|
@@ -129,10 +129,14 @@ impl WorkerClientBag {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
fn capture_heartbeat(&self) -> Option<WorkerHeartbeat> {
|
|
132
|
-
if let Some(
|
|
133
|
-
hb()
|
|
132
|
+
if let Some(heartbeat_data) = self.heartbeat_data.as_ref() {
|
|
133
|
+
if let Some(hb) = heartbeat_data.get() {
|
|
134
|
+
hb()
|
|
135
|
+
} else {
|
|
136
|
+
dbg_panic!("Heartbeat function never set");
|
|
137
|
+
None
|
|
138
|
+
}
|
|
134
139
|
} else {
|
|
135
|
-
dbg_panic!("Heartbeat function never set");
|
|
136
140
|
None
|
|
137
141
|
}
|
|
138
142
|
}
|