diff options
39 files changed, 895 insertions, 95 deletions
@@ -244,10 +244,13 @@ GRPCXX_PUBLIC_HDRS = [ "include/grpcpp/support/byte_buffer.h", "include/grpcpp/support/channel_arguments.h", "include/grpcpp/support/client_callback.h", + "include/grpcpp/support/client_interceptor.h", "include/grpcpp/support/config.h", + "include/grpcpp/support/interceptor.h", "include/grpcpp/support/proto_buffer_reader.h", "include/grpcpp/support/proto_buffer_writer.h", "include/grpcpp/support/server_callback.h", + "include/grpcpp/support/server_interceptor.h", "include/grpcpp/support/slice.h", "include/grpcpp/support/status.h", "include/grpcpp/support/status_code_enum.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ddd9d22ba..1f34bf841d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2989,10 +2989,13 @@ foreach(_hdr include/grpcpp/support/byte_buffer.h include/grpcpp/support/channel_arguments.h include/grpcpp/support/client_callback.h + include/grpcpp/support/client_interceptor.h include/grpcpp/support/config.h + include/grpcpp/support/interceptor.h include/grpcpp/support/proto_buffer_reader.h include/grpcpp/support/proto_buffer_writer.h include/grpcpp/support/server_callback.h + include/grpcpp/support/server_interceptor.h include/grpcpp/support/slice.h include/grpcpp/support/status.h include/grpcpp/support/status_code_enum.h @@ -3574,10 +3577,13 @@ foreach(_hdr include/grpcpp/support/byte_buffer.h include/grpcpp/support/channel_arguments.h include/grpcpp/support/client_callback.h + include/grpcpp/support/client_interceptor.h include/grpcpp/support/config.h + include/grpcpp/support/interceptor.h include/grpcpp/support/proto_buffer_reader.h include/grpcpp/support/proto_buffer_writer.h include/grpcpp/support/server_callback.h + include/grpcpp/support/server_interceptor.h include/grpcpp/support/slice.h include/grpcpp/support/status.h include/grpcpp/support/status_code_enum.h @@ -4526,10 +4532,13 @@ foreach(_hdr include/grpcpp/support/byte_buffer.h include/grpcpp/support/channel_arguments.h include/grpcpp/support/client_callback.h + include/grpcpp/support/client_interceptor.h include/grpcpp/support/config.h + include/grpcpp/support/interceptor.h include/grpcpp/support/proto_buffer_reader.h include/grpcpp/support/proto_buffer_writer.h include/grpcpp/support/server_callback.h + include/grpcpp/support/server_interceptor.h include/grpcpp/support/slice.h include/grpcpp/support/status.h include/grpcpp/support/status_code_enum.h @@ -5382,10 +5382,13 @@ PUBLIC_HEADERS_CXX += \ include/grpcpp/support/byte_buffer.h \ include/grpcpp/support/channel_arguments.h \ include/grpcpp/support/client_callback.h \ + include/grpcpp/support/client_interceptor.h \ include/grpcpp/support/config.h \ + include/grpcpp/support/interceptor.h \ include/grpcpp/support/proto_buffer_reader.h \ include/grpcpp/support/proto_buffer_writer.h \ include/grpcpp/support/server_callback.h \ + include/grpcpp/support/server_interceptor.h \ include/grpcpp/support/slice.h \ include/grpcpp/support/status.h \ include/grpcpp/support/status_code_enum.h \ @@ -5976,10 +5979,13 @@ PUBLIC_HEADERS_CXX += \ include/grpcpp/support/byte_buffer.h \ include/grpcpp/support/channel_arguments.h \ include/grpcpp/support/client_callback.h \ + include/grpcpp/support/client_interceptor.h \ include/grpcpp/support/config.h \ + include/grpcpp/support/interceptor.h \ include/grpcpp/support/proto_buffer_reader.h \ include/grpcpp/support/proto_buffer_writer.h \ include/grpcpp/support/server_callback.h \ + include/grpcpp/support/server_interceptor.h \ include/grpcpp/support/slice.h \ include/grpcpp/support/status.h \ include/grpcpp/support/status_code_enum.h \ @@ -6885,10 +6891,13 @@ PUBLIC_HEADERS_CXX += \ include/grpcpp/support/byte_buffer.h \ include/grpcpp/support/channel_arguments.h \ include/grpcpp/support/client_callback.h \ + include/grpcpp/support/client_interceptor.h \ include/grpcpp/support/config.h \ + include/grpcpp/support/interceptor.h \ include/grpcpp/support/proto_buffer_reader.h \ include/grpcpp/support/proto_buffer_writer.h \ include/grpcpp/support/server_callback.h \ + include/grpcpp/support/server_interceptor.h \ include/grpcpp/support/slice.h \ include/grpcpp/support/status.h \ include/grpcpp/support/status_code_enum.h \ diff --git a/build.yaml b/build.yaml index a6158a3dc6..830123110a 100644 --- a/build.yaml +++ b/build.yaml @@ -1366,10 +1366,13 @@ filegroups: - include/grpcpp/support/byte_buffer.h - include/grpcpp/support/channel_arguments.h - include/grpcpp/support/client_callback.h + - include/grpcpp/support/client_interceptor.h - include/grpcpp/support/config.h + - include/grpcpp/support/interceptor.h - include/grpcpp/support/proto_buffer_reader.h - include/grpcpp/support/proto_buffer_writer.h - include/grpcpp/support/server_callback.h + - include/grpcpp/support/server_interceptor.h - include/grpcpp/support/slice.h - include/grpcpp/support/status.h - include/grpcpp/support/status_code_enum.h diff --git a/examples/BUILD b/examples/BUILD index 0f18cfa9ba..22f2f0a4f1 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -51,3 +51,18 @@ cc_binary( defines = ["BAZEL_BUILD"], deps = [":helloworld", "//:grpc++"], ) + +cc_binary( + name = "metadata_client", + srcs = ["cpp/metadata/greeter_client.cc"], + defines = ["BAZEL_BUILD"], + deps = [":helloworld", "//:grpc++"], +) + +cc_binary( + name = "metadata_server", + srcs = ["cpp/metadata/greeter_server.cc"], + defines = ["BAZEL_BUILD"], + deps = [":helloworld", "//:grpc++"], +) + diff --git a/examples/cpp/metadata/Makefile b/examples/cpp/metadata/Makefile new file mode 100644 index 0000000000..46be8bfaa3 --- /dev/null +++ b/examples/cpp/metadata/Makefile @@ -0,0 +1,96 @@ +# +# Copyright 2018 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. +# + HOST_SYSTEM = $(shell uname | cut -f 1 -d_) +SYSTEM ?= $(HOST_SYSTEM) +CXX = g++ +CPPFLAGS += `pkg-config --cflags protobuf grpc` +CXXFLAGS += -std=c++11 +ifeq ($(SYSTEM),Darwin) +LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\ + -lgrpc++_reflection\ + -ldl +else +LDFLAGS += -L/usr/local/lib `pkg-config --libs protobuf grpc++ grpc`\ + -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed\ + -ldl +endif +PROTOC = protoc +GRPC_CPP_PLUGIN = grpc_cpp_plugin +GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` + PROTOS_PATH = ../../protos + vpath %.proto $(PROTOS_PATH) + all: system-check greeter_client greeter_server + greeter_client: helloworld.pb.o helloworld.grpc.pb.o greeter_client.o + $(CXX) $^ $(LDFLAGS) -o $@ + greeter_server: helloworld.pb.o helloworld.grpc.pb.o greeter_server.o + $(CXX) $^ $(LDFLAGS) -o $@ + .PRECIOUS: %.grpc.pb.cc +%.grpc.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< + .PRECIOUS: %.pb.cc +%.pb.cc: %.proto + $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $< + clean: + rm -f *.o *.pb.cc *.pb.h greeter_client greeter_server + # The following is to test your system and ensure a smoother experience. +# They are by no means necessary to actually compile a grpc-enabled software. + PROTOC_CMD = which $(PROTOC) +PROTOC_CHECK_CMD = $(PROTOC) --version | grep -q libprotoc.3 +PLUGIN_CHECK_CMD = which $(GRPC_CPP_PLUGIN) +HAS_PROTOC = $(shell $(PROTOC_CMD) > /dev/null && echo true || echo false) +ifeq ($(HAS_PROTOC),true) +HAS_VALID_PROTOC = $(shell $(PROTOC_CHECK_CMD) 2> /dev/null && echo true || echo false) +endif +HAS_PLUGIN = $(shell $(PLUGIN_CHECK_CMD) > /dev/null && echo true || echo false) + SYSTEM_OK = false +ifeq ($(HAS_VALID_PROTOC),true) +ifeq ($(HAS_PLUGIN),true) +SYSTEM_OK = true +endif +endif + system-check: +ifneq ($(HAS_VALID_PROTOC),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have protoc 3.0.0 installed in your path." + @echo "Please install Google protocol buffers 3.0.0 and its compiler." + @echo "You can find it here:" + @echo + @echo " https://github.com/google/protobuf/releases/tag/v3.0.0" + @echo + @echo "Here is what I get when trying to evaluate your version of protoc:" + @echo + -$(PROTOC) --version + @echo + @echo +endif +ifneq ($(HAS_PLUGIN),true) + @echo " DEPENDENCY ERROR" + @echo + @echo "You don't have the grpc c++ protobuf plugin installed in your path." + @echo "Please install grpc. You can find it here:" + @echo + @echo " https://github.com/grpc/grpc" + @echo + @echo "Here is what I get when trying to detect if you have the plugin:" + @echo + -which $(GRPC_CPP_PLUGIN) + @echo + @echo +endif +ifneq ($(SYSTEM_OK),true) + @false +endif diff --git a/examples/cpp/metadata/README.md b/examples/cpp/metadata/README.md new file mode 100644 index 0000000000..96ad3d19bd --- /dev/null +++ b/examples/cpp/metadata/README.md @@ -0,0 +1,66 @@ +# Metadata Example + +## Overview + +This example shows you how to add custom headers on the client and server and +how to access them. + +Custom metadata must follow the "Custom-Metadata" format listed in +https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md, with the +exception of binary headers, which don't have to be base64 encoded. + +### Get the tutorial source code + The example code for this and our other examples lives in the `examples` directory. Clone this repository to your local machine by running the following command: + ```sh +$ git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc +``` + Change your current directory to examples/cpp/metadata + ```sh +$ cd examples/cpp/metadata +``` + +### Generating gRPC code + To generate the client and server side interfaces: + ```sh +$ make helloworld.grpc.pb.cc helloworld.pb.cc +``` +Which internally invokes the proto-compiler as: + ```sh +$ protoc -I ../../protos/ --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin ../../protos/helloworld.proto +$ protoc -I ../../protos/ --cpp_out=. ../../protos/helloworld.proto +``` +### Try it! +Build client and server: + +```sh +$ make +``` + +Run the server, which will listen on port 50051: + +```sh +$ ./greeter_server +``` + +Run the client (in a different terminal): + +```sh +$ ./greeter_client +``` + +If things go smoothly, you will see in the client terminal: + +"Client received initial metadata from server: initial metadata value" +"Client received trailing metadata from server: trailing metadata value" +"Client received message: Hello World" + + +And in the server terminal: + +"Header key: custom-bin , value: 01234567" +"Header key: custom-header , value: Custom Value" +"Header key: user-agent , value: grpc-c++/1.16.0-dev grpc-c/6.0.0-dev (linux; chttp2; gao)" + +We did not add the user-agent metadata as a custom header. This shows how +the gRPC framework adds some headers under the hood that may show up in the +metadata map. diff --git a/examples/cpp/metadata/greeter_client.cc b/examples/cpp/metadata/greeter_client.cc new file mode 100644 index 0000000000..8049438993 --- /dev/null +++ b/examples/cpp/metadata/greeter_client.cc @@ -0,0 +1,95 @@ +/* + * + * Copyright 2015 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. + * + */ + +#include <iostream> +#include <memory> +#include <string> + +#include <grpcpp/grpcpp.h> + +#ifdef BAZEL_BUILD +#include "examples/protos/helloworld.grpc.pb.h" +#else +#include "helloworld.grpc.pb.h" +#endif + +using grpc::Channel; +using grpc::ClientContext; +using grpc::Status; +using helloworld::HelloRequest; +using helloworld::HelloReply; +using helloworld::Greeter; + +class CustomHeaderClient { + public: + CustomHeaderClient(std::shared_ptr<Channel> channel) + : stub_(Greeter::NewStub(channel)) {} + + // Assembles the client's payload, sends it and presents the response back + // from the server. + std::string SayHello(const std::string& user) { + // Data we are sending to the server. + HelloRequest request; + request.set_name(user); + + // Container for the data we expect from the server. + HelloReply reply; + + // Context for the client. It could be used to convey extra information to + // the server and/or tweak certain RPC behaviors. + ClientContext context; + + // Setting custom metadata to be sent to the server + context.AddMetadata("custom-header", "Custom Value"); + + // Setting custom binary metadata + char bytes[8] = {'\0', '\1', '\2', '\3', + '\4', '\5', '\6', '\7'}; + context.AddMetadata("custom-bin", grpc::string(bytes, 8)); + + // The actual RPC. + Status status = stub_->SayHello(&context, request, &reply); + + // Act upon its status. + if (status.ok()) { + std::cout << "Client received initial metadata from server: " << context.GetServerInitialMetadata().find("custom-server-metadata")->second << std::endl; + std::cout << "Client received trailing metadata from server: " << context.GetServerTrailingMetadata().find("custom-trailing-metadata")->second << std::endl; + return reply.message(); + } else { + std::cout << status.error_code() << ": " << status.error_message() + << std::endl; + return "RPC failed"; + } + } + + private: + std::unique_ptr<Greeter::Stub> stub_; +}; + +int main(int argc, char** argv) { + // Instantiate the client. It requires a channel, out of which the actual RPCs + // are created. This channel models a connection to an endpoint (in this case, + // localhost at port 50051). We indicate that the channel isn't authenticated + // (use of InsecureChannelCredentials()). + CustomHeaderClient greeter(grpc::CreateChannel( + "localhost:50051", grpc::InsecureChannelCredentials())); + std::string user("world"); + std::string reply = greeter.SayHello(user); + std::cout << "Client received message: " << reply << std::endl; + return 0; +} diff --git a/examples/cpp/metadata/greeter_server.cc b/examples/cpp/metadata/greeter_server.cc new file mode 100644 index 0000000000..a9a4f33cb0 --- /dev/null +++ b/examples/cpp/metadata/greeter_server.cc @@ -0,0 +1,94 @@ +/* + * + * Copyright 2015 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. + * + */ + +#include <iostream> +#include <memory> +#include <string> + +#include <grpcpp/grpcpp.h> + +#ifdef BAZEL_BUILD +#include "examples/protos/helloworld.grpc.pb.h" +#else +#include "helloworld.grpc.pb.h" +#endif + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::Status; +using helloworld::HelloRequest; +using helloworld::HelloReply; +using helloworld::Greeter; + +// Logic and data behind the server's behavior. +class GreeterServiceImpl final : public Greeter::Service { + Status SayHello(ServerContext* context, const HelloRequest* request, + HelloReply* reply) override { + std::string prefix("Hello "); + + // Get the client's initial metadata + std::cout << "Client metadata: " << std::endl; + const std::multimap<grpc::string_ref, grpc::string_ref> metadata = context->client_metadata(); + for (auto iter = metadata.begin(); iter != metadata.end(); ++iter) { + std::cout << "Header key: " << iter->first << ", value: "; + // Check for binary value + size_t isbin = iter->first.find("-bin"); + if ((isbin != std::string::npos) && (isbin + 4 == iter->first.size())) { + std::cout << std::hex; + for (auto c : iter->second) { + std::cout << static_cast<unsigned int>(c); + } + std::cout << std::dec; + } else { + std::cout << iter->second; + } + std::cout << std::endl; + } + + context->AddInitialMetadata("custom-server-metadata", "initial metadata value"); + context->AddTrailingMetadata("custom-trailing-metadata", "trailing metadata value"); + reply->set_message(prefix + request->name()); + return Status::OK; + } +}; + +void RunServer() { + std::string server_address("0.0.0.0:50051"); + GreeterServiceImpl service; + + ServerBuilder builder; + // Listen on the given address without any authentication mechanism. + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + // Register "service" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *synchronous* service. + builder.RegisterService(&service); + // Finally assemble the server. + std::unique_ptr<Server> server(builder.BuildAndStart()); + std::cout << "Server listening on " << server_address << std::endl; + + // Wait for the server to shutdown. Note that some other thread must be + // responsible for shutting down the server for this call to ever return. + server->Wait(); +} + +int main(int argc, char** argv) { + RunServer(); + + return 0; +} diff --git a/gRPC-C++.podspec b/gRPC-C++.podspec index 414e788a96..f918fe8ad9 100644 --- a/gRPC-C++.podspec +++ b/gRPC-C++.podspec @@ -116,10 +116,13 @@ Pod::Spec.new do |s| 'include/grpcpp/support/byte_buffer.h', 'include/grpcpp/support/channel_arguments.h', 'include/grpcpp/support/client_callback.h', + 'include/grpcpp/support/client_interceptor.h', 'include/grpcpp/support/config.h', + 'include/grpcpp/support/interceptor.h', 'include/grpcpp/support/proto_buffer_reader.h', 'include/grpcpp/support/proto_buffer_writer.h', 'include/grpcpp/support/server_callback.h', + 'include/grpcpp/support/server_interceptor.h', 'include/grpcpp/support/slice.h', 'include/grpcpp/support/status.h', 'include/grpcpp/support/status_code_enum.h', diff --git a/include/grpc/impl/codegen/compression_types.h b/include/grpc/impl/codegen/compression_types.h index e35d892967..f778b005b9 100644 --- a/include/grpc/impl/codegen/compression_types.h +++ b/include/grpc/impl/codegen/compression_types.h @@ -52,7 +52,8 @@ extern "C" { "grpc.compression_enabled_algorithms_bitset" /** \} */ -/** The various compression algorithms supported by gRPC */ +/** The various compression algorithms supported by gRPC (not sorted by + * compression level) */ typedef enum { GRPC_COMPRESS_NONE = 0, GRPC_COMPRESS_DEFLATE, diff --git a/include/grpcpp/impl/codegen/client_context.h b/include/grpcpp/impl/codegen/client_context.h index 0a71f3d9b6..5946488566 100644 --- a/include/grpcpp/impl/codegen/client_context.h +++ b/include/grpcpp/impl/codegen/client_context.h @@ -200,6 +200,13 @@ class ClientContext { /// end in "-bin". /// \param meta_value The metadata value. If its value is binary, the key name /// must end in "-bin". + /// + /// Metadata must conform to the following format: + /// Custom-Metadata -> Binary-Header / ASCII-Header + /// Binary-Header -> {Header-Name "-bin" } {binary value} + /// ASCII-Header -> Header-Name ASCII-Value + /// Header-Name -> 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . + /// ASCII-Value -> 1*( %x20-%x7E ) ; space and printable ASCII void AddMetadata(const grpc::string& meta_key, const grpc::string& meta_value); diff --git a/include/grpcpp/impl/codegen/client_interceptor.h b/include/grpcpp/impl/codegen/client_interceptor.h index 2bae11a251..da75bde499 100644 --- a/include/grpcpp/impl/codegen/client_interceptor.h +++ b/include/grpcpp/impl/codegen/client_interceptor.h @@ -38,9 +38,17 @@ class InterceptorBatchMethodsImpl; namespace experimental { class ClientRpcInfo; +// A factory interface for creation of client interceptors. A vector of +// factories can be provided at channel creation which will be used to create a +// new vector of client interceptors per RPC. Client interceptor authors should +// create a subclass of ClientInterceptorFactorInterface which creates objects +// of their interceptors. class ClientInterceptorFactoryInterface { public: virtual ~ClientInterceptorFactoryInterface() {} + // Returns a pointer to an Interceptor object on successful creation, nullptr + // otherwise. If nullptr is returned, this server interceptor factory is + // ignored for the purposes of that RPC. virtual Interceptor* CreateClientInterceptor(ClientRpcInfo* info) = 0; }; } // namespace experimental @@ -120,8 +128,11 @@ class ClientRpcInfo { } for (auto it = creators.begin() + interceptor_pos; it != creators.end(); ++it) { - interceptors_.push_back(std::unique_ptr<experimental::Interceptor>( - (*it)->CreateClientInterceptor(this))); + auto* interceptor = (*it)->CreateClientInterceptor(this); + if (interceptor != nullptr) { + interceptors_.push_back( + std::unique_ptr<experimental::Interceptor>(interceptor)); + } } if (internal::g_global_client_interceptor_factory != nullptr) { interceptors_.push_back(std::unique_ptr<experimental::Interceptor>( diff --git a/include/grpcpp/impl/codegen/server_context.h b/include/grpcpp/impl/codegen/server_context.h index ccb5925e7d..affe61b547 100644 --- a/include/grpcpp/impl/codegen/server_context.h +++ b/include/grpcpp/impl/codegen/server_context.h @@ -131,6 +131,13 @@ class ServerContext { /// end in "-bin". /// \param value The metadata value. If its value is binary, the key name /// must end in "-bin". + /// + /// Metadata must conform to the following format: + /// Custom-Metadata -> Binary-Header / ASCII-Header + /// Binary-Header -> {Header-Name "-bin" } {binary value} + /// ASCII-Header -> Header-Name ASCII-Value + /// Header-Name -> 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . + /// ASCII-Value -> 1*( %x20-%x7E ) ; space and printable ASCII void AddInitialMetadata(const grpc::string& key, const grpc::string& value); /// Add the (\a key, \a value) pair to the initial metadata @@ -145,6 +152,13 @@ class ServerContext { /// it must end in "-bin". /// \param value The metadata value. If its value is binary, the key name /// must end in "-bin". + /// + /// Metadata must conform to the following format: + /// Custom-Metadata -> Binary-Header / ASCII-Header + /// Binary-Header -> {Header-Name "-bin" } {binary value} + /// ASCII-Header -> Header-Name ASCII-Value + /// Header-Name -> 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . + /// ASCII-Value -> 1*( %x20-%x7E ) ; space and printable ASCII void AddTrailingMetadata(const grpc::string& key, const grpc::string& value); /// IsCancelled is always safe to call when using sync or callback API. diff --git a/include/grpcpp/impl/codegen/server_interceptor.h b/include/grpcpp/impl/codegen/server_interceptor.h index afc3c198cc..8652ec5c64 100644 --- a/include/grpcpp/impl/codegen/server_interceptor.h +++ b/include/grpcpp/impl/codegen/server_interceptor.h @@ -37,9 +37,17 @@ class InterceptorBatchMethodsImpl; namespace experimental { class ServerRpcInfo; +// A factory interface for creation of server interceptors. A vector of +// factories can be provided to ServerBuilder which will be used to create a new +// vector of server interceptors per RPC. Server interceptor authors should +// create a subclass of ServerInterceptorFactorInterface which creates objects +// of their interceptors. class ServerInterceptorFactoryInterface { public: virtual ~ServerInterceptorFactoryInterface() {} + // Returns a pointer to an Interceptor object on successful creation, nullptr + // otherwise. If nullptr is returned, this server interceptor factory is + // ignored for the purposes of that RPC. virtual Interceptor* CreateServerInterceptor(ServerRpcInfo* info) = 0; }; @@ -90,8 +98,11 @@ class ServerRpcInfo { std::unique_ptr<experimental::ServerInterceptorFactoryInterface>>& creators) { for (const auto& creator : creators) { - interceptors_.push_back(std::unique_ptr<experimental::Interceptor>( - creator->CreateServerInterceptor(this))); + auto* interceptor = creator->CreateServerInterceptor(this); + if (interceptor != nullptr) { + interceptors_.push_back( + std::unique_ptr<experimental::Interceptor>(interceptor)); + } } } diff --git a/include/grpcpp/support/client_interceptor.h b/include/grpcpp/support/client_interceptor.h new file mode 100644 index 0000000000..50810e3fe3 --- /dev/null +++ b/include/grpcpp/support/client_interceptor.h @@ -0,0 +1,24 @@ +/* + * + * Copyright 2015 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. + * + */ + +#ifndef GRPCPP_SUPPORT_CLIENT_INTERCEPTOR_H +#define GRPCPP_SUPPORT_CLIENT_INTERCEPTOR_H + +#include <grpcpp/impl/codegen/client_interceptor.h> + +#endif // GRPCPP_SUPPORT_CLIENT_INTERCEPTOR_H diff --git a/include/grpcpp/support/interceptor.h b/include/grpcpp/support/interceptor.h new file mode 100644 index 0000000000..7ff79516ba --- /dev/null +++ b/include/grpcpp/support/interceptor.h @@ -0,0 +1,24 @@ +/* + * + * Copyright 2015 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. + * + */ + +#ifndef GRPCPP_SUPPORT_INTERCEPTOR_H +#define GRPCPP_SUPPORT_INTERCEPTOR_H + +#include <grpcpp/impl/codegen/interceptor.h> + +#endif // GRPCPP_SUPPORT_INTERCEPTOR_H diff --git a/include/grpcpp/support/server_interceptor.h b/include/grpcpp/support/server_interceptor.h new file mode 100644 index 0000000000..b0a6229b66 --- /dev/null +++ b/include/grpcpp/support/server_interceptor.h @@ -0,0 +1,24 @@ +/* + * + * Copyright 2015 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. + * + */ + +#ifndef GRPCPP_SUPPORT_SERVER_INTERCEPTOR_H +#define GRPCPP_SUPPORT_SERVER_INTERCEPTOR_H + +#include <grpcpp/impl/codegen/server_interceptor.h> + +#endif // GRPCPP_SUPPORT_SERVER_INTERCEPTOR_H diff --git a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc index a9a5965ed1..6671e708c8 100644 --- a/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc +++ b/src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb.cc @@ -525,8 +525,7 @@ void GrpcLb::BalancerCallState::Orphan() { void GrpcLb::BalancerCallState::StartQuery() { GPR_ASSERT(lb_call_ != nullptr); if (grpc_lb_glb_trace.enabled()) { - gpr_log(GPR_INFO, - "[grpclb %p] Starting LB call (lb_calld: %p, lb_call: %p)", + gpr_log(GPR_INFO, "[grpclb %p] lb_calld=%p: Starting LB call %p", grpclb_policy_.get(), this, lb_call_); } // Create the ops. @@ -670,8 +669,9 @@ void GrpcLb::BalancerCallState::SendClientLoadReportLocked() { grpc_call_error call_error = grpc_call_start_batch_and_execute( lb_call_, &op, 1, &client_load_report_closure_); if (GPR_UNLIKELY(call_error != GRPC_CALL_OK)) { - gpr_log(GPR_ERROR, "[grpclb %p] call_error=%d", grpclb_policy_.get(), - call_error); + gpr_log(GPR_ERROR, + "[grpclb %p] lb_calld=%p call_error=%d sending client load report", + grpclb_policy_.get(), this, call_error); GPR_ASSERT(GRPC_CALL_OK == call_error); } } @@ -732,15 +732,17 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( &initial_response->client_stats_report_interval)); if (grpc_lb_glb_trace.enabled()) { gpr_log(GPR_INFO, - "[grpclb %p] Received initial LB response message; " - "client load reporting interval = %" PRId64 " milliseconds", - grpclb_policy, lb_calld->client_stats_report_interval_); + "[grpclb %p] lb_calld=%p: Received initial LB response " + "message; client load reporting interval = %" PRId64 + " milliseconds", + grpclb_policy, lb_calld, + lb_calld->client_stats_report_interval_); } } else if (grpc_lb_glb_trace.enabled()) { gpr_log(GPR_INFO, - "[grpclb %p] Received initial LB response message; client load " - "reporting NOT enabled", - grpclb_policy); + "[grpclb %p] lb_calld=%p: Received initial LB response message; " + "client load reporting NOT enabled", + grpclb_policy, lb_calld); } grpc_grpclb_initial_response_destroy(initial_response); lb_calld->seen_initial_response_ = true; @@ -750,15 +752,17 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( GPR_ASSERT(lb_calld->lb_call_ != nullptr); if (grpc_lb_glb_trace.enabled()) { gpr_log(GPR_INFO, - "[grpclb %p] Serverlist with %" PRIuPTR " servers received", - grpclb_policy, serverlist->num_servers); + "[grpclb %p] lb_calld=%p: Serverlist with %" PRIuPTR + " servers received", + grpclb_policy, lb_calld, serverlist->num_servers); for (size_t i = 0; i < serverlist->num_servers; ++i) { grpc_resolved_address addr; ParseServer(serverlist->servers[i], &addr); char* ipport; grpc_sockaddr_to_string(&ipport, &addr, false); - gpr_log(GPR_INFO, "[grpclb %p] Serverlist[%" PRIuPTR "]: %s", - grpclb_policy, i, ipport); + gpr_log(GPR_INFO, + "[grpclb %p] lb_calld=%p: Serverlist[%" PRIuPTR "]: %s", + grpclb_policy, lb_calld, i, ipport); gpr_free(ipport); } } @@ -778,9 +782,9 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( if (grpc_grpclb_serverlist_equals(grpclb_policy->serverlist_, serverlist)) { if (grpc_lb_glb_trace.enabled()) { gpr_log(GPR_INFO, - "[grpclb %p] Incoming server list identical to current, " - "ignoring.", - grpclb_policy); + "[grpclb %p] lb_calld=%p: Incoming server list identical to " + "current, ignoring.", + grpclb_policy, lb_calld); } grpc_grpclb_destroy_serverlist(serverlist); } else { // New serverlist. @@ -806,8 +810,9 @@ void GrpcLb::BalancerCallState::OnBalancerMessageReceivedLocked( char* response_slice_str = grpc_dump_slice(response_slice, GPR_DUMP_ASCII | GPR_DUMP_HEX); gpr_log(GPR_ERROR, - "[grpclb %p] Invalid LB response received: '%s'. Ignoring.", - grpclb_policy, response_slice_str); + "[grpclb %p] lb_calld=%p: Invalid LB response received: '%s'. " + "Ignoring.", + grpclb_policy, lb_calld, response_slice_str); gpr_free(response_slice_str); } grpc_slice_unref_internal(response_slice); @@ -838,9 +843,9 @@ void GrpcLb::BalancerCallState::OnBalancerStatusReceivedLocked( char* status_details = grpc_slice_to_c_string(lb_calld->lb_call_status_details_); gpr_log(GPR_INFO, - "[grpclb %p] Status from LB server received. Status = %d, details " - "= '%s', (lb_calld: %p, lb_call: %p), error '%s'", - grpclb_policy, lb_calld->lb_call_status_, status_details, lb_calld, + "[grpclb %p] lb_calld=%p: Status from LB server received. " + "Status = %d, details = '%s', (lb_call: %p), error '%s'", + grpclb_policy, lb_calld, lb_calld->lb_call_status_, status_details, lb_calld->lb_call_, grpc_error_string(error)); gpr_free(status_details); } @@ -1592,6 +1597,10 @@ void GrpcLb::CreateRoundRobinPolicyLocked(const Args& args) { this); return; } + if (grpc_lb_glb_trace.enabled()) { + gpr_log(GPR_INFO, "[grpclb %p] Created new RR policy %p", this, + rr_policy_.get()); + } // TODO(roth): We currently track this ref manually. Once the new // ClosureRef API is done, pass the RefCountedPtr<> along with the closure. auto self = Ref(DEBUG_LOCATION, "on_rr_reresolution_requested"); @@ -1685,10 +1694,6 @@ void GrpcLb::CreateOrUpdateRoundRobinPolicyLocked() { lb_policy_args.client_channel_factory = client_channel_factory(); lb_policy_args.args = args; CreateRoundRobinPolicyLocked(lb_policy_args); - if (grpc_lb_glb_trace.enabled()) { - gpr_log(GPR_INFO, "[grpclb %p] Created new RR policy %p", this, - rr_policy_.get()); - } } grpc_channel_args_destroy(args); } diff --git a/src/core/lib/security/credentials/jwt/jwt_verifier.cc b/src/core/lib/security/credentials/jwt/jwt_verifier.cc index c7d1b36ff0..cdef0f322a 100644 --- a/src/core/lib/security/credentials/jwt/jwt_verifier.cc +++ b/src/core/lib/security/credentials/jwt/jwt_verifier.cc @@ -31,7 +31,9 @@ #include <grpc/support/sync.h> extern "C" { +#include <openssl/bn.h> #include <openssl/pem.h> +#include <openssl/rsa.h> } #include "src/core/lib/gpr/string.h" diff --git a/src/core/tsi/ssl_transport_security.cc b/src/core/tsi/ssl_transport_security.cc index efaf733503..fb6ea19210 100644 --- a/src/core/tsi/ssl_transport_security.cc +++ b/src/core/tsi/ssl_transport_security.cc @@ -156,9 +156,13 @@ static unsigned long openssl_thread_id_cb(void) { #endif static void init_openssl(void) { +#if OPENSSL_API_COMPAT >= 0x10100000L + OPENSSL_init_ssl(0, NULL); +#else SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); +#endif #if OPENSSL_VERSION_NUMBER < 0x10100000 if (!CRYPTO_get_locking_callback()) { int num_locks = CRYPTO_num_locks(); @@ -1649,7 +1653,11 @@ tsi_result tsi_create_ssl_client_handshaker_factory_with_options( return TSI_INVALID_ARGUMENT; } +#if defined(OPENSSL_NO_TLS1_2_METHOD) || OPENSSL_API_COMPAT >= 0x10100000L + ssl_context = SSL_CTX_new(TLS_method()); +#else ssl_context = SSL_CTX_new(TLSv1_2_method()); +#endif if (ssl_context == nullptr) { gpr_log(GPR_ERROR, "Could not create ssl context."); return TSI_INVALID_ARGUMENT; @@ -1806,7 +1814,11 @@ tsi_result tsi_create_ssl_server_handshaker_factory_with_options( for (i = 0; i < options->num_key_cert_pairs; i++) { do { +#if defined(OPENSSL_NO_TLS1_2_METHOD) || OPENSSL_API_COMPAT >= 0x10100000L + impl->ssl_contexts[i] = SSL_CTX_new(TLS_method()); +#else impl->ssl_contexts[i] = SSL_CTX_new(TLSv1_2_method()); +#endif if (impl->ssl_contexts[i] == nullptr) { gpr_log(GPR_ERROR, "Could not create ssl context."); result = TSI_OUT_OF_RESOURCES; diff --git a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets index 5f76c03ce5..3fe1ccc918 100644 --- a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets +++ b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets @@ -22,9 +22,8 @@ <Target Name="gRPC_ResolvePluginFullPath" AfterTargets="Protobuf_ResolvePlatform"> <PropertyGroup> <!-- TODO(kkm): Do not use Protobuf_PackagedToolsPath, roll gRPC's own. --> - <!-- TODO(kkm): Do not package windows x64 builds (#13098). --> <gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' " - >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\$(gRPC_PluginFileName).exe</gRPC_PluginFullPath> + >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)\$(gRPC_PluginFileName).exe</gRPC_PluginFullPath> <gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' " >$(Protobuf_PackagedToolsPath)/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/$(gRPC_PluginFileName)</gRPC_PluginFullPath> </PropertyGroup> diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets index 1d233d23a8..26f9efb5a8 100644 --- a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets @@ -74,9 +74,8 @@ <!-- Next try OS and CPU resolved by ProtoToolsPlatform. --> <Protobuf_ToolsOs Condition=" '$(Protobuf_ToolsOs)' == '' ">$(_Protobuf_ToolsOs)</Protobuf_ToolsOs> <Protobuf_ToolsCpu Condition=" '$(Protobuf_ToolsCpu)' == '' ">$(_Protobuf_ToolsCpu)</Protobuf_ToolsCpu> - <!-- TODO(kkm): Do not package windows x64 builds (#13098). --> <Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' " - >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\protoc.exe</Protobuf_ProtocFullPath> + >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)\protoc.exe</Protobuf_ProtocFullPath> <Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' " >$(Protobuf_PackagedToolsPath)/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/protoc</Protobuf_ProtocFullPath> </PropertyGroup> diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi index 141116df5d..3c33b46dbb 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx.pxi @@ -49,7 +49,7 @@ cdef grpc_event _next(grpc_completion_queue *c_completion_queue, deadline): cdef _interpret_event(grpc_event c_event): cdef _Tag tag if c_event.type == GRPC_QUEUE_TIMEOUT: - # NOTE(nathaniel): For now we coopt ConnectivityEvent here. + # TODO(ericgribkoff) Do not coopt ConnectivityEvent here. return None, ConnectivityEvent(GRPC_QUEUE_TIMEOUT, False, None) elif c_event.type == GRPC_QUEUE_SHUTDOWN: # NOTE(nathaniel): For now we coopt ConnectivityEvent here. diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi index ce701724fd..e89e02b171 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx.pxi @@ -128,7 +128,10 @@ cdef class Server: with nogil: grpc_server_cancel_all_calls(self.c_server) - def __dealloc__(self): + # TODO(https://github.com/grpc/grpc/issues/17515) Determine what, if any, + # portion of this is safe to call from __dealloc__, and potentially remove + # backup_shutdown_queue. + def destroy(self): if self.c_server != NULL: if not self.is_started: pass @@ -146,4 +149,8 @@ cdef class Server: while not self.is_shutdown: time.sleep(0) grpc_server_destroy(self.c_server) - grpc_shutdown() + self.c_server = NULL + + def __dealloc(self): + if self.c_server == NULL: + grpc_shutdown() diff --git a/src/python/grpcio/grpc/_server.py b/src/python/grpcio/grpc/_server.py index 3bbfa47da5..eb750ef1a8 100644 --- a/src/python/grpcio/grpc/_server.py +++ b/src/python/grpcio/grpc/_server.py @@ -48,7 +48,7 @@ _CANCELLED = 'cancelled' _EMPTY_FLAGS = 0 -_UNEXPECTED_EXIT_SERVER_GRACE = 1.0 +_DEALLOCATED_SERVER_CHECK_PERIOD_S = 1.0 def _serialized_request(request_event): @@ -676,6 +676,9 @@ class _ServerState(object): self.rpc_states = set() self.due = set() + # A "volatile" flag to interrupt the daemon serving thread + self.server_deallocated = False + def _add_generic_handlers(state, generic_handlers): with state.lock: @@ -702,6 +705,7 @@ def _request_call(state): # TODO(https://github.com/grpc/grpc/issues/6597): delete this function. def _stop_serving(state): if not state.rpc_states and not state.due: + state.server.destroy() for shutdown_event in state.shutdown_events: shutdown_event.set() state.stage = _ServerStage.STOPPED @@ -715,49 +719,69 @@ def _on_call_completed(state): state.active_rpc_count -= 1 -def _serve(state): - while True: - event = state.completion_queue.poll() - if event.tag is _SHUTDOWN_TAG: +def _process_event_and_continue(state, event): + should_continue = True + if event.tag is _SHUTDOWN_TAG: + with state.lock: + state.due.remove(_SHUTDOWN_TAG) + if _stop_serving(state): + should_continue = False + elif event.tag is _REQUEST_CALL_TAG: + with state.lock: + state.due.remove(_REQUEST_CALL_TAG) + concurrency_exceeded = ( + state.maximum_concurrent_rpcs is not None and + state.active_rpc_count >= state.maximum_concurrent_rpcs) + rpc_state, rpc_future = _handle_call( + event, state.generic_handlers, state.interceptor_pipeline, + state.thread_pool, concurrency_exceeded) + if rpc_state is not None: + state.rpc_states.add(rpc_state) + if rpc_future is not None: + state.active_rpc_count += 1 + rpc_future.add_done_callback( + lambda unused_future: _on_call_completed(state)) + if state.stage is _ServerStage.STARTED: + _request_call(state) + elif _stop_serving(state): + should_continue = False + else: + rpc_state, callbacks = event.tag(event) + for callback in callbacks: + callable_util.call_logging_exceptions(callback, + 'Exception calling callback!') + if rpc_state is not None: with state.lock: - state.due.remove(_SHUTDOWN_TAG) + state.rpc_states.remove(rpc_state) if _stop_serving(state): - return - elif event.tag is _REQUEST_CALL_TAG: - with state.lock: - state.due.remove(_REQUEST_CALL_TAG) - concurrency_exceeded = ( - state.maximum_concurrent_rpcs is not None and - state.active_rpc_count >= state.maximum_concurrent_rpcs) - rpc_state, rpc_future = _handle_call( - event, state.generic_handlers, state.interceptor_pipeline, - state.thread_pool, concurrency_exceeded) - if rpc_state is not None: - state.rpc_states.add(rpc_state) - if rpc_future is not None: - state.active_rpc_count += 1 - rpc_future.add_done_callback( - lambda unused_future: _on_call_completed(state)) - if state.stage is _ServerStage.STARTED: - _request_call(state) - elif _stop_serving(state): - return - else: - rpc_state, callbacks = event.tag(event) - for callback in callbacks: - callable_util.call_logging_exceptions( - callback, 'Exception calling callback!') - if rpc_state is not None: - with state.lock: - state.rpc_states.remove(rpc_state) - if _stop_serving(state): - return + should_continue = False + return should_continue + + +def _serve(state): + while True: + timeout = time.time() + _DEALLOCATED_SERVER_CHECK_PERIOD_S + event = state.completion_queue.poll(timeout) + if state.server_deallocated: + _begin_shutdown_once(state) + if event.completion_type != cygrpc.CompletionType.queue_timeout: + if not _process_event_and_continue(state, event): + return # We want to force the deletion of the previous event # ~before~ we poll again; if the event has a reference # to a shutdown Call object, this can induce spinlock. event = None +def _begin_shutdown_once(state): + with state.lock: + if state.stage is _ServerStage.STARTED: + state.server.shutdown(state.completion_queue, _SHUTDOWN_TAG) + state.stage = _ServerStage.GRACE + state.shutdown_events = [] + state.due.add(_SHUTDOWN_TAG) + + def _stop(state, grace): with state.lock: if state.stage is _ServerStage.STOPPED: @@ -765,11 +789,7 @@ def _stop(state, grace): shutdown_event.set() return shutdown_event else: - if state.stage is _ServerStage.STARTED: - state.server.shutdown(state.completion_queue, _SHUTDOWN_TAG) - state.stage = _ServerStage.GRACE - state.shutdown_events = [] - state.due.add(_SHUTDOWN_TAG) + _begin_shutdown_once(state) shutdown_event = threading.Event() state.shutdown_events.append(shutdown_event) if grace is None: @@ -840,7 +860,9 @@ class _Server(grpc.Server): return _stop(self._state, grace) def __del__(self): - _stop(self._state, None) + # We can not grab a lock in __del__(), so set a flag to signal the + # serving daemon thread (if it exists) to initiate shutdown. + self._state.server_deallocated = True def create_server(thread_pool, generic_rpc_handlers, interceptors, options, diff --git a/src/python/grpcio_tests/setup.py b/src/python/grpcio_tests/setup.py index f9cb9d0cec..800b865da6 100644 --- a/src/python/grpcio_tests/setup.py +++ b/src/python/grpcio_tests/setup.py @@ -37,13 +37,19 @@ PACKAGE_DIRECTORIES = { } INSTALL_REQUIRES = ( - 'coverage>=4.0', 'enum34>=1.0.4', + 'coverage>=4.0', + 'enum34>=1.0.4', 'grpcio>={version}'.format(version=grpc_version.VERSION), - 'grpcio-channelz>={version}'.format(version=grpc_version.VERSION), + # TODO(https://github.com/pypa/warehouse/issues/5196) + # Re-enable it once we got the name back + # 'grpcio-channelz>={version}'.format(version=grpc_version.VERSION), 'grpcio-status>={version}'.format(version=grpc_version.VERSION), 'grpcio-tools>={version}'.format(version=grpc_version.VERSION), 'grpcio-health-checking>={version}'.format(version=grpc_version.VERSION), - 'oauth2client>=1.4.7', 'protobuf>=3.6.0', 'six>=1.10', 'google-auth>=1.0.0', + 'oauth2client>=1.4.7', + 'protobuf>=3.6.0', + 'six>=1.10', + 'google-auth>=1.0.0', 'requests>=2.14.2') if not PY3: diff --git a/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py b/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py index 8ca5189522..c63ff5cd84 100644 --- a/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py +++ b/src/python/grpcio_tests/tests/channelz/_channelz_servicer_test.py @@ -88,11 +88,10 @@ def _generate_channel_server_pairs(n): def _close_channel_server_pairs(pairs): for pair in pairs: pair.server.stop(None) - # TODO(ericgribkoff) This del should not be required - del pair.server pair.channel.close() +@unittest.skip('https://github.com/pypa/warehouse/issues/5196') class ChannelzServicerTest(unittest.TestCase): def _send_successful_unary_unary(self, idx): diff --git a/src/python/grpcio_tests/tests/tests.json b/src/python/grpcio_tests/tests/tests.json index b27e6f2693..f202a3f932 100644 --- a/src/python/grpcio_tests/tests/tests.json +++ b/src/python/grpcio_tests/tests/tests.json @@ -57,6 +57,7 @@ "unit._reconnect_test.ReconnectTest", "unit._resource_exhausted_test.ResourceExhaustedTest", "unit._rpc_test.RPCTest", + "unit._server_shutdown_test.ServerShutdown", "unit._server_ssl_cert_config_test.ServerSSLCertConfigFetcherParamsChecks", "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestCertConfigReuse", "unit._server_ssl_cert_config_test.ServerSSLCertReloadTestWithClientAuth", diff --git a/src/python/grpcio_tests/tests/unit/BUILD.bazel b/src/python/grpcio_tests/tests/unit/BUILD.bazel index 4f850220f8..1b462ec67a 100644 --- a/src/python/grpcio_tests/tests/unit/BUILD.bazel +++ b/src/python/grpcio_tests/tests/unit/BUILD.bazel @@ -28,6 +28,7 @@ GRPCIO_TESTS_UNIT = [ # TODO(ghostwriternr): To be added later. # "_server_ssl_cert_config_test.py", "_server_test.py", + "_server_shutdown_test.py", "_session_cache_test.py", ] @@ -50,6 +51,11 @@ py_library( ) py_library( + name = "_server_shutdown_scenarios", + srcs = ["_server_shutdown_scenarios.py"], +) + +py_library( name = "_thread_pool", srcs = ["_thread_pool.py"], ) @@ -70,6 +76,7 @@ py_library( ":resources", ":test_common", ":_exit_scenarios", + ":_server_shutdown_scenarios", ":_thread_pool", ":_from_grpc_import_star", "//src/python/grpcio_tests/tests/unit/framework/common", diff --git a/src/python/grpcio_tests/tests/unit/_server_shutdown_scenarios.py b/src/python/grpcio_tests/tests/unit/_server_shutdown_scenarios.py new file mode 100644 index 0000000000..1d1fdba11e --- /dev/null +++ b/src/python/grpcio_tests/tests/unit/_server_shutdown_scenarios.py @@ -0,0 +1,97 @@ +# Copyright 2018 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. +"""Defines a number of module-scope gRPC scenarios to test server shutdown.""" + +import argparse +import os +import threading +import time +import logging + +import grpc +from tests.unit import test_common + +from concurrent import futures +from six.moves import queue + +WAIT_TIME = 1000 + +REQUEST = b'request' +RESPONSE = b'response' + +SERVER_RAISES_EXCEPTION = 'server_raises_exception' +SERVER_DEALLOCATED = 'server_deallocated' +SERVER_FORK_CAN_EXIT = 'server_fork_can_exit' + +FORK_EXIT = '/test/ForkExit' + + +def fork_and_exit(request, servicer_context): + pid = os.fork() + if pid == 0: + os._exit(0) + return RESPONSE + + +class GenericHandler(grpc.GenericRpcHandler): + + def service(self, handler_call_details): + if handler_call_details.method == FORK_EXIT: + return grpc.unary_unary_rpc_method_handler(fork_and_exit) + else: + return None + + +def run_server(port_queue): + server = test_common.test_server() + port = server.add_insecure_port('[::]:0') + port_queue.put(port) + server.add_generic_rpc_handlers((GenericHandler(),)) + server.start() + # threading.Event.wait() does not exhibit the bug identified in + # https://github.com/grpc/grpc/issues/17093, sleep instead + time.sleep(WAIT_TIME) + + +def run_test(args): + if args.scenario == SERVER_RAISES_EXCEPTION: + server = test_common.test_server() + server.start() + raise Exception() + elif args.scenario == SERVER_DEALLOCATED: + server = test_common.test_server() + server.start() + server.__del__() + while server._state.stage != grpc._server._ServerStage.STOPPED: + pass + elif args.scenario == SERVER_FORK_CAN_EXIT: + port_queue = queue.Queue() + thread = threading.Thread(target=run_server, args=(port_queue,)) + thread.daemon = True + thread.start() + port = port_queue.get() + channel = grpc.insecure_channel('localhost:%d' % port) + multi_callable = channel.unary_unary(FORK_EXIT) + result, call = multi_callable.with_call(REQUEST, wait_for_ready=True) + os.wait() + else: + raise ValueError('unknown test scenario') + + +if __name__ == '__main__': + logging.basicConfig() + parser = argparse.ArgumentParser() + parser.add_argument('scenario', type=str) + args = parser.parse_args() + run_test(args) diff --git a/src/python/grpcio_tests/tests/unit/_server_shutdown_test.py b/src/python/grpcio_tests/tests/unit/_server_shutdown_test.py new file mode 100644 index 0000000000..47446d65a5 --- /dev/null +++ b/src/python/grpcio_tests/tests/unit/_server_shutdown_test.py @@ -0,0 +1,90 @@ +# Copyright 2018 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. +"""Tests clean shutdown of server on various interpreter exit conditions. + +The tests in this module spawn a subprocess for each test case, the +test is considered successful if it doesn't hang/timeout. +""" + +import atexit +import os +import subprocess +import sys +import threading +import unittest +import logging + +from tests.unit import _server_shutdown_scenarios + +SCENARIO_FILE = os.path.abspath( + os.path.join( + os.path.dirname(os.path.realpath(__file__)), + '_server_shutdown_scenarios.py')) +INTERPRETER = sys.executable +BASE_COMMAND = [INTERPRETER, SCENARIO_FILE] + +processes = [] +process_lock = threading.Lock() + + +# Make sure we attempt to clean up any +# processes we may have left running +def cleanup_processes(): + with process_lock: + for process in processes: + try: + process.kill() + except Exception: # pylint: disable=broad-except + pass + + +atexit.register(cleanup_processes) + + +def wait(process): + with process_lock: + processes.append(process) + process.wait() + + +class ServerShutdown(unittest.TestCase): + + # Currently we shut down a server (if possible) after the Python server + # instance is garbage collected. This behavior may change in the future. + def test_deallocated_server_stops(self): + process = subprocess.Popen( + BASE_COMMAND + [_server_shutdown_scenarios.SERVER_DEALLOCATED], + stdout=sys.stdout, + stderr=sys.stderr) + wait(process) + + def test_server_exception_exits(self): + process = subprocess.Popen( + BASE_COMMAND + [_server_shutdown_scenarios.SERVER_RAISES_EXCEPTION], + stdout=sys.stdout, + stderr=sys.stderr) + wait(process) + + @unittest.skipIf(os.name == 'nt', 'fork not supported on windows') + def test_server_fork_can_exit(self): + process = subprocess.Popen( + BASE_COMMAND + [_server_shutdown_scenarios.SERVER_FORK_CAN_EXIT], + stdout=sys.stdout, + stderr=sys.stderr) + wait(process) + + +if __name__ == '__main__': + logging.basicConfig() + unittest.main(verbosity=2) diff --git a/test/cpp/end2end/client_interceptors_end2end_test.cc b/test/cpp/end2end/client_interceptors_end2end_test.cc index 968b5d49d1..8abf4eb3f4 100644 --- a/test/cpp/end2end/client_interceptors_end2end_test.cc +++ b/test/cpp/end2end/client_interceptors_end2end_test.cc @@ -23,11 +23,11 @@ #include <grpcpp/client_context.h> #include <grpcpp/create_channel.h> #include <grpcpp/generic/generic_stub.h> -#include <grpcpp/impl/codegen/client_interceptor.h> #include <grpcpp/impl/codegen/proto_utils.h> #include <grpcpp/server.h> #include <grpcpp/server_builder.h> #include <grpcpp/server_context.h> +#include <grpcpp/support/client_interceptor.h> #include "src/proto/grpc/testing/echo.grpc.pb.h" #include "test/core/util/port.h" @@ -467,6 +467,28 @@ TEST_F(ClientInterceptorsEnd2endTest, EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20); } +TEST_F(ClientInterceptorsEnd2endTest, + ClientInterceptorFactoryAllowsNullptrReturn) { + ChannelArguments args; + DummyInterceptor::Reset(); + std::vector<std::unique_ptr<experimental::ClientInterceptorFactoryInterface>> + creators; + creators.push_back(std::unique_ptr<LoggingInterceptorFactory>( + new LoggingInterceptorFactory())); + // Add 20 dummy interceptors and 20 null interceptors + for (auto i = 0; i < 20; i++) { + creators.push_back(std::unique_ptr<DummyInterceptorFactory>( + new DummyInterceptorFactory())); + creators.push_back( + std::unique_ptr<NullInterceptorFactory>(new NullInterceptorFactory())); + } + auto channel = server_->experimental().InProcessChannelWithInterceptors( + args, std::move(creators)); + MakeCallbackCall(channel); + // Make sure all 20 dummy interceptors were run + EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20); +} + class ClientInterceptorsStreamingEnd2endTest : public ::testing::Test { protected: ClientInterceptorsStreamingEnd2endTest() { diff --git a/test/cpp/end2end/interceptors_util.h b/test/cpp/end2end/interceptors_util.h index d886e32494..659e613d2e 100644 --- a/test/cpp/end2end/interceptors_util.h +++ b/test/cpp/end2end/interceptors_util.h @@ -82,6 +82,22 @@ class DummyInterceptorFactory } }; +/* This interceptor factory returns nullptr on interceptor creation */ +class NullInterceptorFactory + : public experimental::ClientInterceptorFactoryInterface, + public experimental::ServerInterceptorFactoryInterface { + public: + virtual experimental::Interceptor* CreateClientInterceptor( + experimental::ClientRpcInfo* info) override { + return nullptr; + } + + virtual experimental::Interceptor* CreateServerInterceptor( + experimental::ServerRpcInfo* info) override { + return nullptr; + } +}; + class EchoTestServiceStreamingImpl : public EchoTestService::Service { public: ~EchoTestServiceStreamingImpl() override {} diff --git a/test/cpp/end2end/server_interceptors_end2end_test.cc b/test/cpp/end2end/server_interceptors_end2end_test.cc index 9460a7d6c6..53d8c4dc96 100644 --- a/test/cpp/end2end/server_interceptors_end2end_test.cc +++ b/test/cpp/end2end/server_interceptors_end2end_test.cc @@ -24,10 +24,10 @@ #include <grpcpp/create_channel.h> #include <grpcpp/generic/generic_stub.h> #include <grpcpp/impl/codegen/proto_utils.h> -#include <grpcpp/impl/codegen/server_interceptor.h> #include <grpcpp/server.h> #include <grpcpp/server_builder.h> #include <grpcpp/server_context.h> +#include <grpcpp/support/server_interceptor.h> #include "src/proto/grpc/testing/echo.grpc.pb.h" #include "test/core/util/port.h" @@ -176,9 +176,12 @@ class ServerInterceptorsEnd2endSyncUnaryTest : public ::testing::Test { creators.push_back( std::unique_ptr<experimental::ServerInterceptorFactoryInterface>( new LoggingInterceptorFactory())); + // Add 20 dummy interceptor factories and null interceptor factories for (auto i = 0; i < 20; i++) { creators.push_back(std::unique_ptr<DummyInterceptorFactory>( new DummyInterceptorFactory())); + creators.push_back(std::unique_ptr<NullInterceptorFactory>( + new NullInterceptorFactory())); } builder.experimental().SetInterceptorCreators(std::move(creators)); server_ = builder.BuildAndStart(); diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++ index a5f817997d..1ab3a394b9 100644 --- a/tools/doxygen/Doxyfile.c++ +++ b/tools/doxygen/Doxyfile.c++ @@ -1008,10 +1008,13 @@ include/grpcpp/support/async_unary_call.h \ include/grpcpp/support/byte_buffer.h \ include/grpcpp/support/channel_arguments.h \ include/grpcpp/support/client_callback.h \ +include/grpcpp/support/client_interceptor.h \ include/grpcpp/support/config.h \ +include/grpcpp/support/interceptor.h \ include/grpcpp/support/proto_buffer_reader.h \ include/grpcpp/support/proto_buffer_writer.h \ include/grpcpp/support/server_callback.h \ +include/grpcpp/support/server_interceptor.h \ include/grpcpp/support/slice.h \ include/grpcpp/support/status.h \ include/grpcpp/support/status_code_enum.h \ diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal index 1535aa06d3..5f488d5194 100644 --- a/tools/doxygen/Doxyfile.c++.internal +++ b/tools/doxygen/Doxyfile.c++.internal @@ -1010,10 +1010,13 @@ include/grpcpp/support/async_unary_call.h \ include/grpcpp/support/byte_buffer.h \ include/grpcpp/support/channel_arguments.h \ include/grpcpp/support/client_callback.h \ +include/grpcpp/support/client_interceptor.h \ include/grpcpp/support/config.h \ +include/grpcpp/support/interceptor.h \ include/grpcpp/support/proto_buffer_reader.h \ include/grpcpp/support/proto_buffer_writer.h \ include/grpcpp/support/server_callback.h \ +include/grpcpp/support/server_interceptor.h \ include/grpcpp/support/slice.h \ include/grpcpp/support/status.h \ include/grpcpp/support/status_code_enum.h \ diff --git a/tools/internal_ci/helper_scripts/prepare_build_macos_rc b/tools/internal_ci/helper_scripts/prepare_build_macos_rc index bafe0d98c1..d80bcbd183 100644 --- a/tools/internal_ci/helper_scripts/prepare_build_macos_rc +++ b/tools/internal_ci/helper_scripts/prepare_build_macos_rc @@ -19,14 +19,6 @@ launchctl limit maxfiles ulimit -a -# synchronize the clock -date -sudo systemsetup -setusingnetworktime off -sudo systemsetup -setnetworktimeserver "$( ipconfig getoption en0 server_identifier )" -sudo systemsetup -settimezone America/Los_Angeles -sudo systemsetup -setusingnetworktime on -date - # Add GCP credentials for BQ access pip install google-api-python-client oauth2client --user python export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/GrpcTesting-d0eeee2db331.json diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json index 290c1d5248..46e5d54c85 100644 --- a/tools/run_tests/generated/sources_and_headers.json +++ b/tools/run_tests/generated/sources_and_headers.json @@ -11261,10 +11261,13 @@ "include/grpcpp/support/byte_buffer.h", "include/grpcpp/support/channel_arguments.h", "include/grpcpp/support/client_callback.h", + "include/grpcpp/support/client_interceptor.h", "include/grpcpp/support/config.h", + "include/grpcpp/support/interceptor.h", "include/grpcpp/support/proto_buffer_reader.h", "include/grpcpp/support/proto_buffer_writer.h", "include/grpcpp/support/server_callback.h", + "include/grpcpp/support/server_interceptor.h", "include/grpcpp/support/slice.h", "include/grpcpp/support/status.h", "include/grpcpp/support/status_code_enum.h", @@ -11367,10 +11370,13 @@ "include/grpcpp/support/byte_buffer.h", "include/grpcpp/support/channel_arguments.h", "include/grpcpp/support/client_callback.h", + "include/grpcpp/support/client_interceptor.h", "include/grpcpp/support/config.h", + "include/grpcpp/support/interceptor.h", "include/grpcpp/support/proto_buffer_reader.h", "include/grpcpp/support/proto_buffer_writer.h", "include/grpcpp/support/server_callback.h", + "include/grpcpp/support/server_interceptor.h", "include/grpcpp/support/slice.h", "include/grpcpp/support/status.h", "include/grpcpp/support/status_code_enum.h", |