Compare commits
52 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
1d85b01fc1 | |
|
|
17d9ce26e4 | |
|
|
02384e3918 | |
|
|
bcc62b00b9 | |
|
|
bbeafc7ee8 | |
|
|
18dda3c586 | |
|
|
cdc97245c7 | |
|
|
dc2d5f447c | |
|
|
1d85d9f7b9 | |
|
|
3a1aaa4279 | |
|
|
8147f9e647 | |
|
|
36bfe355da | |
|
|
420ed4bd5a | |
|
|
af855eb64e | |
|
|
218146b83c | |
|
|
3bfe761a6e | |
|
|
90b0ea387e | |
|
|
dc4e17f9cc | |
|
|
fc302e37f2 | |
|
|
2c03f37c47 | |
|
|
2e9625d4da | |
|
|
3f072f44cb | |
|
|
ae50337551 | |
|
|
fd1889564f | |
|
|
aff8712136 | |
|
|
30ec4bc93e | |
|
|
d6f7a8f942 | |
|
|
0553241986 | |
|
|
c34b22eda3 | |
|
|
2e696fa051 | |
|
|
2d9bdfc048 | |
|
|
b9f099b98e | |
|
|
a672e22bd1 | |
|
|
ae5ee43667 | |
|
|
5c53533a1d | |
|
|
3618772809 | |
|
|
53d69cc581 | |
|
|
93692265b0 | |
|
|
0172cd96f6 | |
|
|
c828f70902 | |
|
|
2d25d83cf2 | |
|
|
c697d4bfe9 | |
|
|
d0f397841a | |
|
|
22d4401efc | |
|
|
80a429899d | |
|
|
0da56bcc0e | |
|
|
4870127fb2 | |
|
|
31a829787b | |
|
|
730599bd5b | |
|
|
99c5319a76 | |
|
|
4d118154a9 | |
|
|
a4c35cb5ff |
3
BUILD
3
BUILD
|
|
@ -155,7 +155,7 @@ g_stands_for = "golazo" # @unused
|
|||
|
||||
core_version = "24.0.0" # @unused
|
||||
|
||||
version = "1.46.0-dev" # @unused
|
||||
version = "1.46.7" # @unused
|
||||
|
||||
GPR_PUBLIC_HDRS = [
|
||||
"include/grpc/support/alloc.h",
|
||||
|
|
@ -2688,6 +2688,7 @@ grpc_cc_library(
|
|||
external_deps = ["absl/strings"],
|
||||
language = "c++",
|
||||
deps = [
|
||||
"capture",
|
||||
"gpr_base",
|
||||
"grpc_base",
|
||||
"grpc_service_config",
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@
|
|||
cmake_minimum_required(VERSION 3.5.1)
|
||||
|
||||
set(PACKAGE_NAME "grpc")
|
||||
set(PACKAGE_VERSION "1.46.0-dev")
|
||||
set(PACKAGE_VERSION "1.46.7")
|
||||
set(gRPC_CORE_VERSION "24.0.0")
|
||||
set(gRPC_CORE_SOVERSION "24")
|
||||
set(gRPC_CPP_VERSION "1.46.0-dev")
|
||||
set(gRPC_CPP_VERSION "1.46.7")
|
||||
set(gRPC_CPP_SOVERSION "1.46")
|
||||
set(gRPC_CSHARP_VERSION "2.46.0-dev")
|
||||
set(gRPC_CSHARP_VERSION "2.46.7")
|
||||
set(gRPC_CSHARP_SOVERSION "2.46")
|
||||
set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}")
|
||||
set(PACKAGE_TARNAME "${PACKAGE_NAME}-${PACKAGE_VERSION}")
|
||||
|
|
@ -261,6 +261,8 @@ if(MSVC)
|
|||
set(_gRPC_C_CXX_FLAGS "${_gRPC_C_CXX_FLAGS} /wd4987 /wd4774 /wd4819 /wd4996 /wd4619")
|
||||
# Silences thousands of trucation warnings
|
||||
set(_gRPC_C_CXX_FLAGS "${_gRPC_C_CXX_FLAGS} /wd4503")
|
||||
# Tell MSVC to build grpc using utf-8
|
||||
set(_gRPC_C_CXX_FLAGS "${_gRPC_C_CXX_FLAGS} /utf-8")
|
||||
endif()
|
||||
if (MINGW)
|
||||
add_definitions(-D_WIN32_WINNT=0x600)
|
||||
|
|
@ -292,7 +294,7 @@ endif()
|
|||
if (gRPC_XDS_USER_AGENT_IS_CSHARP)
|
||||
# The value of the defines needs to contain quotes.
|
||||
# See https://github.com/grpc/grpc/blob/fbf32836a418cc84f58786700273b65cb9174e1d/src/core/ext/xds/xds_api.cc#L854
|
||||
add_definitions("-DGRPC_XDS_USER_AGENT_NAME_SUFFIX=\"csharp\"" "-DGRPC_XDS_USER_AGENT_VERSION_SUFFIX=\"2.46.0-dev\"")
|
||||
add_definitions("-DGRPC_XDS_USER_AGENT_NAME_SUFFIX=\"csharp\"" "-DGRPC_XDS_USER_AGENT_VERSION_SUFFIX=\"2.46.7\"")
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
|
|
@ -17218,6 +17220,7 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
|
|||
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.pb.h
|
||||
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/tls.grpc.pb.h
|
||||
src/cpp/server/csds/csds.cc
|
||||
test/cpp/end2end/connection_delay_injector.cc
|
||||
test/cpp/end2end/rls_server.cc
|
||||
test/cpp/end2end/test_service_impl.cc
|
||||
test/cpp/end2end/xds/xds_end2end_test.cc
|
||||
|
|
|
|||
|
|
@ -447,8 +447,8 @@ Q = @
|
|||
endif
|
||||
|
||||
CORE_VERSION = 24.0.0
|
||||
CPP_VERSION = 1.46.0-dev
|
||||
CSHARP_VERSION = 2.46.0-dev
|
||||
CPP_VERSION = 1.46.7
|
||||
CSHARP_VERSION = 2.46.7
|
||||
|
||||
CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
|
||||
CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
|
||||
|
|
|
|||
10
Rakefile
10
Rakefile
|
|
@ -34,11 +34,11 @@ Rake::ExtensionTask.new('grpc_c', spec) do |ext|
|
|||
'universal-darwin'
|
||||
]
|
||||
ext.cross_compiling do |spec|
|
||||
spec.files = %w( etc/roots.pem grpc_c.32.ruby grpc_c.64.ruby )
|
||||
spec.files += Dir.glob('src/ruby/bin/**/*')
|
||||
spec.files += Dir.glob('src/ruby/ext/**/*')
|
||||
spec.files += Dir.glob('src/ruby/lib/**/*')
|
||||
spec.files += Dir.glob('src/ruby/pb/**/*')
|
||||
spec.files = spec.files.select {
|
||||
|file| file.start_with?(
|
||||
"src/ruby/bin/", "src/ruby/ext/", "src/ruby/lib/", "src/ruby/pb/")
|
||||
}
|
||||
spec.files += %w( etc/roots.pem grpc_c.32.ruby grpc_c.64.ruby )
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/_metadata.py.template`!!!
|
||||
|
||||
__version__ = """1.46.0.dev0"""
|
||||
__version__ = """1.46.7"""
|
||||
|
|
|
|||
|
|
@ -195,11 +195,11 @@ def grpc_deps():
|
|||
name = "boringssl",
|
||||
# Use github mirror instead of https://boringssl.googlesource.com/boringssl
|
||||
# to obtain a boringssl archive with consistent sha256
|
||||
sha256 = "534fa658bd845fd974b50b10f444d392dfd0d93768c4a51b61263fd37d851c40",
|
||||
strip_prefix = "boringssl-b9232f9e27e5668bc0414879dcdedb2a59ea75f2",
|
||||
sha256 = "ad0b806b6c5cbd6cae121c608945d5fed468748e330632e8d53315089ad52c67",
|
||||
strip_prefix = "boringssl-6195bf8242156c9a2fa75702eee058f91b86a88b",
|
||||
urls = [
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/google/boringssl/archive/b9232f9e27e5668bc0414879dcdedb2a59ea75f2.tar.gz",
|
||||
"https://github.com/google/boringssl/archive/b9232f9e27e5668bc0414879dcdedb2a59ea75f2.tar.gz",
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/google/boringssl/archive/6195bf8242156c9a2fa75702eee058f91b86a88b.tar.gz",
|
||||
"https://github.com/google/boringssl/archive/6195bf8242156c9a2fa75702eee058f91b86a88b.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -207,22 +207,22 @@ def grpc_deps():
|
|||
http_archive(
|
||||
name = "zlib",
|
||||
build_file = "@com_github_grpc_grpc//third_party:zlib.BUILD",
|
||||
sha256 = "6d4d6640ca3121620995ee255945161821218752b551a1a180f4215f7d124d45",
|
||||
strip_prefix = "zlib-cacf7f1d4e3d44d871b605da3b647f07d718623f",
|
||||
sha256 = "90f43a9c998740e8a0db24b0af0147033db2aaaa99423129abbd76640757cac9",
|
||||
strip_prefix = "zlib-04f42ceca40f73e2978b50e93806c2a18c1281fc",
|
||||
urls = [
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/madler/zlib/archive/cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz",
|
||||
"https://github.com/madler/zlib/archive/cacf7f1d4e3d44d871b605da3b647f07d718623f.tar.gz",
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/madler/zlib/archive/04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz",
|
||||
"https://github.com/madler/zlib/archive/04f42ceca40f73e2978b50e93806c2a18c1281fc.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
if "com_google_protobuf" not in native.existing_rules():
|
||||
http_archive(
|
||||
name = "com_google_protobuf",
|
||||
sha256 = "bab1685f92cc4ea5b6420026eef6c7973ae96fc21f4f1a3ee626dc6ca6d77c12",
|
||||
strip_prefix = "protobuf-22d0e265de7d2b3d2e9a00d071313502e7d4cccf",
|
||||
sha256 = "245da8acf76b24317c885b8157b7ddcbe5e4919bd8ff60def824c8ed3ee49a8e",
|
||||
strip_prefix = "protobuf-b464cfbee18c71c40e761a5273ad369f3547294b",
|
||||
urls = [
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/protocolbuffers/protobuf/archive/22d0e265de7d2b3d2e9a00d071313502e7d4cccf.tar.gz",
|
||||
"https://github.com/protocolbuffers/protobuf/archive/22d0e265de7d2b3d2e9a00d071313502e7d4cccf.tar.gz",
|
||||
"https://storage.googleapis.com/grpc-bazel-mirror/github.com/protocolbuffers/protobuf/archive/b464cfbee18c71c40e761a5273ad369f3547294b.tar.gz",
|
||||
"https://github.com/protocolbuffers/protobuf/archive/b464cfbee18c71c40e761a5273ad369f3547294b.tar.gz",
|
||||
],
|
||||
patches = ["@com_github_grpc_grpc//third_party:protobuf.patch"],
|
||||
patch_args = ["-p1"],
|
||||
|
|
@ -366,10 +366,10 @@ def grpc_deps():
|
|||
if "io_bazel_rules_go" not in native.existing_rules():
|
||||
http_archive(
|
||||
name = "io_bazel_rules_go",
|
||||
sha256 = "f2dcd210c7095febe54b804bb1cd3a58fe8435a909db2ec04e31542631cf715c",
|
||||
sha256 = "69de5c704a05ff37862f7e0f5534d4f479418afc21806c887db544a316f3cb6b",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.31.0/rules_go-v0.31.0.zip",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/v0.31.0/rules_go-v0.31.0.zip",
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8408,6 +8408,7 @@ targets:
|
|||
language: c++
|
||||
headers:
|
||||
- src/cpp/server/csds/csds.h
|
||||
- test/cpp/end2end/connection_delay_injector.h
|
||||
- test/cpp/end2end/counted_service.h
|
||||
- test/cpp/end2end/rls_server.h
|
||||
- test/cpp/end2end/test_service_impl.h
|
||||
|
|
@ -8457,6 +8458,7 @@ targets:
|
|||
- src/proto/grpc/testing/xds/v3/string.proto
|
||||
- src/proto/grpc/testing/xds/v3/tls.proto
|
||||
- src/cpp/server/csds/csds.cc
|
||||
- test/cpp/end2end/connection_delay_injector.cc
|
||||
- test/cpp/end2end/rls_server.cc
|
||||
- test/cpp/end2end/test_service_impl.cc
|
||||
- test/cpp/end2end/xds/xds_end2end_test.cc
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ settings:
|
|||
core_version: 24.0.0
|
||||
csharp_major_version: 2
|
||||
g_stands_for: golazo
|
||||
protobuf_version: 3.19.4
|
||||
version: 1.46.0-dev
|
||||
protobuf_version: 3.19.5
|
||||
version: 1.46.7
|
||||
targets:
|
||||
- name: gen_hpack_tables
|
||||
build: tool
|
||||
|
|
|
|||
|
|
@ -1163,7 +1163,7 @@ if test "$PHP_GRPC" != "no"; then
|
|||
-D_HAS_EXCEPTIONS=0 -DNOMINMAX -DGRPC_ARES=0 \
|
||||
-DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK=1 \
|
||||
-DGRPC_XDS_USER_AGENT_NAME_SUFFIX='"\"PHP\""' \
|
||||
-DGRPC_XDS_USER_AGENT_VERSION_SUFFIX='"\"1.46.0dev\""')
|
||||
-DGRPC_XDS_USER_AGENT_VERSION_SUFFIX='"\"1.46.7\""')
|
||||
|
||||
PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/census)
|
||||
PHP_ADD_BUILD_DIR($ext_builddir/src/core/ext/filters/channel_idle)
|
||||
|
|
|
|||
|
|
@ -45,4 +45,4 @@
|
|||
- 1.43 'g' stands for ['green'](https://github.com/grpc/grpc/tree/v1.43.x)
|
||||
- 1.44 'g' stands for ['great'](https://github.com/grpc/grpc/tree/v1.44.x)
|
||||
- 1.45 'g' stands for ['gravity'](https://github.com/grpc/grpc/tree/v1.45.x)
|
||||
- 1.46 'g' stands for ['golazo'](https://github.com/grpc/grpc/tree/master)
|
||||
- 1.46 'g' stands for ['golazo'](https://github.com/grpc/grpc/tree/v1.46.x)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ and the client, and run them.
|
|||
PREREQUISITES
|
||||
-------------
|
||||
|
||||
- The [.NET Core SDK 2.1+](https://www.microsoft.com/net/core)
|
||||
- The [.NET Core SDK 3.1+](https://www.microsoft.com/net/core)
|
||||
|
||||
You can also build the solution `Greeter.sln` using Visual Studio 2017,
|
||||
but it's not a requirement.
|
||||
|
|
@ -36,4 +36,4 @@ Tutorial
|
|||
You can find a more detailed tutorial about Grpc in [gRPC Basics: C#][]
|
||||
|
||||
[helloworld.proto]:../../protos/helloworld.proto
|
||||
[gRPC Basics: C#]:https://grpc.io/docs/languages/csharp/basics
|
||||
[gRPC Basics: C#]:../RouteGuide/basics.md
|
||||
|
|
|
|||
|
|
@ -0,0 +1,221 @@
|
|||
### Prerequisites
|
||||
|
||||
Whether you're using Windows, OS X, or Linux, you can follow this
|
||||
example by using either an IDE and its build tools,
|
||||
or by using the the .NET Core SDK command line tools.
|
||||
|
||||
First, make sure you have installed the
|
||||
[gRPC C# prerequisites](https://github.com/grpc/grpc/blob/v1.46.x/src/csharp/README.md#prerequisites).
|
||||
You will also need Git to download the sample code.
|
||||
|
||||
### Download the example
|
||||
|
||||
You'll need a local copy of the example code to work through this quick start.
|
||||
Download the example code from our GitHub repository (the following command
|
||||
clones the entire repository, but you just need the examples for this quick start
|
||||
and other tutorials):
|
||||
|
||||
```sh
|
||||
# Clone the repository to get the example code:
|
||||
$ git clone -b v1.46.x --depth 1 --shallow-submodules https://github.com/grpc/grpc
|
||||
$ cd grpc
|
||||
```
|
||||
|
||||
This document will walk you through the "Hello World" example.
|
||||
The projects and source files can be found in the `examples/csharp/Helloworld` directory.
|
||||
|
||||
The example in this walkthrough already adds the necessary
|
||||
dependencies for you (`Grpc`, `Grpc.Tools` and `Google.Protobuf` NuGet packages).
|
||||
|
||||
### Build the example
|
||||
|
||||
#### Using Visual Studio (or Visual Studio for Mac)
|
||||
|
||||
* Open the solution `Greeter.sln` with Visual Studio
|
||||
* Build the solution
|
||||
|
||||
#### Using .NET Core SDK from the command line
|
||||
|
||||
From the `examples/csharp/Helloworld` directory:
|
||||
|
||||
```sh
|
||||
> dotnet build Greeter.sln
|
||||
```
|
||||
|
||||
### Run a gRPC application
|
||||
|
||||
From the `examples/csharp/Helloworld` directory:
|
||||
|
||||
* Run the server:
|
||||
|
||||
```sh
|
||||
> cd GreeterServer
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
* From another terminal, run the client:
|
||||
|
||||
```sh
|
||||
> cd GreeterClient
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
Congratulations! You've just run a client-server application with gRPC.
|
||||
|
||||
### Update the gRPC service
|
||||
|
||||
Now let's look at how to update the application with an extra method on the
|
||||
server for the client to call. Our gRPC service is defined using protocol
|
||||
buffers; you can find out lots more about how to define a service in a `.proto`
|
||||
file in [Basics tutorial](../RouteGuide/basics.md). For now all you need to know is that both the
|
||||
server and the client "stub" have a `SayHello` RPC method that takes a
|
||||
`HelloRequest` parameter from the client and returns a `HelloResponse` from the
|
||||
server, and that this method is defined like this:
|
||||
|
||||
|
||||
```proto
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
```
|
||||
|
||||
Let's update this so that the `Greeter` service has two methods. Edit
|
||||
`examples/protos/helloworld.proto` and update it with a new `SayHelloAgain`
|
||||
method, with the same request and response types:
|
||||
|
||||
```proto
|
||||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
// Sends another greeting
|
||||
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
```
|
||||
|
||||
Remember to save the file!
|
||||
|
||||
### Generate gRPC code
|
||||
|
||||
Next we need to update the gRPC code used by our application to use the new service definition.
|
||||
|
||||
The `Grpc.Tools` NuGet package contains the protoc and protobuf C# plugin binaries needed
|
||||
to generate the code. Starting from version 1.17 the package also integrates with
|
||||
MSBuild to provide [automatic C# code generation](https://github.com/grpc/grpc/blob/v1.46.x/src/csharp/BUILD-INTEGRATION.md)
|
||||
from `.proto` files.
|
||||
|
||||
This example project already depends on the `Grpc.Tools` NuGet package so just re-building the solution
|
||||
is enough to regenerate the code from our modified `.proto` file.
|
||||
|
||||
You can rebuild just like we first built the original
|
||||
example by running `dotnet build Greeter.sln` or by clicking "Build" in Visual Studio.
|
||||
|
||||
The build regenerates the following files
|
||||
under the `Greeter/obj/Debug/TARGET_FRAMEWORK` directory:
|
||||
|
||||
* `Helloworld.cs` contains all the protocol buffer code to populate,
|
||||
serialize, and retrieve our request and response message types
|
||||
* `HelloworldGrpc.cs` provides generated client and server classes,
|
||||
including:
|
||||
* an abstract class `Greeter.GreeterBase` to inherit from when defining
|
||||
Greeter service implementations
|
||||
* a class `Greeter.GreeterClient` that can be used to access remote Greeter
|
||||
instances
|
||||
|
||||
### Update and run the application
|
||||
|
||||
We now have new generated server and client code, but we still need to implement
|
||||
and call the new method in the human-written parts of our example application.
|
||||
|
||||
#### Update the server
|
||||
|
||||
With the `Greeter.sln` open in your IDE, open `GreeterServer/Program.cs`.
|
||||
Implement the new method by editing the GreeterImpl class like this:
|
||||
|
||||
```C#
|
||||
class GreeterImpl : Greeter.GreeterBase
|
||||
{
|
||||
// Server side handler of the SayHello RPC
|
||||
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
|
||||
{
|
||||
return Task.FromResult(new HelloReply { Message = "Hello " + request.Name });
|
||||
}
|
||||
|
||||
// Server side handler for the SayHelloAgain RPC
|
||||
public override Task<HelloReply> SayHelloAgain(HelloRequest request, ServerCallContext context)
|
||||
{
|
||||
return Task.FromResult(new HelloReply { Message = "Hello again " + request.Name });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Update the client
|
||||
|
||||
With the same `Greeter.sln` open in your IDE, open `GreeterClient/Program.cs`.
|
||||
Call the new method like this:
|
||||
|
||||
```C#
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
|
||||
|
||||
var client = new Greeter.GreeterClient(channel);
|
||||
String user = "you";
|
||||
|
||||
var reply = client.SayHello(new HelloRequest { Name = user });
|
||||
Console.WriteLine("Greeting: " + reply.Message);
|
||||
|
||||
var secondReply = client.SayHelloAgain(new HelloRequest { Name = user });
|
||||
Console.WriteLine("Greeting: " + secondReply.Message);
|
||||
|
||||
channel.ShutdownAsync().Wait();
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
```
|
||||
|
||||
#### Rebuild the modified example
|
||||
|
||||
Rebuild the newly modified example just like we first built the original
|
||||
example by running `dotnet build Greeter.sln` or by clicking "Build" in Visual Studio.
|
||||
|
||||
#### Run!
|
||||
|
||||
Just like we did before, from the `examples/csharp/Helloworld` directory:
|
||||
|
||||
1. Run the server:
|
||||
|
||||
```sh
|
||||
> cd GreeterServer
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
2. From another terminal, run the client:
|
||||
|
||||
```sh
|
||||
> cd GreeterClient
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
|
|
@ -3,4 +3,4 @@
|
|||
The files in this folder are the samples used in [gRPC Basics: C#][],
|
||||
a detailed tutorial for using gRPC in C#.
|
||||
|
||||
[gRPC Basics: C#]:https://grpc.io/docs/languages/csharp/basics
|
||||
[gRPC Basics: C#]:basics.md
|
||||
|
|
|
|||
|
|
@ -0,0 +1,480 @@
|
|||
This tutorial provides a basic C# programmer's introduction to working with gRPC.
|
||||
|
||||
By walking through this example you'll learn how to:
|
||||
|
||||
- Define a service in a .proto file.
|
||||
- Generate server and client code using the protocol buffer compiler.
|
||||
- Use the C# gRPC API to write a simple client and server for your service.
|
||||
|
||||
It assumes that you have read the [Introduction to gRPC](/docs/what-is-grpc/introduction/) and are familiar
|
||||
with [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). Note that the
|
||||
example in this tutorial uses the proto3 version of the protocol buffers
|
||||
language: you can find out more in the
|
||||
[proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) and
|
||||
[C# generated code reference](https://developers.google.com/protocol-buffers/docs/reference/csharp-generated).
|
||||
|
||||
### Why use gRPC?
|
||||
|
||||
Our example is a simple route mapping application that lets clients get information about features on their route, create a summary of their route, and exchange route information such as traffic updates with the server and other clients.
|
||||
|
||||
With gRPC we can define our service once in a `.proto` file and generate clients and servers in any of gRPC’s supported languages, which in turn can be run in environments ranging from servers inside a large data center to your own tablet — all the complexity of communication between different languages and environments is handled for you by gRPC. We also get all the advantages of working with protocol buffers, including efficient serialization, a simple IDL, and easy interface updating.
|
||||
|
||||
### Example code and setup
|
||||
|
||||
The example code for our tutorial is in
|
||||
[grpc/grpc/examples/csharp/RouteGuide](https://github.com/grpc/grpc/tree/v1.46.x/examples/csharp/RouteGuide). To
|
||||
download the example, clone the `grpc` repository by running the following
|
||||
command:
|
||||
|
||||
```sh
|
||||
$ git clone -b v1.46.x --depth 1 --shallow-submodules https://github.com/grpc/grpc
|
||||
$ cd grpc
|
||||
```
|
||||
|
||||
All the files for this tutorial are in the directory
|
||||
`examples/csharp/RouteGuide`. Open the solution
|
||||
`examples/csharp/RouteGuide/RouteGuide.sln` from Visual Studio (Windows or Mac) or Visual Studio Code.
|
||||
For additional installation details, see the [How to use
|
||||
instructions](https://github.com/grpc/grpc/tree/v1.46.x/src/csharp#how-to-use).
|
||||
|
||||
### Defining the service
|
||||
|
||||
Our first step (as you'll know from the [Introduction to gRPC](/docs/what-is-grpc/introduction/)) is to
|
||||
define the gRPC *service* and the method *request* and *response* types using
|
||||
[protocol buffers](https://developers.google.com/protocol-buffers/docs/overview).
|
||||
You can see the complete .proto file in
|
||||
[`examples/protos/route_guide.proto`](https://github.com/grpc/grpc/blob/v1.46.x/examples/protos/route_guide.proto).
|
||||
|
||||
To define a service, you specify a named `service` in your .proto file:
|
||||
|
||||
```protobuf
|
||||
service RouteGuide {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Then you define `rpc` methods inside your service definition, specifying their
|
||||
request and response types. gRPC lets you define four kinds of service method,
|
||||
all of which are used in the `RouteGuide` service:
|
||||
|
||||
- A *simple RPC* where the client sends a request to the server using the client
|
||||
object and waits for a response to come back, just like a normal function
|
||||
call.
|
||||
|
||||
```protobuf
|
||||
// Obtains the feature at a given position.
|
||||
rpc GetFeature(Point) returns (Feature) {}
|
||||
```
|
||||
|
||||
- A *server-side streaming RPC* where the client sends a request to the server
|
||||
and gets a stream to read a sequence of messages back. The client reads from
|
||||
the returned stream until there are no more messages. As you can see in our
|
||||
example, you specify a server-side streaming method by placing the `stream`
|
||||
keyword before the *response* type.
|
||||
|
||||
```protobuf
|
||||
// Obtains the Features available within the given Rectangle. Results are
|
||||
// streamed rather than returned at once (e.g. in a response message with a
|
||||
// repeated field), as the rectangle may cover a large area and contain a
|
||||
// huge number of features.
|
||||
rpc ListFeatures(Rectangle) returns (stream Feature) {}
|
||||
```
|
||||
|
||||
- A *client-side streaming RPC* where the client writes a sequence of messages
|
||||
and sends them to the server, again using a provided stream. Once the client
|
||||
has finished writing the messages, it waits for the server to read them all
|
||||
and return its response. You specify a client-side streaming method by placing
|
||||
the `stream` keyword before the *request* type.
|
||||
|
||||
```protobuf
|
||||
// Accepts a stream of Points on a route being traversed, returning a
|
||||
// RouteSummary when traversal is completed.
|
||||
rpc RecordRoute(stream Point) returns (RouteSummary) {}
|
||||
```
|
||||
|
||||
- A *bidirectional streaming RPC* where both sides send a sequence of messages
|
||||
using a read-write stream. The two streams operate independently, so clients
|
||||
and servers can read and write in whatever order they like: for example, the
|
||||
server could wait to receive all the client messages before writing its
|
||||
responses, or it could alternately read a message then write a message, or
|
||||
some other combination of reads and writes. The order of messages in each
|
||||
stream is preserved. You specify this type of method by placing the `stream`
|
||||
keyword before both the request and the response.
|
||||
|
||||
```protobuf
|
||||
// Accepts a stream of RouteNotes sent while a route is being traversed,
|
||||
// while receiving other RouteNotes (e.g. from other users).
|
||||
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
|
||||
```
|
||||
|
||||
Our `.proto` file also contains protocol buffer message type definitions for all
|
||||
the request and response types used in our service methods - for example, here's
|
||||
the `Point` message type:
|
||||
|
||||
```protobuf
|
||||
// Points are represented as latitude-longitude pairs in the E7 representation
|
||||
// (degrees multiplied by 10**7 and rounded to the nearest integer).
|
||||
// Latitudes should be in the range +/- 90 degrees and longitude should be in
|
||||
// the range +/- 180 degrees (inclusive).
|
||||
message Point {
|
||||
int32 latitude = 1;
|
||||
int32 longitude = 2;
|
||||
}
|
||||
```
|
||||
|
||||
### Generating client and server code
|
||||
|
||||
Next we need to generate the gRPC client and server interfaces from our .proto
|
||||
service definition. This can be done by invoking the protocol buffer compiler `protoc` with
|
||||
a special gRPC C# plugin from the command line, but starting from version
|
||||
1.17 the `Grpc.Tools` NuGet package integrates with MSBuild to provide [automatic C# code generation](https://github.com/grpc/grpc/blob/v1.46.x/src/csharp/BUILD-INTEGRATION.md)
|
||||
from `.proto` files, which gives much better developer experience by running
|
||||
the right commands for you as part of the build.
|
||||
|
||||
This example already has a dependency on `Grpc.Tools` NuGet package and the
|
||||
`route_guide.proto` has already been added to the project, so the only thing
|
||||
needed to generate the client and server code is to build the solution.
|
||||
That can be done by running `dotnet build RouteGuide.sln` or building directly
|
||||
in Visual Studio.
|
||||
|
||||
The build regenerates the following files
|
||||
under the `RouteGuide/obj/Debug/TARGET_FRAMEWORK` directory:
|
||||
|
||||
- `RouteGuide.cs` contains all the protocol buffer code to populate,
|
||||
serialize, and retrieve our request and response message types
|
||||
- `RouteGuideGrpc.cs` provides generated client and server classes,
|
||||
including:
|
||||
- an abstract class `RouteGuide.RouteGuideBase` to inherit from when defining
|
||||
RouteGuide service implementations
|
||||
- a class `RouteGuide.RouteGuideClient` that can be used to access remote
|
||||
RouteGuide instances
|
||||
|
||||
### Creating the server {#server}
|
||||
|
||||
First let's look at how we create a `RouteGuide` server. If you're only
|
||||
interested in creating gRPC clients, you can skip this section and go straight
|
||||
to [Creating the client](#client) (though you might find it interesting
|
||||
anyway!).
|
||||
|
||||
There are two parts to making our `RouteGuide` service do its job:
|
||||
|
||||
- Implementing the service functionality by inheriting from the base class
|
||||
generated from our service definition: doing the actual "work" of our service.
|
||||
- Running a gRPC server to listen for requests from clients and return the
|
||||
service responses.
|
||||
|
||||
You can find our example `RouteGuide` server in
|
||||
[examples/csharp/RouteGuide/RouteGuideServer/RouteGuideImpl.cs](https://github.com/grpc/grpc/blob/v1.46.x/examples/csharp/RouteGuide/RouteGuideServer/RouteGuideImpl.cs).
|
||||
Let's take a closer look at how it works.
|
||||
|
||||
#### Implementing RouteGuide
|
||||
|
||||
As you can see, our server has a `RouteGuideImpl` class that inherits from the
|
||||
generated `RouteGuide.RouteGuideBase`:
|
||||
|
||||
```csharp
|
||||
// RouteGuideImpl provides an implementation of the RouteGuide service.
|
||||
public class RouteGuideImpl : RouteGuide.RouteGuideBase
|
||||
```
|
||||
|
||||
##### Simple RPC
|
||||
|
||||
`RouteGuideImpl` implements all our service methods. Let's look at the simplest
|
||||
type first, `GetFeature`, which just gets a `Point` from the client and returns
|
||||
the corresponding feature information from its database in a `Feature`.
|
||||
|
||||
```csharp
|
||||
public override Task<Feature> GetFeature(Point request, Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
return Task.FromResult(CheckFeature(request));
|
||||
}
|
||||
```
|
||||
|
||||
The method is passed a context for the RPC (which is empty in the alpha
|
||||
release), the client's `Point` protocol buffer request, and returns a `Feature`
|
||||
protocol buffer. In the method we create the `Feature` with the appropriate
|
||||
information, and then return it. To allow asynchronous implementation, the
|
||||
method returns `Task<Feature>` rather than just `Feature`. You are free to
|
||||
perform your computations synchronously and return the result once you've
|
||||
finished, just as we do in the example.
|
||||
|
||||
##### Server-side streaming RPC
|
||||
|
||||
Now let's look at something a bit more complicated - a streaming RPC.
|
||||
`ListFeatures` is a server-side streaming RPC, so we need to send back multiple
|
||||
`Feature` protocol buffers to our client.
|
||||
|
||||
```csharp
|
||||
// in RouteGuideImpl
|
||||
public override async Task ListFeatures(Rectangle request,
|
||||
Grpc.Core.IServerStreamWriter<Feature> responseStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
var responses = features.FindAll( (feature) => feature.Exists() && request.Contains(feature.Location) );
|
||||
foreach (var response in responses)
|
||||
{
|
||||
await responseStream.WriteAsync(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, here the request object is a `Rectangle` in which our client
|
||||
wants to find `Feature`s, but instead of returning a simple response we need to
|
||||
write responses to an asynchronous stream `IServerStreamWriter` using async
|
||||
method `WriteAsync`.
|
||||
|
||||
##### Client-side streaming RPC
|
||||
|
||||
Similarly, the client-side streaming method `RecordRoute` uses an
|
||||
[IAsyncEnumerator](https://github.com/Reactive-Extensions/Rx.NET/blob/master/Ix.NET/Source/System.Interactive.Async/IAsyncEnumerator.cs),
|
||||
to read the stream of requests using the async method `MoveNext` and the
|
||||
`Current` property.
|
||||
|
||||
```csharp
|
||||
public override async Task<RouteSummary> RecordRoute(Grpc.Core.IAsyncStreamReader<Point> requestStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
int pointCount = 0;
|
||||
int featureCount = 0;
|
||||
int distance = 0;
|
||||
Point previous = null;
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
while (await requestStream.MoveNext())
|
||||
{
|
||||
var point = requestStream.Current;
|
||||
pointCount++;
|
||||
if (CheckFeature(point).Exists())
|
||||
{
|
||||
featureCount++;
|
||||
}
|
||||
if (previous != null)
|
||||
{
|
||||
distance += (int) previous.GetDistance(point);
|
||||
}
|
||||
previous = point;
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
return new RouteSummary
|
||||
{
|
||||
PointCount = pointCount,
|
||||
FeatureCount = featureCount,
|
||||
Distance = distance,
|
||||
ElapsedTime = (int)(stopwatch.ElapsedMilliseconds / 1000)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
##### Bidirectional streaming RPC
|
||||
|
||||
Finally, let's look at our bidirectional streaming RPC `RouteChat`.
|
||||
|
||||
```csharp
|
||||
public override async Task RouteChat(Grpc.Core.IAsyncStreamReader<RouteNote> requestStream,
|
||||
Grpc.Core.IServerStreamWriter<RouteNote> responseStream,
|
||||
Grpc.Core.ServerCallContext context)
|
||||
{
|
||||
while (await requestStream.MoveNext())
|
||||
{
|
||||
var note = requestStream.Current;
|
||||
List<RouteNote> prevNotes = AddNoteForLocation(note.Location, note);
|
||||
foreach (var prevNote in prevNotes)
|
||||
{
|
||||
await responseStream.WriteAsync(prevNote);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here the method receives both `requestStream` and `responseStream` arguments.
|
||||
Reading the requests is done the same way as in the client-side streaming method
|
||||
`RecordRoute`. Writing the responses is done the same way as in the server-side
|
||||
streaming method `ListFeatures`.
|
||||
|
||||
#### Starting the server
|
||||
|
||||
Once we've implemented all our methods, we also need to start up a gRPC server
|
||||
so that clients can actually use our service. The following snippet shows how we
|
||||
do this for our `RouteGuide` service:
|
||||
|
||||
```csharp
|
||||
var features = RouteGuideUtil.LoadFeatures();
|
||||
|
||||
Server server = new Server
|
||||
{
|
||||
Services = { RouteGuide.BindService(new RouteGuideImpl(features)) },
|
||||
Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
|
||||
};
|
||||
server.Start();
|
||||
|
||||
Console.WriteLine("RouteGuide server listening on port " + port);
|
||||
Console.WriteLine("Press any key to stop the server...");
|
||||
Console.ReadKey();
|
||||
|
||||
server.ShutdownAsync().Wait();
|
||||
```
|
||||
As you can see, we build and start our server using `Grpc.Core.Server` class. To
|
||||
do this, we:
|
||||
|
||||
1. Create an instance of `Grpc.Core.Server`.
|
||||
1. Create an instance of our service implementation class `RouteGuideImpl`.
|
||||
1. Register our service implementation by adding its service definition to the
|
||||
`Services` collection (We obtain the service definition from the generated
|
||||
`RouteGuide.BindService` method).
|
||||
1. Specify the address and port we want to use to listen for client requests.
|
||||
This is done by adding `ServerPort` to the `Ports` collection.
|
||||
1. Call `Start` on the server instance to start an RPC server for our service.
|
||||
|
||||
### Creating the client {#client}
|
||||
|
||||
In this section, we'll look at creating a C# client for our `RouteGuide`
|
||||
service. You can see our complete example client code in
|
||||
[examples/csharp/RouteGuide/RouteGuideClient/Program.cs](https://github.com/grpc/grpc/blob/v1.46.x/examples/csharp/RouteGuide/RouteGuideClient/Program.cs).
|
||||
|
||||
#### Creating a client object
|
||||
|
||||
To call service methods, we first need to create a client object (also referred
|
||||
to as *stub* for other gRPC languages).
|
||||
|
||||
First, we need to create a gRPC client channel that will connect to gRPC server.
|
||||
Then, we create an instance of the `RouteGuide.RouteGuideClient` class generated
|
||||
from our .proto, passing the channel as an argument.
|
||||
|
||||
```csharp
|
||||
Channel channel = new Channel("127.0.0.1:50052", ChannelCredentials.Insecure);
|
||||
var client = new RouteGuide.RouteGuideClient(channel);
|
||||
|
||||
// YOUR CODE GOES HERE
|
||||
|
||||
channel.ShutdownAsync().Wait();
|
||||
```
|
||||
|
||||
#### Calling service methods
|
||||
|
||||
Now let's look at how we call our service methods. gRPC C# provides asynchronous
|
||||
versions of each of the supported method types. For convenience, gRPC C# also
|
||||
provides a synchronous method stub, but only for simple (single request/single
|
||||
response) RPCs.
|
||||
|
||||
##### Simple RPC
|
||||
|
||||
Calling the simple RPC `GetFeature` in a synchronous way is nearly as
|
||||
straightforward as calling a local method.
|
||||
|
||||
```csharp
|
||||
Point request = new Point { Latitude = 409146138, Longitude = -746188906 };
|
||||
Feature feature = client.GetFeature(request);
|
||||
```
|
||||
|
||||
As you can see, we create and populate a request protocol buffer object (in our
|
||||
case `Point`), and call the desired method on the client object, passing it the
|
||||
request. If the RPC finishes with success, the response protocol buffer (in our
|
||||
case `Feature`) is returned. Otherwise, an exception of type `RpcException` is
|
||||
thrown, indicating the status code of the problem.
|
||||
|
||||
Alternatively, if you are in an async context, you can call an asynchronous
|
||||
version of the method and use the `await` keyword to await the result:
|
||||
|
||||
```csharp
|
||||
Point request = new Point { Latitude = 409146138, Longitude = -746188906 };
|
||||
Feature feature = await client.GetFeatureAsync(request);
|
||||
```
|
||||
|
||||
##### Streaming RPCs
|
||||
|
||||
Now let's look at our streaming methods. If you've already read [Creating the
|
||||
server](#server) some of this may look very familiar - streaming RPCs are
|
||||
implemented in a similar way on both sides. The difference with respect to
|
||||
simple call is that the client methods return an instance of a call object. This
|
||||
provides access to request/response streams and/or the asynchronous result,
|
||||
depending on the streaming type you are using.
|
||||
|
||||
Here's where we call the server-side streaming method `ListFeatures`, which has
|
||||
the property `ReponseStream` of type `IAsyncEnumerator<Feature>`
|
||||
|
||||
```csharp
|
||||
using (var call = client.ListFeatures(request))
|
||||
{
|
||||
while (await call.ResponseStream.MoveNext())
|
||||
{
|
||||
Feature feature = call.ResponseStream.Current;
|
||||
Console.WriteLine("Received " + feature.ToString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The client-side streaming method `RecordRoute` is similar, except we use the
|
||||
property `RequestStream` to write the requests one by one using `WriteAsync`,
|
||||
and eventually signal that no more requests will be sent using `CompleteAsync`.
|
||||
The method result can be obtained through the property `ResponseAsync`.
|
||||
|
||||
```csharp
|
||||
using (var call = client.RecordRoute())
|
||||
{
|
||||
foreach (var point in points)
|
||||
{
|
||||
await call.RequestStream.WriteAsync(point);
|
||||
}
|
||||
await call.RequestStream.CompleteAsync();
|
||||
|
||||
RouteSummary summary = await call.ResponseAsync;
|
||||
}
|
||||
```
|
||||
|
||||
Finally, let's look at our bidirectional streaming RPC `RouteChat`. In this
|
||||
case, we write the request to `RequestStream` and receive the responses from
|
||||
`ResponseStream`. As you can see from the example, the streams are independent
|
||||
of each other.
|
||||
|
||||
```csharp
|
||||
using (var call = client.RouteChat())
|
||||
{
|
||||
var responseReaderTask = Task.Run(async () =>
|
||||
{
|
||||
while (await call.ResponseStream.MoveNext())
|
||||
{
|
||||
var note = call.ResponseStream.Current;
|
||||
Console.WriteLine("Received " + note);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (RouteNote request in requests)
|
||||
{
|
||||
await call.RequestStream.WriteAsync(request);
|
||||
}
|
||||
await call.RequestStream.CompleteAsync();
|
||||
await responseReaderTask;
|
||||
}
|
||||
```
|
||||
|
||||
### Try it out!
|
||||
|
||||
Build the client and server:
|
||||
|
||||
Using Visual Studio (or Visual Studio For Mac)
|
||||
: Open the solution `examples/csharp/RouteGuide/RouteGuide.sln` and select **Build**.
|
||||
|
||||
Using `dotnet` command line tool
|
||||
|
||||
: Run `dotnet build RouteGuide.sln` from the `examples/csharp/RouteGuide`
|
||||
directory. For additional instructions on building the gRPC example with the
|
||||
`dotnet` command line tool, see [Quick start](../Greeter/quickstart.md).
|
||||
|
||||
Run the server:
|
||||
|
||||
```sh
|
||||
> cd RouteGuideServer
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
From a different terminal, run the client:
|
||||
|
||||
```sh
|
||||
> cd RouteGuideClient
|
||||
> dotnet run
|
||||
```
|
||||
|
||||
You can also run the server and client directly from Visual Studio.
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'gRPC-C++'
|
||||
# TODO (mxyan): use version that match gRPC version when pod is stabilized
|
||||
version = '1.46.0-dev'
|
||||
version = '1.46.7'
|
||||
s.version = version
|
||||
s.summary = 'gRPC C++ library'
|
||||
s.homepage = 'https://grpc.io'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'gRPC-Core'
|
||||
version = '1.46.0-dev'
|
||||
version = '1.46.7'
|
||||
s.version = version
|
||||
s.summary = 'Core cross-platform gRPC library, written in C'
|
||||
s.homepage = 'https://grpc.io'
|
||||
|
|
@ -169,7 +169,7 @@ Pod::Spec.new do |s|
|
|||
ss.header_mappings_dir = '.'
|
||||
ss.libraries = 'z'
|
||||
ss.dependency "#{s.name}/Interface", version
|
||||
ss.dependency 'BoringSSL-GRPC', '0.0.24'
|
||||
ss.dependency 'BoringSSL-GRPC', '0.0.25'
|
||||
ss.dependency 'Libuv-gRPC', '0.0.10'
|
||||
ss.dependency 'abseil/base/base', abseil_version
|
||||
ss.dependency 'abseil/base/core_headers', abseil_version
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'gRPC-ProtoRPC'
|
||||
version = '1.46.0-dev'
|
||||
version = '1.46.7'
|
||||
s.version = version
|
||||
s.summary = 'RPC library for Protocol Buffers, based on gRPC'
|
||||
s.homepage = 'https://grpc.io'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'gRPC-RxLibrary'
|
||||
version = '1.46.0-dev'
|
||||
version = '1.46.7'
|
||||
s.version = version
|
||||
s.summary = 'Reactive Extensions library for iOS/OSX.'
|
||||
s.homepage = 'https://grpc.io'
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'gRPC'
|
||||
version = '1.46.0-dev'
|
||||
version = '1.46.7'
|
||||
s.version = version
|
||||
s.summary = 'gRPC client library for iOS/OSX'
|
||||
s.homepage = 'https://grpc.io'
|
||||
|
|
|
|||
|
|
@ -19,15 +19,13 @@ Gem::Specification.new do |s|
|
|||
s.files += %w( etc/roots.pem )
|
||||
s.files += Dir.glob('src/ruby/bin/**/*')
|
||||
s.files += Dir.glob('src/ruby/ext/**/*')
|
||||
s.files += Dir.glob('src/ruby/lib/**/*').reject do |f|
|
||||
# Binaries are included by rake-compiler and would lead to circular dependencies here
|
||||
File.fnmatch("**/?.?/grpc_c.so", f)
|
||||
end
|
||||
s.files += Dir.glob('src/ruby/lib/**/*')
|
||||
s.files += Dir.glob('src/ruby/pb/**/*').reject do |f|
|
||||
f.match(%r{^src/ruby/pb/test})
|
||||
end
|
||||
s.files += Dir.glob('include/grpc/**/*')
|
||||
s.test_files = Dir.glob('src/ruby/spec/**/*')
|
||||
s.test_files += Dir.glob('src/ruby/pb/test/**/*')
|
||||
s.bindir = 'src/ruby/bin'
|
||||
s.require_paths = %w( src/ruby/lib src/ruby/bin src/ruby/pb )
|
||||
s.platform = Gem::Platform::RUBY
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@
|
|||
<date>2019-09-24</date>
|
||||
<time>16:06:07</time>
|
||||
<version>
|
||||
<release>1.46.0dev</release>
|
||||
<api>1.46.0dev</api>
|
||||
<release>1.46.7</release>
|
||||
<api>1.46.7</api>
|
||||
</version>
|
||||
<stability>
|
||||
<release>beta</release>
|
||||
<api>beta</api>
|
||||
<release>stable</release>
|
||||
<api>stable</api>
|
||||
</stability>
|
||||
<license>Apache 2.0</license>
|
||||
<notes>
|
||||
- gRPC Core 1.46.0 update
|
||||
- gRPC Core 1.46.7 update
|
||||
</notes>
|
||||
<contents>
|
||||
<dir baseinstalldir="/" name="/">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// generated by generate_boringssl_prefix_header.sh on BoringSSL commit: b9232f9e27e5668bc0414879dcdedb2a59ea75f2
|
||||
// generated by generate_boringssl_prefix_header.sh on BoringSSL commit: 6195bf8242156c9a2fa75702eee058f91b86a88b
|
||||
|
||||
// Copyright (c) 2018, Google Inc.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class PriorityLb : public LoadBalancingPolicy {
|
|||
bool ignore_reresolution_requests);
|
||||
void ExitIdleLocked();
|
||||
void ResetBackoffLocked();
|
||||
void DeactivateLocked();
|
||||
void MaybeDeactivateLocked();
|
||||
void MaybeReactivateLocked();
|
||||
|
||||
void Orphan() override;
|
||||
|
|
@ -217,6 +217,8 @@ class PriorityLb : public LoadBalancingPolicy {
|
|||
absl::Status connectivity_status_;
|
||||
RefCountedPtr<RefCountedPicker> picker_wrapper_;
|
||||
|
||||
bool seen_ready_or_idle_since_transient_failure_ = true;
|
||||
|
||||
OrphanablePtr<DeactivationTimer> deactivation_timer_;
|
||||
OrphanablePtr<FailoverTimer> failover_timer_;
|
||||
};
|
||||
|
|
@ -225,14 +227,37 @@ class PriorityLb : public LoadBalancingPolicy {
|
|||
|
||||
void ShutdownLocked() override;
|
||||
|
||||
// Returns UINT32_MAX if child is not in current priority list.
|
||||
// Returns the priority of the specified child name, or UINT32_MAX if
|
||||
// the child is not in the current priority list.
|
||||
uint32_t GetChildPriorityLocked(const std::string& child_name) const;
|
||||
|
||||
// Called when a child's connectivity state has changed.
|
||||
// May propagate the update to the channel or trigger choosing a new
|
||||
// priority.
|
||||
void HandleChildConnectivityStateChangeLocked(ChildPriority* child);
|
||||
|
||||
// Deletes a child. Called when the child's deactivation timer fires.
|
||||
void DeleteChild(ChildPriority* child);
|
||||
|
||||
void TryNextPriorityLocked(bool report_connecting);
|
||||
void SelectPriorityLocked(uint32_t priority);
|
||||
// Iterates through the list of priorities to choose one:
|
||||
// - If the child for a priority doesn't exist, creates it.
|
||||
// - If a child's failover timer is pending, returns without selecting
|
||||
// a priority while we wait for the child to attempt to connect. In
|
||||
// this case, if report_connecting is true, reports CONNECTING state to
|
||||
// the channel.
|
||||
// - If the child is connected, it will be used as the current priority.
|
||||
// - Otherwise, continues on to the next child.
|
||||
// Reports TRANSIENT_FAILURE to the channel if all children are not
|
||||
// connected.
|
||||
//
|
||||
// This method is idempotent; it should yield the same result every
|
||||
// time as a function of the state of the children.
|
||||
void ChoosePriorityLocked(bool report_connecting);
|
||||
|
||||
// Sets the specified priority as the current priority.
|
||||
// Deactivates any children at lower priorities.
|
||||
// Returns the child's picker to the channel.
|
||||
void SetCurrentPriorityLocked(uint32_t priority);
|
||||
|
||||
const Duration child_failover_timeout_;
|
||||
|
||||
|
|
@ -246,6 +271,8 @@ class PriorityLb : public LoadBalancingPolicy {
|
|||
|
||||
bool update_in_progress_ = false;
|
||||
|
||||
// All children that currently exist.
|
||||
// Some of these children may be in deactivated state.
|
||||
std::map<std::string, OrphanablePtr<ChildPriority>> children_;
|
||||
// The priority that is being used.
|
||||
uint32_t current_priority_ = UINT32_MAX;
|
||||
|
|
@ -312,7 +339,7 @@ void PriorityLb::UpdateLocked(UpdateArgs args) {
|
|||
current_child_from_before_update_ = children_[child_name].get();
|
||||
// Unset current_priority_, since it was an index into the old
|
||||
// config's priority list and may no longer be valid. It will be
|
||||
// reset later by TryNextPriorityLocked(), but we unset it here in
|
||||
// reset later by ChoosePriorityLocked(), but we unset it here in
|
||||
// case updating any of our children triggers a state update.
|
||||
current_priority_ = UINT32_MAX;
|
||||
}
|
||||
|
|
@ -332,7 +359,7 @@ void PriorityLb::UpdateLocked(UpdateArgs args) {
|
|||
auto config_it = config_->children().find(child_name);
|
||||
if (config_it == config_->children().end()) {
|
||||
// Existing child not found in new config. Deactivate it.
|
||||
child->DeactivateLocked();
|
||||
child->MaybeDeactivateLocked();
|
||||
} else {
|
||||
// Existing child found in new config. Update it.
|
||||
child->UpdateLocked(config_it->second.config,
|
||||
|
|
@ -341,7 +368,7 @@ void PriorityLb::UpdateLocked(UpdateArgs args) {
|
|||
}
|
||||
update_in_progress_ = false;
|
||||
// Try to get connected.
|
||||
TryNextPriorityLocked(/*report_connecting=*/children_.empty());
|
||||
ChoosePriorityLocked(/*report_connecting=*/children_.empty());
|
||||
}
|
||||
|
||||
uint32_t PriorityLb::GetChildPriorityLocked(
|
||||
|
|
@ -380,11 +407,11 @@ void PriorityLb::HandleChildConnectivityStateChangeLocked(
|
|||
} else {
|
||||
// If it's no longer READY or IDLE, we should stop using it.
|
||||
// We already started trying other priorities as a result of the
|
||||
// update, but calling TryNextPriorityLocked() ensures that we will
|
||||
// update, but calling ChoosePriorityLocked() ensures that we will
|
||||
// properly select between CONNECTING and TRANSIENT_FAILURE as the
|
||||
// new state to report to our parent.
|
||||
current_child_from_before_update_ = nullptr;
|
||||
TryNextPriorityLocked(/*report_connecting=*/true);
|
||||
ChoosePriorityLocked(/*report_connecting=*/true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -396,52 +423,26 @@ void PriorityLb::HandleChildConnectivityStateChangeLocked(
|
|||
"priority %u",
|
||||
this, child_priority, child->name().c_str(), current_priority_);
|
||||
}
|
||||
// Ignore priorities not in the current config.
|
||||
if (child_priority == UINT32_MAX) return;
|
||||
// Ignore lower-than-current priorities.
|
||||
if (child_priority > current_priority_) return;
|
||||
// If a child reports TRANSIENT_FAILURE, start trying the next priority.
|
||||
// Note that even if this is for a higher-than-current priority, we
|
||||
// may still need to create some children between this priority and
|
||||
// the current one (e.g., if we got an update that inserted new
|
||||
// priorities ahead of the current one).
|
||||
if (child->connectivity_state() == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
TryNextPriorityLocked(
|
||||
/*report_connecting=*/child_priority == current_priority_);
|
||||
return;
|
||||
}
|
||||
// The update is for a higher-than-current priority (or for any
|
||||
// priority if we don't have any current priority).
|
||||
if (child_priority < current_priority_) {
|
||||
// If the child reports READY or IDLE, switch to that priority.
|
||||
// Otherwise, ignore the update.
|
||||
if (child->connectivity_state() == GRPC_CHANNEL_READY ||
|
||||
child->connectivity_state() == GRPC_CHANNEL_IDLE) {
|
||||
SelectPriorityLocked(child_priority);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// The current priority has returned a new picker, so pass it up to
|
||||
// our parent.
|
||||
channel_control_helper()->UpdateState(child->connectivity_state(),
|
||||
child->connectivity_status(),
|
||||
child->GetPicker());
|
||||
// Unconditionally call ChoosePriorityLocked(). It should do the
|
||||
// right thing based on the state of all children.
|
||||
ChoosePriorityLocked(
|
||||
/*report_connecting=*/child_priority == current_priority_);
|
||||
}
|
||||
|
||||
void PriorityLb::DeleteChild(ChildPriority* child) {
|
||||
// If this was the current child from before the most recent update,
|
||||
// stop using it. We already started trying other priorities as a
|
||||
// result of the update, but calling TryNextPriorityLocked() ensures that
|
||||
// result of the update, but calling ChoosePriorityLocked() ensures that
|
||||
// we will properly select between CONNECTING and TRANSIENT_FAILURE as the
|
||||
// new state to report to our parent.
|
||||
if (current_child_from_before_update_ == child) {
|
||||
current_child_from_before_update_ = nullptr;
|
||||
TryNextPriorityLocked(/*report_connecting=*/true);
|
||||
ChoosePriorityLocked(/*report_connecting=*/true);
|
||||
}
|
||||
children_.erase(child->name());
|
||||
}
|
||||
|
||||
void PriorityLb::TryNextPriorityLocked(bool report_connecting) {
|
||||
void PriorityLb::ChoosePriorityLocked(bool report_connecting) {
|
||||
current_priority_ = UINT32_MAX;
|
||||
for (uint32_t priority = 0; priority < config_->priorities().size();
|
||||
++priority) {
|
||||
|
|
@ -471,7 +472,7 @@ void PriorityLb::TryNextPriorityLocked(bool report_connecting) {
|
|||
// If the child is in state READY or IDLE, switch to it.
|
||||
if (child->connectivity_state() == GRPC_CHANNEL_READY ||
|
||||
child->connectivity_state() == GRPC_CHANNEL_IDLE) {
|
||||
SelectPriorityLocked(priority);
|
||||
SetCurrentPriorityLocked(priority);
|
||||
return;
|
||||
}
|
||||
// Child is not READY or IDLE.
|
||||
|
|
@ -491,6 +492,13 @@ void PriorityLb::TryNextPriorityLocked(bool report_connecting) {
|
|||
return;
|
||||
}
|
||||
// Child has been failing for a while. Move on to the next priority.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_priority_trace)) {
|
||||
gpr_log(GPR_INFO,
|
||||
"[priority_lb %p] skipping priority %u, child %s: state=%s, "
|
||||
"failover timer not pending",
|
||||
this, priority, child_name.c_str(),
|
||||
ConnectivityStateName(child->connectivity_state()));
|
||||
}
|
||||
}
|
||||
// If there are no more priorities to try, report TRANSIENT_FAILURE.
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_priority_trace)) {
|
||||
|
|
@ -506,7 +514,7 @@ void PriorityLb::TryNextPriorityLocked(bool report_connecting) {
|
|||
absl::make_unique<TransientFailurePicker>(status));
|
||||
}
|
||||
|
||||
void PriorityLb::SelectPriorityLocked(uint32_t priority) {
|
||||
void PriorityLb::SetCurrentPriorityLocked(uint32_t priority) {
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_priority_trace)) {
|
||||
gpr_log(GPR_INFO, "[priority_lb %p] selected priority %u, child %s", this,
|
||||
priority, config_->priorities()[priority].c_str());
|
||||
|
|
@ -517,7 +525,7 @@ void PriorityLb::SelectPriorityLocked(uint32_t priority) {
|
|||
for (uint32_t p = priority + 1; p < config_->priorities().size(); ++p) {
|
||||
const std::string& child_name = config_->priorities()[p];
|
||||
auto it = children_.find(child_name);
|
||||
if (it != children_.end()) it->second->DeactivateLocked();
|
||||
if (it != children_.end()) it->second->MaybeDeactivateLocked();
|
||||
}
|
||||
// Update picker.
|
||||
auto& child = children_[config_->priorities()[priority]];
|
||||
|
|
@ -742,9 +750,6 @@ PriorityLb::ChildPriority::CreateChildPolicyLocked(
|
|||
}
|
||||
|
||||
void PriorityLb::ChildPriority::ExitIdleLocked() {
|
||||
if (connectivity_state_ == GRPC_CHANNEL_IDLE && failover_timer_ == nullptr) {
|
||||
failover_timer_ = MakeOrphanable<FailoverTimer>(Ref());
|
||||
}
|
||||
child_policy_->ExitIdleLocked();
|
||||
}
|
||||
|
||||
|
|
@ -766,20 +771,31 @@ void PriorityLb::ChildPriority::OnConnectivityStateUpdateLocked(
|
|||
connectivity_state_ = state;
|
||||
connectivity_status_ = status;
|
||||
picker_wrapper_ = MakeRefCounted<RefCountedPicker>(std::move(picker));
|
||||
// If READY or IDLE or TRANSIENT_FAILURE, cancel failover timer.
|
||||
if (state == GRPC_CHANNEL_READY || state == GRPC_CHANNEL_IDLE ||
|
||||
state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
// If we transition to state CONNECTING and we've not seen
|
||||
// TRANSIENT_FAILURE more recently than READY or IDLE, start failover
|
||||
// timer if not already pending.
|
||||
// In any other state, update seen_ready_or_idle_since_transient_failure_
|
||||
// and cancel failover timer.
|
||||
if (state == GRPC_CHANNEL_CONNECTING) {
|
||||
if (seen_ready_or_idle_since_transient_failure_ &&
|
||||
failover_timer_ == nullptr) {
|
||||
failover_timer_ = MakeOrphanable<FailoverTimer>(Ref());
|
||||
}
|
||||
} else if (state == GRPC_CHANNEL_READY || state == GRPC_CHANNEL_IDLE) {
|
||||
seen_ready_or_idle_since_transient_failure_ = true;
|
||||
failover_timer_.reset();
|
||||
} else if (state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
seen_ready_or_idle_since_transient_failure_ = false;
|
||||
failover_timer_.reset();
|
||||
}
|
||||
// Notify the parent policy.
|
||||
priority_policy_->HandleChildConnectivityStateChangeLocked(this);
|
||||
}
|
||||
|
||||
void PriorityLb::ChildPriority::DeactivateLocked() {
|
||||
// If already deactivated, don't do it again.
|
||||
if (deactivation_timer_ != nullptr) return;
|
||||
failover_timer_.reset();
|
||||
deactivation_timer_ = MakeOrphanable<DeactivationTimer>(Ref());
|
||||
void PriorityLb::ChildPriority::MaybeDeactivateLocked() {
|
||||
if (deactivation_timer_ == nullptr) {
|
||||
deactivation_timer_ = MakeOrphanable<DeactivationTimer>(Ref());
|
||||
}
|
||||
}
|
||||
|
||||
void PriorityLb::ChildPriority::MaybeReactivateLocked() {
|
||||
|
|
|
|||
|
|
@ -143,8 +143,6 @@ class RingHash : public LoadBalancingPolicy {
|
|||
|
||||
const ServerAddress& address() const { return address_; }
|
||||
|
||||
bool seen_failure_since_ready() const { return seen_failure_since_ready_; }
|
||||
|
||||
// Performs connectivity state updates that need to be done both when we
|
||||
// first start watching and when a watcher notification is received.
|
||||
void UpdateConnectivityStateLocked(
|
||||
|
|
@ -157,10 +155,18 @@ class RingHash : public LoadBalancingPolicy {
|
|||
grpc_connectivity_state connectivity_state) override;
|
||||
|
||||
ServerAddress address_;
|
||||
|
||||
// Last logical connectivity state seen.
|
||||
// Note that this may differ from the state actually reported by the
|
||||
// subchannel in some cases; for example, once this is set to
|
||||
// TRANSIENT_FAILURE, we do not change it again until we get READY,
|
||||
// so we skip any interim stops in CONNECTING.
|
||||
grpc_connectivity_state last_connectivity_state_ = GRPC_CHANNEL_SHUTDOWN;
|
||||
|
||||
// Connectivity state seen by picker.
|
||||
// Uses an atomic so that it can be accessed outside of the WorkSerializer.
|
||||
std::atomic<grpc_connectivity_state> connectivity_state_for_picker_{
|
||||
GRPC_CHANNEL_IDLE};
|
||||
bool seen_failure_since_ready_ = false;
|
||||
};
|
||||
|
||||
// A list of subchannels.
|
||||
|
|
@ -193,9 +199,10 @@ class RingHash : public LoadBalancingPolicy {
|
|||
|
||||
// Updates the RH policy's connectivity state based on the
|
||||
// subchannel list's state counters, creating new picker and new ring.
|
||||
// Furthermore, return a bool indicating whether the aggregated state is
|
||||
// Transient Failure.
|
||||
bool UpdateRingHashConnectivityStateLocked();
|
||||
// The index parameter indicates the index into the list of the subchannel
|
||||
// whose status report triggered the call to
|
||||
// UpdateRingHashConnectivityStateLocked().
|
||||
void UpdateRingHashConnectivityStateLocked(size_t index);
|
||||
|
||||
// Create a new ring from this subchannel list.
|
||||
RefCountedPtr<Ring> MakeRing();
|
||||
|
|
@ -508,7 +515,7 @@ RingHash::PickResult RingHash::Picker::Pick(PickArgs args) {
|
|||
//
|
||||
|
||||
void RingHash::RingHashSubchannelList::StartWatchingLocked() {
|
||||
if (num_subchannels() == 0) return;
|
||||
GPR_ASSERT(num_subchannels() != 0);
|
||||
// Check current state of each subchannel synchronously.
|
||||
for (size_t i = 0; i < num_subchannels(); ++i) {
|
||||
grpc_connectivity_state state =
|
||||
|
|
@ -522,7 +529,10 @@ void RingHash::RingHashSubchannelList::StartWatchingLocked() {
|
|||
}
|
||||
}
|
||||
// Send updated state to parent based on reported subchannel states.
|
||||
UpdateRingHashConnectivityStateLocked();
|
||||
// Pretend we're getting this update from the last subchannel, so that
|
||||
// if we need to proactively start connecting, we'll start from the
|
||||
// first subchannel.
|
||||
UpdateRingHashConnectivityStateLocked(num_subchannels() - 1);
|
||||
}
|
||||
|
||||
void RingHash::RingHashSubchannelList::UpdateStateCountersLocked(
|
||||
|
|
@ -552,47 +562,72 @@ void RingHash::RingHashSubchannelList::UpdateStateCountersLocked(
|
|||
}
|
||||
}
|
||||
|
||||
// Sets the RH policy's connectivity state and generates a new picker based
|
||||
// on the current subchannel list or requests an re-attempt by returning true..
|
||||
bool RingHash::RingHashSubchannelList::UpdateRingHashConnectivityStateLocked() {
|
||||
void RingHash::RingHashSubchannelList::UpdateRingHashConnectivityStateLocked(
|
||||
size_t index) {
|
||||
RingHash* p = static_cast<RingHash*>(policy());
|
||||
// Only set connectivity state if this is the current subchannel list.
|
||||
if (p->subchannel_list_.get() != this) return false;
|
||||
if (p->subchannel_list_.get() != this) return;
|
||||
// The overall aggregation rules here are:
|
||||
// 1. If there is at least one subchannel in READY state, report READY.
|
||||
// 2. If there are 2 or more subchannels in TRANSIENT_FAILURE state, report
|
||||
// TRANSIENT_FAILURE.
|
||||
// TRANSIENT_FAILURE.
|
||||
// 3. If there is at least one subchannel in CONNECTING state, report
|
||||
// CONNECTING.
|
||||
// 4. If there is at least one subchannel in IDLE state, report IDLE.
|
||||
// 5. Otherwise, report TRANSIENT_FAILURE.
|
||||
// CONNECTING.
|
||||
// 4. If there is one subchannel in TRANSIENT_FAILURE state and there is
|
||||
// more than one subchannel, report CONNECTING.
|
||||
// 5. If there is at least one subchannel in IDLE state, report IDLE.
|
||||
// 6. Otherwise, report TRANSIENT_FAILURE.
|
||||
//
|
||||
// We set start_connection_attempt to true if we match rules 2, 3, or 6.
|
||||
grpc_connectivity_state state;
|
||||
absl::Status status;
|
||||
bool start_connection_attempt = false;
|
||||
if (num_ready_ > 0) {
|
||||
/* READY */
|
||||
p->channel_control_helper()->UpdateState(
|
||||
GRPC_CHANNEL_READY, absl::Status(),
|
||||
absl::make_unique<Picker>(p->Ref(DEBUG_LOCATION, "RingHashPicker"),
|
||||
p->ring_));
|
||||
return false;
|
||||
state = GRPC_CHANNEL_READY;
|
||||
} else if (num_transient_failure_ >= 2) {
|
||||
state = GRPC_CHANNEL_TRANSIENT_FAILURE;
|
||||
status = absl::UnavailableError("connections to backends failing");
|
||||
start_connection_attempt = true;
|
||||
} else if (num_connecting_ > 0) {
|
||||
state = GRPC_CHANNEL_CONNECTING;
|
||||
} else if (num_transient_failure_ == 1 && num_subchannels() > 1) {
|
||||
state = GRPC_CHANNEL_CONNECTING;
|
||||
start_connection_attempt = true;
|
||||
} else if (num_idle_ > 0) {
|
||||
state = GRPC_CHANNEL_IDLE;
|
||||
} else {
|
||||
state = GRPC_CHANNEL_TRANSIENT_FAILURE;
|
||||
status = absl::UnavailableError("connections to backends failing");
|
||||
start_connection_attempt = true;
|
||||
}
|
||||
if (num_connecting_ > 0 && num_transient_failure_ < 2) {
|
||||
p->channel_control_helper()->UpdateState(
|
||||
GRPC_CHANNEL_CONNECTING, absl::Status(),
|
||||
absl::make_unique<QueuePicker>(p->Ref(DEBUG_LOCATION, "QueuePicker")));
|
||||
return false;
|
||||
}
|
||||
if (num_idle_ > 0 && num_transient_failure_ < 2) {
|
||||
p->channel_control_helper()->UpdateState(
|
||||
GRPC_CHANNEL_IDLE, absl::Status(),
|
||||
absl::make_unique<Picker>(p->Ref(DEBUG_LOCATION, "RingHashPicker"),
|
||||
p->ring_));
|
||||
return false;
|
||||
}
|
||||
absl::Status status =
|
||||
absl::UnavailableError("connections to backend failing or idle");
|
||||
// Generate new picker and return it to the channel.
|
||||
// Note that we use our own picker regardless of connectivity state.
|
||||
p->channel_control_helper()->UpdateState(
|
||||
GRPC_CHANNEL_TRANSIENT_FAILURE, status,
|
||||
absl::make_unique<TransientFailurePicker>(status));
|
||||
return true;
|
||||
state, status,
|
||||
absl::make_unique<Picker>(p->Ref(DEBUG_LOCATION, "RingHashPicker"),
|
||||
p->ring_));
|
||||
// While the ring_hash policy is reporting TRANSIENT_FAILURE, it will
|
||||
// not be getting any pick requests from the priority policy.
|
||||
// However, because the ring_hash policy does not attempt to
|
||||
// reconnect to subchannels unless it is getting pick requests,
|
||||
// it will need special handling to ensure that it will eventually
|
||||
// recover from TRANSIENT_FAILURE state once the problem is resolved.
|
||||
// Specifically, it will make sure that it is attempting to connect to
|
||||
// at least one subchannel at any given time. After a given subchannel
|
||||
// fails a connection attempt, it will move on to the next subchannel
|
||||
// in the ring. It will keep doing this until one of the subchannels
|
||||
// successfully connects, at which point it will report READY and stop
|
||||
// proactively trying to connect. The policy will remain in
|
||||
// TRANSIENT_FAILURE until at least one subchannel becomes connected,
|
||||
// even if subchannels are in state CONNECTING during that time.
|
||||
//
|
||||
// Note that we do the same thing when the policy is in state
|
||||
// CONNECTING, just to ensure that we don't remain in CONNECTING state
|
||||
// indefinitely if there are no new picks coming in.
|
||||
if (start_connection_attempt) {
|
||||
size_t next_index = (index + 1) % num_subchannels();
|
||||
subchannel(next_index)->subchannel()->AttemptToConnect();
|
||||
}
|
||||
}
|
||||
|
||||
RefCountedPtr<RingHash::Ring> RingHash::RingHashSubchannelList::MakeRing() {
|
||||
|
|
@ -617,25 +652,34 @@ void RingHash::RingHashSubchannelData::UpdateConnectivityStateLocked(
|
|||
ConnectivityStateName(last_connectivity_state_),
|
||||
ConnectivityStateName(connectivity_state));
|
||||
}
|
||||
// Decide what state to report for aggregation purposes.
|
||||
// Decide what state to report for the purposes of aggregation and
|
||||
// picker behavior.
|
||||
// If we haven't seen a failure since the last time we were in state
|
||||
// READY, then we report the state change as-is. However, once we do see
|
||||
// a failure, we report TRANSIENT_FAILURE and do not report any subsequent
|
||||
// state changes until we go back into state READY.
|
||||
if (!seen_failure_since_ready_) {
|
||||
if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
seen_failure_since_ready_ = true;
|
||||
}
|
||||
subchannel_list()->UpdateStateCountersLocked(last_connectivity_state_,
|
||||
connectivity_state);
|
||||
} else {
|
||||
if (connectivity_state == GRPC_CHANNEL_READY) {
|
||||
seen_failure_since_ready_ = false;
|
||||
subchannel_list()->UpdateStateCountersLocked(
|
||||
GRPC_CHANNEL_TRANSIENT_FAILURE, connectivity_state);
|
||||
if (last_connectivity_state_ == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
// If not transitioning to READY, ignore the update, since we want
|
||||
// to continue to consider ourselves in TRANSIENT_FAILURE.
|
||||
if (connectivity_state != GRPC_CHANNEL_READY) return;
|
||||
} else if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
// If we go from READY to TF, treat it as IDLE.
|
||||
// This transition can be caused by a "normal" connection failure, such
|
||||
// as the server closing the connection due to a max-age setting. In
|
||||
// this case, we want to have RPCs that hash to this subchannel wait for
|
||||
// the reconnection attempt rather than assuming that the subchannel is
|
||||
// bad and moving on to a subsequent subchannel in the ring.
|
||||
if (last_connectivity_state_ == GRPC_CHANNEL_READY) {
|
||||
connectivity_state = GRPC_CHANNEL_IDLE;
|
||||
}
|
||||
}
|
||||
// Record last seen connectivity state.
|
||||
// Update state counters used for aggregation.
|
||||
subchannel_list()->UpdateStateCountersLocked(last_connectivity_state_,
|
||||
connectivity_state);
|
||||
// Update state seen by picker.
|
||||
connectivity_state_for_picker_.store(connectivity_state,
|
||||
std::memory_order_relaxed);
|
||||
// Update last seen connectivity state.
|
||||
last_connectivity_state_ = connectivity_state;
|
||||
}
|
||||
|
||||
|
|
@ -643,15 +687,11 @@ void RingHash::RingHashSubchannelData::ProcessConnectivityChangeLocked(
|
|||
grpc_connectivity_state connectivity_state) {
|
||||
RingHash* p = static_cast<RingHash*>(subchannel_list()->policy());
|
||||
GPR_ASSERT(subchannel() != nullptr);
|
||||
// Update connectivity state used by picker.
|
||||
connectivity_state_for_picker_.store(connectivity_state,
|
||||
std::memory_order_relaxed);
|
||||
// If the new state is TRANSIENT_FAILURE, re-resolve.
|
||||
// Only do this if we've started watching, not at startup time.
|
||||
// Otherwise, if the subchannel was already in state TRANSIENT_FAILURE
|
||||
// when the subchannel list was created, we'd wind up in a constant
|
||||
// loop of re-resolution.
|
||||
// Also attempt to reconnect.
|
||||
if (connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
if (GRPC_TRACE_FLAG_ENABLED(grpc_lb_ring_hash_trace)) {
|
||||
gpr_log(GPR_INFO,
|
||||
|
|
@ -665,28 +705,7 @@ void RingHash::RingHashSubchannelData::ProcessConnectivityChangeLocked(
|
|||
UpdateConnectivityStateLocked(connectivity_state);
|
||||
// Update the RH policy's connectivity state, creating new picker and new
|
||||
// ring.
|
||||
bool transient_failure =
|
||||
subchannel_list()->UpdateRingHashConnectivityStateLocked();
|
||||
// While the ring_hash policy is reporting TRANSIENT_FAILURE, it will
|
||||
// not be getting any pick requests from the priority policy.
|
||||
// However, because the ring_hash policy does not attempt to
|
||||
// reconnect to subchannels unless it is getting pick requests,
|
||||
// it will need special handling to ensure that it will eventually
|
||||
// recover from TRANSIENT_FAILURE state once the problem is resolved.
|
||||
// Specifically, it will make sure that it is attempting to connect to
|
||||
// at least one subchannel at any given time. After a given subchannel
|
||||
// fails a connection attempt, it will move on to the next subchannel
|
||||
// in the ring. It will keep doing this until one of the subchannels
|
||||
// successfully connects, at which point it will report READY and stop
|
||||
// proactively trying to connect. The policy will remain in
|
||||
// TRANSIENT_FAILURE until at least one subchannel becomes connected,
|
||||
// even if subchannels are in state CONNECTING during that time.
|
||||
if (transient_failure &&
|
||||
connectivity_state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
size_t next_index = (Index() + 1) % subchannel_list()->num_subchannels();
|
||||
RingHashSubchannelData* next_sd = subchannel_list()->subchannel(next_index);
|
||||
next_sd->subchannel()->AttemptToConnect();
|
||||
}
|
||||
subchannel_list()->UpdateRingHashConnectivityStateLocked(Index());
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ class WeightedTargetLb : public LoadBalancingPolicy {
|
|||
|
||||
RefCountedPtr<ChildPickerWrapper> picker_wrapper_;
|
||||
grpc_connectivity_state connectivity_state_ = GRPC_CHANNEL_CONNECTING;
|
||||
bool seen_failure_since_ready_ = false;
|
||||
|
||||
OrphanablePtr<DelayedRemovalTimer> delayed_removal_timer_;
|
||||
};
|
||||
|
|
@ -585,19 +584,12 @@ void WeightedTargetLb::WeightedChild::OnConnectivityStateUpdateLocked(
|
|||
// If the child reports IDLE, immediately tell it to exit idle.
|
||||
if (state == GRPC_CHANNEL_IDLE) child_policy_->ExitIdleLocked();
|
||||
// Decide what state to report for aggregation purposes.
|
||||
// If we haven't seen a failure since the last time we were in state
|
||||
// READY, then we report the state change as-is. However, once we do see
|
||||
// a failure, we report TRANSIENT_FAILURE and ignore any subsequent state
|
||||
// changes until we go back into state READY.
|
||||
if (!seen_failure_since_ready_) {
|
||||
if (state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
seen_failure_since_ready_ = true;
|
||||
}
|
||||
} else {
|
||||
if (state != GRPC_CHANNEL_READY) return;
|
||||
seen_failure_since_ready_ = false;
|
||||
// If the last recorded state was TRANSIENT_FAILURE and the new state
|
||||
// is something other than READY, don't change the state.
|
||||
if (connectivity_state_ != GRPC_CHANNEL_TRANSIENT_FAILURE ||
|
||||
state == GRPC_CHANNEL_READY) {
|
||||
connectivity_state_ = state;
|
||||
}
|
||||
connectivity_state_ = state;
|
||||
// Notify the LB policy.
|
||||
weighted_target_policy_->UpdateStateLocked();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -504,8 +504,7 @@ void XdsClusterImplLb::MaybeUpdatePickerLocked() {
|
|||
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_cluster_impl_lb_trace)) {
|
||||
gpr_log(GPR_INFO,
|
||||
"[xds_cluster_impl_lb %p] updating connectivity (drop all): "
|
||||
"state=READY "
|
||||
"picker=%p",
|
||||
"state=READY picker=%p",
|
||||
this, drop_picker.get());
|
||||
}
|
||||
channel_control_helper()->UpdateState(GRPC_CHANNEL_READY, absl::Status(),
|
||||
|
|
@ -518,8 +517,7 @@ void XdsClusterImplLb::MaybeUpdatePickerLocked() {
|
|||
if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_cluster_impl_lb_trace)) {
|
||||
gpr_log(GPR_INFO,
|
||||
"[xds_cluster_impl_lb %p] updating connectivity: state=%s "
|
||||
"status=(%s) "
|
||||
"picker=%p",
|
||||
"status=(%s) picker=%p",
|
||||
this, ConnectivityStateName(state_), status_.ToString().c_str(),
|
||||
drop_picker.get());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,6 @@ class XdsClusterManagerLb : public LoadBalancingPolicy {
|
|||
|
||||
RefCountedPtr<ChildPickerWrapper> picker_wrapper_;
|
||||
grpc_connectivity_state connectivity_state_ = GRPC_CHANNEL_IDLE;
|
||||
bool seen_failure_since_ready_ = false;
|
||||
|
||||
// States for delayed removal.
|
||||
grpc_timer delayed_removal_timer_;
|
||||
|
|
@ -554,19 +553,13 @@ void XdsClusterManagerLb::ClusterChild::Helper::UpdateState(
|
|||
MakeRefCounted<ChildPickerWrapper>(xds_cluster_manager_child_->name_,
|
||||
std::move(picker));
|
||||
// Decide what state to report for aggregation purposes.
|
||||
// If we haven't seen a failure since the last time we were in state
|
||||
// READY, then we report the state change as-is. However, once we do see
|
||||
// a failure, we report TRANSIENT_FAILURE and ignore any subsequent state
|
||||
// changes until we go back into state READY.
|
||||
if (!xds_cluster_manager_child_->seen_failure_since_ready_) {
|
||||
if (state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
|
||||
xds_cluster_manager_child_->seen_failure_since_ready_ = true;
|
||||
}
|
||||
} else {
|
||||
if (state != GRPC_CHANNEL_READY) return;
|
||||
xds_cluster_manager_child_->seen_failure_since_ready_ = false;
|
||||
// If the last recorded state was TRANSIENT_FAILURE and the new state
|
||||
// is something other than READY, don't change the state.
|
||||
if (xds_cluster_manager_child_->connectivity_state_ !=
|
||||
GRPC_CHANNEL_TRANSIENT_FAILURE ||
|
||||
state == GRPC_CHANNEL_READY) {
|
||||
xds_cluster_manager_child_->connectivity_state_ = state;
|
||||
}
|
||||
xds_cluster_manager_child_->connectivity_state_ = state;
|
||||
// Notify the LB policy.
|
||||
xds_cluster_manager_child_->xds_cluster_manager_policy_->UpdateStateLocked();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "src/core/ext/filters/fault_injection/service_config_parser.h"
|
||||
#include "src/core/lib/channel/channel_stack.h"
|
||||
#include "src/core/lib/channel/status_util.h"
|
||||
#include "src/core/lib/gprpp/capture.h"
|
||||
#include "src/core/lib/promise/sleep.h"
|
||||
#include "src/core/lib/promise/try_seq.h"
|
||||
#include "src/core/lib/service_config/service_config_call_data.h"
|
||||
|
|
@ -62,6 +63,33 @@ inline bool UnderFraction(const uint32_t numerator,
|
|||
return random_number < numerator;
|
||||
}
|
||||
|
||||
// Tracks an active faults lifetime.
|
||||
// Increments g_active_faults when created, and decrements it when destroyed.
|
||||
class FaultHandle {
|
||||
public:
|
||||
explicit FaultHandle(bool active) : active_(active) {
|
||||
if (active) {
|
||||
g_active_faults.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
~FaultHandle() {
|
||||
if (active_) {
|
||||
g_active_faults.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
FaultHandle(const FaultHandle&) = delete;
|
||||
FaultHandle& operator=(const FaultHandle&) = delete;
|
||||
FaultHandle(FaultHandle&& other) noexcept
|
||||
: active_(absl::exchange(other.active_, false)) {}
|
||||
FaultHandle& operator=(FaultHandle&& other) noexcept {
|
||||
std::swap(active_, other.active_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class FaultInjectionFilter::InjectionDecision {
|
||||
|
|
@ -73,15 +101,16 @@ class FaultInjectionFilter::InjectionDecision {
|
|||
abort_request_(abort_request) {}
|
||||
|
||||
std::string ToString() const;
|
||||
Timestamp DelayUntil() const;
|
||||
Timestamp DelayUntil();
|
||||
absl::Status MaybeAbort() const;
|
||||
|
||||
private:
|
||||
bool HaveActiveFaultsQuota(bool increment) const;
|
||||
bool HaveActiveFaultsQuota() const;
|
||||
|
||||
uint32_t max_faults_;
|
||||
Duration delay_time_;
|
||||
absl::optional<absl::Status> abort_request_;
|
||||
FaultHandle active_fault_{false};
|
||||
};
|
||||
|
||||
absl::StatusOr<FaultInjectionFilter> FaultInjectionFilter::Create(
|
||||
|
|
@ -104,9 +133,12 @@ ArenaPromise<ServerMetadataHandle> FaultInjectionFilter::MakeCallPromise(
|
|||
gpr_log(GPR_INFO, "chand=%p: Fault injection triggered %s", this,
|
||||
decision.ToString().c_str());
|
||||
}
|
||||
auto delay = decision.DelayUntil();
|
||||
return TrySeq(
|
||||
Sleep(decision.DelayUntil()),
|
||||
[decision]() { return decision.MaybeAbort(); },
|
||||
Sleep(delay),
|
||||
Capture(
|
||||
[](InjectionDecision* decision) { return decision->MaybeAbort(); },
|
||||
std::move(decision)),
|
||||
next_promise_factory(std::move(call_args)));
|
||||
}
|
||||
|
||||
|
|
@ -190,17 +222,13 @@ FaultInjectionFilter::MakeInjectionDecision(
|
|||
: absl::nullopt);
|
||||
}
|
||||
|
||||
bool FaultInjectionFilter::InjectionDecision::HaveActiveFaultsQuota(
|
||||
bool increment) const {
|
||||
if (g_active_faults.load(std::memory_order_acquire) >= max_faults_) {
|
||||
return false;
|
||||
}
|
||||
if (increment) g_active_faults.fetch_add(1, std::memory_order_relaxed);
|
||||
return true;
|
||||
bool FaultInjectionFilter::InjectionDecision::HaveActiveFaultsQuota() const {
|
||||
return g_active_faults.load(std::memory_order_acquire) < max_faults_;
|
||||
}
|
||||
|
||||
Timestamp FaultInjectionFilter::InjectionDecision::DelayUntil() const {
|
||||
if (delay_time_ != Duration::Zero() && HaveActiveFaultsQuota(true)) {
|
||||
Timestamp FaultInjectionFilter::InjectionDecision::DelayUntil() {
|
||||
if (delay_time_ != Duration::Zero() && HaveActiveFaultsQuota()) {
|
||||
active_fault_ = FaultHandle{true};
|
||||
return ExecCtx::Get()->Now() + delay_time_;
|
||||
}
|
||||
return Timestamp::InfPast();
|
||||
|
|
@ -208,7 +236,7 @@ Timestamp FaultInjectionFilter::InjectionDecision::DelayUntil() const {
|
|||
|
||||
absl::Status FaultInjectionFilter::InjectionDecision::MaybeAbort() const {
|
||||
if (abort_request_.has_value() &&
|
||||
(delay_time_ != Duration::Zero() || HaveActiveFaultsQuota(false))) {
|
||||
(delay_time_ != Duration::Zero() || HaveActiveFaultsQuota())) {
|
||||
return abort_request_.value();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@
|
|||
|
||||
namespace grpc_core {
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO(donnadionne): check to see if federation is enabled, this will be
|
||||
// removed once federation is fully integrated and enabled by default.
|
||||
bool XdsFederationEnabled() {
|
||||
|
|
@ -56,6 +54,8 @@ bool XdsFederationEnabled() {
|
|||
return parse_succeeded && parsed_value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
grpc_error_handle ParseChannelCreds(const Json::Object& json, size_t idx,
|
||||
XdsBootstrap::XdsServer* server) {
|
||||
std::vector<grpc_error_handle> error_list;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
|
||||
namespace grpc_core {
|
||||
|
||||
bool XdsFederationEnabled();
|
||||
|
||||
class XdsClient;
|
||||
|
||||
class XdsBootstrap {
|
||||
|
|
|
|||
|
|
@ -788,7 +788,7 @@ void XdsClient::ChannelState::AdsCallState::AdsResponseParser::ParseResource(
|
|||
}
|
||||
// Check the resource name.
|
||||
auto resource_name =
|
||||
XdsClient::ParseXdsResourceName(result->name, result_.type);
|
||||
xds_client()->ParseXdsResourceName(result->name, result_.type);
|
||||
if (!resource_name.ok()) {
|
||||
result_.errors.emplace_back(absl::StrCat(
|
||||
"resource index ", idx, ": Cannot parse xDS resource name \"",
|
||||
|
|
@ -1788,6 +1788,7 @@ XdsClient::XdsClient(std::unique_ptr<XdsBootstrap> bootstrap,
|
|||
bootstrap_(std::move(bootstrap)),
|
||||
args_(ModifyChannelArgs(args)),
|
||||
request_timeout_(GetRequestTimeout(args)),
|
||||
xds_federation_enabled_(XdsFederationEnabled()),
|
||||
interested_parties_(grpc_pollset_set_create()),
|
||||
certificate_provider_store_(MakeOrphanable<CertificateProviderStore>(
|
||||
bootstrap_->certificate_providers())),
|
||||
|
|
@ -1930,6 +1931,7 @@ void XdsClient::CancelResourceWatch(const XdsResourceType* type,
|
|||
}
|
||||
if (shutting_down_) return;
|
||||
// Find authority.
|
||||
if (!resource_name.ok()) return;
|
||||
auto authority_it = authority_state_map_.find(resource_name->authority);
|
||||
if (authority_it == authority_state_map_.end()) return;
|
||||
AuthorityState& authority_state = authority_it->second;
|
||||
|
|
@ -1982,7 +1984,7 @@ absl::StatusOr<XdsClient::XdsResourceName> XdsClient::ParseXdsResourceName(
|
|||
absl::string_view name, const XdsResourceType* type) {
|
||||
// Old-style names use the empty string for authority.
|
||||
// authority is prefixed with "old:" to indicate that it's an old-style name.
|
||||
if (!absl::StartsWith(name, "xdstp:")) {
|
||||
if (!xds_federation_enabled_ || !absl::StartsWith(name, "xdstp:")) {
|
||||
return XdsResourceName{"old:", {std::string(name), {}}};
|
||||
}
|
||||
// New style name. Parse URI.
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ class XdsClient : public DualRefCounted<XdsClient> {
|
|||
const XdsResourceType* GetResourceTypeLocked(absl::string_view resource_type)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
|
||||
|
||||
static absl::StatusOr<XdsResourceName> ParseXdsResourceName(
|
||||
absl::StatusOr<XdsResourceName> ParseXdsResourceName(
|
||||
absl::string_view name, const XdsResourceType* type);
|
||||
static std::string ConstructFullXdsResourceName(
|
||||
absl::string_view authority, absl::string_view resource_type,
|
||||
|
|
@ -302,6 +302,7 @@ class XdsClient : public DualRefCounted<XdsClient> {
|
|||
std::unique_ptr<XdsBootstrap> bootstrap_;
|
||||
grpc_channel_args* args_;
|
||||
const Duration request_timeout_;
|
||||
const bool xds_federation_enabled_;
|
||||
grpc_pollset_set* interested_parties_;
|
||||
OrphanablePtr<CertificateProviderStore> certificate_provider_store_;
|
||||
XdsApi api_;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@
|
|||
|
||||
#include "src/core/lib/gprpp/status_helper.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/cord.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
|
@ -220,23 +223,10 @@ absl::optional<std::string> StatusGetStr(const absl::Status& status,
|
|||
|
||||
void StatusSetTime(absl::Status* status, StatusTimeProperty key,
|
||||
absl::Time time) {
|
||||
#if !defined(__clang__) && defined(_MSC_VER) && _MSC_VER < 1910
|
||||
// Abseil has a workaround for MSVC 2015 which prevents absl::Time
|
||||
// from being is_trivially_copyable but it's still safe to be
|
||||
// memcopied.
|
||||
#elif defined(__GNUG__) && __GNUC__ < 5
|
||||
// GCC versions < 5 do not support std::is_trivially_copyable
|
||||
#else
|
||||
static_assert(std::is_trivially_copyable<absl::Time>::value,
|
||||
"absl::Time needs to be able to be memcopied");
|
||||
#endif
|
||||
// This is required not to get uninitialized padding of absl::Time.
|
||||
alignas(absl::Time) char buf[sizeof(time)] = {
|
||||
0,
|
||||
};
|
||||
new (buf) absl::Time(time);
|
||||
std::string time_str =
|
||||
absl::FormatTime(absl::RFC3339_full, time, absl::UTCTimeZone());
|
||||
status->SetPayload(GetStatusTimePropertyUrl(key),
|
||||
absl::Cord(absl::string_view(buf, sizeof(time))));
|
||||
absl::Cord(std::move(time_str)));
|
||||
}
|
||||
|
||||
absl::optional<absl::Time> StatusGetTime(const absl::Status& status,
|
||||
|
|
@ -245,14 +235,16 @@ absl::optional<absl::Time> StatusGetTime(const absl::Status& status,
|
|||
status.GetPayload(GetStatusTimePropertyUrl(key));
|
||||
if (p.has_value()) {
|
||||
absl::optional<absl::string_view> sv = p->TryFlat();
|
||||
absl::Time time;
|
||||
if (sv.has_value()) {
|
||||
// copy the content before casting to avoid misaligned address access
|
||||
alignas(absl::Time) char buf[sizeof(const absl::Time)];
|
||||
memcpy(buf, sv->data(), sizeof(const absl::Time));
|
||||
return *reinterpret_cast<const absl::Time*>(buf);
|
||||
if (absl::ParseTime(absl::RFC3339_full, sv.value(), &time, nullptr)) {
|
||||
return time;
|
||||
}
|
||||
} else {
|
||||
std::string s = std::string(*p);
|
||||
return *reinterpret_cast<const absl::Time*>(s.c_str());
|
||||
if (absl::ParseTime(absl::RFC3339_full, s, &time, nullptr)) {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
|
@ -320,9 +312,14 @@ std::string StatusToString(const absl::Status& status) {
|
|||
absl::CHexEscape(payload_view), "\""));
|
||||
} else if (absl::StartsWith(type_url, kTypeTimeTag)) {
|
||||
type_url.remove_prefix(kTypeTimeTag.size());
|
||||
absl::Time t =
|
||||
*reinterpret_cast<const absl::Time*>(payload_view.data());
|
||||
kvs.push_back(absl::StrCat(type_url, ":\"", absl::FormatTime(t), "\""));
|
||||
absl::Time t;
|
||||
if (absl::ParseTime(absl::RFC3339_full, payload_view, &t, nullptr)) {
|
||||
kvs.push_back(
|
||||
absl::StrCat(type_url, ":\"", absl::FormatTime(t), "\""));
|
||||
} else {
|
||||
kvs.push_back(absl::StrCat(type_url, ":\"",
|
||||
absl::CHexEscape(payload_view), "\""));
|
||||
}
|
||||
} else {
|
||||
kvs.push_back(absl::StrCat(type_url, ":\"",
|
||||
absl::CHexEscape(payload_view), "\""));
|
||||
|
|
|
|||
|
|
@ -29,6 +29,18 @@
|
|||
#include "src/core/lib/gpr/time_precise.h"
|
||||
#include "src/core/lib/gpr/useful.h"
|
||||
|
||||
#define GRPC_LOG_EVERY_N_SEC(n, severity, format, ...) \
|
||||
do { \
|
||||
static std::atomic<uint64_t> prev{0}; \
|
||||
uint64_t now = grpc_core::Timestamp::FromTimespecRoundDown( \
|
||||
gpr_now(GPR_CLOCK_MONOTONIC)) \
|
||||
.milliseconds_after_process_epoch(); \
|
||||
if (prev == 0 || now - prev > (n)*1000) { \
|
||||
prev = now; \
|
||||
gpr_log(severity, format, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
namespace grpc_core {
|
||||
|
||||
namespace time_detail {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,17 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <grpc/support/atm.h>
|
||||
|
||||
// FIXME: "posix" files shouldn't be depending on _GNU_SOURCE
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include "src/core/lib/iomgr/port.h"
|
||||
|
||||
#ifdef GRPC_POSIX_SOCKET_TCP_SERVER
|
||||
|
|
@ -43,6 +47,9 @@
|
|||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
#include <grpc/byte_buffer.h>
|
||||
#include <grpc/event_engine/endpoint_config.h>
|
||||
#include <grpc/event_engine/event_engine.h>
|
||||
#include <grpc/support/alloc.h>
|
||||
#include <grpc/support/log.h>
|
||||
#include <grpc/support/sync.h>
|
||||
|
|
@ -61,8 +68,11 @@
|
|||
#include "src/core/lib/iomgr/tcp_server_utils_posix.h"
|
||||
#include "src/core/lib/iomgr/unix_sockets_posix.h"
|
||||
#include "src/core/lib/resource_quota/api.h"
|
||||
#include "src/core/lib/transport/error_utils.h"
|
||||
|
||||
static std::atomic<int64_t> num_dropped_connections{0};
|
||||
static constexpr grpc_core::Duration kRetryAcceptWaitTime{
|
||||
grpc_core::Duration::Seconds(1)};
|
||||
|
||||
static grpc_error_handle tcp_server_create(grpc_closure* shutdown_complete,
|
||||
const grpc_channel_args* args,
|
||||
|
|
@ -206,21 +216,37 @@ static void on_read(void* arg, grpc_error_handle err) {
|
|||
if (fd < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else if (errno == EAGAIN || errno == ECONNABORTED ||
|
||||
errno == EWOULDBLOCK) {
|
||||
}
|
||||
// When the process runs out of fds, accept4() returns EMFILE. When this
|
||||
// happens, the connection is left in the accept queue until either a
|
||||
// read event triggers the on_read callback, or time has passed and the
|
||||
// accept should be re-tried regardless. This callback is not cancelled,
|
||||
// so a spurious wakeup may occur even when there's nothing to accept.
|
||||
// This is not a performant code path, but if an fd limit has been
|
||||
// reached, the system is likely in an unhappy state regardless.
|
||||
if (errno == EMFILE) {
|
||||
GRPC_LOG_EVERY_N_SEC(1, GPR_ERROR, "%s",
|
||||
"File descriptor limit reached. Retrying.");
|
||||
grpc_fd_notify_on_read(sp->emfd, &sp->read_closure);
|
||||
if (gpr_atm_full_xchg(&sp->retry_timer_armed, true)) return;
|
||||
grpc_timer_init(&sp->retry_timer,
|
||||
grpc_core::ExecCtx::Get()->Now() + kRetryAcceptWaitTime,
|
||||
&sp->retry_closure);
|
||||
return;
|
||||
}
|
||||
if (errno == EAGAIN || errno == ECONNABORTED || errno == EWOULDBLOCK) {
|
||||
grpc_fd_notify_on_read(sp->emfd, &sp->read_closure);
|
||||
return;
|
||||
} else {
|
||||
gpr_mu_lock(&sp->server->mu);
|
||||
if (!sp->server->shutdown_listeners) {
|
||||
gpr_log(GPR_ERROR, "Failed accept4: %s", strerror(errno));
|
||||
} else {
|
||||
/* if we have shutdown listeners, accept4 could fail, and we
|
||||
needn't notify users */
|
||||
}
|
||||
gpr_mu_unlock(&sp->server->mu);
|
||||
goto error;
|
||||
}
|
||||
gpr_mu_lock(&sp->server->mu);
|
||||
if (!sp->server->shutdown_listeners) {
|
||||
gpr_log(GPR_ERROR, "Failed accept4: %s", strerror(errno));
|
||||
} else {
|
||||
// if we have shutdown listeners, accept4 could fail, and we
|
||||
// needn't notify users
|
||||
}
|
||||
gpr_mu_unlock(&sp->server->mu);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sp->server->memory_quota->IsMemoryPressureHigh()) {
|
||||
|
|
@ -405,6 +431,7 @@ static grpc_error_handle clone_port(grpc_tcp_listener* listener,
|
|||
sp->port_index = listener->port_index;
|
||||
sp->fd_index = listener->fd_index + count - i;
|
||||
GPR_ASSERT(sp->emfd);
|
||||
grpc_tcp_server_listener_initialize_retry_timer(sp);
|
||||
while (listener->server->tail->next != nullptr) {
|
||||
listener->server->tail = listener->server->tail->next;
|
||||
}
|
||||
|
|
@ -577,6 +604,7 @@ static void tcp_server_shutdown_listeners(grpc_tcp_server* s) {
|
|||
if (s->active_ports) {
|
||||
grpc_tcp_listener* sp;
|
||||
for (sp = s->head; sp; sp = sp->next) {
|
||||
grpc_timer_cancel(&sp->retry_timer);
|
||||
grpc_fd_shutdown(sp->emfd,
|
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING("Server shutdown"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "src/core/lib/iomgr/resolve_address.h"
|
||||
#include "src/core/lib/iomgr/socket_utils_posix.h"
|
||||
#include "src/core/lib/iomgr/tcp_server.h"
|
||||
#include "src/core/lib/iomgr/timer.h"
|
||||
#include "src/core/lib/resource_quota/memory_quota.h"
|
||||
|
||||
/* one listening port */
|
||||
|
|
@ -47,6 +48,11 @@ typedef struct grpc_tcp_listener {
|
|||
identified while iterating through 'next'. */
|
||||
struct grpc_tcp_listener* sibling;
|
||||
int is_sibling;
|
||||
// If an accept4() call fails, a timer is started to drain the accept queue in
|
||||
// case no further connection attempts reach the gRPC server.
|
||||
grpc_closure retry_closure;
|
||||
grpc_timer retry_timer;
|
||||
gpr_atm retry_timer_armed;
|
||||
} grpc_tcp_listener;
|
||||
|
||||
/* the overall server */
|
||||
|
|
@ -126,4 +132,10 @@ grpc_error_handle grpc_tcp_server_prepare_socket(
|
|||
/* Ruturn true if the platform supports ifaddrs */
|
||||
bool grpc_tcp_server_have_ifaddrs(void);
|
||||
|
||||
#endif /* GRPC_CORE_LIB_IOMGR_TCP_SERVER_UTILS_POSIX_H */
|
||||
/* Initialize (but don't start) the timer and callback to retry accept4() on a
|
||||
listening socket after file descriptors have been exhausted. This must be
|
||||
called when creating a new listener. */
|
||||
void grpc_tcp_server_listener_initialize_retry_timer(
|
||||
grpc_tcp_listener* listener);
|
||||
|
||||
#endif /* GRPC_SRC_CORE_LIB_IOMGR_TCP_SERVER_UTILS_POSIX_H */
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <grpc/support/port_platform.h>
|
||||
|
||||
#include <grpc/support/atm.h>
|
||||
|
||||
#include "src/core/lib/iomgr/port.h"
|
||||
|
||||
#ifdef GRPC_POSIX_SOCKET_TCP_SERVER_UTILS_COMMON
|
||||
|
|
@ -80,44 +82,59 @@ static int get_max_accept_queue_size(void) {
|
|||
return s_max_accept_queue_size;
|
||||
}
|
||||
|
||||
static void listener_retry_timer_cb(void* arg, grpc_error_handle err) {
|
||||
// Do nothing if cancelled.
|
||||
if (err != GRPC_ERROR_NONE) return;
|
||||
grpc_tcp_listener* listener = static_cast<grpc_tcp_listener*>(arg);
|
||||
gpr_atm_no_barrier_store(&listener->retry_timer_armed, false);
|
||||
if (!grpc_fd_is_shutdown(listener->emfd)) {
|
||||
grpc_fd_set_readable(listener->emfd);
|
||||
}
|
||||
}
|
||||
|
||||
void grpc_tcp_server_listener_initialize_retry_timer(
|
||||
grpc_tcp_listener* listener) {
|
||||
gpr_atm_no_barrier_store(&listener->retry_timer_armed, false);
|
||||
grpc_timer_init_unset(&listener->retry_timer);
|
||||
GRPC_CLOSURE_INIT(&listener->retry_closure, listener_retry_timer_cb, listener,
|
||||
grpc_schedule_on_exec_ctx);
|
||||
}
|
||||
|
||||
static grpc_error_handle add_socket_to_server(grpc_tcp_server* s, int fd,
|
||||
const grpc_resolved_address* addr,
|
||||
unsigned port_index,
|
||||
unsigned fd_index,
|
||||
grpc_tcp_listener** listener) {
|
||||
grpc_tcp_listener* sp = nullptr;
|
||||
int port = -1;
|
||||
|
||||
grpc_error_handle err =
|
||||
grpc_tcp_server_prepare_socket(s, fd, addr, s->so_reuseport, &port);
|
||||
if (err == GRPC_ERROR_NONE) {
|
||||
GPR_ASSERT(port > 0);
|
||||
std::string addr_str = grpc_sockaddr_to_string(addr, true);
|
||||
std::string name = absl::StrCat("tcp-server-listener:", addr_str);
|
||||
gpr_mu_lock(&s->mu);
|
||||
s->nports++;
|
||||
GPR_ASSERT(!s->on_accept_cb && "must add ports before starting server");
|
||||
sp = static_cast<grpc_tcp_listener*>(gpr_malloc(sizeof(grpc_tcp_listener)));
|
||||
sp->next = nullptr;
|
||||
if (s->head == nullptr) {
|
||||
s->head = sp;
|
||||
} else {
|
||||
s->tail->next = sp;
|
||||
}
|
||||
s->tail = sp;
|
||||
sp->server = s;
|
||||
sp->fd = fd;
|
||||
sp->emfd = grpc_fd_create(fd, name.c_str(), true);
|
||||
memcpy(&sp->addr, addr, sizeof(grpc_resolved_address));
|
||||
sp->port = port;
|
||||
sp->port_index = port_index;
|
||||
sp->fd_index = fd_index;
|
||||
sp->is_sibling = 0;
|
||||
sp->sibling = nullptr;
|
||||
GPR_ASSERT(sp->emfd);
|
||||
gpr_mu_unlock(&s->mu);
|
||||
if (err != GRPC_ERROR_NONE) return err;
|
||||
GPR_ASSERT(port > 0);
|
||||
std::string addr_str = grpc_sockaddr_to_string(addr, true);
|
||||
std::string name = absl::StrCat("tcp-server-listener:", addr_str);
|
||||
gpr_mu_lock(&s->mu);
|
||||
s->nports++;
|
||||
grpc_tcp_listener* sp =
|
||||
static_cast<grpc_tcp_listener*>(gpr_malloc(sizeof(grpc_tcp_listener)));
|
||||
sp->next = nullptr;
|
||||
if (s->head == nullptr) {
|
||||
s->head = sp;
|
||||
} else {
|
||||
s->tail->next = sp;
|
||||
}
|
||||
|
||||
s->tail = sp;
|
||||
sp->server = s;
|
||||
sp->fd = fd;
|
||||
sp->emfd = grpc_fd_create(fd, name.c_str(), true);
|
||||
grpc_tcp_server_listener_initialize_retry_timer(sp);
|
||||
memcpy(&sp->addr, addr, sizeof(grpc_resolved_address));
|
||||
sp->port = port;
|
||||
sp->port_index = port_index;
|
||||
sp->fd_index = fd_index;
|
||||
sp->is_sibling = 0;
|
||||
sp->sibling = nullptr;
|
||||
GPR_ASSERT(sp->emfd);
|
||||
gpr_mu_unlock(&s->mu);
|
||||
*listener = sp;
|
||||
return err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,5 +22,5 @@
|
|||
#include <grpcpp/grpcpp.h>
|
||||
|
||||
namespace grpc {
|
||||
std::string Version() { return "1.46.0-dev"; }
|
||||
std::string Version() { return "1.46.7"; }
|
||||
} // namespace grpc
|
||||
|
|
|
|||
|
|
@ -89,6 +89,11 @@ namespace Grpc.Core
|
|||
/// <summary>
|
||||
/// Returns <c>true</c> if this credential type allows being composed by <c>CompositeCredentials</c>.
|
||||
/// </summary>
|
||||
/// <remark>
|
||||
/// Note: No longer used. Decision on whether composition is allowed now happens in
|
||||
/// <see cref="ChannelCredentialsConfiguratorBase.SetCompositeCredentials(object, ChannelCredentials, CallCredentials)"/>.
|
||||
/// Internal property left for safety because Grpc.Core has internal access to Grpc.Core.Api.
|
||||
/// </remark>
|
||||
internal virtual bool IsComposable => false;
|
||||
|
||||
private sealed class InsecureCredentials : ChannelCredentials
|
||||
|
|
@ -118,11 +123,6 @@ namespace Grpc.Core
|
|||
{
|
||||
this.channelCredentials = GrpcPreconditions.CheckNotNull(channelCredentials);
|
||||
this.callCredentials = GrpcPreconditions.CheckNotNull(callCredentials);
|
||||
|
||||
if (!channelCredentials.IsComposable)
|
||||
{
|
||||
throw new ArgumentException(string.Format("CallCredentials can't be composed with {0}. CallCredentials must be used with secure channel credentials like SslCredentials.", channelCredentials.GetType().Name));
|
||||
}
|
||||
}
|
||||
|
||||
public override void InternalPopulateConfiguration(ChannelCredentialsConfiguratorBase configurator, object state)
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ namespace Grpc.Core
|
|||
/// <summary>
|
||||
/// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
|
||||
/// </summary>
|
||||
public const string CurrentAssemblyFileVersion = "2.46.0.0";
|
||||
public const string CurrentAssemblyFileVersion = "2.46.7.0";
|
||||
|
||||
/// <summary>
|
||||
/// Current version of gRPC C#
|
||||
/// </summary>
|
||||
public const string CurrentVersion = "2.46.0-dev";
|
||||
public const string CurrentVersion = "2.46.7";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,6 +179,220 @@ namespace Grpc.Core.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ServerStreamingCall_DisposeCallBeforeAllRead()
|
||||
{
|
||||
// Test to reproduce https://github.com/grpc/grpc/issues/8451
|
||||
// "Client channel socket leaks unless read stream drained explicitly"
|
||||
// This version of the test disposes the call before the response stream is
|
||||
// fully read.
|
||||
|
||||
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => {
|
||||
try
|
||||
{
|
||||
// send responses to the client with a short delay between each response
|
||||
foreach (string resp in request.Split(new[] { ' ' }))
|
||||
{
|
||||
await responseStream.WriteAsync(resp);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// don't care about server side exceptions in this test
|
||||
}
|
||||
});
|
||||
|
||||
// The 'using' statement makes sure that Dispose() is called at the end
|
||||
// which is the point of the test - to make sure resources aren't leaked
|
||||
// when Dispose() is called.
|
||||
using (var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "A B C D E"))
|
||||
{
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
|
||||
try
|
||||
{
|
||||
// Just call MoveNext once rather than consuming the entire stream
|
||||
// to check that resources are released when the entire stream hasn't
|
||||
// been read
|
||||
await call.ResponseStream.MoveNext();
|
||||
Assert.AreEqual("A", call.ResponseStream.Current);
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
Assert.Fail("Unexpected exception thrown {}", ex);
|
||||
}
|
||||
} // Dispose happens here
|
||||
|
||||
// short delay to allow callbacks to fire
|
||||
await Task.Delay(500);
|
||||
|
||||
// Resources for the call should have been cleaned up and thus no reference
|
||||
// to the call in the channel. This would fail with a count of 1 before the
|
||||
// fix for #8451.
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ServerStreamingCall_DisposeCallBeforeAnyRead()
|
||||
{
|
||||
// Test to reproduce https://github.com/grpc/grpc/issues/8451
|
||||
// "Client channel socket leaks unless read stream drained explicitly"
|
||||
// This version of the test disposes the call before any of the response stream is
|
||||
// read.
|
||||
|
||||
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => {
|
||||
try
|
||||
{
|
||||
// send responses to the client with a short delay between each response
|
||||
foreach (string resp in request.Split(new[] { ' ' }))
|
||||
{
|
||||
await responseStream.WriteAsync(resp);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// don't care about server side exceptions in this test
|
||||
}
|
||||
});
|
||||
|
||||
// The 'using' statement makes sure that Dispose() is called at the end
|
||||
// which is the point of the test - to make sure resources aren't leaked
|
||||
// when Dispose() is called.
|
||||
using (var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "A B C D E"))
|
||||
{
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
|
||||
// don't read the response steam
|
||||
} // Dispose happens here
|
||||
|
||||
// short delay to allow callbacks to fire
|
||||
await Task.Delay(500);
|
||||
|
||||
// Resources for the call should have been cleaned up and thus no reference
|
||||
// to the call in the channel. This would fail with a count of 1 before the
|
||||
// fix for #8451.
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ServerStreamingCall_CancelCallBeforeAllRead()
|
||||
{
|
||||
// Test to reproduce https://github.com/grpc/grpc/issues/8451
|
||||
// "Client channel socket leaks unless read stream drained explicitly"
|
||||
// This version of the test cancels the call before the response stream is
|
||||
// fully read.
|
||||
|
||||
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => {
|
||||
try
|
||||
{
|
||||
// send responses to the client with a short delay between each response
|
||||
foreach (string resp in request.Split(new[] { ' ' }))
|
||||
{
|
||||
await responseStream.WriteAsync(resp);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// don't care about server side exceptions in this test
|
||||
}
|
||||
});
|
||||
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
|
||||
var call = Calls.AsyncServerStreamingCall(
|
||||
helper.CreateServerStreamingCall(new CallOptions(cancellationToken: tokenSource.Token)),
|
||||
"A B C D E");
|
||||
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
|
||||
try
|
||||
{
|
||||
await call.ResponseStream.MoveNext();
|
||||
Assert.AreEqual("A", call.ResponseStream.Current);
|
||||
// To reproduce issue #8451 - cancel the call and don't
|
||||
// read the response stream to the end
|
||||
tokenSource.Cancel();
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
Assert.Fail("Unexpected exception thrown {}", ex);
|
||||
}
|
||||
|
||||
// short delay to allow callbacks to fire
|
||||
await Task.Delay(500);
|
||||
|
||||
// Note: this version of the test does not explicitly Dispose the call. It
|
||||
// is assuming the cancel of the call triggers the cleanup.
|
||||
|
||||
// Resources for the call should have been cleaned up and thus no reference
|
||||
// to the call in the channel. This would fail with a count of 1 before the
|
||||
// fix for #8451.
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ServerStreamingCall_CancelCallBeforeAllReadAndDispose()
|
||||
{
|
||||
// Test to reproduce https://github.com/grpc/grpc/issues/8451
|
||||
// "Client channel socket leaks unless read stream drained explicitly"
|
||||
// This version of the test cancels the call before the response stream is
|
||||
// fully read. It also calls Dispose on the call after the Cancel to check that
|
||||
// the cleanup doesn't happen twice
|
||||
|
||||
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => {
|
||||
try
|
||||
{
|
||||
// send responses to the client with a short delay between each response
|
||||
foreach (string resp in request.Split(new[] { ' ' }))
|
||||
{
|
||||
await responseStream.WriteAsync(resp);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// don't care about server side exceptions in this test
|
||||
}
|
||||
});
|
||||
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
|
||||
// 'using' makes sure Dispose happens
|
||||
using (var call = Calls.AsyncServerStreamingCall(
|
||||
helper.CreateServerStreamingCall(new CallOptions(cancellationToken: tokenSource.Token)),
|
||||
"A B C D E"))
|
||||
{
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
|
||||
try
|
||||
{
|
||||
await call.ResponseStream.MoveNext();
|
||||
Assert.AreEqual("A", call.ResponseStream.Current);
|
||||
// To reproduce issue #8451 - cancel the call and don't read
|
||||
// the response stream to the end
|
||||
tokenSource.Cancel();
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
Assert.Fail("Unexpected exception thrown {}", ex);
|
||||
}
|
||||
} // Dispose happens here
|
||||
|
||||
// short delay to allow callbacks to fire
|
||||
await Task.Delay(500);
|
||||
|
||||
// Note: this version of the test does not explicitly Dispose the call. It
|
||||
// is assuming the cancel of the call triggers the cleanup.
|
||||
|
||||
// Resources for the call should have been cleaned up and thus no reference
|
||||
// to the call in the channel. This would fail with a count of 1 before the
|
||||
// fix for #8451.
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanDisposeDefaultCancellationRegistration()
|
||||
{
|
||||
|
|
@ -189,5 +403,43 @@ namespace Grpc.Core.Tests
|
|||
|
||||
using (obj) {}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ServerStreamingCall_CancelAfterServerHasFinished()
|
||||
{
|
||||
// Test to verify the fix for https://github.com/grpc/grpc/issues/17761.
|
||||
// Fix appears to have happened in c-core between tags v1.30.0 (broken) and v1.31.0-pre1 (fixed).
|
||||
//
|
||||
// Scenario: the client cancels the request after the server has already successfully
|
||||
// returned the results but before the client starts reading.
|
||||
//
|
||||
// Expected behaviour is that the client sees a Cancelled status and no messages received.
|
||||
|
||||
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => {
|
||||
await responseStream.WriteAsync("abc");
|
||||
});
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(new CallOptions(cancellationToken: cts.Token)), "request1");
|
||||
// make sure the response and status sent by the server is received on the client side
|
||||
await Task.Delay(2000);
|
||||
|
||||
// cancel the call before actually reading the response
|
||||
cts.Cancel();
|
||||
var moveNextTask = call.ResponseStream.MoveNext();
|
||||
|
||||
try
|
||||
{
|
||||
// cannot use Assert.ThrowsAsync because it uses Task.Wait and would deadlock.
|
||||
await moveNextTask;
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (RpcException ex)
|
||||
{
|
||||
// expect a Cancelled status
|
||||
Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,18 +24,6 @@ namespace Grpc.Core.Tests
|
|||
{
|
||||
public class ChannelCredentialsTest
|
||||
{
|
||||
[Test]
|
||||
public void InsecureCredentials_IsNonComposable()
|
||||
{
|
||||
Assert.IsFalse(ChannelCredentials.Insecure.IsComposable);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SecureCredentials_IsComposable()
|
||||
{
|
||||
Assert.IsTrue(ChannelCredentials.SecureSsl.IsComposable);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChannelCredentials_CreateComposite()
|
||||
{
|
||||
|
|
@ -44,10 +32,6 @@ namespace Grpc.Core.Tests
|
|||
|
||||
Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(null, new FakeCallCredentials()));
|
||||
Assert.Throws(typeof(ArgumentNullException), () => ChannelCredentials.Create(new FakeChannelCredentials(true), null));
|
||||
|
||||
// forbid composing non-composable
|
||||
var ex = Assert.Throws(typeof(ArgumentException), () => ChannelCredentials.Create(new FakeChannelCredentials(false), new FakeCallCredentials()));
|
||||
Assert.AreEqual("CallCredentials can't be composed with FakeChannelCredentials. CallCredentials must be used with secure channel credentials like SslCredentials.", ex.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
@ -63,5 +47,16 @@ namespace Grpc.Core.Tests
|
|||
var nativeCreds4 = ChannelCredentials.SecureSsl.ToNativeCredentials();
|
||||
Assert.AreSame(nativeCreds3, nativeCreds4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChannelCredentials_MalformedSslCredentialsCanStillCreateNativeCredentials()
|
||||
{
|
||||
// pass malformed root pem certs, but creation of native credentials still passes,
|
||||
// since the credentials are parsed lazily by the C core.
|
||||
using (var nativeCreds = new SslCredentials("MALFORMED_ROOT_CERTS_THAT_WILL_THROW_WHEN_CREATING_NATIVE_CREDENTIALS").ToNativeCredentials())
|
||||
{
|
||||
Assert.IsFalse(nativeCreds.IsInvalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,5 +123,16 @@ namespace Grpc.Core.Tests
|
|||
// check that Channel.ShutdownAsync has run
|
||||
Assert.AreEqual(ChannelState.Shutdown, channel.State);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CompositeCredentialsWithInsecureThrow()
|
||||
{
|
||||
var compositeCredentials = ChannelCredentials.Create(
|
||||
ChannelCredentials.Insecure,
|
||||
CallCredentials.FromInterceptor((context, metadata) => TaskUtils.CompletedTask));
|
||||
var ex = Assert.Throws(typeof(InvalidOperationException), () => new Channel("localhost", compositeCredentials));
|
||||
Assert.AreEqual("CallCredentials can't be composed with InsecureCredentials. " +
|
||||
"CallCredentials must be used with secure channel credentials like SslCredentials.", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -472,6 +472,275 @@ namespace Grpc.Core.Internal.Tests
|
|||
AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_CancelCall_Issue8451_1()
|
||||
{
|
||||
// Test client cancelling the call before the reading the server stream to the end.
|
||||
// Test for resource leak https://github.com/grpc/grpc/issues/8451
|
||||
|
||||
// The callbacks OnReceivedResponseHeaders, OnReceivedStatusOnClient and OnReceivedMessage may
|
||||
// occur in any order together with calls to MoveNext. We need to test that the resources
|
||||
// are released correctly no matter the order of the calls, and that when the call is cancelled
|
||||
// or there is an error (such as server unavailable) that MoveNext throws the correct exception.
|
||||
|
||||
// This test is testing the order:
|
||||
// OnReceivedResponseHeaders
|
||||
// MoveNext
|
||||
// OnReceivedMessage
|
||||
// Cancel (which could be because of a Dispose or explicit cancel)
|
||||
// OnReceivedStatusOnClient
|
||||
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
var responseStream = new ClientResponseStream<string, string>(asyncCall);
|
||||
|
||||
// Check that the client has a reference to exactly one call
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
// Check that the call is not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
|
||||
Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
|
||||
|
||||
// read some data
|
||||
var readTask1 = responseStream.MoveNext();
|
||||
fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
|
||||
Assert.IsTrue(readTask1.Result);
|
||||
Assert.AreEqual("response1", responseStream.Current);
|
||||
|
||||
// Cancel the call. This will trigger ReceivedStatusOnClientCallback to be called with a Cancelled status.
|
||||
asyncCall.Cancel();
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultCancelled, new Metadata()));
|
||||
|
||||
// Check that the resources have been released and that the client no longer references the call
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_CancelCall_Issue8451_2()
|
||||
{
|
||||
// Test client cancelling the call before the reading the server stream to the end.
|
||||
// Test for resource leak https://github.com/grpc/grpc/issues/8451
|
||||
|
||||
// The callbacks OnReceivedResponseHeaders, OnReceivedStatusOnClient and OnReceivedMessage may
|
||||
// occur in any order together with calls to MoveNext. We need to test that the resources
|
||||
// are released correctly no matter the order of the calls, and that when the call is cancelled
|
||||
// or there is an error (such as server unavailable) that MoveNext throws the correct exception.
|
||||
|
||||
// This test is testing the order:
|
||||
// Cancel (which could be because of a Dispose or explicit cancel)
|
||||
// OnReceivedStatusOnClient
|
||||
// OnReceivedResponseHeaders
|
||||
// MoveNext - fails with Exception
|
||||
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
var responseStream = new ClientResponseStream<string, string>(asyncCall);
|
||||
|
||||
// Check that the client has a reference to exactly one call
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
// Check that the call is not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// Cancel the call. This will trigger ReceivedStatusOnClientCallback to be called with a Cancelled status.
|
||||
asyncCall.Cancel();
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultCancelled, new Metadata()));
|
||||
|
||||
// OnReceivedResponseHeaders with have success == false as call is cancelled
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(false, new Metadata());
|
||||
Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
|
||||
|
||||
// read some data - this should fail as the call has been cancelled
|
||||
var ex = Assert.ThrowsAsync<RpcException>(async () => await responseStream.MoveNext());
|
||||
Assert.AreEqual(Status.DefaultCancelled, ex.Status);
|
||||
|
||||
// Check that the resources have been released and that the client no longer references the call
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_CancelCall_Issue8451_3()
|
||||
{
|
||||
// Test client cancelling the call before the reading the server stream to the end.
|
||||
// Test for resource leak https://github.com/grpc/grpc/issues/8451
|
||||
|
||||
// The callbacks OnReceivedResponseHeaders, OnReceivedStatusOnClient and OnReceivedMessage may
|
||||
// occur in any order together with calls to MoveNext. We need to test that the resources
|
||||
// are released correctly no matter the order of the calls, and that when the call is cancelled
|
||||
// or there is an error (such as server unavailable) that MoveNext throws the correct exception.
|
||||
|
||||
// This test is testing the order:
|
||||
// OnReceivedResponseHeaders
|
||||
// Cancel (which could be because of a Dispose or explicit cancel)
|
||||
// MoveNext - OK as cancel status not yet picked up in callback
|
||||
// OnReceivedMessage
|
||||
// OnReceivedStatusOnClient - delayed callback from the Cancel
|
||||
// MoveNext - fails with Exception
|
||||
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
var responseStream = new ClientResponseStream<string, string>(asyncCall);
|
||||
|
||||
// Check that the client has a reference to exactly one call
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
// Check that the call is not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
|
||||
Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
|
||||
|
||||
// cancel the call, but delay the callback with the cancelled status until later
|
||||
asyncCall.Cancel();
|
||||
// Not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// read some data
|
||||
var readTask1 = responseStream.MoveNext();
|
||||
fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
|
||||
Assert.IsTrue(readTask1.Result);
|
||||
Assert.AreEqual("response1", responseStream.Current);
|
||||
|
||||
// Trigger ReceivedStatusOnClientCallback to be called with a Cancelled status.
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultCancelled, new Metadata()));
|
||||
|
||||
// read some more data - this should fail as the call has been cancelled
|
||||
var ex = Assert.ThrowsAsync<RpcException>(async () => await responseStream.MoveNext());
|
||||
Assert.AreEqual(Status.DefaultCancelled, ex.Status);
|
||||
|
||||
// Check that the resources have been released and that the client no longer references the call
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_CancelCall_Issue8451_4()
|
||||
{
|
||||
// Test client cancelling the call before the reading the server stream to the end.
|
||||
// Test for resource leak https://github.com/grpc/grpc/issues/8451
|
||||
|
||||
// The callbacks OnReceivedResponseHeaders, OnReceivedStatusOnClient and OnReceivedMessage may
|
||||
// occur in any order together with calls to MoveNext. We need to test that the resources
|
||||
// are released correctly no matter the order of the calls, and that when the call is cancelled
|
||||
// or there is an error (such as server unavailable) that MoveNext throws the correct exception.
|
||||
|
||||
// This case tests the case when OnReceivedStatusOnClient occurs before the OnReceivedMessage
|
||||
// for data already inflight.
|
||||
|
||||
// This test is testing the order:
|
||||
// OnReceivedResponseHeaders
|
||||
// MoveNext task started
|
||||
// Cancel
|
||||
// OnReceivedStatusOnClient
|
||||
// OnReceivedMessage
|
||||
// MoveNext task completes OK
|
||||
// another MoveNext - fails with Exception
|
||||
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
var responseStream = new ClientResponseStream<string, string>(asyncCall);
|
||||
|
||||
// Check that the client has a reference to exactly one call
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
// Check that the call is not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
|
||||
Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
|
||||
|
||||
// Prime for a read - this sets the OnReceivedMessage callback handler in fakeCall
|
||||
// indicating that we are waiting to receive some data
|
||||
var readTask1 = responseStream.MoveNext();
|
||||
|
||||
// Cancel the call
|
||||
asyncCall.Cancel();
|
||||
// Not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// Trigger ReceivedStatusOnClientCallback to be called with a Cancelled status.
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultCancelled, new Metadata()));
|
||||
|
||||
// Check not yet disposed the call as there is data inflight
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// Read some data. Although we've already received this cancel status this will succeed as the
|
||||
// the data was already inflight. Note: called with "success = true"
|
||||
fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateResponsePayload());
|
||||
Assert.IsTrue(readTask1.Result);
|
||||
Assert.AreEqual("response1", responseStream.Current);
|
||||
|
||||
// check has disposed the call as there is no more data inflight and we previously received an error status
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
|
||||
// Read some more data - this should fail as the call has been cancelled
|
||||
var ex = Assert.ThrowsAsync<RpcException>(async () => await responseStream.MoveNext());
|
||||
Assert.AreEqual(Status.DefaultCancelled, ex.Status);
|
||||
|
||||
// Check that the resources have been released and that the client no longer references the call
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_CancelCall_Issue8451_5()
|
||||
{
|
||||
// Test client cancelling the call before the reading the server stream to the end.
|
||||
// Test for resource leak https://github.com/grpc/grpc/issues/8451
|
||||
|
||||
// The callbacks OnReceivedResponseHeaders, OnReceivedStatusOnClient and OnReceivedMessage may
|
||||
// occur in any order together with calls to MoveNext. We need to test that the resources
|
||||
// are released correctly no matter the order of the calls, and that when the call is cancelled
|
||||
// or there is an error (such as server unavailable) that MoveNext throws the correct exception.
|
||||
|
||||
// This case tests the case when OnReceivedStatusOnClient occurs before the OnReceivedMessage
|
||||
// for data already inflight.
|
||||
|
||||
// Same as ServerStreaming_CancelCall_Issue8451_4() but with "success = false" in the
|
||||
// OnReceivedMessage callback
|
||||
|
||||
// This test is testing the order:
|
||||
// OnReceivedResponseHeaders
|
||||
// MoveNext task started
|
||||
// Cancel
|
||||
// OnReceivedStatusOnClient
|
||||
// OnReceivedMessage (with success = false)
|
||||
// MoveNext task completes with error
|
||||
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
var responseStream = new ClientResponseStream<string, string>(asyncCall);
|
||||
|
||||
// Check that the client has a reference to exactly one call
|
||||
Assert.AreEqual(1, channel.GetCallReferenceCount());
|
||||
// Check that the call is not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata());
|
||||
Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count);
|
||||
|
||||
// Prime for a read - this sets the OnReceivedMessage callback handler in fakeCall
|
||||
// indicating that we are waiting to receive some data
|
||||
var readTask1 = responseStream.MoveNext();
|
||||
|
||||
// Cancel the call
|
||||
asyncCall.Cancel();
|
||||
// Not yet disposed
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// Trigger ReceivedStatusOnClientCallback to be called with a Cancelled status.
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultCancelled, new Metadata()));
|
||||
|
||||
// Check not yet disposed the call as there is data inflight
|
||||
Assert.IsFalse(fakeCall.IsDisposed);
|
||||
|
||||
// Read some data.
|
||||
// Note: being called with "success = false" - this may occur if the native code
|
||||
// completes the callback indicating that there has already been an error
|
||||
fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, CreateResponsePayload());
|
||||
var ex = Assert.ThrowsAsync<RpcException>(async () => await readTask1);
|
||||
Assert.AreEqual(Status.DefaultCancelled, ex.Status);
|
||||
|
||||
// Check that the resources have been released and that the client no longer references the call
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_RequestSerializationExceptionDoesntLeakResources()
|
||||
{
|
||||
|
|
@ -493,6 +762,56 @@ namespace Grpc.Core.Internal.Tests
|
|||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_ServerUnavailableDoesntLeakResources1()
|
||||
{
|
||||
// Test for https://github.com/grpc/grpc/issues/28153
|
||||
// Memory leak if resources not released when server unavailable
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
|
||||
// error reading response headers - possibly because server unavailable
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(false, new Metadata());
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(new Status(StatusCode.Unavailable, ""), new Metadata()));
|
||||
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_ServerUnavailableDoesntLeakResources2()
|
||||
{
|
||||
// Test for https://github.com/grpc/grpc/issues/28153
|
||||
// Memory leak if resources not released when server unavailable
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
|
||||
// Test OnReceivedStatusOnClient before OnReceivedResponseHeaders
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(new Status(StatusCode.Unavailable, ""), new Metadata()));
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(false, new Metadata());
|
||||
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ServerStreaming_ServerUnavailableDoesntLeakResources3()
|
||||
{
|
||||
// Test for https://github.com/grpc/grpc/issues/28153
|
||||
// Memory leak if resources not released when server unavailable
|
||||
asyncCall.StartServerStreamingCall("request1");
|
||||
|
||||
// error reading response headers - possibly because server unavailable
|
||||
fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(false, new Metadata());
|
||||
fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(new Status(StatusCode.Unavailable, ""), new Metadata()));
|
||||
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
|
||||
// Test a cancel/dispose is OK even though the call has already been disposed
|
||||
asyncCall.Cancel();
|
||||
|
||||
Assert.IsTrue(fakeCall.IsDisposed);
|
||||
Assert.AreEqual(0, channel.GetCallReferenceCount());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DuplexStreaming_NoRequestNoResponse_Success1()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -109,5 +109,55 @@ namespace Grpc.Core.Tests
|
|||
// just create a server, don't start it, and make sure it doesn't prevent shutdown.
|
||||
var server = new Server();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BindPortsNoStartAndShutdownServer()
|
||||
{
|
||||
// Test binding ports but no Start - that the bound ports are
|
||||
// released on Shutdown.
|
||||
// See https://github.com/grpc/grpc/issues/23390
|
||||
|
||||
int boundPort = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// Create server, bind ports then shutdown (without start)
|
||||
Server server = new Server {
|
||||
Ports = { new ServerPort("localhost", ServerPort.PickUnused, ServerCredentials.Insecure) }
|
||||
};
|
||||
|
||||
// find the port chosen so we can use it later
|
||||
boundPort = server.Ports.Single().BoundPort;
|
||||
Assert.Greater(boundPort, 0);
|
||||
|
||||
server.ShutdownAsync().Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Failed to create and shutdown first server: {e}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create new server, bind to same port as before, start and shutdown
|
||||
|
||||
// Note: there is a small possibility that the port may have been reused by a different
|
||||
// test being run in parallel in between the previous Shutdown and using it here
|
||||
// causing this test to be flaky.
|
||||
|
||||
Server server2 = new Server {
|
||||
Ports = { new ServerPort("localhost", boundPort, ServerCredentials.Insecure) }
|
||||
};
|
||||
|
||||
server2.Start();
|
||||
server2.ShutdownAsync().Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Failed to create and shutdown second server: {e}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,20 +69,29 @@ namespace Grpc.Core
|
|||
EnsureUserAgentChannelOption(this.options);
|
||||
this.environment = GrpcEnvironment.AddRef();
|
||||
|
||||
this.completionQueue = this.environment.PickCompletionQueue();
|
||||
using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
|
||||
try
|
||||
{
|
||||
var nativeCredentials = credentials.ToNativeCredentials();
|
||||
if (nativeCredentials != null)
|
||||
this.completionQueue = this.environment.PickCompletionQueue();
|
||||
using (var nativeChannelArgs = ChannelOptions.CreateChannelArgs(this.options.Values))
|
||||
{
|
||||
this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
|
||||
var nativeCredentials = credentials.ToNativeCredentials();
|
||||
if (nativeCredentials != null)
|
||||
{
|
||||
this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
|
||||
}
|
||||
}
|
||||
GrpcEnvironment.RegisterChannel(this);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Constructor can't be async.
|
||||
_ = Task.Run(async () => await GrpcEnvironment.ReleaseAsync().ConfigureAwait(false));
|
||||
throw;
|
||||
}
|
||||
GrpcEnvironment.RegisterChannel(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -433,6 +433,11 @@ namespace Grpc.Core.Internal
|
|||
return new RpcException(finishedStatus.Value.Status, finishedStatus.Value.Trailers);
|
||||
}
|
||||
|
||||
protected override bool IsFinishedWithNonOkStatusClientOnly
|
||||
{
|
||||
get { return finishedStatus.HasValue && finishedStatus.Value.Status.StatusCode != StatusCode.OK; }
|
||||
}
|
||||
|
||||
protected override Task CheckSendAllowedOrEarlyResult()
|
||||
{
|
||||
var earlyResult = CheckSendPreconditionsClientSide();
|
||||
|
|
@ -648,14 +653,20 @@ namespace Grpc.Core.Internal
|
|||
if (status.StatusCode != StatusCode.OK)
|
||||
{
|
||||
streamingResponseCallFinishedTcs.SetException(new RpcException(status, receivedStatus.Trailers));
|
||||
if (status.StatusCode == StatusCode.Cancelled || origCancelRequested)
|
||||
{
|
||||
// Make sure the exception set to the Task is observed,
|
||||
// otherwise this can trigger "Unobserved exception" when the response stream
|
||||
// is not read until its end and the task created by the TCS is garbage collected.
|
||||
// See https://github.com/grpc/grpc/issues/17458
|
||||
var _ = streamingResponseCallFinishedTcs.Task.Exception;
|
||||
}
|
||||
// Make sure the exception set to the Task is observed,
|
||||
// otherwise this can trigger "Unobserved exception" when the response stream
|
||||
// is not read until its end (by invoking MoveNext() until the point where it either returns false or throws an exception)
|
||||
// and the task created by the TCS is garbage collected.
|
||||
// Note that the streamingResponseCallFinishedTcs.Task itself is never exposed to the user
|
||||
// and its result only ever "observed" by MoveNext() implementation:
|
||||
// https://github.com/grpc/grpc/blob/1cd6e69347cbf62a012477fe184ee6fa8f25d32c/src/csharp/Grpc.Core/Internal/ClientResponseStream.cs#L59.
|
||||
// Even though generally the response stream should always be read until the end (since that's how you get streaming call's responses and status code)
|
||||
// there are cases where this shouldn't be strictly required and thus the user
|
||||
// shouldn't need to worry about observing result of an internal task that's
|
||||
// only exposed to them indirectly.
|
||||
// See https://github.com/grpc/grpc/issues/17458
|
||||
// See https://github.com/grpc/grpc/issues/24421
|
||||
var _ = streamingResponseCallFinishedTcs.Task.Exception;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,22 @@ namespace Grpc.Core.Internal
|
|||
return streamingReadTcs.Task;
|
||||
}
|
||||
|
||||
if (IsFinishedWithNonOkStatusClientOnly)
|
||||
{
|
||||
// The gRPC call has already failed with an error.
|
||||
|
||||
if (streamingReadTcs == null)
|
||||
{
|
||||
// Return an empty result rather than starting a new read.
|
||||
// MoveNext will interpret this as end of stream and error handling will
|
||||
// happen as expected.
|
||||
streamingReadTcs = new TaskCompletionSource<TRead>();
|
||||
streamingReadTcs.SetResult(default(TRead));
|
||||
}
|
||||
|
||||
return streamingReadTcs.Task;
|
||||
}
|
||||
|
||||
GrpcPreconditions.CheckState(streamingReadTcs == null, "Only one read can be pending at a time");
|
||||
GrpcPreconditions.CheckState(!disposed);
|
||||
|
||||
|
|
@ -166,13 +182,21 @@ namespace Grpc.Core.Internal
|
|||
/// <summary>
|
||||
/// If there are no more pending actions and no new actions can be started, releases
|
||||
/// the underlying native resources.
|
||||
/// Only call when myLock is held.
|
||||
/// </summary>
|
||||
protected bool ReleaseResourcesIfPossible()
|
||||
{
|
||||
if (!disposed && call != null)
|
||||
{
|
||||
// Have sends completed?
|
||||
bool noMoreSendCompletions = streamingWriteTcs == null && (halfcloseRequested || cancelRequested || finished);
|
||||
if (noMoreSendCompletions && readingDone && finished && !receiveResponseHeadersPending)
|
||||
|
||||
// Have reads completed (normally or with error)?
|
||||
// Not sure if the check for "streamingReadTcs == null" is necessary (indicating no pending reads) if an error status
|
||||
// has already been received since I don't think a new read would be started.
|
||||
bool readingFinished = readingDone || (IsFinishedWithNonOkStatusClientOnly && streamingReadTcs == null);
|
||||
|
||||
if (noMoreSendCompletions && readingFinished && finished && !receiveResponseHeadersPending)
|
||||
{
|
||||
ReleaseResources();
|
||||
return true;
|
||||
|
|
@ -192,6 +216,17 @@ namespace Grpc.Core.Internal
|
|||
/// </summary>
|
||||
protected abstract Exception GetRpcExceptionClientOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Client - returns true if the client has received an error status in the
|
||||
/// OnReceivedStatusOnClient callback.
|
||||
/// Server - always returns false as this is a client side method.
|
||||
/// Only access when myLock is held.
|
||||
/// </summary>
|
||||
protected abstract bool IsFinishedWithNonOkStatusClientOnly
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected void ReleaseResources()
|
||||
{
|
||||
if (call != null)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,10 @@ namespace Grpc.Core.Internal
|
|||
{
|
||||
throw new InvalidOperationException("Call be only called for client calls");
|
||||
}
|
||||
protected override bool IsFinishedWithNonOkStatusClientOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
protected override void OnAfterReleaseResourcesLocked()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Grpc.Core;
|
||||
|
|
@ -91,7 +92,7 @@ namespace Grpc.Core.Internal
|
|||
{
|
||||
UIntPtr detailsLength;
|
||||
IntPtr detailsPtr = Native.grpcsharp_batch_context_recv_status_on_client_details(this, out detailsLength);
|
||||
string details = MarshalUtils.PtrToStringUTF8(detailsPtr, (int)detailsLength.ToUInt32());
|
||||
string details = StatusDetailsPtrToStringUTF8(detailsPtr, (int)detailsLength.ToUInt32());
|
||||
string debugErrorString = Marshal.PtrToStringAnsi(Native.grpcsharp_batch_context_recv_status_on_client_error_string(this));
|
||||
var status = new Status(Native.grpcsharp_batch_context_recv_status_on_client_status(this), details, debugErrorString != null ? new CoreErrorDetailException(debugErrorString) : null);
|
||||
|
||||
|
|
@ -136,6 +137,27 @@ namespace Grpc.Core.Internal
|
|||
return true;
|
||||
}
|
||||
|
||||
// This method decodes the status details string "safely" and returns an empty
|
||||
// string in case it seems corrupt.
|
||||
// This is to stop UnaryCall from hanging if there is an issue with decoding
|
||||
// the UTF8 status details string (likely due to an unexplained corruption).
|
||||
// See https://github.com/grpc/grpc/issues/29854
|
||||
// TODO(jtattermusch): remove this workaround once #29854 is fully explained
|
||||
// and fixed.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private string StatusDetailsPtrToStringUTF8(IntPtr ptr, int len)
|
||||
{
|
||||
try
|
||||
{
|
||||
return MarshalUtils.PtrToStringUTF8(ptr, len);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.Error(e, "Failed to decode status details string for a call (corrupt UTF8 string?). An empty status details string will be returned by the client.");
|
||||
// return an empty status (the status code will be preserved)
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void IOpCompletionCallback.OnComplete(bool success)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -82,7 +82,15 @@ namespace Grpc.Core.Internal
|
|||
{
|
||||
using (var callCreds = callCredentials.ToNativeCredentials())
|
||||
{
|
||||
var nativeComposite = ChannelCredentialsSafeHandle.CreateComposite(channelCredentials.ToNativeCredentials(), callCreds);
|
||||
var nativeChannelCredentials = channelCredentials.ToNativeCredentials();
|
||||
// nativeChannelCredentials == null means insecure channel credentials were used.
|
||||
if (nativeChannelCredentials == null)
|
||||
{
|
||||
throw new InvalidOperationException($"CallCredentials can't be composed with {channelCredentials.GetType().Name}. " +
|
||||
$"CallCredentials must be used with secure channel credentials like SslCredentials.");
|
||||
}
|
||||
|
||||
var nativeComposite = ChannelCredentialsSafeHandle.CreateComposite(nativeChannelCredentials, callCreds);
|
||||
if (nativeComposite.IsInvalid)
|
||||
{
|
||||
throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials.");
|
||||
|
|
|
|||
|
|
@ -220,8 +220,26 @@ namespace Grpc.Core
|
|||
lock (myLock)
|
||||
{
|
||||
GrpcPreconditions.CheckState(!shutdownRequested);
|
||||
|
||||
if (!startRequested)
|
||||
{
|
||||
Logger.Warning("Shutdown called without Start being called.");
|
||||
|
||||
if (serverPortList.Count > 0)
|
||||
{
|
||||
// Ports have been added. They will already be bound and listening.
|
||||
// Need to call Start so that the grpc core native library is in the
|
||||
// correct state when shutdown is called. Otherwise the listening ports
|
||||
// will not be released. See https://github.com/grpc/grpc/issues/23390
|
||||
|
||||
// lock is re-entrant so OK to call Start() while holding the lock.
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
shutdownRequested = true;
|
||||
}
|
||||
|
||||
GrpcEnvironment.UnregisterServer(this);
|
||||
|
||||
var cq = environment.CompletionQueues.First(); // any cq will do
|
||||
|
|
@ -381,9 +399,9 @@ namespace Grpc.Core
|
|||
// Start asynchronous handler for the call.
|
||||
// Don't await, the continuations will run on gRPC thread pool once triggered
|
||||
// by cq.Next().
|
||||
#pragma warning disable 4014
|
||||
#pragma warning disable 4014
|
||||
HandleCallAsync(newRpc, cq, (server, state) => server.AllowOneRpc(state));
|
||||
#pragma warning restore 4014
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,5 +96,41 @@ namespace Grpc.IntegrationTesting
|
|||
|
||||
Assert.AreEqual(0, unobservedTaskExceptionCounter.Count);
|
||||
}
|
||||
[Test]
|
||||
public async Task NoUnobservedTaskExceptionForUnavailableServers()
|
||||
{
|
||||
// Verify that https://github.com/grpc/grpc/issues/24421 has been fixed.
|
||||
|
||||
var unobservedTaskExceptionCounter = new AtomicCounter();
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) => {
|
||||
unobservedTaskExceptionCounter.Increment();
|
||||
Console.WriteLine("Detected unobserved task exception: " + e.Exception);
|
||||
};
|
||||
var channelToNonexistentBackend = new Channel("nonexistentbackend:100", ChannelCredentials.Insecure);
|
||||
var client = new TestService.TestServiceClient(channelToNonexistentBackend);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Console.WriteLine($"Starting iteration {i}");
|
||||
try
|
||||
{
|
||||
var call = client.FullDuplexCall();
|
||||
using (call)
|
||||
{
|
||||
var req = call.RequestStream;
|
||||
// try to write a request (which will throw RpcException) and intentionally
|
||||
// skip calling call.ReponseStream.MoveNext()
|
||||
await call.RequestStream.WriteAsync(new StreamingOutputCallRequest());
|
||||
}
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// eat exception, we expect a call to non-existent backend to fail.
|
||||
}
|
||||
// Make it more likely to trigger the "Unobserved task exception" warning
|
||||
GC.Collect();
|
||||
}
|
||||
await channelToNonexistentBackend.ShutdownAsync();
|
||||
Assert.AreEqual(0, unobservedTaskExceptionCounter.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!-- This file is generated -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<GrpcCsharpVersion>2.46.0-dev</GrpcCsharpVersion>
|
||||
<GoogleProtobufVersion>3.19.4</GoogleProtobufVersion>
|
||||
<GrpcCsharpVersion>2.46.7</GrpcCsharpVersion>
|
||||
<GoogleProtobufVersion>3.19.5</GoogleProtobufVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ Pod::Spec.new do |s|
|
|||
# exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
|
||||
# before them.
|
||||
s.name = '!ProtoCompiler-gRPCCppPlugin'
|
||||
v = '1.46.0-dev'
|
||||
v = '1.46.7'
|
||||
s.version = v
|
||||
s.summary = 'The gRPC ProtoC plugin generates C++ files from .proto services.'
|
||||
s.description = <<-DESC
|
||||
|
|
@ -100,7 +100,7 @@ Pod::Spec.new do |s|
|
|||
s.preserve_paths = plugin
|
||||
|
||||
# Restrict the protoc version to the one supported by this plugin.
|
||||
s.dependency '!ProtoCompiler', '3.19.4'
|
||||
s.dependency '!ProtoCompiler', '3.19.5'
|
||||
# For the Protobuf dependency not to complain:
|
||||
s.ios.deployment_target = '9.0'
|
||||
s.osx.deployment_target = '10.10'
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ Pod::Spec.new do |s|
|
|||
# exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
|
||||
# before them.
|
||||
s.name = '!ProtoCompiler-gRPCPlugin'
|
||||
v = '1.46.0-dev'
|
||||
v = '1.46.7'
|
||||
s.version = v
|
||||
s.summary = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
|
||||
s.description = <<-DESC
|
||||
|
|
@ -102,7 +102,7 @@ Pod::Spec.new do |s|
|
|||
s.preserve_paths = plugin
|
||||
|
||||
# Restrict the protoc version to the one supported by this plugin.
|
||||
s.dependency '!ProtoCompiler', '3.19.4'
|
||||
s.dependency '!ProtoCompiler', '3.19.5'
|
||||
# For the Protobuf dependency not to complain:
|
||||
s.ios.deployment_target = '9.0'
|
||||
s.osx.deployment_target = '10.10'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Pod::Spec.new do |s|
|
|||
# exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
|
||||
# before them.
|
||||
s.name = '!ProtoCompiler'
|
||||
v = '3.19.4'
|
||||
v = '3.19.5'
|
||||
s.version = v
|
||||
s.summary = 'The Protobuf Compiler (protoc) generates Objective-C files from .proto files'
|
||||
s.description = <<-DESC
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -22,4 +22,4 @@
|
|||
// instead. This file can be regenerated from the template by running
|
||||
// `tools/buildgen/generate_projects.sh`.
|
||||
|
||||
#define GRPC_OBJC_VERSION_STRING @"1.46.0-dev"
|
||||
#define GRPC_OBJC_VERSION_STRING @"1.46.7"
|
||||
|
|
|
|||
|
|
@ -22,5 +22,5 @@
|
|||
// instead. This file can be regenerated from the template by running
|
||||
// `tools/buildgen/generate_projects.sh`.
|
||||
|
||||
#define GRPC_OBJC_VERSION_STRING @"1.46.0-dev"
|
||||
#define GRPC_OBJC_VERSION_STRING @"1.46.7"
|
||||
#define GRPC_C_VERSION_STRING @"24.0.0"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "grpc/grpc-dev",
|
||||
"description": "gRPC library for PHP - for Development use only",
|
||||
"license": "Apache-2.0",
|
||||
"version": "1.46.0",
|
||||
"version": "1.46.7",
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"google/protobuf": "^v3.3.0"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@
|
|||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
#define PHP_GRPC_VERSION "1.46.0dev"
|
||||
#define PHP_GRPC_VERSION "1.46.7"
|
||||
|
||||
#endif /* VERSION_H */
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
|
||||
|
||||
__version__ = """1.46.0.dev0"""
|
||||
__version__ = """1.46.7"""
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_admin/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_channelz/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_csds/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_status/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_testing/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
# AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
|
||||
|
||||
VERSION = '1.46.0.dev0'
|
||||
VERSION = '1.46.7'
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ end
|
|||
|
||||
ENV['CPPFLAGS'] = '-DGPR_BACKWARDS_COMPATIBILITY_MODE'
|
||||
ENV['CPPFLAGS'] += ' -DGRPC_XDS_USER_AGENT_NAME_SUFFIX="\"RUBY\"" '
|
||||
ENV['CPPFLAGS'] += ' -DGRPC_XDS_USER_AGENT_VERSION_SUFFIX="\"1.46.0.dev\"" '
|
||||
ENV['CPPFLAGS'] += ' -DGRPC_XDS_USER_AGENT_VERSION_SUFFIX="\"1.46.7\"" '
|
||||
|
||||
output_dir = File.expand_path(RbConfig::CONFIG['topdir'])
|
||||
grpc_lib_dir = File.join(output_dir, 'libs', grpc_config)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ begin
|
|||
ruby_version_dirname = /(\d+\.\d+)/.match(RUBY_VERSION).to_s
|
||||
distrib_lib_dir = File.expand_path(ruby_version_dirname,
|
||||
File.dirname(__FILE__))
|
||||
if File.directory?(distrib_lib_dir)
|
||||
if !Dir.glob("#{distrib_lib_dir}/grpc_c*").empty?
|
||||
require "#{distrib_lib_dir}/grpc_c"
|
||||
else
|
||||
require 'grpc/grpc_c'
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@
|
|||
|
||||
# GRPC contains the General RPC module.
|
||||
module GRPC
|
||||
VERSION = '1.46.0.dev'
|
||||
VERSION = '1.46.7'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@
|
|||
|
||||
module GRPC
|
||||
module Tools
|
||||
VERSION = '1.46.0.dev'
|
||||
VERSION = '1.46.7'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -299,6 +299,8 @@
|
|||
set(_gRPC_C_CXX_FLAGS "<%text>${_gRPC_C_CXX_FLAGS}</%text> /wd4987 /wd4774 /wd4819 /wd4996 /wd4619")
|
||||
# Silences thousands of trucation warnings
|
||||
set(_gRPC_C_CXX_FLAGS "<%text>${_gRPC_C_CXX_FLAGS}</%text> /wd4503")
|
||||
# Tell MSVC to build grpc using utf-8
|
||||
set(_gRPC_C_CXX_FLAGS "<%text>${_gRPC_C_CXX_FLAGS}</%text> /utf-8")
|
||||
endif()
|
||||
if (MINGW)
|
||||
add_definitions(-D_WIN32_WINNT=0x600)
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
ss.header_mappings_dir = '.'
|
||||
ss.libraries = 'z'
|
||||
ss.dependency "#{s.name}/Interface", version
|
||||
ss.dependency 'BoringSSL-GRPC', '0.0.24'
|
||||
ss.dependency 'BoringSSL-GRPC', '0.0.25'
|
||||
ss.dependency 'Libuv-gRPC', '0.0.10'
|
||||
% for abseil_spec in grpc_abseil_specs:
|
||||
ss.dependency '${abseil_spec}', abseil_version
|
||||
|
|
|
|||
|
|
@ -21,15 +21,13 @@
|
|||
s.files += %w( etc/roots.pem )
|
||||
s.files += Dir.glob('src/ruby/bin/**/*')
|
||||
s.files += Dir.glob('src/ruby/ext/**/*')
|
||||
s.files += Dir.glob('src/ruby/lib/**/*').reject do |f|
|
||||
# Binaries are included by rake-compiler and would lead to circular dependencies here
|
||||
File.fnmatch("**/?.?/grpc_c.so", f)
|
||||
end
|
||||
s.files += Dir.glob('src/ruby/lib/**/*')
|
||||
s.files += Dir.glob('src/ruby/pb/**/*').reject do |f|
|
||||
f.match(%r{^src/ruby/pb/test})
|
||||
end
|
||||
s.files += Dir.glob('include/grpc/**/*')
|
||||
s.test_files = Dir.glob('src/ruby/spec/**/*')
|
||||
s.test_files += Dir.glob('src/ruby/pb/test/**/*')
|
||||
s.bindir = 'src/ruby/bin'
|
||||
s.require_paths = %w( src/ruby/lib src/ruby/bin src/ruby/pb )
|
||||
s.platform = Gem::Platform::RUBY
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'BoringSSL-GRPC'
|
||||
version = '0.0.24'
|
||||
version = '0.0.25'
|
||||
s.version = version
|
||||
s.summary = 'BoringSSL is a fork of OpenSSL that is designed to meet Google\'s needs.'
|
||||
# Adapted from the homepage:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
%YAML 1.2
|
||||
--- |
|
||||
# Copyright 2021 The gRPC Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM debian:stretch
|
||||
|
||||
<%include file="../../apt_get_basic.include"/>
|
||||
<%include file="../../run_tests_python_deps.include"/>
|
||||
<%include file="../../cmake.include"/>
|
||||
<%include file="../../run_tests_addons.include"/>
|
||||
|
||||
# Java required by Android SDK
|
||||
RUN apt-get update && apt-get -y install openjdk-8-jdk && apt-get clean
|
||||
|
||||
# Install Android SDK
|
||||
ENV ANDROID_SDK_VERSION 4333796
|
||||
RUN mkdir -p /opt/android-sdk && cd /opt/android-sdk && ${'\\'}
|
||||
wget -q https://dl.google.com/android/repository/sdk-tools-linux-$ANDROID_SDK_VERSION.zip && ${'\\'}
|
||||
unzip -q sdk-tools-linux-$ANDROID_SDK_VERSION.zip && ${'\\'}
|
||||
rm sdk-tools-linux-$ANDROID_SDK_VERSION.zip
|
||||
ENV ANDROID_SDK_PATH /opt/android-sdk
|
||||
ENV ANDROID_HOME /opt/android-sdk
|
||||
RUN yes | $ANDROID_SDK_PATH/tools/bin/sdkmanager --licenses # accept all licenses
|
||||
|
||||
# Install ndk-bundle and cmake using sdkmanager
|
||||
RUN mkdir -p ~/.android && touch ~/.android/repositories.cfg
|
||||
RUN $ANDROID_SDK_PATH/tools/bin/sdkmanager ndk-bundle 'cmake;3.6.4111459'
|
||||
|
||||
# Install Android NDK
|
||||
RUN curl -L -o android-ndk.zip http://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip && ${'\\'}
|
||||
unzip -q android-ndk.zip -d /opt && ${'\\'}
|
||||
rm -f android-ndk.zip
|
||||
ENV ANDROID_NDK_PATH /opt/android-ndk-r16b
|
||||
ENV ANDROID_NDK_HOME /opt/android-ndk-r16b
|
||||
|
||||
# Install gcloud
|
||||
RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-407.0.0-linux-x86_64.tar.gz && ${'\\'}
|
||||
tar -xf google-cloud-cli-407.0.0-linux-x86_64.tar.gz && ${'\\'}
|
||||
./google-cloud-sdk/install.sh --bash-completion=false --path-update=true && ${'\\'}
|
||||
rm -rf google-cloud-cli-407.0.0-linux-x86_64.tar.gz
|
||||
RUN ln -s /google-cloud-sdk/bin/gcloud /usr/local/bin/gcloud
|
||||
|
||||
# Define the default command.
|
||||
CMD ["bash"]
|
||||
|
|
@ -605,8 +605,7 @@ TEST_F(ClientLbEnd2endTest, PickFirstBackOffMinReconnect) {
|
|||
response_generator.SetNextResolution(ports);
|
||||
// Make connection delay a 10% longer than it's willing to in order to make
|
||||
// sure we are hitting the codepath that waits for the min reconnect backoff.
|
||||
ConnectionDelayInjector delay_injector;
|
||||
auto injected_delay = delay_injector.SetDelay(
|
||||
ConnectionDelayInjector delay_injector(
|
||||
grpc_core::Duration::Milliseconds(kMinReconnectBackOffMs * 1.10));
|
||||
const gpr_timespec t0 = gpr_now(GPR_CLOCK_MONOTONIC);
|
||||
channel->WaitForConnected(
|
||||
|
|
@ -2128,6 +2127,7 @@ TEST_F(ClientLbAddressTest, Basic) {
|
|||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
grpc::testing::TestEnvironment env(&argc, argv);
|
||||
grpc::testing::ConnectionAttemptInjector::Init();
|
||||
const auto result = RUN_ALL_TESTS();
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,96 +20,98 @@
|
|||
#include "absl/memory/memory.h"
|
||||
#include "absl/utility/utility.h"
|
||||
|
||||
#include "src/core/lib/channel/channel_args.h"
|
||||
#include "src/core/lib/gprpp/time.h"
|
||||
#include "src/core/lib/iomgr/tcp_client.h"
|
||||
#include "src/core/lib/iomgr/timer.h"
|
||||
|
||||
// defined in tcp_client.cc
|
||||
extern grpc_tcp_client_vtable* grpc_tcp_client_impl;
|
||||
|
||||
namespace grpc {
|
||||
namespace testing {
|
||||
|
||||
//
|
||||
// ConnectionAttemptInjector
|
||||
//
|
||||
|
||||
namespace {
|
||||
|
||||
grpc_tcp_client_vtable* g_original_vtable = nullptr;
|
||||
|
||||
std::atomic<grpc_core::Duration> g_delay;
|
||||
|
||||
class InjectedDelay {
|
||||
public:
|
||||
InjectedDelay(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline)
|
||||
: closure_(closure),
|
||||
endpoint_(ep),
|
||||
interested_parties_(interested_parties),
|
||||
channel_args_(grpc_channel_args_copy(channel_args)),
|
||||
deadline_(deadline) {
|
||||
memcpy(&address_, addr, sizeof(grpc_resolved_address));
|
||||
GRPC_CLOSURE_INIT(&timer_callback_, TimerCallback, this, nullptr);
|
||||
grpc_core::Duration duration = g_delay.load();
|
||||
grpc_core::Timestamp now = grpc_core::ExecCtx::Get()->Now();
|
||||
duration = std::min(duration, deadline_ - now);
|
||||
grpc_timer_init(&timer_, now + duration, &timer_callback_);
|
||||
}
|
||||
|
||||
~InjectedDelay() { grpc_channel_args_destroy(channel_args_); }
|
||||
|
||||
private:
|
||||
static void TimerCallback(void* arg, grpc_error_handle /*error*/) {
|
||||
auto* self = static_cast<InjectedDelay*>(arg);
|
||||
g_original_vtable->connect(self->closure_, self->endpoint_,
|
||||
self->interested_parties_, self->channel_args_,
|
||||
&self->address_, self->deadline_);
|
||||
delete self;
|
||||
}
|
||||
|
||||
grpc_timer timer_;
|
||||
grpc_closure timer_callback_;
|
||||
|
||||
// Original args.
|
||||
grpc_closure* closure_;
|
||||
grpc_endpoint** endpoint_;
|
||||
grpc_pollset_set* interested_parties_;
|
||||
const grpc_channel_args* channel_args_;
|
||||
grpc_resolved_address address_;
|
||||
grpc_core::Timestamp deadline_;
|
||||
};
|
||||
std::atomic<ConnectionAttemptInjector*> g_injector{nullptr};
|
||||
|
||||
void TcpConnectWithDelay(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline) {
|
||||
new InjectedDelay(closure, ep, interested_parties, channel_args, addr,
|
||||
deadline);
|
||||
ConnectionAttemptInjector* injector = g_injector.load();
|
||||
if (injector == nullptr) {
|
||||
g_original_vtable->connect(closure, ep, interested_parties, channel_args,
|
||||
addr, deadline);
|
||||
return;
|
||||
}
|
||||
injector->HandleConnection(closure, ep, interested_parties, channel_args,
|
||||
addr, deadline);
|
||||
}
|
||||
|
||||
grpc_tcp_client_vtable kDelayedConnectVTable = {TcpConnectWithDelay};
|
||||
|
||||
} // namespace
|
||||
|
||||
ConnectionDelayInjector::InjectedDelay::~InjectedDelay() {
|
||||
g_delay.store(grpc_core::Duration());
|
||||
void ConnectionAttemptInjector::Init() {
|
||||
g_original_vtable = grpc_tcp_client_impl;
|
||||
grpc_tcp_client_impl = &kDelayedConnectVTable;
|
||||
}
|
||||
|
||||
ConnectionDelayInjector::ConnectionDelayInjector() {
|
||||
g_original_vtable =
|
||||
absl::exchange(grpc_tcp_client_impl, &kDelayedConnectVTable);
|
||||
ConnectionAttemptInjector::ConnectionAttemptInjector() {
|
||||
GPR_ASSERT(g_injector.exchange(this) == nullptr);
|
||||
}
|
||||
|
||||
ConnectionDelayInjector::~ConnectionDelayInjector() {
|
||||
grpc_tcp_client_impl = g_original_vtable;
|
||||
ConnectionAttemptInjector::~ConnectionAttemptInjector() {
|
||||
g_injector.store(nullptr);
|
||||
}
|
||||
|
||||
std::unique_ptr<ConnectionDelayInjector::InjectedDelay>
|
||||
ConnectionDelayInjector::SetDelay(grpc_core::Duration duration) {
|
||||
GPR_ASSERT(g_delay.exchange(duration) == grpc_core::Duration());
|
||||
return absl::make_unique<ConnectionDelayInjector::InjectedDelay>();
|
||||
void ConnectionAttemptInjector::AttemptConnection(
|
||||
grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties, const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr, grpc_core::Timestamp deadline) {
|
||||
g_original_vtable->connect(closure, ep, interested_parties, channel_args,
|
||||
addr, deadline);
|
||||
}
|
||||
|
||||
//
|
||||
// ConnectionDelayInjector
|
||||
//
|
||||
|
||||
class ConnectionDelayInjector::InjectedDelay {
|
||||
public:
|
||||
InjectedDelay(grpc_core::Duration duration, grpc_closure* closure,
|
||||
grpc_endpoint** ep, grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline)
|
||||
: attempt_(closure, ep, interested_parties, channel_args, addr,
|
||||
deadline) {
|
||||
GRPC_CLOSURE_INIT(&timer_callback_, TimerCallback, this, nullptr);
|
||||
grpc_core::Timestamp now = grpc_core::ExecCtx::Get()->Now();
|
||||
duration = std::min(duration, deadline - now);
|
||||
grpc_timer_init(&timer_, now + duration, &timer_callback_);
|
||||
}
|
||||
|
||||
private:
|
||||
static void TimerCallback(void* arg, grpc_error_handle /*error*/) {
|
||||
auto* self = static_cast<InjectedDelay*>(arg);
|
||||
self->attempt_.Resume();
|
||||
delete self;
|
||||
}
|
||||
|
||||
QueuedAttempt attempt_;
|
||||
grpc_timer timer_;
|
||||
grpc_closure timer_callback_;
|
||||
};
|
||||
|
||||
void ConnectionDelayInjector::HandleConnection(
|
||||
grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties, const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr, grpc_core::Timestamp deadline) {
|
||||
new InjectedDelay(duration_, closure, ep, interested_parties, channel_args,
|
||||
addr, deadline);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "src/core/lib/channel/channel_args.h"
|
||||
#include "src/core/lib/gprpp/time.h"
|
||||
#include "src/core/lib/iomgr/tcp_client.h"
|
||||
#include "src/core/lib/iomgr/timer.h"
|
||||
|
||||
namespace grpc {
|
||||
namespace testing {
|
||||
|
|
@ -25,31 +28,104 @@ namespace testing {
|
|||
// Allows injecting connection-establishment delays into C-core.
|
||||
// Typical usage:
|
||||
//
|
||||
// ConnectionDelayInjector delay_injector;
|
||||
// auto scoped_delay =
|
||||
// delay_injector.SetDelay(grpc_core::Duration::Seconds(10));
|
||||
// // At grpc_init() time.
|
||||
// ConnectionAttemptInjector::Init();
|
||||
//
|
||||
// When ConnectionDelayInjector is instantiated, it replaces the iomgr
|
||||
// TCP client vtable, and it sets it back to the original value when it
|
||||
// is destroyed.
|
||||
// // When an injection is desired.
|
||||
// ConnectionDelayInjector delay_injector(grpc_core::Duration::Seconds(10));
|
||||
//
|
||||
// When SetDelay() is called, it sets the global delay, which will
|
||||
// automatically be unset when the result goes out of scope.
|
||||
//
|
||||
// The injection is global, so there must be only one ConnectionDelayInjector
|
||||
// object at any one time, and there must be only one scoped delay in effect
|
||||
// at any one time.
|
||||
class ConnectionDelayInjector {
|
||||
// The injection is global, so there must be only one ConnectionAttemptInjector
|
||||
// object at any one time.
|
||||
class ConnectionAttemptInjector {
|
||||
public:
|
||||
class InjectedDelay {
|
||||
// Global initializer. Replaces the iomgr TCP client vtable.
|
||||
// Must be called exactly once before any TCP connections are established.
|
||||
static void Init();
|
||||
|
||||
ConnectionAttemptInjector();
|
||||
virtual ~ConnectionAttemptInjector();
|
||||
|
||||
// Invoked for every TCP connection attempt.
|
||||
// Implementations must eventually either invoke the closure
|
||||
// themselves or delegate to the iomgr implementation by calling
|
||||
// AttemptConnection(). QueuedAttempt may be used to queue an attempt
|
||||
// for asynchronous processing.
|
||||
virtual void HandleConnection(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline) = 0;
|
||||
|
||||
protected:
|
||||
// Represents a queued attempt.
|
||||
// The caller must invoke either Resume() or Fail() before destroying.
|
||||
class QueuedAttempt {
|
||||
public:
|
||||
~InjectedDelay();
|
||||
QueuedAttempt(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline)
|
||||
: closure_(closure),
|
||||
endpoint_(ep),
|
||||
interested_parties_(interested_parties),
|
||||
channel_args_(grpc_channel_args_copy(channel_args)),
|
||||
deadline_(deadline) {
|
||||
memcpy(&address_, addr, sizeof(address_));
|
||||
}
|
||||
|
||||
~QueuedAttempt() {
|
||||
GPR_ASSERT(closure_ == nullptr);
|
||||
grpc_channel_args_destroy(channel_args_);
|
||||
}
|
||||
|
||||
// Caller must invoke this from a thread with an ExecCtx.
|
||||
void Resume() {
|
||||
GPR_ASSERT(closure_ != nullptr);
|
||||
AttemptConnection(closure_, endpoint_, interested_parties_, channel_args_,
|
||||
&address_, deadline_);
|
||||
closure_ = nullptr;
|
||||
}
|
||||
|
||||
// Caller must invoke this from a thread with an ExecCtx.
|
||||
void Fail(grpc_error_handle error) {
|
||||
GPR_ASSERT(closure_ != nullptr);
|
||||
grpc_core::ExecCtx::Run(DEBUG_LOCATION, closure_, error);
|
||||
closure_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
grpc_closure* closure_;
|
||||
grpc_endpoint** endpoint_;
|
||||
grpc_pollset_set* interested_parties_;
|
||||
const grpc_channel_args* channel_args_;
|
||||
grpc_resolved_address address_;
|
||||
grpc_core::Timestamp deadline_;
|
||||
};
|
||||
|
||||
ConnectionDelayInjector();
|
||||
~ConnectionDelayInjector();
|
||||
static void AttemptConnection(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline);
|
||||
};
|
||||
|
||||
std::unique_ptr<InjectedDelay> SetDelay(grpc_core::Duration duration);
|
||||
// A concrete implementation that injects a fixed delay.
|
||||
class ConnectionDelayInjector : public ConnectionAttemptInjector {
|
||||
public:
|
||||
explicit ConnectionDelayInjector(grpc_core::Duration duration)
|
||||
: duration_(duration) {}
|
||||
|
||||
void HandleConnection(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline) override;
|
||||
|
||||
private:
|
||||
class InjectedDelay;
|
||||
|
||||
grpc_core::Duration duration_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ grpc_cc_test(
|
|||
"//src/proto/grpc/testing/xds/v3:router_proto",
|
||||
"//src/proto/grpc/testing/xds/v3:tls_proto",
|
||||
"//test/core/util:grpc_test_util",
|
||||
"//test/cpp/end2end:connection_delay_injector",
|
||||
"//test/cpp/end2end:rls_server",
|
||||
"//test/cpp/util:test_config",
|
||||
"//test/cpp/util:test_util",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
#include "src/core/ext/xds/xds_client.h"
|
||||
#include "src/core/ext/xds/xds_listener.h"
|
||||
#include "src/core/lib/address_utils/parse_address.h"
|
||||
#include "src/core/lib/address_utils/sockaddr_utils.h"
|
||||
#include "src/core/lib/channel/channel_args.h"
|
||||
#include "src/core/lib/gpr/env.h"
|
||||
#include "src/core/lib/gpr/string.h"
|
||||
|
|
@ -104,6 +105,7 @@
|
|||
#include "src/proto/grpc/testing/xds/v3/tls.grpc.pb.h"
|
||||
#include "test/core/util/port.h"
|
||||
#include "test/core/util/test_config.h"
|
||||
#include "test/cpp/end2end/connection_delay_injector.h"
|
||||
#include "test/cpp/end2end/rls_server.h"
|
||||
#include "test/cpp/end2end/xds/xds_end2end_test_lib.h"
|
||||
#include "test/cpp/util/test_config.h"
|
||||
|
|
@ -1239,6 +1241,42 @@ TEST_P(XdsFederationTest, FederationServer) {
|
|||
WaitForAllBackends();
|
||||
}
|
||||
|
||||
using XdsFederationDisabledTest = XdsEnd2endTest;
|
||||
|
||||
TEST_P(XdsFederationDisabledTest, FederationDisabledWithNewStyleNames) {
|
||||
const char* kNewRouteConfigName =
|
||||
"xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/"
|
||||
"new_route_config_name";
|
||||
const char* kNewClusterName =
|
||||
"xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/"
|
||||
"cluster_name";
|
||||
const char* kNewEdsResourceName =
|
||||
"xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/"
|
||||
"edsservice_name";
|
||||
InitClient();
|
||||
CreateAndStartBackends(1);
|
||||
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
|
||||
balancer_->ads_service()->SetEdsResource(
|
||||
BuildEdsResource(args, kNewEdsResourceName));
|
||||
// New cluster
|
||||
Cluster new_cluster = default_cluster_;
|
||||
new_cluster.set_name(kNewClusterName);
|
||||
new_cluster.mutable_eds_cluster_config()->set_service_name(
|
||||
kNewEdsResourceName);
|
||||
balancer_->ads_service()->SetCdsResource(new_cluster);
|
||||
// New RouteConfig
|
||||
RouteConfiguration new_route_config = default_route_config_;
|
||||
new_route_config.set_name(kNewRouteConfigName);
|
||||
new_route_config.mutable_virtual_hosts(0)
|
||||
->mutable_routes(0)
|
||||
->mutable_route()
|
||||
->set_cluster(kNewClusterName);
|
||||
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
|
||||
new_route_config);
|
||||
// Channel should work.
|
||||
CheckRpcSendOk();
|
||||
}
|
||||
|
||||
using XdsFederationLoadReportingTest = XdsFederationTest;
|
||||
|
||||
// Channel is created with URI "xds://xds.example.com/server.example.com".
|
||||
|
|
@ -4814,6 +4852,209 @@ TEST_P(CdsTest, AggregateClusterFallBackFromRingHashAtStartup) {
|
|||
EXPECT_TRUE(found);
|
||||
}
|
||||
|
||||
TEST_P(CdsTest, AggregateClusterFallBackFromRingHashToLogicalDnsAtStartup) {
|
||||
ScopedExperimentalEnvVar env_var(
|
||||
"GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER");
|
||||
CreateAndStartBackends(1);
|
||||
const char* kEdsClusterName = "eds_cluster";
|
||||
const char* kLogicalDNSClusterName = "logical_dns_cluster";
|
||||
// Populate EDS resource.
|
||||
EdsResourceArgs args({
|
||||
{"locality0",
|
||||
{MakeNonExistantEndpoint(), MakeNonExistantEndpoint()},
|
||||
kDefaultLocalityWeight,
|
||||
0},
|
||||
{"locality1",
|
||||
{MakeNonExistantEndpoint(), MakeNonExistantEndpoint()},
|
||||
kDefaultLocalityWeight,
|
||||
1},
|
||||
});
|
||||
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
|
||||
// Populate new CDS resources.
|
||||
Cluster eds_cluster = default_cluster_;
|
||||
eds_cluster.set_name(kEdsClusterName);
|
||||
balancer_->ads_service()->SetCdsResource(eds_cluster);
|
||||
// Populate LOGICAL_DNS cluster.
|
||||
auto logical_dns_cluster = default_cluster_;
|
||||
logical_dns_cluster.set_name(kLogicalDNSClusterName);
|
||||
logical_dns_cluster.set_type(Cluster::LOGICAL_DNS);
|
||||
auto* address = logical_dns_cluster.mutable_load_assignment()
|
||||
->add_endpoints()
|
||||
->add_lb_endpoints()
|
||||
->mutable_endpoint()
|
||||
->mutable_address()
|
||||
->mutable_socket_address();
|
||||
address->set_address(kServerName);
|
||||
address->set_port_value(443);
|
||||
balancer_->ads_service()->SetCdsResource(logical_dns_cluster);
|
||||
// Create Aggregate Cluster
|
||||
auto cluster = default_cluster_;
|
||||
cluster.set_lb_policy(Cluster::RING_HASH);
|
||||
CustomClusterType* custom_cluster = cluster.mutable_cluster_type();
|
||||
custom_cluster->set_name("envoy.clusters.aggregate");
|
||||
ClusterConfig cluster_config;
|
||||
cluster_config.add_clusters(kEdsClusterName);
|
||||
cluster_config.add_clusters(kLogicalDNSClusterName);
|
||||
custom_cluster->mutable_typed_config()->PackFrom(cluster_config);
|
||||
balancer_->ads_service()->SetCdsResource(cluster);
|
||||
// Set up route with channel id hashing
|
||||
auto new_route_config = default_route_config_;
|
||||
auto* route = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
|
||||
auto* hash_policy = route->mutable_route()->add_hash_policy();
|
||||
hash_policy->mutable_filter_state()->set_key("io.grpc.channel_id");
|
||||
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
|
||||
new_route_config);
|
||||
// Set Logical DNS result
|
||||
{
|
||||
grpc_core::ExecCtx exec_ctx;
|
||||
grpc_core::Resolver::Result result;
|
||||
result.addresses = CreateAddressListFromPortList(GetBackendPorts());
|
||||
logical_dns_cluster_resolver_response_generator_->SetResponse(
|
||||
std::move(result));
|
||||
}
|
||||
// Inject connection delay to make this act more realistically.
|
||||
ConnectionDelayInjector delay_injector(
|
||||
grpc_core::Duration::Milliseconds(500) * grpc_test_slowdown_factor());
|
||||
// Send RPC. Need the timeout to be long enough to account for the
|
||||
// subchannel connection delays.
|
||||
CheckRpcSendOk(1, RpcOptions().set_timeout_ms(3500));
|
||||
}
|
||||
|
||||
// This test covers a bug found in the following scenario:
|
||||
// 1. P0 reports TRANSIENT_FAILURE, so we start connecting to P1.
|
||||
// 2. While P1 is still in CONNECTING, P0 goes back to READY, so we
|
||||
// switch back to P0, deactivating P1.
|
||||
// 3. P0 then goes back to TRANSIENT_FAILURE, and we reactivate P1.
|
||||
// The bug caused us to fail to choose P1 even though it is in state
|
||||
// CONNECTING (because the failover timer was not running), so we
|
||||
// incorrectly failed the RPCs.
|
||||
TEST_P(CdsTest, AggregateClusterFallBackWithConnectivityChurn) {
|
||||
ScopedExperimentalEnvVar env_var(
|
||||
"GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER");
|
||||
CreateAndStartBackends(2);
|
||||
const char* kClusterName1 = "cluster1";
|
||||
const char* kClusterName2 = "cluster2";
|
||||
const char* kEdsServiceName2 = "eds_service_name2";
|
||||
// Populate EDS resources.
|
||||
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}});
|
||||
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
|
||||
args = EdsResourceArgs({{"locality1", CreateEndpointsForBackends(1, 2)}});
|
||||
balancer_->ads_service()->SetEdsResource(
|
||||
BuildEdsResource(args, kEdsServiceName2));
|
||||
// Populate new CDS resources.
|
||||
Cluster cluster1 = default_cluster_;
|
||||
cluster1.set_name(kClusterName1);
|
||||
balancer_->ads_service()->SetCdsResource(cluster1);
|
||||
Cluster cluster2 = default_cluster_;
|
||||
cluster2.set_name(kClusterName2);
|
||||
cluster2.mutable_eds_cluster_config()->set_service_name(kEdsServiceName2);
|
||||
balancer_->ads_service()->SetCdsResource(cluster2);
|
||||
// Create Aggregate Cluster
|
||||
auto cluster = default_cluster_;
|
||||
CustomClusterType* custom_cluster = cluster.mutable_cluster_type();
|
||||
custom_cluster->set_name("envoy.clusters.aggregate");
|
||||
ClusterConfig cluster_config;
|
||||
cluster_config.add_clusters(kClusterName1);
|
||||
cluster_config.add_clusters(kClusterName2);
|
||||
custom_cluster->mutable_typed_config()->PackFrom(cluster_config);
|
||||
balancer_->ads_service()->SetCdsResource(cluster);
|
||||
// This class injects itself into all TCP connection attempts made
|
||||
// against iomgr. It intercepts the attempts for the P0 and P1
|
||||
// backends and allows them to proceed as desired to simulate the case
|
||||
// being tested.
|
||||
class ConnectionInjector : public ConnectionAttemptInjector {
|
||||
public:
|
||||
ConnectionInjector(int p0_port, int p1_port)
|
||||
: p0_port_(p0_port), p1_port_(p1_port) {}
|
||||
|
||||
void HandleConnection(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline) override {
|
||||
{
|
||||
grpc_core::MutexLock lock(&mu_);
|
||||
const int port = grpc_sockaddr_get_port(addr);
|
||||
gpr_log(GPR_INFO, "==> HandleConnection(): state_=%d, port=%d", state_,
|
||||
port);
|
||||
switch (state_) {
|
||||
case kInit:
|
||||
// Make P0 report TF, which should trigger us to try to connect to
|
||||
// P1.
|
||||
if (port == p0_port_) {
|
||||
gpr_log(GPR_INFO, "*** INJECTING FAILURE FOR P0 ENDPOINT");
|
||||
grpc_core::ExecCtx::Run(DEBUG_LOCATION, closure,
|
||||
GRPC_ERROR_CREATE_FROM_STATIC_STRING(
|
||||
"injected connection failure"));
|
||||
state_ = kP0Failed;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kP0Failed:
|
||||
// Hold connection attempt to P1 so that it stays in CONNECTING.
|
||||
if (port == p1_port_) {
|
||||
gpr_log(GPR_INFO,
|
||||
"*** DELAYING CONNECTION ATTEMPT FOR P1 ENDPOINT");
|
||||
queued_p1_attempt_ = absl::make_unique<QueuedAttempt>(
|
||||
closure, ep, interested_parties, channel_args, addr,
|
||||
deadline);
|
||||
state_ = kDone;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kDone:
|
||||
// P0 should attempt reconnection. Log it to make the test
|
||||
// easier to debug, but allow it to complete, so that the
|
||||
// priority policy deactivates P1.
|
||||
if (port == p0_port_) {
|
||||
gpr_log(GPR_INFO,
|
||||
"*** INTERCEPTING CONNECTION ATTEMPT FOR P0 ENDPOINT");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
AttemptConnection(closure, ep, interested_parties, channel_args, addr,
|
||||
deadline);
|
||||
}
|
||||
|
||||
// Invoked by the test when the RPC to the P0 backend has succeeded
|
||||
// and it's ready to allow the P1 connection attempt to proceed.
|
||||
void CompletePriority1Connection() {
|
||||
grpc_core::ExecCtx exec_ctx;
|
||||
std::unique_ptr<QueuedAttempt> attempt;
|
||||
{
|
||||
grpc_core::MutexLock lock(&mu_);
|
||||
GPR_ASSERT(state_ == kDone);
|
||||
attempt = std::move(queued_p1_attempt_);
|
||||
}
|
||||
attempt->Resume();
|
||||
}
|
||||
|
||||
private:
|
||||
const int p0_port_;
|
||||
const int p1_port_;
|
||||
|
||||
grpc_core::Mutex mu_;
|
||||
enum {
|
||||
kInit,
|
||||
kP0Failed,
|
||||
kDone,
|
||||
} state_ ABSL_GUARDED_BY(mu_) = kInit;
|
||||
std::unique_ptr<QueuedAttempt> queued_p1_attempt_ ABSL_GUARDED_BY(mu_);
|
||||
};
|
||||
ConnectionInjector connection_attempt_injector(backends_[0]->port(),
|
||||
backends_[1]->port());
|
||||
// Wait for P0 backend.
|
||||
// Increase timeout to account for subchannel connection delays.
|
||||
WaitForBackend(0, WaitForBackendOptions(), RpcOptions().set_timeout_ms(2000));
|
||||
// Bring down the P0 backend.
|
||||
ShutdownBackend(0);
|
||||
// Allow the connection attempt to the P1 backend to resume.
|
||||
connection_attempt_injector.CompletePriority1Connection();
|
||||
// Wait for P1 backend to start getting traffic.
|
||||
WaitForBackend(1);
|
||||
}
|
||||
|
||||
TEST_P(CdsTest, AggregateClusterEdsToLogicalDns) {
|
||||
ScopedExperimentalEnvVar env_var(
|
||||
"GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER");
|
||||
|
|
@ -5696,6 +5937,88 @@ TEST_P(CdsTest, RingHashIdleToReady) {
|
|||
EXPECT_EQ(GRPC_CHANNEL_READY, channel_->GetState(false));
|
||||
}
|
||||
|
||||
// Test that the channel will transition to READY once it starts
|
||||
// connecting even if there are no RPCs being sent to the picker.
|
||||
TEST_P(CdsTest, RingHashContinuesConnectingWithoutPicks) {
|
||||
// Create EDS resource.
|
||||
CreateAndStartBackends(1);
|
||||
auto non_existant_endpoint = MakeNonExistantEndpoint();
|
||||
EdsResourceArgs args(
|
||||
{{"locality0", {non_existant_endpoint, CreateEndpoint(0)}}});
|
||||
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
|
||||
// Change CDS resource to use RING_HASH.
|
||||
auto cluster = default_cluster_;
|
||||
cluster.set_lb_policy(Cluster::RING_HASH);
|
||||
balancer_->ads_service()->SetCdsResource(cluster);
|
||||
// Add hash policy to RDS resource.
|
||||
auto new_route_config = default_route_config_;
|
||||
auto* route = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
|
||||
auto* hash_policy = route->mutable_route()->add_hash_policy();
|
||||
hash_policy->mutable_header()->set_header_name("address_hash");
|
||||
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
|
||||
new_route_config);
|
||||
// A connection injector that cancels the RPC after seeing the
|
||||
// connection attempt for the non-existant endpoint.
|
||||
class ConnectionInjector : public ConnectionAttemptInjector {
|
||||
public:
|
||||
explicit ConnectionInjector(int port) : port_(port) {}
|
||||
|
||||
void HandleConnection(grpc_closure* closure, grpc_endpoint** ep,
|
||||
grpc_pollset_set* interested_parties,
|
||||
const grpc_channel_args* channel_args,
|
||||
const grpc_resolved_address* addr,
|
||||
grpc_core::Timestamp deadline) override {
|
||||
{
|
||||
grpc_core::MutexLock lock(&mu_);
|
||||
const int port = grpc_sockaddr_get_port(addr);
|
||||
gpr_log(GPR_INFO, "==> HandleConnection(): seen_port_=%d, port=%d",
|
||||
seen_port_, port);
|
||||
// Initial attempt should be for port0_, which should fail.
|
||||
// Cancel the RPC at this point, so that it's no longer
|
||||
// queued when the LB policy updates the picker.
|
||||
if (!seen_port_ && port == port_) {
|
||||
gpr_log(GPR_INFO, "*** SEEN P0 CONNECTION ATTEMPT");
|
||||
seen_port_ = true;
|
||||
cond_.Signal();
|
||||
}
|
||||
}
|
||||
AttemptConnection(closure, ep, interested_parties, channel_args, addr,
|
||||
deadline);
|
||||
}
|
||||
|
||||
void WaitForP0ConnectionAttempt() {
|
||||
grpc_core::MutexLock lock(&mu_);
|
||||
while (!seen_port_) {
|
||||
cond_.Wait(&mu_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const int port_;
|
||||
|
||||
grpc_core::Mutex mu_;
|
||||
grpc_core::CondVar cond_;
|
||||
bool seen_port_ ABSL_GUARDED_BY(mu_) = false;
|
||||
};
|
||||
ConnectionInjector connection_injector(non_existant_endpoint.port);
|
||||
// A long-running RPC, just used to send the RPC in another thread.
|
||||
LongRunningRpc rpc;
|
||||
std::vector<std::pair<std::string, std::string>> metadata = {
|
||||
{"address_hash",
|
||||
CreateMetadataValueThatHashesToBackendPort(non_existant_endpoint.port)}};
|
||||
rpc.StartRpc(stub_.get(), RpcOptions().set_timeout_ms(0).set_metadata(
|
||||
std::move(metadata)));
|
||||
// Wait for the RPC to trigger the P0 connection attempt, then cancel it.
|
||||
connection_injector.WaitForP0ConnectionAttempt();
|
||||
rpc.CancelRpc();
|
||||
// Wait for channel to become connected without any pending RPC.
|
||||
EXPECT_TRUE(channel_->WaitForConnected(grpc_timeout_seconds_to_deadline(5)));
|
||||
// RPC should have been cancelled.
|
||||
EXPECT_EQ(StatusCode::CANCELLED, rpc.GetStatus().error_code());
|
||||
// Make sure the backend did not get any requests.
|
||||
EXPECT_EQ(0UL, backends_[0]->backend_service()->request_count());
|
||||
}
|
||||
|
||||
// Test that when the first pick is down leading to a transient failure, we
|
||||
// will move on to the next ring hash entry.
|
||||
TEST_P(CdsTest, RingHashTransientFailureCheckNextOne) {
|
||||
|
|
@ -5840,6 +6163,43 @@ TEST_P(CdsTest, RingHashTransientFailureSkipToAvailableReady) {
|
|||
WaitForBackend(1, WaitForBackendOptions(), rpc_options);
|
||||
}
|
||||
|
||||
// This tests a bug seen in the wild where ring_hash started with no
|
||||
// endpoints and reported TRANSIENT_FAILURE, then got an update with
|
||||
// endpoints and reported IDLE, but the picker update was squelched, so
|
||||
// it failed to ever get reconnected.
|
||||
TEST_P(CdsTest, RingHashReattemptWhenGoingFromTransientFailureToIdle) {
|
||||
CreateAndStartBackends(1);
|
||||
const uint32_t kConnectionTimeoutMilliseconds = 5000;
|
||||
auto cluster = default_cluster_;
|
||||
cluster.set_lb_policy(Cluster::RING_HASH);
|
||||
balancer_->ads_service()->SetCdsResource(cluster);
|
||||
auto new_route_config = default_route_config_;
|
||||
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
|
||||
new_route_config);
|
||||
// Send empty EDS update.
|
||||
EdsResourceArgs args(
|
||||
{{"locality0", std::vector<EdsResourceArgs::Endpoint>()}});
|
||||
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
|
||||
EXPECT_EQ(GRPC_CHANNEL_IDLE, channel_->GetState(false));
|
||||
// Channel should fail RPCs and go into TRANSIENT_FAILURE.
|
||||
CheckRpcSendFailure(
|
||||
DEBUG_LOCATION, StatusCode::UNAVAILABLE,
|
||||
// TODO(roth): As part of https://github.com/grpc/grpc/issues/22883,
|
||||
// figure out how to get a useful resolution note plumbed down to
|
||||
// improve this message.
|
||||
"no ready priority",
|
||||
RpcOptions().set_timeout_ms(kConnectionTimeoutMilliseconds));
|
||||
EXPECT_EQ(GRPC_CHANNEL_TRANSIENT_FAILURE, channel_->GetState(false));
|
||||
// Send EDS update with 1 backend.
|
||||
args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}});
|
||||
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
|
||||
// A wait_for_ready RPC should succeed, and the channel should report READY.
|
||||
CheckRpcSendOk(1, RpcOptions()
|
||||
.set_timeout_ms(kConnectionTimeoutMilliseconds)
|
||||
.set_wait_for_ready(true));
|
||||
EXPECT_EQ(GRPC_CHANNEL_READY, channel_->GetState(false));
|
||||
}
|
||||
|
||||
// Test unspported hash policy types are all ignored before a supported
|
||||
// policy.
|
||||
TEST_P(CdsTest, RingHashUnsupportedHashPolicyUntilChannelIdHashing) {
|
||||
|
|
@ -12482,6 +12842,11 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
.set_enable_rds_testing()),
|
||||
&XdsTestType::Name);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XdsTest, XdsFederationDisabledTest,
|
||||
::testing::Values(XdsTestType().set_enable_rds_testing()),
|
||||
&XdsTestType::Name);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XdsTest, XdsFederationLoadReportingTest,
|
||||
::testing::Values(
|
||||
|
|
@ -12587,6 +12952,7 @@ int main(int argc, char** argv) {
|
|||
absl::make_unique<grpc::testing::FakeCertificateProviderFactory>(
|
||||
"fake2", &grpc::testing::g_fake2_cert_data_map));
|
||||
grpc_init();
|
||||
grpc::testing::ConnectionAttemptInjector::Init();
|
||||
grpc_core::XdsHttpFilterRegistry::RegisterFilter(
|
||||
absl::make_unique<grpc::testing::NoOpHttpFilter>(
|
||||
"grpc.testing.client_only_http_filter",
|
||||
|
|
|
|||
|
|
@ -825,6 +825,19 @@ void XdsEnd2endTest::CheckRpcSendOk(const size_t times,
|
|||
}
|
||||
}
|
||||
|
||||
void XdsEnd2endTest::CheckRpcSendFailure(
|
||||
const grpc_core::DebugLocation& debug_location, StatusCode expected_status,
|
||||
absl::string_view expected_message_regex, const RpcOptions& rpc_options) {
|
||||
const Status status = SendRpc(rpc_options);
|
||||
EXPECT_FALSE(status.ok())
|
||||
<< debug_location.file() << ":" << debug_location.line();
|
||||
EXPECT_EQ(expected_status, status.error_code())
|
||||
<< debug_location.file() << ":" << debug_location.line();
|
||||
EXPECT_THAT(status.error_message(),
|
||||
::testing::ContainsRegex(expected_message_regex))
|
||||
<< debug_location.file() << ":" << debug_location.line();
|
||||
}
|
||||
|
||||
void XdsEnd2endTest::CheckRpcSendFailure(
|
||||
const CheckRpcSendFailureOptions& options) {
|
||||
for (size_t i = 0; options.continue_predicate(i); ++i) {
|
||||
|
|
|
|||
|
|
@ -798,7 +798,16 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType> {
|
|||
void CheckRpcSendOk(const size_t times = 1,
|
||||
const RpcOptions& rpc_options = RpcOptions());
|
||||
|
||||
// Options to use with CheckRpcSendFailure().
|
||||
// Sends one RPC, which must fail with the specified status code and
|
||||
// a message matching the specified regex.
|
||||
void CheckRpcSendFailure(const grpc_core::DebugLocation& debug_location,
|
||||
StatusCode expected_status,
|
||||
absl::string_view expected_message_regex,
|
||||
const RpcOptions& rpc_options = RpcOptions());
|
||||
|
||||
// DEPRECATED -- USE THE ABOVE VARIANT INSTEAD.
|
||||
// TODO(roth): Change all existing callers to use the above variant
|
||||
// instead and then remove this.
|
||||
struct CheckRpcSendFailureOptions {
|
||||
std::function<bool(size_t)> continue_predicate = [](size_t i) {
|
||||
return i < 1;
|
||||
|
|
@ -829,8 +838,6 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType> {
|
|||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// Sends RPCs and expects them to fail.
|
||||
void CheckRpcSendFailure(
|
||||
const CheckRpcSendFailureOptions& options = CheckRpcSendFailureOptions());
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ cd "$(dirname "$0")"
|
|||
|
||||
cp -r "$EXTERNAL_GIT_ROOT"/input_artifacts/grpc-*.tgz .
|
||||
|
||||
find . -regex ".*/grpc-[0-9].*.tgz" | cut -b3- | \
|
||||
MAKEFLAGS=-j xargs pecl install
|
||||
# get name of the PHP package archive to test (we don't know
|
||||
# the exact version string in advance)
|
||||
GRPC_PEAR_PACKAGE_NAME=$(find . -regex '.*/grpc-[0-9].*.tgz' | sed 's|./||')
|
||||
|
||||
MAKEFLAGS=-j pecl install "${GRPC_PEAR_PACKAGE_NAME}"
|
||||
|
||||
php -d extension=grpc.so -d max_execution_time=300 distribtest.php
|
||||
|
|
|
|||
|
|
@ -19,7 +19,12 @@ cd "$(dirname "$0")"
|
|||
|
||||
cp -r "$EXTERNAL_GIT_ROOT"/input_artifacts/grpc-*.tgz .
|
||||
|
||||
find . -regex ".*/grpc-[0-9].*.tgz" | cut -b3- | \
|
||||
xargs sudo MAKEFLAGS=-j pecl install
|
||||
# get name of the PHP package archive to test (we don't know
|
||||
# the exact version string in advance)
|
||||
GRPC_PEAR_PACKAGE_NAME=$(find . -regex '.*/grpc-[0-9].*.tgz' | sed 's|./||')
|
||||
|
||||
# Use -j4 since higher parallelism can lead to "resource unavailable"
|
||||
# errors during the build. See b/257261061#comment4
|
||||
sudo MAKEFLAGS=-j4 pecl install "${GRPC_PEAR_PACKAGE_NAME}"
|
||||
|
||||
php -d extension=grpc.so -d max_execution_time=300 distribtest.php
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b9232f9e27e5668bc0414879dcdedb2a59ea75f2
|
||||
Subproject commit 6195bf8242156c9a2fa75702eee058f91b86a88b
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 22d0e265de7d2b3d2e9a00d071313502e7d4cccf
|
||||
Subproject commit b464cfbee18c71c40e761a5273ad369f3547294b
|
||||
|
|
@ -5,7 +5,7 @@ index 97ac28028..8b7585d9d 100644
|
|||
@@ -31,3 +31,9 @@
|
||||
# Copyright 2007 Google Inc. All Rights Reserved.
|
||||
|
||||
__version__ = '3.19.4'
|
||||
__version__ = '3.19.5'
|
||||
+
|
||||
+if __name__ != '__main__':
|
||||
+ try:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-arm64-darwin:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-arm64-darwin:1.2.1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-x64-mingw32:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-x64-mingw32:1.2.1
|
||||
|
||||
RUN find / -name win32.h | while read f ; do sed -i 's/gettimeofday/rb_gettimeofday/' $f ; done
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-x86-linux:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-x86-linux:1.2.1
|
||||
|
||||
#=================
|
||||
# Install ccache
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-x86-mingw32:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-x86-mingw32:1.2.1
|
||||
|
||||
RUN find / -name win32.h | while read f ; do sed -i 's/gettimeofday/rb_gettimeofday/' $f ; done
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-x86_64-darwin:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-x86_64-darwin:1.2.1
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM larskanis/rake-compiler-dock-mri-x86_64-linux:1.1.0
|
||||
FROM larskanis/rake-compiler-dock-mri-x86_64-linux:1.2.1
|
||||
|
||||
#=================
|
||||
# Install ccache
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f
|
||||
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue