aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Jan Tattermusch <jtattermusch@google.com>2015-08-13 10:31:05 -0700
committerGravatar Jan Tattermusch <jtattermusch@google.com>2015-08-13 10:31:05 -0700
commit46e85b02cda4ad4d549e444b78d1e74a3021a9d9 (patch)
tree51bccc374623625a19a5fb53df60bc08c2c7713e
parent58d637444187f36181d3662e85e42aa1d23af92a (diff)
parent58d7310fbc291d2afa9cbb100544fc5c96cd3249 (diff)
Merge remote-tracking branch 'upstream/master' into csharp_upgrade_to_proto3
Conflicts: src/csharp/Grpc.Core/VersionInfo.cs src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj src/csharp/Grpc.IntegrationTesting/InteropClient.cs src/csharp/Grpc.IntegrationTesting/packages.config src/csharp/build_packages.bat
-rw-r--r--BUILD2
-rw-r--r--INSTALL1
-rw-r--r--Makefile4
-rw-r--r--build.json3
-rw-r--r--doc/connectivity-semantics-and-api.md2
-rw-r--r--doc/health-checking.md70
-rw-r--r--include/grpc++/auth_context.h32
-rw-r--r--include/grpc++/client_context.h4
-rw-r--r--include/grpc/census.h55
-rw-r--r--include/grpc/grpc.h39
-rw-r--r--src/compiler/objective_c_generator.cc4
-rw-r--r--src/core/channel/client_channel.c1
-rw-r--r--src/core/security/client_auth_filter.c15
-rw-r--r--src/core/security/google_default_credentials.c6
-rw-r--r--src/core/surface/call.c14
-rw-r--r--src/core/surface/version.c2
-rw-r--r--src/core/tsi/transport_security_interface.h2
-rw-r--r--src/cpp/client/channel.cc32
-rw-r--r--src/cpp/client/client_context.cc9
-rw-r--r--src/cpp/client/secure_credentials.cc9
-rw-r--r--src/cpp/common/auth_property_iterator.cc2
-rw-r--r--src/csharp/Grpc.Auth/GoogleCredential.cs125
-rw-r--r--src/csharp/Grpc.Auth/Grpc.Auth.csproj27
-rw-r--r--src/csharp/Grpc.Auth/OAuth2Interceptors.cs26
-rw-r--r--src/csharp/Grpc.Auth/app.config4
-rw-r--r--src/csharp/Grpc.Auth/packages.config4
-rw-r--r--src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs6
-rw-r--r--src/csharp/Grpc.Core.Tests/ChannelTest.cs6
-rw-r--r--src/csharp/Grpc.Core.Tests/ClientBaseTest.cs62
-rw-r--r--src/csharp/Grpc.Core.Tests/ClientServerTest.cs298
-rw-r--r--src/csharp/Grpc.Core.Tests/CompressionTest.cs128
-rw-r--r--src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs153
-rw-r--r--src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj5
-rw-r--r--src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs8
-rw-r--r--src/csharp/Grpc.Core.Tests/MockServiceHelper.cs244
-rw-r--r--src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs136
-rw-r--r--src/csharp/Grpc.Core.Tests/TimeoutsTest.cs152
-rw-r--r--src/csharp/Grpc.Core/CallInvocationDetails.cs72
-rw-r--r--src/csharp/Grpc.Core/CallOptions.cs109
-rw-r--r--src/csharp/Grpc.Core/Calls.cs46
-rw-r--r--src/csharp/Grpc.Core/Channel.cs24
-rw-r--r--src/csharp/Grpc.Core/ChannelOptions.cs6
-rw-r--r--src/csharp/Grpc.Core/ClientBase.cs44
-rw-r--r--src/csharp/Grpc.Core/CompressionLevel.cs (renamed from src/csharp/Grpc.Core/OperationFailedException.cs)26
-rw-r--r--src/csharp/Grpc.Core/ContextPropagationToken.cs171
-rw-r--r--src/csharp/Grpc.Core/Grpc.Core.csproj5
-rw-r--r--src/csharp/Grpc.Core/GrpcEnvironment.cs32
-rw-r--r--src/csharp/Grpc.Core/IAsyncStreamReader.cs2
-rw-r--r--src/csharp/Grpc.Core/IAsyncStreamWriter.cs8
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCall.cs75
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCallBase.cs14
-rw-r--r--src/csharp/Grpc.Core/Internal/AsyncCallServer.cs35
-rw-r--r--src/csharp/Grpc.Core/Internal/CallSafeHandle.cs41
-rw-r--r--src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs6
-rw-r--r--src/csharp/Grpc.Core/Internal/ClientRequestStream.cs23
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerCallHandler.cs17
-rw-r--r--src/csharp/Grpc.Core/Internal/ServerResponseStream.cs31
-rw-r--r--src/csharp/Grpc.Core/Internal/Timespec.cs2
-rw-r--r--src/csharp/Grpc.Core/KeyCertificatePair.cs4
-rw-r--r--src/csharp/Grpc.Core/Logging/ConsoleLogger.cs13
-rw-r--r--src/csharp/Grpc.Core/Logging/ILogger.cs6
-rw-r--r--src/csharp/Grpc.Core/Marshaller.cs23
-rw-r--r--src/csharp/Grpc.Core/Metadata.cs18
-rw-r--r--src/csharp/Grpc.Core/Method.cs60
-rw-r--r--src/csharp/Grpc.Core/RpcException.cs14
-rw-r--r--src/csharp/Grpc.Core/Server.cs2
-rw-r--r--src/csharp/Grpc.Core/ServerCallContext.cs56
-rw-r--r--src/csharp/Grpc.Core/ServerCredentials.cs2
-rw-r--r--src/csharp/Grpc.Core/ServerMethods.cs4
-rw-r--r--src/csharp/Grpc.Core/ServerPort.cs4
-rw-r--r--src/csharp/Grpc.Core/ServerServiceDefinition.cs8
-rw-r--r--src/csharp/Grpc.Core/Status.cs13
-rw-r--r--src/csharp/Grpc.Core/StatusCode.cs156
-rw-r--r--src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs9
-rw-r--r--src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs3
-rw-r--r--src/csharp/Grpc.Core/Utils/Preconditions.cs25
-rw-r--r--src/csharp/Grpc.Core/Version.cs36
-rw-r--r--src/csharp/Grpc.Core/VersionInfo.cs39
-rw-r--r--src/csharp/Grpc.Core/WriteOptions.cs (renamed from src/csharp/Grpc.Core/Utils/ExceptionHelper.cs)49
-rw-r--r--src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs32
-rw-r--r--src/csharp/Grpc.Examples/MathExamples.cs10
-rw-r--r--src/csharp/Grpc.Examples/MathServiceImpl.cs7
-rw-r--r--src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs8
-rw-r--r--src/csharp/Grpc.IntegrationTesting.Client/app.config4
-rw-r--r--src/csharp/Grpc.IntegrationTesting.Server/app.config4
-rw-r--r--src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj59
-rw-r--r--src/csharp/Grpc.IntegrationTesting/InteropClient.cs109
-rw-r--r--src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs4
-rw-r--r--src/csharp/Grpc.IntegrationTesting/app.config4
-rw-r--r--src/csharp/Grpc.IntegrationTesting/packages.config5
-rw-r--r--src/csharp/doc/README.md2
-rw-r--r--src/csharp/doc/grpc_csharp_public.shfbproj70
-rw-r--r--src/csharp/ext/grpc_csharp_ext.c74
-rw-r--r--src/node/binding.gyp3
-rw-r--r--src/node/examples/perf_test.js2
-rw-r--r--src/node/ext/call.cc19
-rw-r--r--src/node/ext/channel.cc25
-rw-r--r--src/node/ext/channel.h6
-rw-r--r--src/node/interop/interop_client.js33
-rw-r--r--src/node/src/client.js70
-rw-r--r--src/node/test/call_test.js5
-rw-r--r--src/node/test/end_to_end_test.js8
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+OAuth2.m8
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.h56
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.m100
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannel.m13
-rw-r--r--src/objective-c/RxLibrary/GRXBufferedPipe.h9
-rw-r--r--src/objective-c/RxLibrary/GRXForwardingWriter.h10
-rw-r--r--src/objective-c/RxLibrary/GRXForwardingWriter.m6
-rw-r--r--src/objective-c/RxLibrary/GRXImmediateWriter.h13
-rw-r--r--src/objective-c/RxLibrary/GRXWriteable.h8
-rw-r--r--src/objective-c/RxLibrary/GRXWriteable.m4
-rw-r--r--src/objective-c/RxLibrary/GRXWriter.h91
-rw-r--r--src/objective-c/tests/GRPCClientTests.m12
-rw-r--r--src/objective-c/tests/InteropTests.h3
-rw-r--r--src/objective-c/tests/InteropTests.m9
-rw-r--r--src/objective-c/tests/InteropTestsLocalCleartext.m (renamed from include/grpc++/auth_property_iterator.h)52
-rw-r--r--src/objective-c/tests/InteropTestsLocalSSL.m4
-rw-r--r--src/objective-c/tests/RxLibraryUnitTests.m14
-rw-r--r--src/objective-c/tests/Tests.xcodeproj/project.pbxproj10
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/module.c6
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types.h9
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/call.c8
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/channel.c54
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/types/server.c2
-rw-r--r--src/python/grpcio/grpc/_adapter/_c/utility.c21
-rw-r--r--src/python/grpcio/grpc/_adapter/_intermediary_low.py2
-rw-r--r--src/python/grpcio/grpc/_adapter/_low.py18
-rw-r--r--src/python/grpcio/grpc/_adapter/_types.py95
-rw-r--r--src/python/grpcio_test/grpc_interop/_interop_test_case.py3
-rw-r--r--src/python/grpcio_test/grpc_interop/methods.py23
-rw-r--r--src/python/grpcio_test/grpc_protoc_plugin/__init__.py30
-rw-r--r--src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py (renamed from test/compiler/python_plugin_test.py)20
-rw-r--r--src/python/grpcio_test/grpc_protoc_plugin/test.proto (renamed from test/compiler/test.proto)0
-rw-r--r--src/python/grpcio_test/grpc_test/_adapter/_low_test.py30
-rw-r--r--src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py34
-rw-r--r--src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py64
-rw-r--r--src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py45
-rw-r--r--src/python/grpcio_test/setup.py11
-rw-r--r--src/ruby/ext/grpc/rb_call.c14
-rw-r--r--src/ruby/ext/grpc/rb_channel.c145
-rwxr-xr-xsrc/ruby/grpc.gemspec13
-rw-r--r--src/ruby/lib/grpc/generic/client_stub.rb67
-rw-r--r--src/ruby/spec/call_spec.rb2
-rw-r--r--src/ruby/spec/channel_spec.rb4
-rw-r--r--src/ruby/spec/client_server_spec.rb19
-rw-r--r--src/ruby/spec/generic/active_call_spec.rb2
-rw-r--r--test/core/end2end/tests/bad_hostname.c2
-rw-r--r--test/core/security/verify_jwt.c2
-rw-r--r--test/cpp/end2end/end2end_test.cc2
-rw-r--r--test/cpp/qps/client.h82
-rw-r--r--test/cpp/qps/client_async.cc57
-rw-r--r--test/cpp/qps/client_sync.cc27
-rw-r--r--test/cpp/qps/driver.cc102
-rw-r--r--test/cpp/qps/driver.h16
-rw-r--r--test/cpp/qps/interarrival.h14
-rw-r--r--test/cpp/qps/qps_driver.cc1
-rw-r--r--test/cpp/qps/report.cc93
-rw-r--r--test/cpp/qps/server_async.cc40
-rw-r--r--tools/dockerfile/grpc_go/Dockerfile2
-rwxr-xr-xtools/dockerfile/grpc_go/build.sh34
-rw-r--r--tools/doxygen/Doxyfile.c++3
-rw-r--r--tools/doxygen/Doxyfile.c++.internal3
-rw-r--r--tools/doxygen/Doxyfile.core2
-rw-r--r--tools/doxygen/Doxyfile.core.internal2
-rwxr-xr-xtools/run_tests/run_interops.py18
-rwxr-xr-xtools/run_tests/run_interops_build.sh36
-rwxr-xr-xtools/run_tests/run_interops_test.sh11
-rwxr-xr-xtools/run_tests/run_python.sh1
-rw-r--r--tools/run_tests/sources_and_headers.json4
-rw-r--r--vsprojects/grpc++/grpc++.vcxproj1
-rw-r--r--vsprojects/grpc++/grpc++.vcxproj.filters3
-rw-r--r--vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj1
-rw-r--r--vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj.filters3
175 files changed, 3741 insertions, 1580 deletions
diff --git a/BUILD b/BUILD
index dcabd648e4..9eaabbbccd 100644
--- a/BUILD
+++ b/BUILD
@@ -690,7 +690,6 @@ cc_library(
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
@@ -778,7 +777,6 @@ cc_library(
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
diff --git a/INSTALL b/INSTALL
index 8a0a98ad2e..808166dfed 100644
--- a/INSTALL
+++ b/INSTALL
@@ -132,6 +132,7 @@ We will also need to make openssl and install it appropriately
$ cd <git directory>
$ cd third_party/openssl
+ $ ./config
$ sudo make install
$ cd ../../
diff --git a/Makefile b/Makefile
index 181194f78f..46e23db5ea 100644
--- a/Makefile
+++ b/Makefile
@@ -313,7 +313,7 @@ E = @echo
Q = @
endif
-VERSION = 0.10.0.0
+VERSION = 0.10.1.0
CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
@@ -4484,7 +4484,6 @@ PUBLIC_HEADERS_CXX += \
include/grpc++/async_generic_service.h \
include/grpc++/async_unary_call.h \
include/grpc++/auth_context.h \
- include/grpc++/auth_property_iterator.h \
include/grpc++/byte_buffer.h \
include/grpc++/channel_arguments.h \
include/grpc++/channel_interface.h \
@@ -4728,7 +4727,6 @@ PUBLIC_HEADERS_CXX += \
include/grpc++/async_generic_service.h \
include/grpc++/async_unary_call.h \
include/grpc++/auth_context.h \
- include/grpc++/auth_property_iterator.h \
include/grpc++/byte_buffer.h \
include/grpc++/channel_arguments.h \
include/grpc++/channel_interface.h \
diff --git a/build.json b/build.json
index 515cecdc5a..896cbee8c8 100644
--- a/build.json
+++ b/build.json
@@ -7,7 +7,7 @@
"version": {
"major": 0,
"minor": 10,
- "micro": 0,
+ "micro": 1,
"build": 0
}
},
@@ -33,7 +33,6 @@
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
diff --git a/doc/connectivity-semantics-and-api.md b/doc/connectivity-semantics-and-api.md
index 930dff265f..5427900394 100644
--- a/doc/connectivity-semantics-and-api.md
+++ b/doc/connectivity-semantics-and-api.md
@@ -38,7 +38,7 @@ because the server is not yet available), the channel may spend increasingly
large amounts of time in this state.
IDLE: This is the state where the channel is not even trying to create a
-connection because of a lack of new or pending RPCs. New channels MAY be created
+connection because of a lack of new or pending RPCs. New RPCs MAY be created
in this state. Any attempt to start an RPC on the channel will push the channel
out of this state to connecting. When there has been no RPC activity on a channel
for a specified IDLE_TIMEOUT, i.e., no new or pending (active) RPCs for this
diff --git a/doc/health-checking.md b/doc/health-checking.md
new file mode 100644
index 0000000000..0b3f9c6a03
--- /dev/null
+++ b/doc/health-checking.md
@@ -0,0 +1,70 @@
+GRPC Health Checking Protocol
+================================
+
+Health checks are used to probe whether the server is able to handle rpcs. The
+client-to-server health checking can happen from point to point or via some
+control system. A server may choose to reply “unhealthy” because it
+is not ready to take requests, it is shutting down or some other reason.
+The client can act accordingly if the response is not received within some time
+window or the response says unhealthy in it.
+
+
+A GRPC service is used as the health checking mechanism for both simple
+client-to-server scenario and other control systems such as load-balancing.
+Being a high
+level service provides some benefits. Firstly, since it is a GRPC service
+itself, doing a health check is in the same format as a normal rpc. Secondly,
+it has rich semantics such as per-service health status. Thirdly, as a GRPC
+service, it is able reuse all the existing billing, quota infrastructure, etc,
+and thus the server has full control over the access of the health checking
+service.
+
+## Service Definition
+
+The server should export a service defined in the following proto:
+
+```
+syntax = "proto3";
+
+package grpc.health.v1alpha;
+
+message HealthCheckRequest {
+ string service = 1;
+}
+
+message HealthCheckResponse {
+ enum ServingStatus {
+ UNKNOWN = 0;
+ SERVING = 1;
+ NOT_SERVING = 2;
+ }
+ ServingStatus status = 1;
+}
+
+service Health {
+ rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
+}
+```
+
+A client can query the server’s health status by calling the `Check` method, and
+a deadline should be set on the rpc. The client can optionally set the service
+name it wants to query for health status. The suggested format of service name
+is `package_names.ServiceName`, such as `grpc.health.v1alpha.Health`.
+
+The server should register all the services manually and set
+the individual status, including an empty service name and its status. For each
+request received, if the service name can be found in the registry,
+a response must be sent back with an `OK` status and the status field should be
+set to `SERVING` or `NOT_SERVING` accordingly. If the service name is not
+registered, the server returns a `NOT_FOUND` GRPC status.
+
+The server should use an empty string as the key for server’s
+overall health status, so that a client not interested in a specific service can
+query the server's status with an empty request. The server can just do exact
+matching of the service name without support of any kind of wildcard matching.
+However, the service owner has the freedom to implement more complicated
+matching semantics that both the client and server agree upon.
+
+A client can declare the server as unhealthy if the rpc is not finished after
+some amount of time. The client should be able to handle the case where server
+does not have the Health service.
diff --git a/include/grpc++/auth_context.h b/include/grpc++/auth_context.h
index c42105b927..f8ea8ad6f4 100644
--- a/include/grpc++/auth_context.h
+++ b/include/grpc++/auth_context.h
@@ -34,12 +34,42 @@
#ifndef GRPCXX_AUTH_CONTEXT_H
#define GRPCXX_AUTH_CONTEXT_H
+#include <iterator>
#include <vector>
-#include <grpc++/auth_property_iterator.h>
#include <grpc++/config.h>
+struct grpc_auth_context;
+struct grpc_auth_property;
+struct grpc_auth_property_iterator;
+
namespace grpc {
+class SecureAuthContext;
+
+typedef std::pair<grpc::string, grpc::string> AuthProperty;
+
+class AuthPropertyIterator
+ : public std::iterator<std::input_iterator_tag, const AuthProperty> {
+ public:
+ ~AuthPropertyIterator();
+ AuthPropertyIterator& operator++();
+ AuthPropertyIterator operator++(int);
+ bool operator==(const AuthPropertyIterator& rhs) const;
+ bool operator!=(const AuthPropertyIterator& rhs) const;
+ const AuthProperty operator*();
+
+ protected:
+ AuthPropertyIterator();
+ AuthPropertyIterator(const grpc_auth_property* property,
+ const grpc_auth_property_iterator* iter);
+ private:
+ friend class SecureAuthContext;
+ const grpc_auth_property* property_;
+ // The following items form a grpc_auth_property_iterator.
+ const grpc_auth_context* ctx_;
+ size_t index_;
+ const char* name_;
+};
class AuthContext {
public:
diff --git a/include/grpc++/client_context.h b/include/grpc++/client_context.h
index 34945f3282..d7fafac9b3 100644
--- a/include/grpc++/client_context.h
+++ b/include/grpc++/client_context.h
@@ -218,15 +218,11 @@ class ClientContext {
void set_call(grpc_call* call,
const std::shared_ptr<ChannelInterface>& channel);
- grpc_completion_queue* cq() { return cq_; }
- void set_cq(grpc_completion_queue* cq) { cq_ = cq; }
-
grpc::string authority() { return authority_; }
bool initial_metadata_received_;
std::shared_ptr<ChannelInterface> channel_;
grpc_call* call_;
- grpc_completion_queue* cq_;
gpr_timespec deadline_;
grpc::string authority_;
std::shared_ptr<Credentials> creds_;
diff --git a/include/grpc/census.h b/include/grpc/census.h
index 7603dfdce1..a18b997b79 100644
--- a/include/grpc/census.h
+++ b/include/grpc/census.h
@@ -104,6 +104,61 @@ int census_context_deserialize(const char *buffer, census_context **context);
* future census calls will result in undefined behavior. */
void census_context_destroy(census_context *context);
+/* Max number of characters in tag key */
+#define CENSUS_MAX_TAG_KEY_LENGTH 20
+/* Max number of tag value characters */
+#define CENSUS_MAX_TAG_VALUE_LENGTH 50
+
+/* A Census tag set is a collection of key:value string pairs; these form the
+ basis against which Census metrics will be recorded. Keys are unique within
+ a tag set. All contexts have an associated tag set. */
+typedef struct census_tag_set census_tag_set;
+
+/* Returns a pointer to a newly created, empty tag set. If size_hint > 0,
+ indicates that the tag set is intended to hold approximately that number
+ of tags. */
+census_tag_set *census_tag_set_create(size_t size_hint);
+
+/* Add a new tag key/value to an existing tag set; if the tag key already exists
+ in the tag set, then its value is overwritten with the new one. Can also be
+ used to delete a tag, by specifying a NULL value. If key is NULL, returns
+ the number of tags in the tag set.
+ Return values:
+ -1: invalid length key or value
+ non-negative value: the number of tags in the tag set. */
+int census_tag_set_add(census_tag_set *tags, const char *key,
+ const char *value);
+
+/* Destroys a tag set. This function must be called to prevent memory leaks.
+ Once called, the tag set cannot be used again. */
+void census_tag_set_destroy(census_tag_set *tags);
+
+/* Get a contexts tag set. */
+census_tag_set *census_context_tag_set(census_context *context);
+
+/* A read-only representation of a tag for use by census clients. */
+typedef struct {
+ size_t key_len; /* Number of bytes in tag key. */
+ const char *key; /* A pointer to the tag key. May not be null-terminated. */
+ size_t value_len; /* Number of bytes in tag value. */
+ const char *value; /* Pointer to the tag value. May not be null-terminated. */
+} census_tag_const;
+
+/* Used to iterate through a tag sets contents. */
+typedef struct census_tag_set_iterator census_tag_set_iterator;
+
+/* Open a tag set for iteration. The tag set must not be modified while
+ iteration is ongoing. Returns an iterator for use in following functions. */
+census_tag_set_iterator *census_tag_set_open(census_tag_set *tags);
+
+/* Get the next tag in the tag set, by writing into the 'tag' argument. Returns
+ 1 if there is a "next" tag, 0 if there are no more tags. */
+int census_tag_set_next(census_tag_set_iterator *it, census_tag_const *tag);
+
+/* Close an iterator opened by census_tag_set_open(). The iterator will be
+ invalidated, and should not be used once close is called. */
+void census_tag_set_close(census_tag_set_iterator *it);
+
/* A census statistic to be recorded comprises two parts: an ID for the
* particular statistic and the value to be recorded against it. */
typedef struct {
diff --git a/include/grpc/grpc.h b/include/grpc/grpc.h
index bf340e81ca..5915dda2ba 100644
--- a/include/grpc/grpc.h
+++ b/include/grpc/grpc.h
@@ -181,7 +181,9 @@ typedef enum grpc_call_error {
GRPC_CALL_ERROR_INVALID_MESSAGE,
/** completion queue for notification has not been registered with the
server */
- GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE
+ GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE,
+ /** this batch of operations leads to more operations than allowed */
+ GRPC_CALL_ERROR_BATCH_TOO_BIG
} grpc_call_error;
/* Write Flags: */
@@ -256,31 +258,44 @@ void grpc_call_details_destroy(grpc_call_details *details);
typedef enum {
/** Send initial metadata: one and only one instance MUST be sent for each
- call, unless the call was cancelled - in which case this can be skipped */
+ call, unless the call was cancelled - in which case this can be skipped.
+ This op completes after all bytes of metadata have been accepted by
+ outgoing flow control. */
GRPC_OP_SEND_INITIAL_METADATA = 0,
- /** Send a message: 0 or more of these operations can occur for each call */
+ /** Send a message: 0 or more of these operations can occur for each call.
+ This op completes after all bytes for the message have been accepted by
+ outgoing flow control. */
GRPC_OP_SEND_MESSAGE,
/** Send a close from the client: one and only one instance MUST be sent from
the client, unless the call was cancelled - in which case this can be
- skipped */
+ skipped.
+ This op completes after all bytes for the call (including the close)
+ have passed outgoing flow control. */
GRPC_OP_SEND_CLOSE_FROM_CLIENT,
/** Send status from the server: one and only one instance MUST be sent from
the server unless the call was cancelled - in which case this can be
- skipped */
+ skipped.
+ This op completes after all bytes for the call (including the status)
+ have passed outgoing flow control. */
GRPC_OP_SEND_STATUS_FROM_SERVER,
/** Receive initial metadata: one and only one MUST be made on the client,
- must not be made on the server */
+ must not be made on the server.
+ This op completes after all initial metadata has been read from the
+ peer. */
GRPC_OP_RECV_INITIAL_METADATA,
- /** Receive a message: 0 or more of these operations can occur for each call
- */
+ /** Receive a message: 0 or more of these operations can occur for each call.
+ This op completes after all bytes of the received message have been
+ read, or after a half-close has been received on this call. */
GRPC_OP_RECV_MESSAGE,
/** Receive status on the client: one and only one must be made on the client.
- This operation always succeeds, meaning ops paired with this operation
- will also appear to succeed, even though they may not have. In that case
- the status will indicate some failure. */
+ This operation always succeeds, meaning ops paired with this operation
+ will also appear to succeed, even though they may not have. In that case
+ the status will indicate some failure.
+ This op completes after all activity on the call has completed. */
GRPC_OP_RECV_STATUS_ON_CLIENT,
/** Receive close on the server: one and only one must be made on the
- server */
+ server.
+ This op completes after the close has been received by the server. */
GRPC_OP_RECV_CLOSE_ON_SERVER
} grpc_op_type;
diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc
index 69b3805bb1..483c6573a8 100644
--- a/src/compiler/objective_c_generator.cc
+++ b/src/compiler/objective_c_generator.cc
@@ -154,9 +154,9 @@ void PrintAdvancedImplementation(Printer *printer,
printer->Print(" responsesWriteable:[GRXWriteable ");
if (method->server_streaming()) {
- printer->Print("writeableWithStreamHandler:eventHandler]];\n");
+ printer->Print("writeableWithEventHandler:eventHandler]];\n");
} else {
- printer->Print("writeableWithSingleValueHandler:handler]];\n");
+ printer->Print("writeableWithSingleHandler:handler]];\n");
}
printer->Print("}\n");
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c
index a293c93ec6..6c2e6b38a8 100644
--- a/src/core/channel/client_channel.c
+++ b/src/core/channel/client_channel.c
@@ -527,6 +527,7 @@ static void cc_on_config_changed(void *arg, int iomgr_success) {
}
if (old_lb_policy != NULL) {
+ grpc_lb_policy_shutdown(old_lb_policy);
GRPC_LB_POLICY_UNREF(old_lb_policy, "channel");
}
diff --git a/src/core/security/client_auth_filter.c b/src/core/security/client_auth_filter.c
index 0e699874bc..410852da52 100644
--- a/src/core/security/client_auth_filter.c
+++ b/src/core/security/client_auth_filter.c
@@ -75,11 +75,11 @@ typedef struct {
grpc_mdstr *status_key;
} channel_data;
-static void bubble_up_error(grpc_call_element *elem, const char *error_msg) {
+static void bubble_up_error(grpc_call_element *elem, grpc_status_code status,
+ const char *error_msg) {
call_data *calld = elem->call_data;
gpr_log(GPR_ERROR, "Client side authentication failure: %s", error_msg);
- grpc_transport_stream_op_add_cancellation(&calld->op,
- GRPC_STATUS_UNAUTHENTICATED);
+ grpc_transport_stream_op_add_cancellation(&calld->op, status);
grpc_call_next_op(elem, &calld->op);
}
@@ -94,7 +94,8 @@ static void on_credentials_metadata(void *user_data,
grpc_metadata_batch *mdb;
size_t i;
if (status != GRPC_CREDENTIALS_OK) {
- bubble_up_error(elem, "Credentials failed to get metadata.");
+ bubble_up_error(elem, GRPC_STATUS_UNAUTHENTICATED,
+ "Credentials failed to get metadata.");
return;
}
GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT);
@@ -154,7 +155,7 @@ static void send_security_metadata(grpc_call_element *elem,
if (channel_creds_has_md && call_creds_has_md) {
calld->creds = grpc_composite_credentials_create(channel_creds, ctx->creds);
if (calld->creds == NULL) {
- bubble_up_error(elem,
+ bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT,
"Incompatible credentials set on channel and call.");
return;
}
@@ -182,7 +183,7 @@ static void on_host_checked(void *user_data, grpc_security_status status) {
char *error_msg;
gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.",
grpc_mdstr_as_c_string(calld->host));
- bubble_up_error(elem, error_msg);
+ bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg);
gpr_free(error_msg);
}
}
@@ -252,7 +253,7 @@ static void auth_start_transport_op(grpc_call_element *elem,
gpr_asprintf(&error_msg,
"Invalid host %s set in :authority metadata.",
call_host);
- bubble_up_error(elem, error_msg);
+ bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg);
gpr_free(error_msg);
}
return; /* early exit */
diff --git a/src/core/security/google_default_credentials.c b/src/core/security/google_default_credentials.c
index f368819597..d1f228665f 100644
--- a/src/core/security/google_default_credentials.c
+++ b/src/core/security/google_default_credentials.c
@@ -84,6 +84,8 @@ static void on_compute_engine_detection_http_response(
gpr_mu_unlock(GRPC_POLLSET_MU(&detector->pollset));
}
+static void destroy_pollset(void *p) { grpc_pollset_destroy(p); }
+
static int is_stack_running_on_compute_engine(void) {
compute_engine_detector detector;
grpc_httpcli_request request;
@@ -114,12 +116,12 @@ static int is_stack_running_on_compute_engine(void) {
while (!detector.is_done) {
grpc_pollset_worker worker;
grpc_pollset_work(&detector.pollset, &worker,
- gpr_inf_future(GPR_CLOCK_REALTIME));
+ gpr_inf_future(GPR_CLOCK_MONOTONIC));
}
gpr_mu_unlock(GRPC_POLLSET_MU(&detector.pollset));
grpc_httpcli_context_destroy(&context);
- grpc_pollset_destroy(&detector.pollset);
+ grpc_pollset_shutdown(&detector.pollset, destroy_pollset, &detector.pollset);
return detector.success;
}
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 6e566e6a8f..5839d3ac2e 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -1539,6 +1539,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
/* Flag validation: currently allow no flags */
if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_INITIAL_METADATA;
req->data.send_metadata.count = op->data.send_initial_metadata.count;
req->data.send_metadata.metadata =
@@ -1553,6 +1554,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
return GRPC_CALL_ERROR_INVALID_MESSAGE;
}
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_MESSAGE;
req->data.send_message = op->data.send_message;
req->flags = op->flags;
@@ -1564,6 +1566,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
return GRPC_CALL_ERROR_NOT_ON_SERVER;
}
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_CLOSE;
req->flags = op->flags;
break;
@@ -1574,6 +1577,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
return GRPC_CALL_ERROR_NOT_ON_CLIENT;
}
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_TRAILING_METADATA;
req->flags = op->flags;
req->data.send_metadata.count =
@@ -1581,6 +1585,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
req->data.send_metadata.metadata =
op->data.send_status_from_server.trailing_metadata;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_STATUS;
req->data.send_status.code = op->data.send_status_from_server.status;
req->data.send_status.details =
@@ -1590,6 +1595,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
op->data.send_status_from_server.status_details, 0)
: NULL;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_SEND_CLOSE;
break;
case GRPC_OP_RECV_INITIAL_METADATA:
@@ -1599,6 +1605,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
return GRPC_CALL_ERROR_NOT_ON_SERVER;
}
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_INITIAL_METADATA;
req->data.recv_metadata = op->data.recv_initial_metadata;
req->data.recv_metadata->count = 0;
@@ -1608,6 +1615,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
/* Flag validation: currently allow no flags */
if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_MESSAGE;
req->data.recv_message = op->data.recv_message;
req->flags = op->flags;
@@ -1619,22 +1627,26 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
return GRPC_CALL_ERROR_NOT_ON_SERVER;
}
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_STATUS;
req->flags = op->flags;
req->data.recv_status.set_value = set_status_value_directly;
req->data.recv_status.user_data = op->data.recv_status_on_client.status;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_STATUS_DETAILS;
req->data.recv_status_details.details =
op->data.recv_status_on_client.status_details;
req->data.recv_status_details.details_capacity =
op->data.recv_status_on_client.status_details_capacity;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_TRAILING_METADATA;
req->data.recv_metadata =
op->data.recv_status_on_client.trailing_metadata;
req->data.recv_metadata->count = 0;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_CLOSE;
finish_func = finish_batch_with_close;
break;
@@ -1642,12 +1654,14 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops,
/* Flag validation: currently allow no flags */
if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_STATUS;
req->flags = op->flags;
req->data.recv_status.set_value = set_cancelled_value;
req->data.recv_status.user_data =
op->data.recv_close_on_server.cancelled;
req = &reqs[out++];
+ if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG;
req->op = GRPC_IOREQ_RECV_CLOSE;
finish_func = finish_batch_with_close;
break;
diff --git a/src/core/surface/version.c b/src/core/surface/version.c
index 4f5d648371..d7aaba3868 100644
--- a/src/core/surface/version.c
+++ b/src/core/surface/version.c
@@ -37,5 +37,5 @@
#include <grpc/grpc.h>
const char *grpc_version_string(void) {
- return "0.10.0.0";
+ return "0.10.1.0";
}
diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h
index 936b0c25b0..e27e6b9fc9 100644
--- a/src/core/tsi/transport_security_interface.h
+++ b/src/core/tsi/transport_security_interface.h
@@ -158,6 +158,8 @@ tsi_result tsi_frame_protector_protect_flush(
value is expected to be at most max_protected_frame_size minus overhead
which means that max_protected_frame_size is a safe bet. The output value
is the number of bytes actually written.
+ If *unprotected_bytes_size is unchanged, there may be more data remaining
+ to unprotect, and the caller should call this function again.
- This method returns TSI_OK in case of success. Success includes cases where
there is not enough data to output a frame in which case
diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc
index af7366eb01..1c2eecf786 100644
--- a/src/cpp/client/channel.cc
+++ b/src/cpp/client/channel.cc
@@ -61,19 +61,25 @@ Channel::~Channel() { grpc_channel_destroy(c_channel_); }
Call Channel::CreateCall(const RpcMethod& method, ClientContext* context,
CompletionQueue* cq) {
- const char* host_str = host_.empty() ? NULL : host_.c_str();
- auto c_call = method.channel_tag() && context->authority().empty()
- ? grpc_channel_create_registered_call(
- c_channel_, context->propagate_from_call_,
- context->propagation_options_.c_bitmask(), cq->cq(),
- method.channel_tag(), context->raw_deadline())
- : grpc_channel_create_call(
- c_channel_, context->propagate_from_call_,
- context->propagation_options_.c_bitmask(), cq->cq(),
- method.name(), context->authority().empty()
- ? host_str
- : context->authority().c_str(),
- context->raw_deadline());
+ const bool kRegistered = method.channel_tag() && context->authority().empty();
+ grpc_call* c_call = NULL;
+ if (kRegistered) {
+ c_call = grpc_channel_create_registered_call(
+ c_channel_, context->propagate_from_call_,
+ context->propagation_options_.c_bitmask(), cq->cq(),
+ method.channel_tag(), context->raw_deadline());
+ } else {
+ const char* host_str = NULL;
+ if (!context->authority().empty()) {
+ host_str = context->authority().c_str();
+ } else if (!host_.empty()) {
+ host_str = host_.c_str();
+ }
+ c_call = grpc_channel_create_call(c_channel_, context->propagate_from_call_,
+ context->propagation_options_.c_bitmask(),
+ cq->cq(), method.name(), host_str,
+ context->raw_deadline());
+ }
grpc_census_call_set_context(c_call, context->census_context());
GRPC_TIMER_MARK(GRPC_PTAG_CPP_CALL_CREATED, c_call);
context->set_call(c_call, shared_from_this());
diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc
index 1ed2d38961..dd86e7b108 100644
--- a/src/cpp/client/client_context.cc
+++ b/src/cpp/client/client_context.cc
@@ -48,7 +48,6 @@ namespace grpc {
ClientContext::ClientContext()
: initial_metadata_received_(false),
call_(nullptr),
- cq_(nullptr),
deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)),
propagate_from_call_(nullptr) {}
@@ -56,14 +55,6 @@ ClientContext::~ClientContext() {
if (call_) {
grpc_call_destroy(call_);
}
- if (cq_) {
- // Drain cq_.
- grpc_completion_queue_shutdown(cq_);
- while (grpc_completion_queue_next(cq_, gpr_inf_future(GPR_CLOCK_REALTIME))
- .type != GRPC_QUEUE_SHUTDOWN)
- ;
- grpc_completion_queue_destroy(cq_);
- }
}
std::unique_ptr<ClientContext> ClientContext::FromServerContext(
diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc
index 2d6114e06b..6cd6b77fcf 100644
--- a/src/cpp/client/secure_credentials.cc
+++ b/src/cpp/client/secure_credentials.cc
@@ -34,6 +34,7 @@
#include <grpc/support/log.h>
#include <grpc++/channel_arguments.h>
+#include <grpc++/impl/grpc_library.h>
#include "src/cpp/client/channel.h"
#include "src/cpp/client/secure_credentials.h"
@@ -61,12 +62,14 @@ std::shared_ptr<Credentials> WrapCredentials(grpc_credentials* creds) {
} // namespace
std::shared_ptr<Credentials> GoogleDefaultCredentials() {
+ GrpcLibrary init; // To call grpc_init().
return WrapCredentials(grpc_google_default_credentials_create());
}
// Builds SSL Credentials given SSL specific options
std::shared_ptr<Credentials> SslCredentials(
const SslCredentialsOptions& options) {
+ GrpcLibrary init; // To call grpc_init().
grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {
options.pem_private_key.c_str(), options.pem_cert_chain.c_str()};
@@ -78,6 +81,7 @@ std::shared_ptr<Credentials> SslCredentials(
// Builds credentials for use when running in GCE
std::shared_ptr<Credentials> ComputeEngineCredentials() {
+ GrpcLibrary init; // To call grpc_init().
return WrapCredentials(grpc_compute_engine_credentials_create());
}
@@ -85,6 +89,7 @@ std::shared_ptr<Credentials> ComputeEngineCredentials() {
std::shared_ptr<Credentials> ServiceAccountCredentials(
const grpc::string& json_key, const grpc::string& scope,
long token_lifetime_seconds) {
+ GrpcLibrary init; // To call grpc_init().
if (token_lifetime_seconds <= 0) {
gpr_log(GPR_ERROR,
"Trying to create ServiceAccountCredentials "
@@ -100,6 +105,7 @@ std::shared_ptr<Credentials> ServiceAccountCredentials(
// Builds JWT credentials.
std::shared_ptr<Credentials> ServiceAccountJWTAccessCredentials(
const grpc::string& json_key, long token_lifetime_seconds) {
+ GrpcLibrary init; // To call grpc_init().
if (token_lifetime_seconds <= 0) {
gpr_log(GPR_ERROR,
"Trying to create JWTCredentials with non-positive lifetime");
@@ -114,6 +120,7 @@ std::shared_ptr<Credentials> ServiceAccountJWTAccessCredentials(
// Builds refresh token credentials.
std::shared_ptr<Credentials> RefreshTokenCredentials(
const grpc::string& json_refresh_token) {
+ GrpcLibrary init; // To call grpc_init().
return WrapCredentials(
grpc_refresh_token_credentials_create(json_refresh_token.c_str()));
}
@@ -121,6 +128,7 @@ std::shared_ptr<Credentials> RefreshTokenCredentials(
// Builds access token credentials.
std::shared_ptr<Credentials> AccessTokenCredentials(
const grpc::string& access_token) {
+ GrpcLibrary init; // To call grpc_init().
return WrapCredentials(
grpc_access_token_credentials_create(access_token.c_str()));
}
@@ -129,6 +137,7 @@ std::shared_ptr<Credentials> AccessTokenCredentials(
std::shared_ptr<Credentials> IAMCredentials(
const grpc::string& authorization_token,
const grpc::string& authority_selector) {
+ GrpcLibrary init; // To call grpc_init().
return WrapCredentials(grpc_iam_credentials_create(
authorization_token.c_str(), authority_selector.c_str()));
}
diff --git a/src/cpp/common/auth_property_iterator.cc b/src/cpp/common/auth_property_iterator.cc
index e706c6c921..ba88983515 100644
--- a/src/cpp/common/auth_property_iterator.cc
+++ b/src/cpp/common/auth_property_iterator.cc
@@ -31,7 +31,7 @@
*
*/
-#include <grpc++/auth_property_iterator.h>
+#include <grpc++/auth_context.h>
#include <grpc/grpc_security.h>
diff --git a/src/csharp/Grpc.Auth/GoogleCredential.cs b/src/csharp/Grpc.Auth/GoogleCredential.cs
deleted file mode 100644
index 9936cf583c..0000000000
--- a/src/csharp/Grpc.Auth/GoogleCredential.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-#region Copyright notice and license
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-// * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-// * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Security.Cryptography;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Google.Apis.Auth.OAuth2;
-using Google.Apis.Auth.OAuth2.Responses;
-using Newtonsoft.Json.Linq;
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Security;
-
-namespace Grpc.Auth
-{
- // TODO(jtattermusch): Remove this class once possible.
- /// <summary>
- /// A temporary placeholder for Google credential from
- /// Google Auth library for .NET. It emulates the usage pattern
- /// for Usable auth.
- /// </summary>
- public class GoogleCredential
- {
- private const string GoogleApplicationCredentialsEnvName = "GOOGLE_APPLICATION_CREDENTIALS";
- private const string ClientEmailFieldName = "client_email";
- private const string PrivateKeyFieldName = "private_key";
-
- private ServiceCredential credential;
-
- private GoogleCredential(ServiceCredential credential)
- {
- this.credential = credential;
- }
-
- public static GoogleCredential GetApplicationDefault()
- {
- return new GoogleCredential(null);
- }
-
- public bool IsCreateScopedRequired
- {
- get
- {
- return true;
- }
- }
-
- public GoogleCredential CreateScoped(IEnumerable<string> scopes)
- {
- var credsPath = Environment.GetEnvironmentVariable(GoogleApplicationCredentialsEnvName);
- if (credsPath == null)
- {
- // Default to ComputeCredentials if path to JSON key is not set.
- // ComputeCredential is not scoped actually, but for our use case it's
- // fine to treat is as such.
- return new GoogleCredential(new ComputeCredential(new ComputeCredential.Initializer()));
- }
-
- JObject jsonCredentialParameters = JObject.Parse(File.ReadAllText(credsPath));
- string clientEmail = jsonCredentialParameters.GetValue(ClientEmailFieldName).Value<string>();
- string privateKeyString = jsonCredentialParameters.GetValue(PrivateKeyFieldName).Value<string>();
-
- var serviceCredential = new ServiceAccountCredential(
- new ServiceAccountCredential.Initializer(clientEmail)
- {
- Scopes = scopes,
- }.FromPrivateKey(privateKeyString));
- return new GoogleCredential(serviceCredential);
- }
-
- public Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken)
- {
- return credential.RequestAccessTokenAsync(taskCancellationToken);
- }
-
- public TokenResponse Token
- {
- get
- {
- return credential.Token;
- }
- }
-
- internal ServiceCredential InternalCredential
- {
- get
- {
- return credential;
- }
- }
- }
-}
diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
index 8e5036832d..930a34b0c3 100644
--- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj
+++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj
@@ -11,7 +11,7 @@
<AssemblyName>Grpc.Auth</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<DocumentationFile>bin\$(Configuration)\Grpc.Auth.Xml</DocumentationFile>
- <NuGetPackageImportStamp>9b408026</NuGetPackageImportStamp>
+ <NuGetPackageImportStamp>4f8487a9</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -41,28 +41,32 @@
<AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
- <Reference Include="BouncyCastle.Crypto">
+ <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Auth, Version=1.9.2.27817, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.dll</HintPath>
+ <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.2.27820, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
+ <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
</Reference>
- <Reference Include="Google.Apis.Core, Version=1.9.2.27816, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\Google.Apis.Core.1.9.2\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
+ <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks">
+ <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions">
+ <Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop">
+ <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -87,7 +91,6 @@
<Link>Version.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="GoogleCredential.cs" />
<Compile Include="OAuth2Interceptors.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
diff --git a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs
index cc9d2c175f..d628a83246 100644
--- a/src/csharp/Grpc.Auth/OAuth2Interceptors.cs
+++ b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs
@@ -54,7 +54,7 @@ namespace Grpc.Auth
/// </summary>
public static MetadataInterceptorDelegate FromCredential(GoogleCredential googleCredential)
{
- var interceptor = new OAuth2Interceptor(googleCredential.InternalCredential, SystemClock.Default);
+ var interceptor = new OAuth2Interceptor(googleCredential, SystemClock.Default);
return new MetadataInterceptorDelegate(interceptor.InterceptHeaders);
}
@@ -66,7 +66,7 @@ namespace Grpc.Auth
public static MetadataInterceptorDelegate FromAccessToken(string oauth2Token)
{
Preconditions.CheckNotNull(oauth2Token);
- return new MetadataInterceptorDelegate((metadata) =>
+ return new MetadataInterceptorDelegate((authUri, metadata) =>
{
metadata.Add(OAuth2Interceptor.CreateBearerTokenHeader(oauth2Token));
});
@@ -80,10 +80,10 @@ namespace Grpc.Auth
private const string AuthorizationHeader = "Authorization";
private const string Schema = "Bearer";
- private ServiceCredential credential;
+ private ITokenAccess credential;
private IClock clock;
- public OAuth2Interceptor(ServiceCredential credential, IClock clock)
+ public OAuth2Interceptor(ITokenAccess credential, IClock clock)
{
this.credential = credential;
this.clock = clock;
@@ -94,23 +94,15 @@ namespace Grpc.Auth
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
- public string GetAccessToken(CancellationToken cancellationToken)
+ public string GetAccessToken(string authUri, CancellationToken cancellationToken)
{
- if (credential.Token == null || credential.Token.IsExpired(clock))
- {
- // TODO(jtattermusch): Parallel requests will spawn multiple requests to refresh the token once the token expires.
- // TODO(jtattermusch): Rethink synchronous wait to obtain the result.
- if (!credential.RequestAccessTokenAsync(cancellationToken).Result)
- {
- throw new InvalidOperationException("The access token has expired but we can't refresh it");
- }
- }
- return credential.Token.AccessToken;
+ // TODO(jtattermusch): Rethink synchronous wait to obtain the result.
+ return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken: cancellationToken).GetAwaiter().GetResult();
}
- public void InterceptHeaders(Metadata metadata)
+ public void InterceptHeaders(string authUri, Metadata metadata)
{
- var accessToken = GetAccessToken(CancellationToken.None);
+ var accessToken = GetAccessToken(authUri, CancellationToken.None);
metadata.Add(CreateBearerTokenHeader(accessToken));
}
diff --git a/src/csharp/Grpc.Auth/app.config b/src/csharp/Grpc.Auth/app.config
index 0a82bb4f16..84d7534d65 100644
--- a/src/csharp/Grpc.Auth/app.config
+++ b/src/csharp/Grpc.Auth/app.config
@@ -10,6 +10,10 @@
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" />
+ </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> \ No newline at end of file
diff --git a/src/csharp/Grpc.Auth/packages.config b/src/csharp/Grpc.Auth/packages.config
index 29be953bf3..7a02c95db9 100644
--- a/src/csharp/Grpc.Auth/packages.config
+++ b/src/csharp/Grpc.Auth/packages.config
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
- <package id="Google.Apis.Auth" version="1.9.2" targetFramework="net45" />
- <package id="Google.Apis.Core" version="1.9.2" targetFramework="net45" />
+ <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" />
+ <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
diff --git a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
index df09857efe..52be77c846 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs
@@ -67,9 +67,9 @@ namespace Grpc.Core.Internal.Tests
[Test]
public void ConstructorPreconditions()
{
- Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption(null, "abc"); });
- Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption(null, 1); });
- Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption("abc", null); });
+ Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption(null, "abc"); });
+ Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption(null, 1); });
+ Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption("abc", null); });
}
[Test]
diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
index 60b45176e5..2787572924 100644
--- a/src/csharp/Grpc.Core.Tests/ChannelTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs
@@ -50,7 +50,7 @@ namespace Grpc.Core.Tests
[Test]
public void Constructor_RejectsInvalidParams()
{
- Assert.Throws(typeof(NullReferenceException), () => new Channel(null, Credentials.Insecure));
+ Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, Credentials.Insecure));
}
[Test]
@@ -72,11 +72,11 @@ namespace Grpc.Core.Tests
}
[Test]
- public void Target()
+ public void ResolvedTarget()
{
using (var channel = new Channel("127.0.0.1", Credentials.Insecure))
{
- Assert.IsTrue(channel.Target.Contains("127.0.0.1"));
+ Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
}
}
diff --git a/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs b/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs
new file mode 100644
index 0000000000..2dc10ebe97
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ClientBaseTest.cs
@@ -0,0 +1,62 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class ClientBaseTest
+ {
+ [Test]
+ public void GetAuthUriBase_Valid()
+ {
+ Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com"));
+ Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com/"));
+ Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("dns:///some.googleapi.com:443/"));
+ Assert.AreEqual("https://some.googleapi.com/", ClientBase.GetAuthUriBase("some.googleapi.com:443/"));
+ }
+
+ [Test]
+ public void GetAuthUriBase_Invalid()
+ {
+ Assert.IsNull(ClientBase.GetAuthUriBase("some.googleapi.com:"));
+ Assert.IsNull(ClientBase.GetAuthUriBase("https://some.googleapi.com/"));
+ Assert.IsNull(ClientBase.GetAuthUriBase("dns://some.googleapi.com:443")); // just two slashes
+ Assert.IsNull(ClientBase.GetAuthUriBase(""));
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index 64ea21800f..e49fdb5268 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -46,47 +46,18 @@ namespace Grpc.Core.Tests
public class ClientServerTest
{
const string Host = "127.0.0.1";
- const string ServiceName = "tests.Test";
-
- static readonly Method<string, string> EchoMethod = new Method<string, string>(
- MethodType.Unary,
- ServiceName,
- "Echo",
- Marshallers.StringMarshaller,
- Marshallers.StringMarshaller);
-
- static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>(
- MethodType.ClientStreaming,
- ServiceName,
- "ConcatAndEcho",
- Marshallers.StringMarshaller,
- Marshallers.StringMarshaller);
-
- static readonly Method<string, string> NonexistentMethod = new Method<string, string>(
- MethodType.Unary,
- ServiceName,
- "NonexistentMethod",
- Marshallers.StringMarshaller,
- Marshallers.StringMarshaller);
-
- static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
- .AddMethod(EchoMethod, EchoHandler)
- .AddMethod(ConcatAndEchoMethod, ConcatAndEchoHandler)
- .Build();
+ MockServiceHelper helper;
Server server;
Channel channel;
[SetUp]
public void Init()
{
- server = new Server
- {
- Services = { ServiceDefinition },
- Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
- };
+ helper = new MockServiceHelper(Host);
+ server = helper.GetServer();
server.Start();
- channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+ channel = helper.GetChannel();
}
[TearDown]
@@ -103,123 +74,127 @@ namespace Grpc.Core.Tests
}
[Test]
- public void UnaryCall()
+ public async Task UnaryCall()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- Assert.AreEqual("ABC", Calls.BlockingUnaryCall(callDetails, "ABC"));
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ return request;
+ });
+
+ Assert.AreEqual("ABC", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
+
+ Assert.AreEqual("ABC", await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "ABC"));
}
[Test]
public void UnaryCall_ServerHandlerThrows()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- try
+ helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
{
- Calls.BlockingUnaryCall(callDetails, "THROW");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
- }
+ throw new Exception("This was thrown on purpose by a test");
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode);
+
+ var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unknown, ex2.Status.StatusCode);
}
[Test]
public void UnaryCall_ServerHandlerThrowsRpcException()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- try
- {
- Calls.BlockingUnaryCall(callDetails, "THROW_UNAUTHENTICATED");
- Assert.Fail();
- }
- catch (RpcException e)
+ helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
{
- Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
- }
+ throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+
+ var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
}
[Test]
public void UnaryCall_ServerHandlerSetsStatus()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- try
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- Calls.BlockingUnaryCall(callDetails, "SET_UNAUTHENTICATED");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode);
- }
- }
+ context.Status = new Status(StatusCode.Unauthenticated, "");
+ return "";
+ });
- [Test]
- public async Task AsyncUnaryCall()
- {
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- var result = await Calls.AsyncUnaryCall(callDetails, "ABC");
- Assert.AreEqual("ABC", result);
- }
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
- [Test]
- public async Task AsyncUnaryCall_ServerHandlerThrows()
- {
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- try
- {
- await Calls.AsyncUnaryCall(callDetails, "THROW");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
- }
+ var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+ Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
}
[Test]
public async Task ClientStreamingCall()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions());
- var call = Calls.AsyncClientStreamingCall(callDetails);
+ helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ string result = "";
+ await requestStream.ForEachAsync(async (request) =>
+ {
+ result += request;
+ });
+ await Task.Delay(100);
+ return result;
+ });
- await call.RequestStream.WriteAll(new string[] { "A", "B", "C" });
+ var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
+ await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" });
Assert.AreEqual("ABC", await call.ResponseAsync);
}
[Test]
public async Task ClientStreamingCall_CancelAfterBegin()
{
+ var barrier = new TaskCompletionSource<object>();
+
+ helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ barrier.SetResult(null);
+ await requestStream.ToListAsync();
+ return "";
+ });
+
var cts = new CancellationTokenSource();
- var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions(cancellationToken: cts.Token));
- var call = Calls.AsyncClientStreamingCall(callDetails);
+ var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
- // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it.
- await Task.Delay(1000);
+ await barrier.Task; // make sure the handler has started.
cts.Cancel();
- try
- {
- await call.ResponseAsync;
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
- }
+ var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
+ Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
}
[Test]
- public void AsyncUnaryCall_EchoMetadata()
+ public async Task AsyncUnaryCall_EchoMetadata()
{
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
+ {
+ if (metadataEntry.Key != "user-agent")
+ {
+ context.ResponseTrailers.Add(metadataEntry);
+ }
+ }
+ return "";
+ });
+
var headers = new Metadata
{
- new Metadata.Entry("ascii-header", "abcdefg"),
- new Metadata.Entry("binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff }),
+ { "ascii-header", "abcdefg" },
+ { "binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff } }
};
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions(headers: headers));
- var call = Calls.AsyncUnaryCall(callDetails, "ABC");
-
- Assert.AreEqual("ABC", call.ResponseAsync.Result);
+ var call = Calls.AsyncUnaryCall(helper.CreateUnaryCall(new CallOptions(headers: headers)), "ABC");
+ await call;
Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
@@ -236,15 +211,18 @@ namespace Grpc.Core.Tests
public void UnaryCall_DisposedChannel()
{
channel.Dispose();
-
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(callDetails, "ABC"));
+ Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
}
[Test]
public void UnaryCallPerformance()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ return request;
+ });
+
+ var callDetails = helper.CreateUnaryCall();
BenchmarkUtil.RunBenchmark(100, 100,
() => { Calls.BlockingUnaryCall(callDetails, "ABC"); });
}
@@ -252,44 +230,57 @@ namespace Grpc.Core.Tests
[Test]
public void UnknownMethodHandler()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, NonexistentMethod, new CallOptions());
- try
- {
- Calls.BlockingUnaryCall(callDetails, "ABC");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode);
- }
+ var nonexistentMethod = new Method<string, string>(
+ MethodType.Unary,
+ MockServiceHelper.ServiceName,
+ "NonExistentMethod",
+ Marshallers.StringMarshaller,
+ Marshallers.StringMarshaller);
+
+ var callDetails = new CallInvocationDetails<string, string>(channel, nonexistentMethod, new CallOptions());
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(callDetails, "abc"));
+ Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode);
}
[Test]
public void UserAgentStringPresent()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- string userAgent = Calls.BlockingUnaryCall(callDetails, "RETURN-USER-AGENT");
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
+ });
+
+ string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
Assert.IsTrue(userAgent.StartsWith("grpc-csharp/"));
}
[Test]
public void PeerInfoPresent()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- string peer = Calls.BlockingUnaryCall(callDetails, "RETURN-PEER");
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ return context.Peer;
+ });
+
+ string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc");
Assert.IsTrue(peer.Contains(Host));
}
[Test]
public async Task Channel_WaitForStateChangedAsync()
{
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ return request;
+ });
+
Assert.Throws(typeof(TaskCanceledException),
async () => await channel.WaitForStateChangedAsync(channel.State, DateTime.UtcNow.AddMilliseconds(10)));
var stateChangedTask = channel.WaitForStateChangedAsync(channel.State);
- var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions());
- await Calls.AsyncUnaryCall(callDetails, "abc");
+ await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc");
await stateChangedTask;
Assert.AreEqual(ChannelState.Ready, channel.State);
@@ -300,62 +291,9 @@ namespace Grpc.Core.Tests
{
await channel.ConnectAsync();
Assert.AreEqual(ChannelState.Ready, channel.State);
+
await channel.ConnectAsync(DateTime.UtcNow.AddMilliseconds(1000));
Assert.AreEqual(ChannelState.Ready, channel.State);
}
-
- private static async Task<string> EchoHandler(string request, ServerCallContext context)
- {
- foreach (Metadata.Entry metadataEntry in context.RequestHeaders)
- {
- if (metadataEntry.Key != "user-agent")
- {
- context.ResponseTrailers.Add(metadataEntry);
- }
- }
-
- if (request == "RETURN-USER-AGENT")
- {
- return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value;
- }
-
- if (request == "RETURN-PEER")
- {
- return context.Peer;
- }
-
- if (request == "THROW")
- {
- throw new Exception("This was thrown on purpose by a test");
- }
-
- if (request == "THROW_UNAUTHENTICATED")
- {
- throw new RpcException(new Status(StatusCode.Unauthenticated, ""));
- }
-
- if (request == "SET_UNAUTHENTICATED")
- {
- context.Status = new Status(StatusCode.Unauthenticated, "");
- }
-
- return request;
- }
-
- private static async Task<string> ConcatAndEchoHandler(IAsyncStreamReader<string> requestStream, ServerCallContext context)
- {
- string result = "";
- await requestStream.ForEach(async (request) =>
- {
- if (request == "THROW")
- {
- throw new Exception("This was thrown on purpose by a test");
- }
- result += request;
- });
- // simulate processing takes some time.
- await Task.Delay(250);
- return result;
- }
}
}
diff --git a/src/csharp/Grpc.Core.Tests/CompressionTest.cs b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
new file mode 100644
index 0000000000..9547683f60
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/CompressionTest.cs
@@ -0,0 +1,128 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class CompressionTest
+ {
+ MockServiceHelper helper;
+ Server server;
+ Channel channel;
+
+ [SetUp]
+ public void Init()
+ {
+ helper = new MockServiceHelper();
+
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ channel.Dispose();
+ server.ShutdownAsync().Wait();
+ }
+
+ [TestFixtureTearDown]
+ public void CleanupClass()
+ {
+ GrpcEnvironment.Shutdown();
+ }
+
+ [Test]
+ public void WriteOptions_Unary()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+ return request;
+ });
+
+ var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress));
+ Calls.BlockingUnaryCall(helper.CreateUnaryCall(callOptions), "abc");
+ }
+
+ [Test]
+ public async Task WriteOptions_DuplexStreaming()
+ {
+ helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+ {
+ await requestStream.ToListAsync();
+
+ context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+
+ await context.WriteResponseHeadersAsync(new Metadata { { "ascii-header", "abcdefg" } });
+
+ await responseStream.WriteAsync("X");
+
+ responseStream.WriteOptions = null;
+ await responseStream.WriteAsync("Y");
+
+ responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+ await responseStream.WriteAsync("Z");
+ });
+
+ var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress));
+ var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(callOptions));
+
+ // check that write options from call options are propagated to request stream.
+ Assert.IsTrue((call.RequestStream.WriteOptions.Flags & WriteFlags.NoCompress) != 0);
+
+ call.RequestStream.WriteOptions = new WriteOptions();
+ await call.RequestStream.WriteAsync("A");
+
+ call.RequestStream.WriteOptions = null;
+ await call.RequestStream.WriteAsync("B");
+
+ call.RequestStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
+ await call.RequestStream.WriteAsync("C");
+
+ await call.RequestStream.CompleteAsync();
+
+ await call.ResponseStream.ToListAsync();
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
new file mode 100644
index 0000000000..db5f953b0e
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
@@ -0,0 +1,153 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ public class ContextPropagationTest
+ {
+ MockServiceHelper helper;
+ Server server;
+ Channel channel;
+
+ [SetUp]
+ public void Init()
+ {
+ helper = new MockServiceHelper();
+
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ channel.Dispose();
+ server.ShutdownAsync().Wait();
+ }
+
+ [TestFixtureTearDown]
+ public void CleanupClass()
+ {
+ GrpcEnvironment.Shutdown();
+ }
+
+ [Test]
+ public async Task PropagateCancellation()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ // check that we didn't obtain the default cancellation token.
+ Assert.IsTrue(context.CancellationToken.CanBeCanceled);
+ return "PASS";
+ });
+
+ helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ var propagationToken = context.CreatePropagationToken();
+ Assert.IsNotNull(propagationToken.ParentCall);
+
+ var callOptions = new CallOptions(propagationToken: propagationToken);
+ return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+ });
+
+ var cts = new CancellationTokenSource();
+ var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token)));
+ await call.RequestStream.CompleteAsync();
+ Assert.AreEqual("PASS", await call);
+ }
+
+ [Test]
+ public async Task PropagateDeadline()
+ {
+ var deadline = DateTime.UtcNow.AddDays(7);
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ Assert.IsTrue(context.Deadline < deadline.AddMinutes(1));
+ Assert.IsTrue(context.Deadline > deadline.AddMinutes(-1));
+ return "PASS";
+ });
+
+ helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ Assert.Throws(typeof(ArgumentException), () =>
+ {
+ // Trying to override deadline while propagating deadline from parent call will throw.
+ Calls.BlockingUnaryCall(helper.CreateUnaryCall(
+ new CallOptions(deadline: DateTime.UtcNow.AddDays(8),
+ propagationToken: context.CreatePropagationToken())), "");
+ });
+
+ var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken());
+ return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+ });
+
+ var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: deadline)));
+ await call.RequestStream.CompleteAsync();
+ Assert.AreEqual("PASS", await call);
+ }
+
+ [Test]
+ public async Task SuppressDeadlinePropagation()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ Assert.AreEqual(DateTime.MaxValue, context.Deadline);
+ return "PASS";
+ });
+
+ helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ Assert.IsTrue(context.CancellationToken.CanBeCanceled);
+
+ var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken(new ContextPropagationOptions(propagateDeadline: false)));
+ return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz");
+ });
+
+ var cts = new CancellationTokenSource();
+ var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddDays(7))));
+ await call.RequestStream.CompleteAsync();
+ Assert.AreEqual("PASS", await call);
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
index f2bf459dc5..d6a8f52570 100644
--- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
+++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
@@ -63,6 +63,7 @@
<Compile Include="..\Grpc.Core\Version.cs">
<Link>Version.cs</Link>
</Compile>
+ <Compile Include="ClientBaseTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ClientServerTest.cs" />
<Compile Include="ServerTest.cs" />
@@ -77,6 +78,10 @@
<Compile Include="TimeoutsTest.cs" />
<Compile Include="NUnitVersionTest.cs" />
<Compile Include="ChannelTest.cs" />
+ <Compile Include="MockServiceHelper.cs" />
+ <Compile Include="ResponseHeadersTest.cs" />
+ <Compile Include="CompressionTest.cs" />
+ <Compile Include="ContextPropagationTest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
index 9ae12776f3..4ed93c7eca 100644
--- a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
+++ b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
@@ -69,5 +69,13 @@ namespace Grpc.Core.Tests
Assert.IsFalse(object.ReferenceEquals(env1, env2));
}
+
+ [Test]
+ public void GetCoreVersionString()
+ {
+ var coreVersion = GrpcEnvironment.GetCoreVersionString();
+ var parts = coreVersion.Split('.');
+ Assert.AreEqual(4, parts.Length);
+ }
}
}
diff --git a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
index 46469113c5..33534fdd3c 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs
@@ -53,8 +53,8 @@ namespace Grpc.Core.Internal.Tests
{
var metadata = new Metadata
{
- new Metadata.Entry("host", "somehost"),
- new Metadata.Entry("header2", "header value"),
+ { "host", "somehost" },
+ { "header2", "header value" },
};
var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
nativeMetadata.Dispose();
@@ -65,8 +65,8 @@ namespace Grpc.Core.Internal.Tests
{
var metadata = new Metadata
{
- new Metadata.Entry("host", "somehost"),
- new Metadata.Entry("header2", "header value"),
+ { "host", "somehost" },
+ { "header2", "header value" }
};
var nativeMetadata = MetadataArraySafeHandle.Create(metadata);
diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
new file mode 100644
index 0000000000..bb69648d8b
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs
@@ -0,0 +1,244 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ /// <summary>
+ /// Allows setting up a mock service in the client-server tests easily.
+ /// </summary>
+ public class MockServiceHelper
+ {
+ public const string ServiceName = "tests.Test";
+
+ public static readonly Method<string, string> UnaryMethod = new Method<string, string>(
+ MethodType.Unary,
+ ServiceName,
+ "Unary",
+ Marshallers.StringMarshaller,
+ Marshallers.StringMarshaller);
+
+ public static readonly Method<string, string> ClientStreamingMethod = new Method<string, string>(
+ MethodType.ClientStreaming,
+ ServiceName,
+ "ClientStreaming",
+ Marshallers.StringMarshaller,
+ Marshallers.StringMarshaller);
+
+ public static readonly Method<string, string> ServerStreamingMethod = new Method<string, string>(
+ MethodType.ServerStreaming,
+ ServiceName,
+ "ServerStreaming",
+ Marshallers.StringMarshaller,
+ Marshallers.StringMarshaller);
+
+ public static readonly Method<string, string> DuplexStreamingMethod = new Method<string, string>(
+ MethodType.DuplexStreaming,
+ ServiceName,
+ "DuplexStreaming",
+ Marshallers.StringMarshaller,
+ Marshallers.StringMarshaller);
+
+ readonly string host;
+ readonly ServerServiceDefinition serviceDefinition;
+
+ UnaryServerMethod<string, string> unaryHandler;
+ ClientStreamingServerMethod<string, string> clientStreamingHandler;
+ ServerStreamingServerMethod<string, string> serverStreamingHandler;
+ DuplexStreamingServerMethod<string, string> duplexStreamingHandler;
+
+ Server server;
+ Channel channel;
+
+ public MockServiceHelper(string host = null)
+ {
+ this.host = host ?? "localhost";
+
+ serviceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
+ .AddMethod(UnaryMethod, (request, context) => unaryHandler(request, context))
+ .AddMethod(ClientStreamingMethod, (requestStream, context) => clientStreamingHandler(requestStream, context))
+ .AddMethod(ServerStreamingMethod, (request, responseStream, context) => serverStreamingHandler(request, responseStream, context))
+ .AddMethod(DuplexStreamingMethod, (requestStream, responseStream, context) => duplexStreamingHandler(requestStream, responseStream, context))
+ .Build();
+
+ var defaultStatus = new Status(StatusCode.Unknown, "Default mock implementation. Please provide your own.");
+
+ unaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ context.Status = defaultStatus;
+ return "";
+ });
+
+ clientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
+ {
+ context.Status = defaultStatus;
+ return "";
+ });
+
+ serverStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+ {
+ context.Status = defaultStatus;
+ });
+
+ duplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
+ {
+ context.Status = defaultStatus;
+ });
+ }
+
+ /// <summary>
+ /// Returns the default server for this service and creates one if not yet created.
+ /// </summary>
+ public Server GetServer()
+ {
+ if (server == null)
+ {
+ server = new Server
+ {
+ Services = { serviceDefinition },
+ Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
+ };
+ }
+ return server;
+ }
+
+ /// <summary>
+ /// Returns the default channel for this service and creates one if not yet created.
+ /// </summary>
+ public Channel GetChannel()
+ {
+ if (channel == null)
+ {
+ channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure);
+ }
+ return channel;
+ }
+
+ public CallInvocationDetails<string, string> CreateUnaryCall(CallOptions options = default(CallOptions))
+ {
+ return new CallInvocationDetails<string, string>(channel, UnaryMethod, options);
+ }
+
+ public CallInvocationDetails<string, string> CreateClientStreamingCall(CallOptions options = default(CallOptions))
+ {
+ return new CallInvocationDetails<string, string>(channel, ClientStreamingMethod, options);
+ }
+
+ public CallInvocationDetails<string, string> CreateServerStreamingCall(CallOptions options = default(CallOptions))
+ {
+ return new CallInvocationDetails<string, string>(channel, ServerStreamingMethod, options);
+ }
+
+ public CallInvocationDetails<string, string> CreateDuplexStreamingCall(CallOptions options = default(CallOptions))
+ {
+ return new CallInvocationDetails<string, string>(channel, DuplexStreamingMethod, options);
+ }
+
+ public string Host
+ {
+ get
+ {
+ return this.host;
+ }
+ }
+
+ public ServerServiceDefinition ServiceDefinition
+ {
+ get
+ {
+ return this.serviceDefinition;
+ }
+ }
+
+ public UnaryServerMethod<string, string> UnaryHandler
+ {
+ get
+ {
+ return this.unaryHandler;
+ }
+
+ set
+ {
+ unaryHandler = value;
+ }
+ }
+
+ public ClientStreamingServerMethod<string, string> ClientStreamingHandler
+ {
+ get
+ {
+ return this.clientStreamingHandler;
+ }
+
+ set
+ {
+ clientStreamingHandler = value;
+ }
+ }
+
+ public ServerStreamingServerMethod<string, string> ServerStreamingHandler
+ {
+ get
+ {
+ return this.serverStreamingHandler;
+ }
+
+ set
+ {
+ serverStreamingHandler = value;
+ }
+ }
+
+ public DuplexStreamingServerMethod<string, string> DuplexStreamingHandler
+ {
+ get
+ {
+ return this.duplexStreamingHandler;
+ }
+
+ set
+ {
+ duplexStreamingHandler = value;
+ }
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
new file mode 100644
index 0000000000..981b8ea3c8
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
@@ -0,0 +1,136 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests
+{
+ /// <summary>
+ /// Tests for response headers support.
+ /// </summary>
+ public class ResponseHeadersTest
+ {
+ MockServiceHelper helper;
+ Server server;
+ Channel channel;
+
+ Metadata headers;
+
+ [SetUp]
+ public void Init()
+ {
+ helper = new MockServiceHelper();
+
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
+
+ headers = new Metadata { { "ascii-header", "abcdefg" } };
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ channel.Dispose();
+ server.ShutdownAsync().Wait();
+ }
+
+ [TestFixtureTearDown]
+ public void CleanupClass()
+ {
+ GrpcEnvironment.Shutdown();
+ }
+
+ [Test]
+ public void WriteResponseHeaders_NullNotAllowed()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ Assert.Throws(typeof(ArgumentNullException), async () => await context.WriteResponseHeadersAsync(null));
+ return "PASS";
+ });
+
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+ }
+
+ [Test]
+ public void WriteResponseHeaders_AllowedOnlyOnce()
+ {
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ await context.WriteResponseHeadersAsync(headers);
+ try
+ {
+ await context.WriteResponseHeadersAsync(headers);
+ Assert.Fail();
+ }
+ catch (InvalidOperationException expected)
+ {
+ }
+ return "PASS";
+ });
+
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), ""));
+ }
+
+ [Test]
+ public async Task WriteResponseHeaders_NotAllowedAfterWrite()
+ {
+ helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+ {
+ await responseStream.WriteAsync("A");
+ try
+ {
+ await context.WriteResponseHeadersAsync(headers);
+ Assert.Fail();
+ }
+ catch (InvalidOperationException expected)
+ {
+ }
+ await responseStream.WriteAsync("B");
+ });
+
+ var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+ var responses = await call.ResponseStream.ToListAsync();
+ CollectionAssert.AreEqual(new[] { "A", "B" }, responses);
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
index fc395b0acd..d875d601b9 100644
--- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
+++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
@@ -48,38 +48,18 @@ namespace Grpc.Core.Tests
/// </summary>
public class TimeoutsTest
{
- const string Host = "localhost";
- const string ServiceName = "tests.Test";
-
- static readonly Method<string, string> TestMethod = new Method<string, string>(
- MethodType.Unary,
- ServiceName,
- "Test",
- Marshallers.StringMarshaller,
- Marshallers.StringMarshaller);
-
- static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName)
- .AddMethod(TestMethod, TestMethodHandler)
- .Build();
-
- // provides a way how to retrieve an out-of-band result value from server handler
- static TaskCompletionSource<string> stringFromServerHandlerTcs;
-
+ MockServiceHelper helper;
Server server;
Channel channel;
[SetUp]
public void Init()
{
- server = new Server
- {
- Services = { ServiceDefinition },
- Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } }
- };
- server.Start();
- channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure);
+ helper = new MockServiceHelper();
- stringFromServerHandlerTcs = new TaskCompletionSource<string>();
+ server = helper.GetServer();
+ server.Start();
+ channel = helper.GetChannel();
}
[TearDown]
@@ -98,115 +78,83 @@ namespace Grpc.Core.Tests
[Test]
public void InfiniteDeadline()
{
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ Assert.AreEqual(DateTime.MaxValue, context.Deadline);
+ return "PASS";
+ });
+
// no deadline specified, check server sees infinite deadline
- var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
- Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE"));
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
// DateTime.MaxValue deadline specified, check server sees infinite deadline
- var callDetails2 = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions());
- Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails2, "RETURN_DEADLINE"));
+ Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MaxValue)), "abc"));
}
[Test]
public void DeadlineTransferredToServer()
{
- var remainingTimeClient = TimeSpan.FromDays(7);
- var deadline = DateTime.UtcNow + remainingTimeClient;
- Thread.Sleep(1000);
- var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
-
- var serverDeadlineTicksString = Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE");
- var serverDeadline = new DateTime(long.Parse(serverDeadlineTicksString), DateTimeKind.Utc);
-
- // A fairly relaxed check that the deadline set by client and deadline seen by server
- // are in agreement. C core takes care of the work with transferring deadline over the wire,
- // so we don't need an exact check here.
- Assert.IsTrue(Math.Abs((deadline - serverDeadline).TotalMilliseconds) < 5000);
+ var clientDeadline = DateTime.UtcNow + TimeSpan.FromDays(7);
+
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+ {
+ // A fairly relaxed check that the deadline set by client and deadline seen by server
+ // are in agreement. C core takes care of the work with transferring deadline over the wire,
+ // so we don't need an exact check here.
+ Assert.IsTrue(Math.Abs((clientDeadline - context.Deadline).TotalMilliseconds) < 5000);
+ return "PASS";
+ });
+ Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: clientDeadline)), "abc");
}
[Test]
public void DeadlineInThePast()
{
- var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: DateTime.MinValue));
-
- try
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
- Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
- }
+ await Task.Delay(60000);
+ return "FAIL";
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc"));
+ // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+ Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
}
[Test]
public void DeadlineExceededStatusOnTimeout()
{
- var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
- var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
-
- try
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
- Calls.BlockingUnaryCall(callDetails, "TIMEOUT");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
- Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
- }
+ await Task.Delay(60000);
+ return "FAIL";
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+ // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+ Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
}
[Test]
- public void ServerReceivesCancellationOnTimeout()
+ public async Task ServerReceivesCancellationOnTimeout()
{
- var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5));
- var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline));
+ var serverReceivedCancellationTcs = new TaskCompletionSource<bool>();
- try
- {
- Calls.BlockingUnaryCall(callDetails, "CHECK_CANCELLATION_RECEIVED");
- Assert.Fail();
- }
- catch (RpcException e)
- {
- // We can't guarantee the status code is always DeadlineExceeded. See issue #2685.
- Assert.Contains(e.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
- }
- Assert.AreEqual("CANCELLED", stringFromServerHandlerTcs.Task.Result);
- }
-
- private static async Task<string> TestMethodHandler(string request, ServerCallContext context)
- {
- if (request == "TIMEOUT")
- {
- await Task.Delay(60000);
- return "";
- }
-
- if (request == "RETURN_DEADLINE")
- {
- if (context.Deadline == DateTime.MaxValue)
- {
- return "DATETIME_MAXVALUE";
- }
-
- return context.Deadline.Ticks.ToString();
- }
-
- if (request == "CHECK_CANCELLATION_RECEIVED")
+ helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
// wait until cancellation token is fired.
var tcs = new TaskCompletionSource<object>();
context.CancellationToken.Register(() => { tcs.SetResult(null); });
await tcs.Task;
- stringFromServerHandlerTcs.SetResult("CANCELLED");
+ serverReceivedCancellationTcs.SetResult(true);
return "";
- }
+ });
+
+ var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc"));
+ // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+ Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
- return "";
+ Assert.IsTrue(await serverReceivedCancellationTcs.Task);
}
}
}
diff --git a/src/csharp/Grpc.Core/CallInvocationDetails.cs b/src/csharp/Grpc.Core/CallInvocationDetails.cs
index eb23a3a209..6565073fc5 100644
--- a/src/csharp/Grpc.Core/CallInvocationDetails.cs
+++ b/src/csharp/Grpc.Core/CallInvocationDetails.cs
@@ -40,30 +40,60 @@ namespace Grpc.Core
/// <summary>
/// Details about a client-side call to be invoked.
/// </summary>
- public class CallInvocationDetails<TRequest, TResponse>
+ public struct CallInvocationDetails<TRequest, TResponse>
{
readonly Channel channel;
readonly string method;
readonly string host;
readonly Marshaller<TRequest> requestMarshaller;
readonly Marshaller<TResponse> responseMarshaller;
- readonly CallOptions options;
+ CallOptions options;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct.
+ /// </summary>
+ /// <param name="channel">Channel to use for this call.</param>
+ /// <param name="method">Method to call.</param>
+ /// <param name="options">Call options.</param>
public CallInvocationDetails(Channel channel, Method<TRequest, TResponse> method, CallOptions options) :
- this(channel, method.FullName, null, method.RequestMarshaller, method.ResponseMarshaller, options)
+ this(channel, method, null, options)
{
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct.
+ /// </summary>
+ /// <param name="channel">Channel to use for this call.</param>
+ /// <param name="method">Method to call.</param>
+ /// <param name="host">Host that contains the method. if <c>null</c>, default host will be used.</param>
+ /// <param name="options">Call options.</param>
+ public CallInvocationDetails(Channel channel, Method<TRequest, TResponse> method, string host, CallOptions options) :
+ this(channel, method.FullName, host, method.RequestMarshaller, method.ResponseMarshaller, options)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct.
+ /// </summary>
+ /// <param name="channel">Channel to use for this call.</param>
+ /// <param name="method">Qualified method name.</param>
+ /// <param name="host">Host that contains the method.</param>
+ /// <param name="requestMarshaller">Request marshaller.</param>
+ /// <param name="responseMarshaller">Response marshaller.</param>
+ /// <param name="options">Call options.</param>
public CallInvocationDetails(Channel channel, string method, string host, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller, CallOptions options)
{
- this.channel = Preconditions.CheckNotNull(channel);
- this.method = Preconditions.CheckNotNull(method);
+ this.channel = Preconditions.CheckNotNull(channel, "channel");
+ this.method = Preconditions.CheckNotNull(method, "method");
this.host = host;
- this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller);
- this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller);
- this.options = Preconditions.CheckNotNull(options);
+ this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller, "requestMarshaller");
+ this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller, "responseMarshaller");
+ this.options = options;
}
+ /// <summary>
+ /// Get channel associated with this call.
+ /// </summary>
public Channel Channel
{
get
@@ -72,6 +102,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets name of method to be called.
+ /// </summary>
public string Method
{
get
@@ -80,6 +113,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Get name of host.
+ /// </summary>
public string Host
{
get
@@ -88,6 +124,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets marshaller used to serialize requests.
+ /// </summary>
public Marshaller<TRequest> RequestMarshaller
{
get
@@ -96,6 +135,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets marshaller used to deserialized responses.
+ /// </summary>
public Marshaller<TResponse> ResponseMarshaller
{
get
@@ -104,6 +146,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the call options.
+ /// </summary>
public CallOptions Options
{
get
@@ -111,5 +156,16 @@ namespace Grpc.Core
return options;
}
}
+
+ /// <summary>
+ /// Returns new instance of <see cref="CallInvocationDetails"/> with
+ /// <c>Options</c> set to the value provided. Values of all other fields are preserved.
+ /// </summary>
+ public CallInvocationDetails<TRequest, TResponse> WithOptions(CallOptions options)
+ {
+ var newDetails = this;
+ newDetails.options = options;
+ return newDetails;
+ }
}
}
diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs
index 8e9739335f..3dfe80b48c 100644
--- a/src/csharp/Grpc.Core/CallOptions.cs
+++ b/src/csharp/Grpc.Core/CallOptions.cs
@@ -42,24 +42,30 @@ namespace Grpc.Core
/// <summary>
/// Options for calls made by client.
/// </summary>
- public class CallOptions
+ public struct CallOptions
{
- readonly Metadata headers;
- readonly DateTime deadline;
- readonly CancellationToken cancellationToken;
+ Metadata headers;
+ DateTime? deadline;
+ CancellationToken cancellationToken;
+ WriteOptions writeOptions;
+ ContextPropagationToken propagationToken;
/// <summary>
- /// Creates a new instance of <c>CallOptions</c>.
+ /// Creates a new instance of <c>CallOptions</c> struct.
/// </summary>
/// <param name="headers">Headers to be sent with the call.</param>
/// <param name="deadline">Deadline for the call to finish. null means no deadline.</param>
/// <param name="cancellationToken">Can be used to request cancellation of the call.</param>
- public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken))
+ /// <param name="writeOptions">Write options that will be used for this call.</param>
+ /// <param name="propagationToken">Context propagation token obtained from <see cref="ServerCallContext"/>.</param>
+ public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken),
+ WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null)
{
- // TODO(jtattermusch): consider only creating metadata object once it's really needed.
- this.headers = headers != null ? headers : new Metadata();
- this.deadline = deadline.HasValue ? deadline.Value : DateTime.MaxValue;
+ this.headers = headers;
+ this.deadline = deadline;
this.cancellationToken = cancellationToken;
+ this.writeOptions = writeOptions;
+ this.propagationToken = propagationToken;
}
/// <summary>
@@ -73,7 +79,7 @@ namespace Grpc.Core
/// <summary>
/// Call deadline.
/// </summary>
- public DateTime Deadline
+ public DateTime? Deadline
{
get { return deadline; }
}
@@ -85,5 +91,88 @@ namespace Grpc.Core
{
get { return cancellationToken; }
}
+
+ /// <summary>
+ /// Write options that will be used for this call.
+ /// </summary>
+ public WriteOptions WriteOptions
+ {
+ get
+ {
+ return this.writeOptions;
+ }
+ }
+
+ /// <summary>
+ /// Token for propagating parent call context.
+ /// </summary>
+ public ContextPropagationToken PropagationToken
+ {
+ get
+ {
+ return this.propagationToken;
+ }
+ }
+
+ /// <summary>
+ /// Returns new instance of <see cref="CallOptions"/> with
+ /// <c>Headers</c> set to the value provided. Values of all other fields are preserved.
+ /// </summary>
+ public CallOptions WithHeaders(Metadata headers)
+ {
+ var newOptions = this;
+ newOptions.headers = headers;
+ return newOptions;
+ }
+
+ /// <summary>
+ /// Returns new instance of <see cref="CallOptions"/> with
+ /// <c>Deadline</c> set to the value provided. Values of all other fields are preserved.
+ /// </summary>
+ public CallOptions WithDeadline(DateTime deadline)
+ {
+ var newOptions = this;
+ newOptions.deadline = deadline;
+ return newOptions;
+ }
+
+ /// <summary>
+ /// Returns new instance of <see cref="CallOptions"/> with
+ /// <c>CancellationToken</c> set to the value provided. Values of all other fields are preserved.
+ /// </summary>
+ public CallOptions WithCancellationToken(CancellationToken cancellationToken)
+ {
+ var newOptions = this;
+ newOptions.cancellationToken = cancellationToken;
+ return newOptions;
+ }
+
+ /// <summary>
+ /// Returns a new instance of <see cref="CallOptions"/> with
+ /// all previously unset values set to their defaults and deadline and cancellation
+ /// token propagated when appropriate.
+ /// </summary>
+ internal CallOptions Normalize()
+ {
+ var newOptions = this;
+ if (propagationToken != null)
+ {
+ if (propagationToken.Options.IsPropagateDeadline)
+ {
+ Preconditions.CheckArgument(!newOptions.deadline.HasValue,
+ "Cannot propagate deadline from parent call. The deadline has already been set explicitly.");
+ newOptions.deadline = propagationToken.ParentDeadline;
+ }
+ if (propagationToken.Options.IsPropagateCancellation)
+ {
+ Preconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled,
+ "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value.");
+ }
+ }
+
+ newOptions.headers = newOptions.headers ?? Metadata.Empty;
+ newOptions.deadline = newOptions.deadline ?? DateTime.MaxValue;
+ return newOptions;
+ }
}
}
diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs
index 00a8cabf82..7067456638 100644
--- a/src/csharp/Grpc.Core/Calls.cs
+++ b/src/csharp/Grpc.Core/Calls.cs
@@ -31,8 +31,6 @@
#endregion
-using System;
-using System.Threading;
using System.Threading.Tasks;
using Grpc.Core.Internal;
@@ -40,9 +38,20 @@ namespace Grpc.Core
{
/// <summary>
/// Helper methods for generated clients to make RPC calls.
+ /// Most users will use this class only indirectly and will be
+ /// making calls using client object generated from protocol
+ /// buffer definition files.
/// </summary>
public static class Calls
{
+ /// <summary>
+ /// Invokes a simple remote call in a blocking fashion.
+ /// </summary>
+ /// <returns>The response.</returns>
+ /// <param name="call">The call defintion.</param>
+ /// <param name="req">Request message.</param>
+ /// <typeparam name="TRequest">Type of request message.</typeparam>
+ /// <typeparam name="TResponse">The of response message.</typeparam>
public static TResponse BlockingUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req)
where TRequest : class
where TResponse : class
@@ -51,6 +60,14 @@ namespace Grpc.Core
return asyncCall.UnaryCall(req);
}
+ /// <summary>
+ /// Invokes a simple remote call asynchronously.
+ /// </summary>
+ /// <returns>An awaitable call object providing access to the response.</returns>
+ /// <param name="call">The call defintion.</param>
+ /// <param name="req">Request message.</param>
+ /// <typeparam name="TRequest">Type of request message.</typeparam>
+ /// <typeparam name="TResponse">The of response message.</typeparam>
public static AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req)
where TRequest : class
where TResponse : class
@@ -60,6 +77,15 @@ namespace Grpc.Core
return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
}
+ /// <summary>
+ /// Invokes a server streaming call asynchronously.
+ /// In server streaming scenario, client sends on request and server responds with a stream of responses.
+ /// </summary>
+ /// <returns>A call object providing access to the asynchronous response stream.</returns>
+ /// <param name="call">The call defintion.</param>
+ /// <param name="req">Request message.</param>
+ /// <typeparam name="TRequest">Type of request message.</typeparam>
+ /// <typeparam name="TResponse">The of response messages.</typeparam>
public static AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req)
where TRequest : class
where TResponse : class
@@ -70,6 +96,13 @@ namespace Grpc.Core
return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
}
+ /// <summary>
+ /// Invokes a client streaming call asynchronously.
+ /// In client streaming scenario, client sends a stream of requests and server responds with a single response.
+ /// </summary>
+ /// <returns>An awaitable call object providing access to the response.</returns>
+ /// <typeparam name="TRequest">Type of request messages.</typeparam>
+ /// <typeparam name="TResponse">The of response message.</typeparam>
public static AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call)
where TRequest : class
where TResponse : class
@@ -80,6 +113,15 @@ namespace Grpc.Core
return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
}
+ /// <summary>
+ /// Invokes a duplex streaming call asynchronously.
+ /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses.
+ /// The response stream is completely independent and both side can be sending messages at the same time.
+ /// </summary>
+ /// <returns>A call object providing access to the asynchronous request and response streams.</returns>
+ /// <param name="call">The call definition.</param>
+ /// <typeparam name="TRequest">Type of request messages.</typeparam>
+ /// <typeparam name="TResponse">Type of reponse messages.</typeparam>
public static AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call)
where TRequest : class
where TResponse : class
diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs
index 9273ea4582..64c6adf2bf 100644
--- a/src/csharp/Grpc.Core/Channel.cs
+++ b/src/csharp/Grpc.Core/Channel.cs
@@ -49,6 +49,7 @@ namespace Grpc.Core
{
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
+ readonly string target;
readonly GrpcEnvironment environment;
readonly ChannelSafeHandle handle;
readonly List<ChannelOption> options;
@@ -58,12 +59,12 @@ namespace Grpc.Core
/// Creates a channel that connects to a specific host.
/// Port will default to 80 for an unsecure channel and to 443 for a secure channel.
/// </summary>
- /// <param name="host">The name or IP address of the host.</param>
+ /// <param name="target">Target of the channel.</param>
/// <param name="credentials">Credentials to secure the channel.</param>
/// <param name="options">Channel options.</param>
- public Channel(string host, Credentials credentials, IEnumerable<ChannelOption> options = null)
+ public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null)
{
- Preconditions.CheckNotNull(host);
+ this.target = Preconditions.CheckNotNull(target, "target");
this.environment = GrpcEnvironment.GetInstance();
this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
@@ -73,11 +74,11 @@ namespace Grpc.Core
{
if (nativeCredentials != null)
{
- this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, host, nativeChannelArgs);
+ this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs);
}
else
{
- this.handle = ChannelSafeHandle.CreateInsecure(host, nativeChannelArgs);
+ this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs);
}
}
}
@@ -131,8 +132,8 @@ namespace Grpc.Core
return tcs.Task;
}
- /// <summary> Address of the remote endpoint in URI format.</summary>
- public string Target
+ /// <summary>Resolved address of the remote endpoint in URI format.</summary>
+ public string ResolvedTarget
{
get
{
@@ -140,6 +141,15 @@ namespace Grpc.Core
}
}
+ /// <summary>The original target used to create the channel.</summary>
+ public string Target
+ {
+ get
+ {
+ return this.target;
+ }
+ }
+
/// <summary>
/// Allows explicitly requesting channel to connect without starting an RPC.
/// Returned task completes once state Ready was seen. If the deadline is reached,
diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs
index 1e0f90287a..0cb2953f2c 100644
--- a/src/csharp/Grpc.Core/ChannelOptions.cs
+++ b/src/csharp/Grpc.Core/ChannelOptions.cs
@@ -63,8 +63,8 @@ namespace Grpc.Core
public ChannelOption(string name, string stringValue)
{
this.type = OptionType.String;
- this.name = Preconditions.CheckNotNull(name);
- this.stringValue = Preconditions.CheckNotNull(stringValue);
+ this.name = Preconditions.CheckNotNull(name, "name");
+ this.stringValue = Preconditions.CheckNotNull(stringValue, "stringValue");
}
/// <summary>
@@ -75,7 +75,7 @@ namespace Grpc.Core
public ChannelOption(string name, int intValue)
{
this.type = OptionType.Integer;
- this.name = Preconditions.CheckNotNull(name);
+ this.name = Preconditions.CheckNotNull(name, "name");
this.intValue = intValue;
}
diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs
index 88494bb4ac..f240d777b9 100644
--- a/src/csharp/Grpc.Core/ClientBase.cs
+++ b/src/csharp/Grpc.Core/ClientBase.cs
@@ -33,23 +33,30 @@
using System;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using Grpc.Core.Internal;
+using Grpc.Core.Utils;
namespace Grpc.Core
{
- public delegate void MetadataInterceptorDelegate(Metadata metadata);
+ public delegate void MetadataInterceptorDelegate(string authUri, Metadata metadata);
/// <summary>
/// Base class for client-side stubs.
/// </summary>
public abstract class ClientBase
{
+ // Regex for removal of the optional DNS scheme, trailing port, and trailing backslash
+ static readonly Regex ChannelTargetPattern = new Regex(@"^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$");
+
readonly Channel channel;
+ readonly string authUriBase;
public ClientBase(Channel channel)
{
this.channel = channel;
+ this.authUriBase = GetAuthUriBase(channel.Target);
}
/// <summary>
@@ -63,6 +70,18 @@ namespace Grpc.Core
}
/// <summary>
+ /// gRPC supports multiple "hosts" being served by a single server.
+ /// This property can be used to set the target host explicitly.
+ /// By default, this will be set to <c>null</c> with the meaning
+ /// "use default host".
+ /// </summary>
+ public string Host
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
/// Channel associated with this client.
/// </summary>
public Channel Channel
@@ -83,10 +102,27 @@ namespace Grpc.Core
var interceptor = HeaderInterceptor;
if (interceptor != null)
{
- interceptor(options.Headers);
- options.Headers.Freeze();
+ if (options.Headers == null)
+ {
+ options = options.WithHeaders(new Metadata());
+ }
+ var authUri = authUriBase != null ? authUriBase + method.ServiceName : null;
+ interceptor(authUri, options.Headers);
+ }
+ return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options);
+ }
+
+ /// <summary>
+ /// Creates Auth URI base from channel's target (the one passed at channel creation).
+ /// Fully-qualified service name is to be appended to this.
+ /// </summary>
+ internal static string GetAuthUriBase(string target)
+ {
+ var match = ChannelTargetPattern.Match(target);
+ if (!match.Success) {
+ return null;
}
- return new CallInvocationDetails<TRequest, TResponse>(channel, method, options);
+ return "https://" + match.Groups[2].Value + "/";
}
}
}
diff --git a/src/csharp/Grpc.Core/OperationFailedException.cs b/src/csharp/Grpc.Core/CompressionLevel.cs
index 9b1c24d0c1..399652b85e 100644
--- a/src/csharp/Grpc.Core/OperationFailedException.cs
+++ b/src/csharp/Grpc.Core/CompressionLevel.cs
@@ -36,12 +36,28 @@ using System;
namespace Grpc.Core
{
/// <summary>
- /// Thrown when gRPC operation fails.
+ /// Compression level based on grpc_compression_level from grpc/compression.h
/// </summary>
- public class OperationFailedException : Exception
+ public enum CompressionLevel
{
- public OperationFailedException(string message) : base(message)
- {
- }
+ /// <summary>
+ /// No compression.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Low compression.
+ /// </summary>
+ Low,
+
+ /// <summary>
+ /// Medium compression.
+ /// </summary>
+ Medium,
+
+ /// <summary>
+ /// High compression.
+ /// </summary>
+ High,
}
}
diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs
new file mode 100644
index 0000000000..2e4bfc9e47
--- /dev/null
+++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs
@@ -0,0 +1,171 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Threading;
+
+using Grpc.Core.Internal;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+ /// <summary>
+ /// Token for propagating context of server side handlers to child calls.
+ /// In situations when a backend is making calls to another backend,
+ /// it makes sense to propagate properties like deadline and cancellation
+ /// token of the server call to the child call.
+ /// C core provides some other contexts (like tracing context) that
+ /// are not accessible to C# layer, but this token still allows propagating them.
+ /// </summary>
+ public class ContextPropagationToken
+ {
+ /// <summary>
+ /// Default propagation mask used by C core.
+ /// </summary>
+ private const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff;
+
+ /// <summary>
+ /// Default propagation mask used by C# - we want to propagate deadline
+ /// and cancellation token by our own means.
+ /// </summary>
+ internal const ContextPropagationFlags DefaultMask = DefaultCoreMask
+ & ~ContextPropagationFlags.Deadline & ~ContextPropagationFlags.Cancellation;
+
+ readonly CallSafeHandle parentCall;
+ readonly DateTime deadline;
+ readonly CancellationToken cancellationToken;
+ readonly ContextPropagationOptions options;
+
+ internal ContextPropagationToken(CallSafeHandle parentCall, DateTime deadline, CancellationToken cancellationToken, ContextPropagationOptions options)
+ {
+ this.parentCall = Preconditions.CheckNotNull(parentCall);
+ this.deadline = deadline;
+ this.cancellationToken = cancellationToken;
+ this.options = options ?? ContextPropagationOptions.Default;
+ }
+
+ /// <summary>
+ /// Gets the native handle of the parent call.
+ /// </summary>
+ internal CallSafeHandle ParentCall
+ {
+ get
+ {
+ return this.parentCall;
+ }
+ }
+
+ /// <summary>
+ /// Gets the parent call's deadline.
+ /// </summary>
+ internal DateTime ParentDeadline
+ {
+ get
+ {
+ return this.deadline;
+ }
+ }
+
+ /// <summary>
+ /// Gets the parent call's cancellation token.
+ /// </summary>
+ internal CancellationToken ParentCancellationToken
+ {
+ get
+ {
+ return this.cancellationToken;
+ }
+ }
+
+ /// <summary>
+ /// Get the context propagation options.
+ /// </summary>
+ internal ContextPropagationOptions Options
+ {
+ get
+ {
+ return this.options;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Options for <see cref="ContextPropagationToken"/>.
+ /// </summary>
+ public class ContextPropagationOptions
+ {
+ /// <summary>
+ /// The context propagation options that will be used by default.
+ /// </summary>
+ public static readonly ContextPropagationOptions Default = new ContextPropagationOptions();
+
+ bool propagateDeadline;
+ bool propagateCancellation;
+
+
+ /// <summary>
+ /// Creates new context propagation options.
+ /// </summary>
+ /// <param name="propagateDeadline">If set to <c>true</c> parent call's deadline will be propagated to the child call.</param>
+ /// <param name="propagateCancellation">If set to <c>true</c> parent call's cancellation token will be propagated to the child call.</param>
+ public ContextPropagationOptions(bool propagateDeadline = true, bool propagateCancellation = true)
+ {
+ this.propagateDeadline = propagateDeadline;
+ this.propagateCancellation = propagateCancellation;
+ }
+
+ /// <value><c>true</c> if parent call's deadline should be propagated to the child call.</value>
+ public bool IsPropagateDeadline
+ {
+ get { return this.propagateDeadline; }
+ }
+
+ /// <value><c>true</c> if parent call's cancellation token should be propagated to the child call.</value>
+ public bool IsPropagateCancellation
+ {
+ get { return this.propagateCancellation; }
+ }
+ }
+
+ /// <summary>
+ /// Context propagation flags from grpc/grpc.h.
+ /// </summary>
+ [Flags]
+ internal enum ContextPropagationFlags
+ {
+ Deadline = 1,
+ CensusStatsContext = 2,
+ CensusTracingContext = 4,
+ Cancellation = 8
+ }
+}
diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj
index 52defd1965..055aff1444 100644
--- a/src/csharp/Grpc.Core/Grpc.Core.csproj
+++ b/src/csharp/Grpc.Core/Grpc.Core.csproj
@@ -77,14 +77,12 @@
<Compile Include="ServerServiceDefinition.cs" />
<Compile Include="Utils\AsyncStreamExtensions.cs" />
<Compile Include="Utils\BenchmarkUtil.cs" />
- <Compile Include="Utils\ExceptionHelper.cs" />
<Compile Include="Internal\CredentialsSafeHandle.cs" />
<Compile Include="Credentials.cs" />
<Compile Include="Internal\ChannelArgsSafeHandle.cs" />
<Compile Include="Internal\AsyncCompletion.cs" />
<Compile Include="Internal\AsyncCallBase.cs" />
<Compile Include="Internal\AsyncCallServer.cs" />
- <Compile Include="OperationFailedException.cs" />
<Compile Include="Internal\AsyncCall.cs" />
<Compile Include="Utils\Preconditions.cs" />
<Compile Include="Internal\ServerCredentialsSafeHandle.cs" />
@@ -115,6 +113,9 @@
<Compile Include="ChannelState.cs" />
<Compile Include="CallInvocationDetails.cs" />
<Compile Include="CallOptions.cs" />
+ <Compile Include="CompressionLevel.cs" />
+ <Compile Include="WriteOptions.cs" />
+ <Compile Include="ContextPropagationToken.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Grpc.Core.nuspec" />
diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs
index 034a66be3c..30d8c80235 100644
--- a/src/csharp/Grpc.Core/GrpcEnvironment.cs
+++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs
@@ -53,6 +53,9 @@ namespace Grpc.Core
[DllImport("grpc_csharp_ext.dll")]
static extern void grpcsharp_shutdown();
+ [DllImport("grpc_csharp_ext.dll")]
+ static extern IntPtr grpcsharp_version_string(); // returns not-owned const char*
+
static object staticLock = new object();
static GrpcEnvironment instance;
@@ -112,7 +115,7 @@ namespace Grpc.Core
/// </summary>
public static void SetLogger(ILogger customLogger)
{
- Preconditions.CheckNotNull(customLogger);
+ Preconditions.CheckNotNull(customLogger, "customLogger");
logger = customLogger;
}
@@ -164,6 +167,15 @@ namespace Grpc.Core
}
/// <summary>
+ /// Gets version of gRPC C core.
+ /// </summary>
+ internal static string GetCoreVersionString()
+ {
+ var ptr = grpcsharp_version_string(); // the pointer is not owned
+ return Marshal.PtrToStringAnsi(ptr);
+ }
+
+ /// <summary>
/// Shuts down this environment.
/// </summary>
private void Close()
@@ -180,23 +192,5 @@ namespace Grpc.Core
Logger.Info("gRPC shutdown.");
}
-
- /// <summary>
- /// Shuts down this environment asynchronously.
- /// </summary>
- private Task CloseAsync()
- {
- return Task.Run(() =>
- {
- try
- {
- Close();
- }
- catch (Exception e)
- {
- Logger.Error(e, "Error occured while shutting down GrpcEnvironment.");
- }
- });
- }
}
}
diff --git a/src/csharp/Grpc.Core/IAsyncStreamReader.cs b/src/csharp/Grpc.Core/IAsyncStreamReader.cs
index 371fbf27ce..c0a0674e50 100644
--- a/src/csharp/Grpc.Core/IAsyncStreamReader.cs
+++ b/src/csharp/Grpc.Core/IAsyncStreamReader.cs
@@ -43,7 +43,7 @@ namespace Grpc.Core
/// A stream of messages to be read.
/// </summary>
/// <typeparam name="T"></typeparam>
- public interface IAsyncStreamReader<TResponse> : IAsyncEnumerator<TResponse>
+ public interface IAsyncStreamReader<T> : IAsyncEnumerator<T>
{
// TODO(jtattermusch): consider just using IAsyncEnumerator instead of this interface.
}
diff --git a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
index 2000210252..4e2acb9c71 100644
--- a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
+++ b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs
@@ -50,5 +50,13 @@ namespace Grpc.Core
/// </summary>
/// <param name="message">the message to be written. Cannot be null.</param>
Task WriteAsync(T message);
+
+ /// <summary>
+ /// Write options that will be used for the next write.
+ /// If null, default options will be used.
+ /// Once set, this property maintains its value across subsequent
+ /// writes.
+ /// <value>The write options.</value>
+ WriteOptions WriteOptions { get; set; }
}
}
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 414b5c4282..2c3e3d75ea 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -50,7 +50,7 @@ namespace Grpc.Core.Internal
{
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>();
- readonly CallInvocationDetails<TRequest, TResponse> callDetails;
+ readonly CallInvocationDetails<TRequest, TResponse> details;
// Completion of a pending unary response if not null.
TaskCompletionSource<TResponse> unaryResponseTcs;
@@ -63,7 +63,8 @@ namespace Grpc.Core.Internal
public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails)
: base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer)
{
- this.callDetails = callDetails;
+ this.details = callDetails.WithOptions(callDetails.Options.Normalize());
+ this.initialMetadataSent = true; // we always send metadata at the very beginning of the call.
}
// TODO: this method is not Async, so it shouldn't be in AsyncCall class, but
@@ -89,11 +90,11 @@ namespace Grpc.Core.Internal
readingDone = true;
}
- using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+ using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
using (var ctx = BatchContextSafeHandle.Create())
{
- call.StartUnary(payload, ctx, metadataArray);
+ call.StartUnary(ctx, payload, metadataArray, GetWriteFlagsForCall());
var ev = cq.Pluck(ctx.Handle);
bool success = (ev.success != 0);
@@ -108,15 +109,9 @@ namespace Grpc.Core.Internal
}
}
- try
- {
- // Once the blocking call returns, the result should be available synchronously.
- return unaryResponseTcs.Task.Result;
- }
- catch (AggregateException ae)
- {
- throw ExceptionHelper.UnwrapRpcException(ae);
- }
+ // Once the blocking call returns, the result should be available synchronously.
+ // Note that GetAwaiter().GetResult() doesn't wrap exceptions in AggregateException.
+ return unaryResponseTcs.Task.GetAwaiter().GetResult();
}
}
@@ -130,7 +125,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(callDetails.Channel.Environment.CompletionQueue);
+ Initialize(details.Channel.Environment.CompletionQueue);
halfcloseRequested = true;
readingDone = true;
@@ -138,9 +133,9 @@ namespace Grpc.Core.Internal
byte[] payload = UnsafeSerialize(msg);
unaryResponseTcs = new TaskCompletionSource<TResponse>();
- using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+ using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
- call.StartUnary(payload, HandleUnaryResponse, metadataArray);
+ call.StartUnary(HandleUnaryResponse, payload, metadataArray, GetWriteFlagsForCall());
}
return unaryResponseTcs.Task;
}
@@ -157,12 +152,12 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(callDetails.Channel.Environment.CompletionQueue);
+ Initialize(details.Channel.Environment.CompletionQueue);
readingDone = true;
unaryResponseTcs = new TaskCompletionSource<TResponse>();
- using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+ using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
call.StartClientStreaming(HandleUnaryResponse, metadataArray);
}
@@ -181,16 +176,16 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(callDetails.Channel.Environment.CompletionQueue);
+ Initialize(details.Channel.Environment.CompletionQueue);
halfcloseRequested = true;
halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called.
byte[] payload = UnsafeSerialize(msg);
- using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+ using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
- call.StartServerStreaming(payload, HandleFinished, metadataArray);
+ call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall());
}
}
}
@@ -206,9 +201,9 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started);
started = true;
- Initialize(callDetails.Channel.Environment.CompletionQueue);
+ Initialize(details.Channel.Environment.CompletionQueue);
- using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers))
+ using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
call.StartDuplexStreaming(HandleFinished, metadataArray);
}
@@ -219,9 +214,9 @@ namespace Grpc.Core.Internal
/// Sends a streaming request. Only one pending send action is allowed at any given time.
/// completionDelegate is called when the operation finishes.
/// </summary>
- public void StartSendMessage(TRequest msg, AsyncCompletionDelegate<object> completionDelegate)
+ public void StartSendMessage(TRequest msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
{
- StartSendMessageInternal(msg, completionDelegate);
+ StartSendMessageInternal(msg, writeFlags, completionDelegate);
}
/// <summary>
@@ -278,6 +273,14 @@ namespace Grpc.Core.Internal
}
}
+ public CallInvocationDetails<TRequest, TResponse> Details
+ {
+ get
+ {
+ return this.details;
+ }
+ }
+
/// <summary>
/// On client-side, we only fire readCompletionDelegate once all messages have been read
/// and status has been received.
@@ -310,14 +313,17 @@ namespace Grpc.Core.Internal
protected override void OnReleaseResources()
{
- callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
+ details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement();
}
private void Initialize(CompletionQueueSafeHandle cq)
{
- var call = callDetails.Channel.Handle.CreateCall(callDetails.Channel.Environment.CompletionRegistry, cq,
- callDetails.Method, callDetails.Host, Timespec.FromDateTime(callDetails.Options.Deadline));
- callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
+ var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
+
+ var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry,
+ parentCall, ContextPropagationToken.DefaultMask, cq,
+ details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value));
+ details.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
InitializeInternal(call);
RegisterCancellationCallback();
}
@@ -325,7 +331,7 @@ namespace Grpc.Core.Internal
// Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
private void RegisterCancellationCallback()
{
- var token = callDetails.Options.CancellationToken;
+ var token = details.Options.CancellationToken;
if (token.CanBeCanceled)
{
token.Register(() => this.Cancel());
@@ -333,6 +339,15 @@ namespace Grpc.Core.Internal
}
/// <summary>
+ /// Gets WriteFlags set in callDetails.Options.WriteOptions
+ /// </summary>
+ private WriteFlags GetWriteFlagsForCall()
+ {
+ var writeOptions = details.Options.WriteOptions;
+ return writeOptions != null ? writeOptions.Flags : default(WriteFlags);
+ }
+
+ /// <summary>
/// Handler for unary response completion.
/// </summary>
private void HandleUnaryResponse(bool success, BatchContextSafeHandle ctx)
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
index 38f2a5baeb..6ca4bbdafc 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
@@ -71,6 +71,9 @@ namespace Grpc.Core.Internal
protected bool halfclosed;
protected bool finished; // True if close has been received from the peer.
+ protected bool initialMetadataSent;
+ protected long streamingWritesCounter;
+
public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer)
{
this.serializer = Preconditions.CheckNotNull(serializer);
@@ -123,7 +126,7 @@ namespace Grpc.Core.Internal
/// Initiates sending a message. Only one send operation can be active at a time.
/// completionDelegate is invoked upon completion.
/// </summary>
- protected void StartSendMessageInternal(TWrite msg, AsyncCompletionDelegate<object> completionDelegate)
+ protected void StartSendMessageInternal(TWrite msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
{
byte[] payload = UnsafeSerialize(msg);
@@ -132,8 +135,11 @@ namespace Grpc.Core.Internal
Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
CheckSendingAllowed();
- call.StartSendMessage(payload, HandleSendFinished);
+ call.StartSendMessage(HandleSendFinished, payload, writeFlags, !initialMetadataSent);
+
sendCompletionDelegate = completionDelegate;
+ initialMetadataSent = true;
+ streamingWritesCounter++;
}
}
@@ -287,7 +293,7 @@ namespace Grpc.Core.Internal
if (!success)
{
- FireCompletion(origCompletionDelegate, null, new OperationFailedException("Send failed"));
+ FireCompletion(origCompletionDelegate, null, new InvalidOperationException("Send failed"));
}
else
{
@@ -312,7 +318,7 @@ namespace Grpc.Core.Internal
if (!success)
{
- FireCompletion(origCompletionDelegate, null, new OperationFailedException("Halfclose failed"));
+ FireCompletion(origCompletionDelegate, null, new InvalidOperationException("Halfclose failed"));
}
else
{
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
index 513902ee36..3710a65d6b 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
@@ -83,9 +83,9 @@ namespace Grpc.Core.Internal
/// Sends a streaming response. Only one pending send action is allowed at any given time.
/// completionDelegate is called when the operation finishes.
/// </summary>
- public void StartSendMessage(TResponse msg, AsyncCompletionDelegate<object> completionDelegate)
+ public void StartSendMessage(TResponse msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate)
{
- StartSendMessageInternal(msg, completionDelegate);
+ StartSendMessageInternal(msg, writeFlags, completionDelegate);
}
/// <summary>
@@ -98,6 +98,35 @@ namespace Grpc.Core.Internal
}
/// <summary>
+ /// Initiates sending a initial metadata.
+ /// Even though C-core allows sending metadata in parallel to sending messages, we will treat sending metadata as a send message operation
+ /// to make things simpler.
+ /// completionDelegate is invoked upon completion.
+ /// </summary>
+ public void StartSendInitialMetadata(Metadata headers, AsyncCompletionDelegate<object> completionDelegate)
+ {
+ lock (myLock)
+ {
+ Preconditions.CheckNotNull(headers, "metadata");
+ Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
+
+ Preconditions.CheckState(!initialMetadataSent, "Response headers can only be sent once per call.");
+ Preconditions.CheckState(streamingWritesCounter == 0, "Response headers can only be sent before the first write starts.");
+ CheckSendingAllowed();
+
+ Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null");
+
+ using (var metadataArray = MetadataArraySafeHandle.Create(headers))
+ {
+ call.StartSendInitialMetadata(HandleSendFinished, metadataArray);
+ }
+
+ this.initialMetadataSent = true;
+ sendCompletionDelegate = completionDelegate;
+ }
+ }
+
+ /// <summary>
/// Sends call result status, also indicating server is done with streaming responses.
/// Only one pending send action is allowed at any given time.
/// completionDelegate is called when the operation finishes.
@@ -111,7 +140,7 @@ namespace Grpc.Core.Internal
using (var metadataArray = MetadataArraySafeHandle.Create(trailers))
{
- call.StartSendStatusFromServer(status, HandleHalfclosed, metadataArray);
+ call.StartSendStatusFromServer(HandleHalfclosed, status, metadataArray, !initialMetadataSent);
}
halfcloseRequested = true;
readingDone = true;
diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
index 714749b171..3cb01e29bd 100644
--- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
@@ -42,6 +42,8 @@ namespace Grpc.Core.Internal
/// </summary>
internal class CallSafeHandle : SafeHandleZeroIsInvalid
{
+ public static readonly CallSafeHandle NullInstance = new CallSafeHandle();
+
const uint GRPC_WRITE_BUFFER_HINT = 1;
CompletionRegistry completionRegistry;
@@ -53,7 +55,7 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_start_unary(CallSafeHandle call,
- BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray);
+ BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_start_client_streaming(CallSafeHandle call,
@@ -62,7 +64,7 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_start_server_streaming(CallSafeHandle call,
BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len,
- MetadataArraySafeHandle metadataArray);
+ MetadataArraySafeHandle metadataArray, WriteFlags writeFlags);
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_start_duplex_streaming(CallSafeHandle call,
@@ -70,7 +72,7 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_send_message(CallSafeHandle call,
- BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len);
+ BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, WriteFlags writeFlags, bool sendEmptyInitialMetadata);
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_send_close_from_client(CallSafeHandle call,
@@ -78,7 +80,7 @@ namespace Grpc.Core.Internal
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_send_status_from_server(CallSafeHandle call,
- BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray);
+ BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call,
@@ -89,6 +91,10 @@ namespace Grpc.Core.Internal
BatchContextSafeHandle ctx);
[DllImport("grpc_csharp_ext.dll")]
+ static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call,
+ BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray);
+
+ [DllImport("grpc_csharp_ext.dll")]
static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call);
[DllImport("grpc_csharp_ext.dll")]
@@ -103,17 +109,17 @@ namespace Grpc.Core.Internal
this.completionRegistry = completionRegistry;
}
- public void StartUnary(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+ public void StartUnary(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback);
- grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray)
+ grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
.CheckOk();
}
- public void StartUnary(byte[] payload, BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray)
+ public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
- grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray)
+ grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
.CheckOk();
}
@@ -124,11 +130,11 @@ namespace Grpc.Core.Internal
grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk();
}
- public void StartServerStreaming(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+ public void StartServerStreaming(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback);
- grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray).CheckOk();
+ grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk();
}
public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
@@ -138,11 +144,11 @@ namespace Grpc.Core.Internal
grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk();
}
- public void StartSendMessage(byte[] payload, BatchCompletionDelegate callback)
+ public void StartSendMessage(BatchCompletionDelegate callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
{
var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback);
- grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length)).CheckOk();
+ grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk();
}
public void StartSendCloseFromClient(BatchCompletionDelegate callback)
@@ -152,11 +158,11 @@ namespace Grpc.Core.Internal
grpcsharp_call_send_close_from_client(this, ctx).CheckOk();
}
- public void StartSendStatusFromServer(Status status, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+ public void StartSendStatusFromServer(BatchCompletionDelegate callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
{
var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback);
- grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray).CheckOk();
+ grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk();
}
public void StartReceiveMessage(BatchCompletionDelegate callback)
@@ -173,6 +179,13 @@ namespace Grpc.Core.Internal
grpcsharp_call_start_serverside(this, ctx).CheckOk();
}
+ public void StartSendInitialMetadata(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
+ {
+ var ctx = BatchContextSafeHandle.Create();
+ completionRegistry.RegisterBatchCompletion(ctx, callback);
+ grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk();
+ }
+
public void Cancel()
{
grpcsharp_call_cancel(this).CheckOk();
diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
index 7324ebdf57..7f03bf4ea5 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
@@ -47,7 +47,7 @@ namespace Grpc.Core.Internal
static extern ChannelSafeHandle grpcsharp_secure_channel_create(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs);
[DllImport("grpc_csharp_ext.dll")]
- static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline);
+ static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline);
[DllImport("grpc_csharp_ext.dll")]
static extern ChannelState grpcsharp_channel_check_connectivity_state(ChannelSafeHandle channel, int tryToConnect);
@@ -76,9 +76,9 @@ namespace Grpc.Core.Internal
return grpcsharp_secure_channel_create(credentials, target, channelArgs);
}
- public CallSafeHandle CreateCall(CompletionRegistry registry, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline)
+ public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline)
{
- var result = grpcsharp_channel_create_call(this, cq, method, host, deadline);
+ var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline);
result.SetCompletionRegistry(registry);
return result;
}
diff --git a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
index 58f493463b..013f00ff6f 100644
--- a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs
@@ -40,16 +40,18 @@ namespace Grpc.Core.Internal
internal class ClientRequestStream<TRequest, TResponse> : IClientStreamWriter<TRequest>
{
readonly AsyncCall<TRequest, TResponse> call;
+ WriteOptions writeOptions;
public ClientRequestStream(AsyncCall<TRequest, TResponse> call)
{
this.call = call;
+ this.writeOptions = call.Details.Options.WriteOptions;
}
public Task WriteAsync(TRequest message)
{
var taskSource = new AsyncCompletionTaskSource<object>();
- call.StartSendMessage(message, taskSource.CompletionDelegate);
+ call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate);
return taskSource.Task;
}
@@ -59,5 +61,24 @@ namespace Grpc.Core.Internal
call.StartSendCloseFromClient(taskSource.CompletionDelegate);
return taskSource.Task;
}
+
+ public WriteOptions WriteOptions
+ {
+ get
+ {
+ return this.writeOptions;
+ }
+
+ set
+ {
+ writeOptions = value;
+ }
+ }
+
+ private WriteFlags GetWriteFlags()
+ {
+ var options = writeOptions;
+ return options != null ? options.Flags : default(WriteFlags);
+ }
}
}
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 19f0e3c57f..688f9f6fec 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -75,7 +75,7 @@ namespace Grpc.Core.Internal
var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
Status status;
- var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+ var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
Preconditions.CheckArgument(await requestStream.MoveNext());
@@ -131,7 +131,7 @@ namespace Grpc.Core.Internal
var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
Status status;
- var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+ var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
Preconditions.CheckArgument(await requestStream.MoveNext());
@@ -187,7 +187,7 @@ namespace Grpc.Core.Internal
var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
Status status;
- var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+ var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
var result = await handler(requestStream, context);
@@ -247,7 +247,7 @@ namespace Grpc.Core.Internal
var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall);
Status status;
- var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken);
+ var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken);
try
{
await handler(requestStream, responseStream, context);
@@ -304,13 +304,14 @@ namespace Grpc.Core.Internal
return new Status(StatusCode.Unknown, "Exception was thrown by handler.");
}
- public static ServerCallContext NewContext(ServerRpcNew newRpc, string peer, CancellationToken cancellationToken)
+ public static ServerCallContext NewContext<TRequest, TResponse>(ServerRpcNew newRpc, string peer, ServerResponseStream<TRequest, TResponse> serverResponseStream, CancellationToken cancellationToken)
+ where TRequest : class
+ where TResponse : class
{
DateTime realtimeDeadline = newRpc.Deadline.ToClockType(GPRClockType.Realtime).ToDateTime();
- return new ServerCallContext(
- newRpc.Method, newRpc.Host, peer, realtimeDeadline,
- newRpc.RequestMetadata, cancellationToken);
+ return new ServerCallContext(newRpc.Call, newRpc.Method, newRpc.Host, peer, realtimeDeadline,
+ newRpc.RequestMetadata, cancellationToken, serverResponseStream.WriteResponseHeadersAsync, serverResponseStream);
}
}
}
diff --git a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
index 756dcee87f..03e39efc02 100644
--- a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs
@@ -38,11 +38,12 @@ namespace Grpc.Core.Internal
/// <summary>
/// Writes responses asynchronously to an underlying AsyncCallServer object.
/// </summary>
- internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse>
+ internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse>, IHasWriteOptions
where TRequest : class
where TResponse : class
{
readonly AsyncCallServer<TRequest, TResponse> call;
+ WriteOptions writeOptions;
public ServerResponseStream(AsyncCallServer<TRequest, TResponse> call)
{
@@ -52,7 +53,7 @@ namespace Grpc.Core.Internal
public Task WriteAsync(TResponse message)
{
var taskSource = new AsyncCompletionTaskSource<object>();
- call.StartSendMessage(message, taskSource.CompletionDelegate);
+ call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate);
return taskSource.Task;
}
@@ -62,5 +63,31 @@ namespace Grpc.Core.Internal
call.StartSendStatusFromServer(status, trailers, taskSource.CompletionDelegate);
return taskSource.Task;
}
+
+ public Task WriteResponseHeadersAsync(Metadata responseHeaders)
+ {
+ var taskSource = new AsyncCompletionTaskSource<object>();
+ call.StartSendInitialMetadata(responseHeaders, taskSource.CompletionDelegate);
+ return taskSource.Task;
+ }
+
+ public WriteOptions WriteOptions
+ {
+ get
+ {
+ return writeOptions;
+ }
+
+ set
+ {
+ writeOptions = value;
+ }
+ }
+
+ private WriteFlags GetWriteFlags()
+ {
+ var options = writeOptions;
+ return options != null ? options.Flags : default(WriteFlags);
+ }
}
}
diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs
index e83d21f4a4..daf85d5f61 100644
--- a/src/csharp/Grpc.Core/Internal/Timespec.cs
+++ b/src/csharp/Grpc.Core/Internal/Timespec.cs
@@ -211,7 +211,7 @@ namespace Grpc.Core.Internal
return Timespec.InfPast;
}
- Preconditions.CheckArgument(dateTime.Kind == DateTimeKind.Utc, "dateTime");
+ Preconditions.CheckArgument(dateTime.Kind == DateTimeKind.Utc, "dateTime needs of kind DateTimeKind.Utc or be equal to DateTime.MaxValue or DateTime.MinValue.");
try
{
diff --git a/src/csharp/Grpc.Core/KeyCertificatePair.cs b/src/csharp/Grpc.Core/KeyCertificatePair.cs
index 5def15a656..6f691975e9 100644
--- a/src/csharp/Grpc.Core/KeyCertificatePair.cs
+++ b/src/csharp/Grpc.Core/KeyCertificatePair.cs
@@ -54,8 +54,8 @@ namespace Grpc.Core
/// <param name="privateKey">PEM encoded private key.</param>
public KeyCertificatePair(string certificateChain, string privateKey)
{
- this.certificateChain = Preconditions.CheckNotNull(certificateChain);
- this.privateKey = Preconditions.CheckNotNull(privateKey);
+ this.certificateChain = Preconditions.CheckNotNull(certificateChain, "certificateChain");
+ this.privateKey = Preconditions.CheckNotNull(privateKey, "privateKey");
}
/// <summary>
diff --git a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
index c67765c78d..382481d871 100644
--- a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
+++ b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
@@ -42,16 +42,21 @@ namespace Grpc.Core.Logging
readonly Type forType;
readonly string forTypeString;
+ /// <summary>Creates a console logger not associated to any specific type.</summary>
public ConsoleLogger() : this(null)
{
}
+ /// <summary>Creates a console logger that logs messsage specific for given type.</summary>
private ConsoleLogger(Type forType)
{
this.forType = forType;
this.forTypeString = forType != null ? forType.FullName + " " : "";
}
-
+
+ /// <summary>
+ /// Returns a logger associated with the specified type.
+ /// </summary>
public ILogger ForType<T>()
{
if (typeof(T) == forType)
@@ -61,31 +66,37 @@ namespace Grpc.Core.Logging
return new ConsoleLogger(typeof(T));
}
+ /// <summary>Logs a message with severity Debug.</summary>
public void Debug(string message, params object[] formatArgs)
{
Log("D", message, formatArgs);
}
+ /// <summary>Logs a message with severity Info.</summary>
public void Info(string message, params object[] formatArgs)
{
Log("I", message, formatArgs);
}
+ /// <summary>Logs a message with severity Warning.</summary>
public void Warning(string message, params object[] formatArgs)
{
Log("W", message, formatArgs);
}
+ /// <summary>Logs a message and an associated exception with severity Warning.</summary>
public void Warning(Exception exception, string message, params object[] formatArgs)
{
Log("W", message + " " + exception, formatArgs);
}
+ /// <summary>Logs a message with severity Error.</summary>
public void Error(string message, params object[] formatArgs)
{
Log("E", message, formatArgs);
}
+ /// <summary>Logs a message and an associated exception with severity Error.</summary>
public void Error(Exception exception, string message, params object[] formatArgs)
{
Log("E", message + " " + exception, formatArgs);
diff --git a/src/csharp/Grpc.Core/Logging/ILogger.cs b/src/csharp/Grpc.Core/Logging/ILogger.cs
index 0d58f133e3..61e0c91388 100644
--- a/src/csharp/Grpc.Core/Logging/ILogger.cs
+++ b/src/csharp/Grpc.Core/Logging/ILogger.cs
@@ -42,16 +42,22 @@ namespace Grpc.Core.Logging
/// <summary>Returns a logger associated with the specified type.</summary>
ILogger ForType<T>();
+ /// <summary>Logs a message with severity Debug.</summary>
void Debug(string message, params object[] formatArgs);
+ /// <summary>Logs a message with severity Info.</summary>
void Info(string message, params object[] formatArgs);
+ /// <summary>Logs a message with severity Warning.</summary>
void Warning(string message, params object[] formatArgs);
+ /// <summary>Logs a message and an associated exception with severity Warning.</summary>
void Warning(Exception exception, string message, params object[] formatArgs);
+ /// <summary>Logs a message with severity Error.</summary>
void Error(string message, params object[] formatArgs);
+ /// <summary>Logs a message and an associated exception with severity Error.</summary>
void Error(Exception exception, string message, params object[] formatArgs);
}
}
diff --git a/src/csharp/Grpc.Core/Marshaller.cs b/src/csharp/Grpc.Core/Marshaller.cs
index 8b1a929074..f38cb0863f 100644
--- a/src/csharp/Grpc.Core/Marshaller.cs
+++ b/src/csharp/Grpc.Core/Marshaller.cs
@@ -37,19 +37,27 @@ using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
- /// For serializing and deserializing messages.
+ /// Encapsulates the logic for serializing and deserializing messages.
/// </summary>
public struct Marshaller<T>
{
readonly Func<T, byte[]> serializer;
readonly Func<byte[], T> deserializer;
+ /// <summary>
+ /// Initializes a new marshaller.
+ /// </summary>
+ /// <param name="serializer">Function that will be used to serialize messages.</param>
+ /// <param name="deserializer">Function that will be used to deserialize messages.</param>
public Marshaller(Func<T, byte[]> serializer, Func<byte[], T> deserializer)
{
- this.serializer = Preconditions.CheckNotNull(serializer);
- this.deserializer = Preconditions.CheckNotNull(deserializer);
+ this.serializer = Preconditions.CheckNotNull(serializer, "serializer");
+ this.deserializer = Preconditions.CheckNotNull(deserializer, "deserializer");
}
+ /// <summary>
+ /// Gets the serializer function.
+ /// </summary>
public Func<T, byte[]> Serializer
{
get
@@ -58,6 +66,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the deserializer function.
+ /// </summary>
public Func<byte[], T> Deserializer
{
get
@@ -72,11 +83,17 @@ namespace Grpc.Core
/// </summary>
public static class Marshallers
{
+ /// <summary>
+ /// Creates a marshaller from specified serializer and deserializer.
+ /// </summary>
public static Marshaller<T> Create<T>(Func<T, byte[]> serializer, Func<byte[], T> deserializer)
{
return new Marshaller<T>(serializer, deserializer);
}
+ /// <summary>
+ /// Returns a marshaller for <c>string</c> type. This is useful for testing.
+ /// </summary>
public static Marshaller<string> StringMarshaller
{
get
diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs
index 6fd0a7109d..9db2abf46e 100644
--- a/src/csharp/Grpc.Core/Metadata.cs
+++ b/src/csharp/Grpc.Core/Metadata.cs
@@ -114,6 +114,16 @@ namespace Grpc.Core
entries.Add(item);
}
+ public void Add(string key, string value)
+ {
+ Add(new Entry(key, value));
+ }
+
+ public void Add(string key, byte[] valueBytes)
+ {
+ Add(new Entry(key, valueBytes));
+ }
+
public void Clear()
{
CheckWriteable();
@@ -176,15 +186,15 @@ namespace Grpc.Core
public Entry(string key, byte[] valueBytes)
{
- this.key = Preconditions.CheckNotNull(key);
+ this.key = Preconditions.CheckNotNull(key, "key");
this.value = null;
- this.valueBytes = Preconditions.CheckNotNull(valueBytes);
+ this.valueBytes = Preconditions.CheckNotNull(valueBytes, "valueBytes");
}
public Entry(string key, string value)
{
- this.key = Preconditions.CheckNotNull(key);
- this.value = Preconditions.CheckNotNull(value);
+ this.key = Preconditions.CheckNotNull(key, "key");
+ this.value = Preconditions.CheckNotNull(value, "value");
this.valueBytes = null;
}
diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs
index cc047ac9f8..4c208b4a26 100644
--- a/src/csharp/Grpc.Core/Method.cs
+++ b/src/csharp/Grpc.Core/Method.cs
@@ -41,14 +41,21 @@ namespace Grpc.Core
/// </summary>
public enum MethodType
{
- Unary, // Unary request, unary response.
- ClientStreaming, // Streaming request, unary response.
- ServerStreaming, // Unary request, streaming response.
- DuplexStreaming // Streaming request, streaming response.
+ /// <summary>Single request sent from client, single response received from server.</summary>
+ Unary,
+
+ /// <summary>Stream of request sent from client, single response received from server.</summary>
+ ClientStreaming,
+
+ /// <summary>Single request sent from client, stream of responses received from server.</summary>
+ ServerStreaming,
+
+ /// <summary>Both server and client can stream arbitrary number of requests and responses simultaneously.</summary>
+ DuplexStreaming
}
/// <summary>
- /// A description of a service method.
+ /// A description of a remote method.
/// </summary>
public class Method<TRequest, TResponse>
{
@@ -59,16 +66,27 @@ namespace Grpc.Core
readonly Marshaller<TResponse> responseMarshaller;
readonly string fullName;
+ /// <summary>
+ /// Initializes a new instance of the <c>Method</c> class.
+ /// </summary>
+ /// <param name="type">Type of method.</param>
+ /// <param name="serviceName">Name of service this method belongs to.</param>
+ /// <param name="name">Unqualified name of the method.</param>
+ /// <param name="requestMarshaller">Marshaller used for request messages.</param>
+ /// <param name="responseMarshaller">Marshaller used for response messages.</param>
public Method(MethodType type, string serviceName, string name, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller)
{
this.type = type;
- this.serviceName = Preconditions.CheckNotNull(serviceName);
- this.name = Preconditions.CheckNotNull(name);
- this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller);
- this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller);
- this.fullName = GetFullName(serviceName);
+ this.serviceName = Preconditions.CheckNotNull(serviceName, "serviceName");
+ this.name = Preconditions.CheckNotNull(name, "name");
+ this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller, "requestMarshaller");
+ this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller, "responseMarshaller");
+ this.fullName = GetFullName(serviceName, name);
}
+ /// <summary>
+ /// Gets the type of the method.
+ /// </summary>
public MethodType Type
{
get
@@ -77,6 +95,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the name of the service to which this method belongs.
+ /// </summary>
public string ServiceName
{
get
@@ -85,6 +106,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the unqualified name of the method.
+ /// </summary>
public string Name
{
get
@@ -93,6 +117,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the marshaller used for request messages.
+ /// </summary>
public Marshaller<TRequest> RequestMarshaller
{
get
@@ -101,6 +128,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Gets the marshaller used for response messages.
+ /// </summary>
public Marshaller<TResponse> ResponseMarshaller
{
get
@@ -108,7 +138,11 @@ namespace Grpc.Core
return this.responseMarshaller;
}
}
-
+
+ /// <summary>
+ /// Gets the fully qualified name of the method. On the server side, methods are dispatched
+ /// based on this name.
+ /// </summary>
public string FullName
{
get
@@ -120,9 +154,9 @@ namespace Grpc.Core
/// <summary>
/// Gets full name of the method including the service name.
/// </summary>
- internal string GetFullName(string serviceName)
+ internal static string GetFullName(string serviceName, string methodName)
{
- return "/" + Preconditions.CheckNotNull(serviceName) + "/" + this.Name;
+ return "/" + serviceName + "/" + methodName;
}
}
}
diff --git a/src/csharp/Grpc.Core/RpcException.cs b/src/csharp/Grpc.Core/RpcException.cs
index c58578286b..cac417e626 100644
--- a/src/csharp/Grpc.Core/RpcException.cs
+++ b/src/csharp/Grpc.Core/RpcException.cs
@@ -36,22 +36,34 @@ using System;
namespace Grpc.Core
{
/// <summary>
- /// Thrown when remote procedure call fails.
+ /// Thrown when remote procedure call fails. Every <c>RpcException</c> is associated with a resulting <see cref="Status"/> of the call.
/// </summary>
public class RpcException : Exception
{
private readonly Status status;
+ /// <summary>
+ /// Creates a new <c>RpcException</c> associated with given status.
+ /// </summary>
+ /// <param name="status">Resulting status of a call.</param>
public RpcException(Status status) : base(status.ToString())
{
this.status = status;
}
+ /// <summary>
+ /// Creates a new <c>RpcException</c> associated with given status and message.
+ /// </summary>
+ /// <param name="status">Resulting status of a call.</param>
+ /// <param name="message">The exception message.</param>
public RpcException(Status status, string message) : base(message)
{
this.status = status;
}
+ /// <summary>
+ /// Resulting status of the call.
+ /// </summary>
public Status Status
{
get
diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs
index eb5b043d1c..c76f126026 100644
--- a/src/csharp/Grpc.Core/Server.cs
+++ b/src/csharp/Grpc.Core/Server.cs
@@ -192,7 +192,7 @@ namespace Grpc.Core
{
lock (myLock)
{
- Preconditions.CheckNotNull(serverPort.Credentials);
+ Preconditions.CheckNotNull(serverPort.Credentials, "serverPort");
Preconditions.CheckState(!startRequested);
var address = string.Format("{0}:{1}", serverPort.Host, serverPort.Port);
int boundPort;
diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs
index 032b1390db..75d81c64f3 100644
--- a/src/csharp/Grpc.Core/ServerCallContext.cs
+++ b/src/csharp/Grpc.Core/ServerCallContext.cs
@@ -36,15 +36,16 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
+using Grpc.Core.Internal;
+
namespace Grpc.Core
{
/// <summary>
/// Context for a server-side call.
/// </summary>
- public sealed class ServerCallContext
+ public class ServerCallContext
{
- // TODO(jtattermusch): expose method to send initial metadata back to client
-
+ private readonly CallSafeHandle callHandle;
private readonly string method;
private readonly string host;
private readonly string peer;
@@ -54,15 +55,34 @@ namespace Grpc.Core
private readonly Metadata responseTrailers = new Metadata();
private Status status = Status.DefaultSuccess;
+ private Func<Metadata, Task> writeHeadersFunc;
+ private IHasWriteOptions writeOptionsHolder;
- public ServerCallContext(string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken)
+ internal ServerCallContext(CallSafeHandle callHandle, string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+ Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder)
{
+ this.callHandle = callHandle;
this.method = method;
this.host = host;
this.peer = peer;
this.deadline = deadline;
this.requestHeaders = requestHeaders;
this.cancellationToken = cancellationToken;
+ this.writeHeadersFunc = writeHeadersFunc;
+ this.writeOptionsHolder = writeOptionsHolder;
+ }
+
+ public Task WriteResponseHeadersAsync(Metadata responseHeaders)
+ {
+ return writeHeadersFunc(responseHeaders);
+ }
+
+ /// <summary>
+ /// Creates a propagation token to be used to propagate call context to a child call.
+ /// </summary>
+ public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null)
+ {
+ return new ContextPropagationToken(callHandle, deadline, cancellationToken, options);
}
/// <summary>Name of method called in this RPC.</summary>
@@ -110,7 +130,7 @@ namespace Grpc.Core
}
}
- ///<summary>Cancellation token signals when call is cancelled.</summary>
+ /// <summary>Cancellation token signals when call is cancelled.</summary>
public CancellationToken CancellationToken
{
get
@@ -141,5 +161,31 @@ namespace Grpc.Core
status = value;
}
}
+
+ /// <summary>
+ /// Allows setting write options for the following write.
+ /// For streaming response calls, this property is also exposed as on IServerStreamWriter for convenience.
+ /// Both properties are backed by the same underlying value.
+ /// </summary>
+ public WriteOptions WriteOptions
+ {
+ get
+ {
+ return writeOptionsHolder.WriteOptions;
+ }
+
+ set
+ {
+ writeOptionsHolder.WriteOptions = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Allows sharing write options between ServerCallContext and other objects.
+ /// </summary>
+ public interface IHasWriteOptions
+ {
+ WriteOptions WriteOptions { get; set; }
}
}
diff --git a/src/csharp/Grpc.Core/ServerCredentials.cs b/src/csharp/Grpc.Core/ServerCredentials.cs
index c11a1ede08..3c6703d30e 100644
--- a/src/csharp/Grpc.Core/ServerCredentials.cs
+++ b/src/csharp/Grpc.Core/ServerCredentials.cs
@@ -91,7 +91,7 @@ namespace Grpc.Core
{
this.keyCertificatePairs = new List<KeyCertificatePair>(keyCertificatePairs).AsReadOnly();
Preconditions.CheckArgument(this.keyCertificatePairs.Count > 0,
- "At least one KeyCertificatePair needs to be provided");
+ "At least one KeyCertificatePair needs to be provided.");
if (forceClientAuth)
{
Preconditions.CheckNotNull(rootCertificates,
diff --git a/src/csharp/Grpc.Core/ServerMethods.cs b/src/csharp/Grpc.Core/ServerMethods.cs
index d457770203..1f119a80ff 100644
--- a/src/csharp/Grpc.Core/ServerMethods.cs
+++ b/src/csharp/Grpc.Core/ServerMethods.cs
@@ -31,12 +31,8 @@
#endregion
-using System;
-using System.Threading;
using System.Threading.Tasks;
-using Grpc.Core.Internal;
-
namespace Grpc.Core
{
/// <summary>
diff --git a/src/csharp/Grpc.Core/ServerPort.cs b/src/csharp/Grpc.Core/ServerPort.cs
index 55e4bd0062..598404d045 100644
--- a/src/csharp/Grpc.Core/ServerPort.cs
+++ b/src/csharp/Grpc.Core/ServerPort.cs
@@ -62,9 +62,9 @@ namespace Grpc.Core
/// <param name="credentials">credentials to use to secure this port.</param>
public ServerPort(string host, int port, ServerCredentials credentials)
{
- this.host = Preconditions.CheckNotNull(host);
+ this.host = Preconditions.CheckNotNull(host, "host");
this.port = port;
- this.credentials = Preconditions.CheckNotNull(credentials);
+ this.credentials = Preconditions.CheckNotNull(credentials, "credentials");
}
/// <summary>
diff --git a/src/csharp/Grpc.Core/ServerServiceDefinition.cs b/src/csharp/Grpc.Core/ServerServiceDefinition.cs
index a00d156e52..94b0a320c3 100644
--- a/src/csharp/Grpc.Core/ServerServiceDefinition.cs
+++ b/src/csharp/Grpc.Core/ServerServiceDefinition.cs
@@ -79,7 +79,7 @@ namespace Grpc.Core
where TRequest : class
where TResponse : class
{
- callHandlers.Add(method.GetFullName(serviceName), ServerCalls.UnaryCall(method, handler));
+ callHandlers.Add(method.FullName, ServerCalls.UnaryCall(method, handler));
return this;
}
@@ -89,7 +89,7 @@ namespace Grpc.Core
where TRequest : class
where TResponse : class
{
- callHandlers.Add(method.GetFullName(serviceName), ServerCalls.ClientStreamingCall(method, handler));
+ callHandlers.Add(method.FullName, ServerCalls.ClientStreamingCall(method, handler));
return this;
}
@@ -99,7 +99,7 @@ namespace Grpc.Core
where TRequest : class
where TResponse : class
{
- callHandlers.Add(method.GetFullName(serviceName), ServerCalls.ServerStreamingCall(method, handler));
+ callHandlers.Add(method.FullName, ServerCalls.ServerStreamingCall(method, handler));
return this;
}
@@ -109,7 +109,7 @@ namespace Grpc.Core
where TRequest : class
where TResponse : class
{
- callHandlers.Add(method.GetFullName(serviceName), ServerCalls.DuplexStreamingCall(method, handler));
+ callHandlers.Add(method.FullName, ServerCalls.DuplexStreamingCall(method, handler));
return this;
}
diff --git a/src/csharp/Grpc.Core/Status.cs b/src/csharp/Grpc.Core/Status.cs
index 754f6cb3ca..6bd8dc820b 100644
--- a/src/csharp/Grpc.Core/Status.cs
+++ b/src/csharp/Grpc.Core/Status.cs
@@ -29,13 +29,12 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
-using System;
-using System.Runtime.InteropServices;
+using Grpc.Core.Utils;
namespace Grpc.Core
{
/// <summary>
- /// Represents RPC result.
+ /// Represents RPC result, which consists of <see cref="StatusCode"/> and an optional detail string.
/// </summary>
public struct Status
{
@@ -52,6 +51,11 @@ namespace Grpc.Core
readonly StatusCode statusCode;
readonly string detail;
+ /// <summary>
+ /// Creates a new instance of <c>Status</c>.
+ /// </summary>
+ /// <param name="statusCode">Status code.</param>
+ /// <param name="detail">Detail.</param>
public Status(StatusCode statusCode, string detail)
{
this.statusCode = statusCode;
@@ -80,6 +84,9 @@ namespace Grpc.Core
}
}
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Status"/>.
+ /// </summary>
public override string ToString()
{
return string.Format("Status(StatusCode={0}, Detail=\"{1}\")", statusCode, detail);
diff --git a/src/csharp/Grpc.Core/StatusCode.cs b/src/csharp/Grpc.Core/StatusCode.cs
index a9696fa469..90606955af 100644
--- a/src/csharp/Grpc.Core/StatusCode.cs
+++ b/src/csharp/Grpc.Core/StatusCode.cs
@@ -31,8 +31,6 @@
#endregion
-using System;
-
namespace Grpc.Core
{
/// <summary>
@@ -41,101 +39,101 @@ namespace Grpc.Core
/// </summary>
public enum StatusCode
{
- /* Not an error; returned on success */
+ /// <summary>Not an error; returned on success.</summary>
OK = 0,
- /* The operation was cancelled (typically by the caller). */
+
+ /// <summary>The operation was cancelled (typically by the caller).</summary>
Cancelled = 1,
- /* Unknown error. An example of where this error may be returned is
- if a Status value received from another address space belongs to
- an error-space that is not known in this address space. Also
- errors raised by APIs that do not return enough error information
- may be converted to this error. */
+
+ /// <summary>
+ /// Unknown error. An example of where this error may be returned is
+ /// if a Status value received from another address space belongs to
+ /// an error-space that is not known in this address space. Also
+ /// errors raised by APIs that do not return enough error information
+ /// may be converted to this error.
+ /// </summary>
Unknown = 2,
- /* Client specified an invalid argument. Note that this differs
- from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
- that are problematic regardless of the state of the system
- (e.g., a malformed file name). */
+
+ /// <summary>
+ /// Client specified an invalid argument. Note that this differs
+ /// from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
+ /// that are problematic regardless of the state of the system
+ /// (e.g., a malformed file name).
+ /// </summary>
InvalidArgument = 3,
- /* Deadline expired before operation could complete. For operations
- that change the state of the system, this error may be returned
- even if the operation has completed successfully. For example, a
- successful response from a server could have been delayed long
- enough for the deadline to expire. */
+
+ /// <summary>
+ /// Deadline expired before operation could complete. For operations
+ /// that change the state of the system, this error may be returned
+ /// even if the operation has completed successfully. For example, a
+ /// successful response from a server could have been delayed long
+ /// enough for the deadline to expire.
+ /// </summary>
DeadlineExceeded = 4,
- /* Some requested entity (e.g., file or directory) was not found. */
+
+ /// <summary>Some requested entity (e.g., file or directory) was not found.</summary>
NotFound = 5,
- /* Some entity that we attempted to create (e.g., file or directory)
- already exists. */
+
+ /// <summary>Some entity that we attempted to create (e.g., file or directory) already exists.</summary>
AlreadyExists = 6,
- /* The caller does not have permission to execute the specified
- operation. PERMISSION_DENIED must not be used for rejections
- caused by exhausting some resource (use RESOURCE_EXHAUSTED
- instead for those errors). PERMISSION_DENIED must not be
- used if the caller can not be identified (use UNAUTHENTICATED
- instead for those errors). */
+
+ /// <summary>
+ /// The caller does not have permission to execute the specified
+ /// operation. PERMISSION_DENIED must not be used for rejections
+ /// caused by exhausting some resource (use RESOURCE_EXHAUSTED
+ /// instead for those errors). PERMISSION_DENIED must not be
+ /// used if the caller can not be identified (use UNAUTHENTICATED
+ /// instead for those errors).
+ /// </summary>
PermissionDenied = 7,
- /* The request does not have valid authentication credentials for the
- operation. */
+
+ /// <summary>The request does not have valid authentication credentials for the operation.</summary>
Unauthenticated = 16,
- /* Some resource has been exhausted, perhaps a per-user quota, or
- perhaps the entire file system is out of space. */
+
+ /// <summary>
+ /// Some resource has been exhausted, perhaps a per-user quota, or
+ /// perhaps the entire file system is out of space.
+ /// </summary>
ResourceExhausted = 8,
- /* Operation was rejected because the system is not in a state
- required for the operation's execution. For example, directory
- to be deleted may be non-empty, an rmdir operation is applied to
- a non-directory, etc.
-
- A litmus test that may help a service implementor in deciding
- between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
- (a) Use UNAVAILABLE if the client can retry just the failing call.
- (b) Use ABORTED if the client should retry at a higher-level
- (e.g., restarting a read-modify-write sequence).
- (c) Use FAILED_PRECONDITION if the client should not retry until
- the system state has been explicitly fixed. E.g., if an "rmdir"
- fails because the directory is non-empty, FAILED_PRECONDITION
- should be returned since the client should not retry unless
- they have first fixed up the directory by deleting files from it.
- (d) Use FAILED_PRECONDITION if the client performs conditional
- REST Get/Update/Delete on a resource and the resource on the
- server does not match the condition. E.g., conflicting
- read-modify-write on the same resource. */
+
+ /// <summary>
+ /// Operation was rejected because the system is not in a state
+ /// required for the operation's execution. For example, directory
+ /// to be deleted may be non-empty, an rmdir operation is applied to
+ /// a non-directory, etc.
+ /// </summary>
FailedPrecondition = 9,
- /* The operation was aborted, typically due to a concurrency issue
- like sequencer check failures, transaction aborts, etc.
- See litmus test above for deciding between FAILED_PRECONDITION,
- ABORTED, and UNAVAILABLE. */
+ /// <summary>
+ /// The operation was aborted, typically due to a concurrency issue
+ /// like sequencer check failures, transaction aborts, etc.
+ /// </summary>
Aborted = 10,
- /* Operation was attempted past the valid range. E.g., seeking or
- reading past end of file.
-
- Unlike INVALID_ARGUMENT, this error indicates a problem that may
- be fixed if the system state changes. For example, a 32-bit file
- system will generate INVALID_ARGUMENT if asked to read at an
- offset that is not in the range [0,2^32-1], but it will generate
- OUT_OF_RANGE if asked to read from an offset past the current
- file size.
-
- There is a fair bit of overlap between FAILED_PRECONDITION and
- OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific
- error) when it applies so that callers who are iterating through
- a space can easily look for an OUT_OF_RANGE error to detect when
- they are done. */
+
+ /// <summary>
+ /// Operation was attempted past the valid range. E.g., seeking or
+ /// reading past end of file.
+ /// </summary>
OutOfRange = 11,
- /* Operation is not implemented or not supported/enabled in this service. */
+
+ /// <summary>Operation is not implemented or not supported/enabled in this service.</summary>
Unimplemented = 12,
- /* Internal errors. Means some invariants expected by underlying
- system has been broken. If you see one of these errors,
- something is very broken. */
+
+ /// <summary>
+ /// Internal errors. Means some invariants expected by underlying
+ /// system has been broken. If you see one of these errors,
+ /// something is very broken.
+ /// </summary>
Internal = 13,
- /* The service is currently unavailable. This is a most likely a
- transient condition and may be corrected by retrying with
- a backoff.
- See litmus test above for deciding between FAILED_PRECONDITION,
- ABORTED, and UNAVAILABLE. */
+ /// <summary>
+ /// The service is currently unavailable. This is a most likely a
+ /// transient condition and may be corrected by retrying with
+ /// a backoff.
+ /// </summary>
Unavailable = 14,
- /* Unrecoverable data loss or corruption. */
+
+ /// <summary>Unrecoverable data loss or corruption.</summary>
DataLoss = 15
}
}
diff --git a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
index 8a748b45a8..cdf1e51026 100644
--- a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
+++ b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs
@@ -33,7 +33,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
namespace Grpc.Core.Utils
@@ -46,7 +45,7 @@ namespace Grpc.Core.Utils
/// <summary>
/// Reads the entire stream and executes an async action for each element.
/// </summary>
- public static async Task ForEach<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction)
+ public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction)
where T : class
{
while (await streamReader.MoveNext())
@@ -58,7 +57,7 @@ namespace Grpc.Core.Utils
/// <summary>
/// Reads the entire stream and creates a list containing all the elements read.
/// </summary>
- public static async Task<List<T>> ToList<T>(this IAsyncStreamReader<T> streamReader)
+ public static async Task<List<T>> ToListAsync<T>(this IAsyncStreamReader<T> streamReader)
where T : class
{
var result = new List<T>();
@@ -73,7 +72,7 @@ namespace Grpc.Core.Utils
/// Writes all elements from given enumerable to the stream.
/// Completes the stream afterwards unless close = false.
/// </summary>
- public static async Task WriteAll<T>(this IClientStreamWriter<T> streamWriter, IEnumerable<T> elements, bool complete = true)
+ public static async Task WriteAllAsync<T>(this IClientStreamWriter<T> streamWriter, IEnumerable<T> elements, bool complete = true)
where T : class
{
foreach (var element in elements)
@@ -89,7 +88,7 @@ namespace Grpc.Core.Utils
/// <summary>
/// Writes all elements from given enumerable to the stream.
/// </summary>
- public static async Task WriteAll<T>(this IServerStreamWriter<T> streamWriter, IEnumerable<T> elements)
+ public static async Task WriteAllAsync<T>(this IServerStreamWriter<T> streamWriter, IEnumerable<T> elements)
where T : class
{
foreach (var element in elements)
diff --git a/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs b/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs
index 82653c3a1f..eb3a5b16e3 100644
--- a/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs
+++ b/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs
@@ -39,6 +39,9 @@ using System.Threading.Tasks;
namespace Grpc.Core.Utils
{
+ /// <summary>
+ /// Utility methods to run microbenchmarks.
+ /// </summary>
public static class BenchmarkUtil
{
/// <summary>
diff --git a/src/csharp/Grpc.Core/Utils/Preconditions.cs b/src/csharp/Grpc.Core/Utils/Preconditions.cs
index aeb5d210a7..374262f87a 100644
--- a/src/csharp/Grpc.Core/Utils/Preconditions.cs
+++ b/src/csharp/Grpc.Core/Utils/Preconditions.cs
@@ -32,17 +32,16 @@
#endregion
using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Threading.Tasks;
namespace Grpc.Core.Utils
{
+ /// <summary>
+ /// Utility methods to simplify checking preconditions in the code.
+ /// </summary>
public static class Preconditions
{
/// <summary>
- /// Throws ArgumentException if condition is false.
+ /// Throws <see cref="ArgumentException"/> if condition is false.
/// </summary>
public static void CheckArgument(bool condition)
{
@@ -53,7 +52,7 @@ namespace Grpc.Core.Utils
}
/// <summary>
- /// Throws ArgumentException with given message if condition is false.
+ /// Throws <see cref="ArgumentException"/> with given message if condition is false.
/// </summary>
public static void CheckArgument(bool condition, string errorMessage)
{
@@ -64,31 +63,31 @@ namespace Grpc.Core.Utils
}
/// <summary>
- /// Throws NullReferenceException if reference is null.
+ /// Throws <see cref="ArgumentNullException"/> if reference is null.
/// </summary>
public static T CheckNotNull<T>(T reference)
{
if (reference == null)
{
- throw new NullReferenceException();
+ throw new ArgumentNullException();
}
return reference;
}
/// <summary>
- /// Throws NullReferenceException with given message if reference is null.
+ /// Throws <see cref="ArgumentNullException"/> if reference is null.
/// </summary>
- public static T CheckNotNull<T>(T reference, string errorMessage)
+ public static T CheckNotNull<T>(T reference, string paramName)
{
if (reference == null)
{
- throw new NullReferenceException(errorMessage);
+ throw new ArgumentNullException(paramName);
}
return reference;
}
/// <summary>
- /// Throws InvalidOperationException if condition is false.
+ /// Throws <see cref="InvalidOperationException"/> if condition is false.
/// </summary>
public static void CheckState(bool condition)
{
@@ -99,7 +98,7 @@ namespace Grpc.Core.Utils
}
/// <summary>
- /// Throws InvalidOperationException with given message if condition is false.
+ /// Throws <see cref="InvalidOperationException"/> with given message if condition is false.
/// </summary>
public static void CheckState(bool condition, string errorMessage)
{
diff --git a/src/csharp/Grpc.Core/Version.cs b/src/csharp/Grpc.Core/Version.cs
index b5cb652945..d02b301cac 100644
--- a/src/csharp/Grpc.Core/Version.cs
+++ b/src/csharp/Grpc.Core/Version.cs
@@ -1,5 +1,37 @@
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
using System.Reflection;
-using System.Runtime.CompilerServices;
// The current version of gRPC C#.
-[assembly: AssemblyVersion(Grpc.Core.VersionInfo.CurrentVersion + ".*")]
+[assembly: AssemblyVersion(Grpc.Core.VersionInfo.CurrentVersion + ".0")]
diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs
index eb55af8a13..eda821bc31 100644
--- a/src/csharp/Grpc.Core/VersionInfo.cs
+++ b/src/csharp/Grpc.Core/VersionInfo.cs
@@ -1,12 +1,45 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
+#region Copyright notice and license
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
namespace Grpc.Core
{
+ /// <summary>
+ /// Provides info about current version of gRPC.
+ /// </summary>
public static class VersionInfo
{
/// <summary>
- /// Current version of gRPC
+ /// Current version of gRPC C#
/// </summary>
public const string CurrentVersion = "0.7.0";
}
diff --git a/src/csharp/Grpc.Core/Utils/ExceptionHelper.cs b/src/csharp/Grpc.Core/WriteOptions.cs
index c4d6bee058..7ef3189d76 100644
--- a/src/csharp/Grpc.Core/Utils/ExceptionHelper.cs
+++ b/src/csharp/Grpc.Core/WriteOptions.cs
@@ -33,25 +33,50 @@
using System;
-namespace Grpc.Core.Utils
+namespace Grpc.Core
{
- public static class ExceptionHelper
+ /// <summary>
+ /// Flags for write operations.
+ /// </summary>
+ [Flags]
+ public enum WriteFlags
{
/// <summary>
- /// If inner exceptions contain RpcException, rethrows it.
- /// Otherwise, rethrows the original aggregate exception.
- /// Always throws, the exception return type is here only to make the.
+ /// Hint that the write may be buffered and need not go out on the wire immediately.
+ /// gRPC is free to buffer the message until the next non-buffered
+ /// write, or until write stream completion, but it need not buffer completely or at all.
/// </summary>
- public static Exception UnwrapRpcException(AggregateException ae)
+ BufferHint = 0x1,
+
+ /// <summary>
+ /// Force compression to be disabled for a particular write.
+ /// </summary>
+ NoCompress = 0x2
+ }
+
+ /// <summary>
+ /// Options for write operations.
+ /// </summary>
+ public class WriteOptions
+ {
+ /// <summary>
+ /// Default write options.
+ /// </summary>
+ public static readonly WriteOptions Default = new WriteOptions();
+
+ private WriteFlags flags;
+
+ public WriteOptions(WriteFlags flags = default(WriteFlags))
+ {
+ this.flags = flags;
+ }
+
+ public WriteFlags Flags
{
- foreach (var e in ae.InnerExceptions)
+ get
{
- if (e is RpcException)
- {
- throw e;
- }
+ return this.flags;
}
- throw ae;
}
}
}
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index a2348defc3..91c82b586b 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -92,15 +92,8 @@ namespace Math.Tests
[Test]
public void DivByZero()
{
- try
- {
- DivReply response = client.Div(new DivArgs { Dividend = 0, Divisor = 0 });
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
- }
+ var ex = Assert.Throws<RpcException>(() => client.Div(new DivArgs { Dividend = 0, Divisor = 0 }));
+ Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode);
}
[Test]
@@ -116,7 +109,7 @@ namespace Math.Tests
{
using (var call = client.Fib(new FibArgs { Limit = 6 }))
{
- var responses = await call.ResponseStream.ToList();
+ var responses = await call.ResponseStream.ToListAsync();
CollectionAssert.AreEqual(new List<long> { 1, 1, 2, 3, 5, 8 },
responses.ConvertAll((n) => n.Num_));
}
@@ -157,15 +150,10 @@ namespace Math.Tests
using (var call = client.Fib(new FibArgs { Limit = 0 },
deadline: DateTime.UtcNow.AddMilliseconds(500)))
{
- try
- {
- await call.ResponseStream.ToList();
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode);
- }
+ var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.ToListAsync());
+
+ // We can't guarantee the status code always DeadlineExceeded. See issue #2685.
+ Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal });
}
}
@@ -177,7 +165,7 @@ namespace Math.Tests
{
var numbers = new List<long> { 10, 20, 30 }.ConvertAll(n => new Num{ Num_ = n });
- await call.RequestStream.WriteAll(numbers);
+ await call.RequestStream.WriteAllAsync(numbers);
var result = await call.ResponseAsync;
Assert.AreEqual(60, result.Num_);
}
@@ -195,8 +183,8 @@ namespace Math.Tests
using (var call = client.DivMany())
{
- await call.RequestStream.WriteAll(divArgsList);
- var result = await call.ResponseStream.ToList();
+ await call.RequestStream.WriteAllAsync(divArgsList);
+ var result = await call.ResponseStream.ToListAsync();
CollectionAssert.AreEqual(new long[] { 3, 4, 3 }, result.ConvertAll((divReply) => divReply.Quotient));
CollectionAssert.AreEqual(new long[] { 1, 16, 1 }, result.ConvertAll((divReply) => divReply.Remainder));
diff --git a/src/csharp/Grpc.Examples/MathExamples.cs b/src/csharp/Grpc.Examples/MathExamples.cs
index e25384766f..8009ccbbfa 100644
--- a/src/csharp/Grpc.Examples/MathExamples.cs
+++ b/src/csharp/Grpc.Examples/MathExamples.cs
@@ -54,7 +54,7 @@ namespace Math
{
using (var call = client.Fib(new FibArgs { Limit = 5 }))
{
- List<Num> result = await call.ResponseStream.ToList();
+ List<Num> result = await call.ResponseStream.ToListAsync();
Console.WriteLine("Fib Result: " + string.Join("|", result));
}
}
@@ -70,7 +70,7 @@ namespace Math
using (var call = client.Sum())
{
- await call.RequestStream.WriteAll(numbers);
+ await call.RequestStream.WriteAllAsync(numbers);
Console.WriteLine("Sum Result: " + await call.ResponseAsync);
}
}
@@ -85,8 +85,8 @@ namespace Math
};
using (var call = client.DivMany())
{
- await call.RequestStream.WriteAll(divArgsList);
- Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToList()));
+ await call.RequestStream.WriteAllAsync(divArgsList);
+ Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToListAsync()));
}
}
@@ -102,7 +102,7 @@ namespace Math
Num sum;
using (var sumCall = client.Sum())
{
- await sumCall.RequestStream.WriteAll(numbers);
+ await sumCall.RequestStream.WriteAllAsync(numbers);
sum = await sumCall.ResponseAsync;
}
diff --git a/src/csharp/Grpc.Examples/MathServiceImpl.cs b/src/csharp/Grpc.Examples/MathServiceImpl.cs
index 788ce90343..71dc655e46 100644
--- a/src/csharp/Grpc.Examples/MathServiceImpl.cs
+++ b/src/csharp/Grpc.Examples/MathServiceImpl.cs
@@ -75,7 +75,7 @@ namespace Math
public async Task<Num> Sum(IAsyncStreamReader<Num> requestStream, ServerCallContext context)
{
long sum = 0;
- await requestStream.ForEach(async num =>
+ await requestStream.ForEachAsync(async num =>
{
sum += num.Num_;
});
@@ -84,10 +84,7 @@ namespace Math
public async Task DivMany(IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream, ServerCallContext context)
{
- await requestStream.ForEach(async divArgs =>
- {
- await responseStream.WriteAsync(DivInternal(divArgs));
- });
+ await requestStream.ForEachAsync(async divArgs => await responseStream.WriteAsync(DivInternal(divArgs)));
}
static DivReply DivInternal(DivArgs args)
diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
index 04660cd396..8de8645cd1 100644
--- a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
+++ b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs
@@ -92,11 +92,11 @@ namespace Grpc.HealthCheck.Tests
public void NullsRejected()
{
var impl = new HealthServiceImpl();
- Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus(null, "", HealthCheckResponse.Types.ServingStatus.SERVING));
- Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus("", null, HealthCheckResponse.Types.ServingStatus.SERVING));
+ Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus(null, "", HealthCheckResponse.Types.ServingStatus.SERVING));
+ Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus("", null, HealthCheckResponse.Types.ServingStatus.SERVING));
- Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus(null, ""));
- Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus("", null));
+ Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus(null, ""));
+ Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus("", null));
}
private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string host, string service)
diff --git a/src/csharp/Grpc.IntegrationTesting.Client/app.config b/src/csharp/Grpc.IntegrationTesting.Client/app.config
index 0a82bb4f16..84d7534d65 100644
--- a/src/csharp/Grpc.IntegrationTesting.Client/app.config
+++ b/src/csharp/Grpc.IntegrationTesting.Client/app.config
@@ -10,6 +10,10 @@
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" />
+ </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> \ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting.Server/app.config b/src/csharp/Grpc.IntegrationTesting.Server/app.config
index 0a82bb4f16..84d7534d65 100644
--- a/src/csharp/Grpc.IntegrationTesting.Server/app.config
+++ b/src/csharp/Grpc.IntegrationTesting.Server/app.config
@@ -10,6 +10,10 @@
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" />
+ </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> \ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index a8d85e4ef6..b115eac909 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -8,7 +8,7 @@
<RootNamespace>Grpc.IntegrationTesting</RootNamespace>
<AssemblyName>Grpc.IntegrationTesting</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
- <NuGetPackageImportStamp>041c163e</NuGetPackageImportStamp>
+ <NuGetPackageImportStamp>6566287f</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -38,52 +38,51 @@
<AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
- <Reference Include="BouncyCastle.Crypto">
+ <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="Google.Protobuf">
<HintPath>..\packages\Google.Protobuf.3.0.0-a20150813-2093749ca\lib\portable-net45+netcore45+wpa81+wp8\Google.Protobuf.dll</HintPath>
</Reference>
- <Reference Include="nunit.framework">
- <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
+ <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath>
</Reference>
- <Reference Include="System" />
- <Reference Include="System.Interactive.Async">
- <HintPath>..\packages\Ix-Async.1.2.3\lib\net45\System.Interactive.Async.dll</HintPath>
+ <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
</Reference>
- <Reference Include="System.Net" />
- <Reference Include="System.Net.Http" />
- <Reference Include="System.Net.Http.WebRequest" />
- <Reference Include="Microsoft.Threading.Tasks">
+ <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions">
+ <Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop">
+ <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
</Reference>
- <Reference Include="System.Collections.Immutable">
- <HintPath>..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
- </Reference>
- <Reference Include="Google.Apis.Auth">
- <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.dll</HintPath>
- </Reference>
- <Reference Include="Google.Apis.Auth.PlatformServices">
- <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath>
- </Reference>
- <Reference Include="Google.Apis.Core">
- <HintPath>..\packages\Google.Apis.Core.1.9.2\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath>
- </Reference>
- <Reference Include="Newtonsoft.Json">
+ <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
- <Reference Include="System.Net.Http.Extensions">
- <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
+ <Reference Include="nunit.framework">
+ <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
</Reference>
- <Reference Include="System.Net.Http.Primitives">
- <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
+ <Reference Include="System" />
+ <Reference Include="System.Interactive.Async">
+ <HintPath>..\packages\Ix-Async.1.2.3\lib\net45\System.Interactive.Async.dll</HintPath>
</Reference>
+ <Reference Include="System.Net" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Net.Http.WebRequest" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Grpc.Core\Version.cs">
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index 93d0bbe772..3ea1539230 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -43,6 +43,7 @@ using Grpc.Core.Utils;
using Grpc.Testing;
using NUnit.Framework;
using Google.Protobuf;
+using Google.Apis.Auth.OAuth2;
namespace Grpc.IntegrationTesting
{
@@ -97,10 +98,10 @@ namespace Grpc.IntegrationTesting
}
var interopClient = new InteropClient(options);
- interopClient.Run();
+ interopClient.Run().Wait();
}
- private void Run()
+ private async Task Run()
{
Credentials credentials = null;
if (options.useTls)
@@ -120,17 +121,7 @@ namespace Grpc.IntegrationTesting
using (Channel channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions))
{
TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
- if (options.testCase == "service_account_creds" || options.testCase == "compute_engine_creds")
- {
- var credential = GoogleCredential.GetApplicationDefault();
- if (credential.IsCreateScopedRequired)
- {
- credential = credential.CreateScoped(new[] { AuthScope });
- }
- client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
- }
-
- RunTestCaseAsync(options.testCase, client).Wait();
+ await RunTestCaseAsync(options.testCase, client);
}
GrpcEnvironment.Shutdown();
}
@@ -158,16 +149,19 @@ namespace Grpc.IntegrationTesting
await RunEmptyStreamAsync(client);
break;
case "service_account_creds":
- RunServiceAccountCreds(client);
+ await RunServiceAccountCredsAsync(client);
break;
case "compute_engine_creds":
- RunComputeEngineCreds(client);
+ await RunComputeEngineCredsAsync(client);
+ break;
+ case "jwt_token_creds":
+ await RunJwtTokenCredsAsync(client);
break;
case "oauth2_auth_token":
- RunOAuth2AuthToken(client);
+ await RunOAuth2AuthTokenAsync(client);
break;
case "per_rpc_creds":
- RunPerRpcCreds(client);
+ await RunPerRpcCredsAsync(client);
break;
case "cancel_after_begin":
await RunCancelAfterBeginAsync(client);
@@ -216,7 +210,7 @@ namespace Grpc.IntegrationTesting
using (var call = client.StreamingInputCall())
{
- await call.RequestStream.WriteAll(bodySizes);
+ await call.RequestStream.WriteAllAsync(bodySizes);
var response = await call.ResponseAsync;
Assert.AreEqual(74922, response.AggregatedPayloadSize);
@@ -238,7 +232,7 @@ namespace Grpc.IntegrationTesting
using (var call = client.StreamingOutputCall(request))
{
- var responseList = await call.ResponseStream.ToList();
+ var responseList = await call.ResponseStream.ToListAsync();
foreach (var res in responseList)
{
Assert.AreEqual(PayloadType.COMPRESSABLE, res.Payload.Type);
@@ -312,15 +306,19 @@ namespace Grpc.IntegrationTesting
{
await call.RequestStream.CompleteAsync();
- var responseList = await call.ResponseStream.ToList();
+ var responseList = await call.ResponseStream.ToListAsync();
Assert.AreEqual(0, responseList.Count);
}
Console.WriteLine("Passed!");
}
- public static void RunServiceAccountCreds(TestService.ITestServiceClient client)
+ public static async Task RunServiceAccountCredsAsync(TestService.TestServiceClient client)
{
Console.WriteLine("running service_account_creds");
+ var credential = await GoogleCredential.GetApplicationDefaultAsync();
+ credential = credential.CreateScoped(new[] { AuthScope });
+ client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+
var request = new SimpleRequest
{
ResponseType = PayloadType.COMPRESSABLE,
@@ -339,9 +337,13 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!");
}
- public static void RunComputeEngineCreds(TestService.ITestServiceClient client)
+ public static async Task RunComputeEngineCredsAsync(TestService.TestServiceClient client)
{
Console.WriteLine("running compute_engine_creds");
+ var credential = await GoogleCredential.GetApplicationDefaultAsync();
+ Assert.IsFalse(credential.IsCreateScopedRequired);
+ client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+
var request = new SimpleRequest
{
ResponseType = PayloadType.COMPRESSABLE,
@@ -360,12 +362,36 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!");
}
- public static void RunOAuth2AuthToken(TestService.TestServiceClient client)
+ public static async Task RunJwtTokenCredsAsync(TestService.TestServiceClient client)
+ {
+ Console.WriteLine("running jwt_token_creds");
+ var credential = await GoogleCredential.GetApplicationDefaultAsync();
+ // check this a credential with scope support, but don't add the scope.
+ Assert.IsTrue(credential.IsCreateScopedRequired);
+ client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential);
+
+ var request = new SimpleRequest
+ {
+ ResponseType = PayloadType.COMPRESSABLE,
+ ResponseSize = 314159,
+ Payload = CreateZerosPayload(271828),
+ FillUsername = true,
+ FillOauthScope = true
+ };
+
+ var response = client.UnaryCall(request);
+
+ Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type);
+ Assert.AreEqual(314159, response.Payload.Body.Length);
+ Assert.AreEqual(ServiceAccountUser, response.Username);
+ Console.WriteLine("Passed!");
+ }
+
+ public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client)
{
Console.WriteLine("running oauth2_auth_token");
- var credential = GoogleCredential.GetApplicationDefault().CreateScoped(new[] { AuthScope });
- Assert.IsTrue(credential.RequestAccessTokenAsync(CancellationToken.None).Result);
- string oauth2Token = credential.Token.AccessToken;
+ ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
+ string oauth2Token = await credential.GetAccessTokenForRequestAsync();
client.HeaderInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token);
@@ -382,13 +408,12 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!");
}
- public static void RunPerRpcCreds(TestService.TestServiceClient client)
+ public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client)
{
Console.WriteLine("running per_rpc_creds");
- var credential = GoogleCredential.GetApplicationDefault().CreateScoped(new[] { AuthScope });
- Assert.IsTrue(credential.RequestAccessTokenAsync(CancellationToken.None).Result);
- string oauth2Token = credential.Token.AccessToken;
+ ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope });
+ string oauth2Token = await credential.GetAccessTokenForRequestAsync();
var headerInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token);
var request = new SimpleRequest
@@ -398,7 +423,7 @@ namespace Grpc.IntegrationTesting
};
var headers = new Metadata();
- headerInterceptor(headers);
+ headerInterceptor("", headers);
var response = client.UnaryCall(request, headers: headers);
Assert.AreEqual(AuthScopeResponse, response.OauthScope);
@@ -417,15 +442,8 @@ namespace Grpc.IntegrationTesting
await Task.Delay(1000);
cts.Cancel();
- try
- {
- var response = await call.ResponseAsync;
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
- }
+ var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync);
+ Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
}
Console.WriteLine("Passed!");
}
@@ -450,15 +468,8 @@ namespace Grpc.IntegrationTesting
cts.Cancel();
- try
- {
- await call.ResponseStream.MoveNext();
- Assert.Fail();
- }
- catch (RpcException e)
- {
- Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode);
- }
+ var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
+ Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode);
}
Console.WriteLine("Passed!");
}
diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
index 4ab1266f25..c5bfcf08c0 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
@@ -69,7 +69,7 @@ namespace Grpc.Testing
public async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
{
int sum = 0;
- await requestStream.ForEach(async request =>
+ await requestStream.ForEachAsync(async request =>
{
sum += request.Payload.Body.Length;
});
@@ -78,7 +78,7 @@ namespace Grpc.Testing
public async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context)
{
- await requestStream.ForEach(async request =>
+ await requestStream.ForEachAsync(async request =>
{
foreach (var responseParam in request.ResponseParameters)
{
diff --git a/src/csharp/Grpc.IntegrationTesting/app.config b/src/csharp/Grpc.IntegrationTesting/app.config
index 0a82bb4f16..84d7534d65 100644
--- a/src/csharp/Grpc.IntegrationTesting/app.config
+++ b/src/csharp/Grpc.IntegrationTesting/app.config
@@ -10,6 +10,10 @@
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" />
+ </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration> \ No newline at end of file
diff --git a/src/csharp/Grpc.IntegrationTesting/packages.config b/src/csharp/Grpc.IntegrationTesting/packages.config
index b6ed3c8532..54b594bb63 100644
--- a/src/csharp/Grpc.IntegrationTesting/packages.config
+++ b/src/csharp/Grpc.IntegrationTesting/packages.config
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.7.0" targetFramework="net45" />
- <package id="Google.Apis.Auth" version="1.9.2" targetFramework="net45" />
- <package id="Google.Apis.Core" version="1.9.2" targetFramework="net45" />
<package id="Google.Protobuf" version="3.0.0-a20150813-2093749ca" targetFramework="net45" />
+ <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" />
+ <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" />
+ <package id="Google.ProtocolBuffers" version="2.4.1.521" targetFramework="net45" />
<package id="Ix-Async" version="1.2.3" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" />
diff --git a/src/csharp/doc/README.md b/src/csharp/doc/README.md
new file mode 100644
index 0000000000..585500b5ca
--- /dev/null
+++ b/src/csharp/doc/README.md
@@ -0,0 +1,2 @@
+
+SandCastle project files to generate HTML reference documentation. \ No newline at end of file
diff --git a/src/csharp/doc/grpc_csharp_public.shfbproj b/src/csharp/doc/grpc_csharp_public.shfbproj
new file mode 100644
index 0000000000..05c93f4a13
--- /dev/null
+++ b/src/csharp/doc/grpc_csharp_public.shfbproj
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <!-- The configuration and platform will be used to determine which assemblies to include from solution and
+ project documentation sources -->
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{77e3da09-fc92-486f-a90a-99ca788e8b59}</ProjectGuid>
+ <SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion>
+ <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
+ <AssemblyName>Documentation</AssemblyName>
+ <RootNamespace>Documentation</RootNamespace>
+ <Name>Documentation</Name>
+ <!-- SHFB properties -->
+ <FrameworkVersion>.NET Framework 4.5</FrameworkVersion>
+ <OutputPath>..\..\..\doc\ref\csharp\html</OutputPath>
+ <Language>en-US</Language>
+ <DocumentationSources>
+ <DocumentationSource sourceFile="..\Grpc.Auth\Grpc.Auth.csproj" />
+<DocumentationSource sourceFile="..\Grpc.Core\Grpc.Core.csproj" /></DocumentationSources>
+ <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
+ <HelpFileFormat>Website</HelpFileFormat>
+ <IndentHtml>False</IndentHtml>
+ <KeepLogFile>True</KeepLogFile>
+ <DisableCodeBlockComponent>False</DisableCodeBlockComponent>
+ <CleanIntermediates>True</CleanIntermediates>
+ <HelpFileVersion>1.0.0.0</HelpFileVersion>
+ <MaximumGroupParts>2</MaximumGroupParts>
+ <NamespaceGrouping>False</NamespaceGrouping>
+ <SyntaxFilters>Standard</SyntaxFilters>
+ <SdkLinkTarget>Blank</SdkLinkTarget>
+ <RootNamespaceContainer>True</RootNamespaceContainer>
+ <PresentationStyle>VS2013</PresentationStyle>
+ <Preliminary>False</Preliminary>
+ <NamingMethod>MemberName</NamingMethod>
+ <HelpTitle>gRPC C#</HelpTitle>
+ <ContentPlacement>AboveNamespaces</ContentPlacement>
+ <HtmlHelpName>Documentation</HtmlHelpName>
+ </PropertyGroup>
+ <!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform
+ the build. The others are optional common platform types that may appear. -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' ">
+ </PropertyGroup>
+ <!-- Import the SHFB build targets -->
+ <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
+ <!-- The pre-build and post-build event properties must appear *after* the targets file import in order to be
+ evaluated correctly. -->
+ <PropertyGroup>
+ <PreBuildEvent>
+ </PreBuildEvent>
+ <PostBuildEvent>
+ </PostBuildEvent>
+ <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
+ </PropertyGroup>
+</Project> \ No newline at end of file
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index 048887bc12..9379ae01f1 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -376,10 +376,12 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_channel_destroy(grpc_channel *channel) {
}
GPR_EXPORT grpc_call *GPR_CALLTYPE
-grpcsharp_channel_create_call(grpc_channel *channel, grpc_completion_queue *cq,
+grpcsharp_channel_create_call(grpc_channel *channel, grpc_call *parent_call,
+ gpr_uint32 propagation_mask,
+ grpc_completion_queue *cq,
const char *method, const char *host,
gpr_timespec deadline) {
- return grpc_channel_create_call(channel, NULL, GRPC_PROPAGATE_DEFAULTS, cq,
+ return grpc_channel_create_call(channel, parent_call, propagation_mask, cq,
method, host, deadline);
}
@@ -497,7 +499,7 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_call_destroy(grpc_call *call) {
GPR_EXPORT grpc_call_error GPR_CALLTYPE
grpcsharp_call_start_unary(grpc_call *call, grpcsharp_batch_context *ctx,
const char *send_buffer, size_t send_buffer_len,
- grpc_metadata_array *initial_metadata) {
+ grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) {
/* TODO: don't use magic number */
grpc_op ops[6];
ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
@@ -511,7 +513,7 @@ grpcsharp_call_start_unary(grpc_call *call, grpcsharp_batch_context *ctx,
ops[1].op = GRPC_OP_SEND_MESSAGE;
ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
ops[1].data.send_message = ctx->send_message;
- ops[1].flags = 0;
+ ops[1].flags = write_flags;
ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
ops[2].flags = 0;
@@ -578,7 +580,7 @@ grpcsharp_call_start_client_streaming(grpc_call *call,
GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer,
- size_t send_buffer_len, grpc_metadata_array *initial_metadata) {
+ size_t send_buffer_len, grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) {
/* TODO: don't use magic number */
grpc_op ops[5];
ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
@@ -592,7 +594,7 @@ GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
ops[1].op = GRPC_OP_SEND_MESSAGE;
ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
ops[1].data.send_message = ctx->send_message;
- ops[1].flags = 0;
+ ops[1].flags = write_flags;
ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
ops[2].flags = 0;
@@ -651,15 +653,22 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call,
GPR_EXPORT grpc_call_error GPR_CALLTYPE
grpcsharp_call_send_message(grpc_call *call, grpcsharp_batch_context *ctx,
- const char *send_buffer, size_t send_buffer_len) {
+ const char *send_buffer, size_t send_buffer_len,
+ gpr_uint32 write_flags,
+ gpr_int32 send_empty_initial_metadata) {
/* TODO: don't use magic number */
- grpc_op ops[1];
+ grpc_op ops[2];
+ size_t nops = send_empty_initial_metadata ? 2 : 1;
ops[0].op = GRPC_OP_SEND_MESSAGE;
ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len);
ops[0].data.send_message = ctx->send_message;
- ops[0].flags = 0;
+ ops[0].flags = write_flags;
+ ops[1].op = GRPC_OP_SEND_INITIAL_METADATA;
+ ops[1].data.send_initial_metadata.count = 0;
+ ops[1].data.send_initial_metadata.metadata = NULL;
+ ops[1].flags = 0;
- return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+ return grpc_call_start_batch(call, ops, nops, ctx);
}
GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -675,9 +684,11 @@ grpcsharp_call_send_close_from_client(grpc_call *call,
GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server(
grpc_call *call, grpcsharp_batch_context *ctx, grpc_status_code status_code,
- const char *status_details, grpc_metadata_array *trailing_metadata) {
+ const char *status_details, grpc_metadata_array *trailing_metadata,
+ gpr_int32 send_empty_initial_metadata) {
/* TODO: don't use magic number */
- grpc_op ops[1];
+ grpc_op ops[2];
+ size_t nops = send_empty_initial_metadata ? 2 : 1;
ops[0].op = GRPC_OP_SEND_STATUS_FROM_SERVER;
ops[0].data.send_status_from_server.status = status_code;
ops[0].data.send_status_from_server.status_details =
@@ -689,8 +700,12 @@ GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server(
ops[0].data.send_status_from_server.trailing_metadata =
ctx->send_status_from_server.trailing_metadata.metadata;
ops[0].flags = 0;
+ ops[1].op = GRPC_OP_SEND_INITIAL_METADATA;
+ ops[1].data.send_initial_metadata.count = 0;
+ ops[1].data.send_initial_metadata.metadata = NULL;
+ ops[1].flags = 0;
- return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+ return grpc_call_start_batch(call, ops, nops, ctx);
}
GPR_EXPORT grpc_call_error GPR_CALLTYPE
@@ -706,16 +721,28 @@ grpcsharp_call_recv_message(grpc_call *call, grpcsharp_batch_context *ctx) {
GPR_EXPORT grpc_call_error GPR_CALLTYPE
grpcsharp_call_start_serverside(grpc_call *call, grpcsharp_batch_context *ctx) {
/* TODO: don't use magic number */
- grpc_op ops[2];
- ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
- ops[0].data.send_initial_metadata.count = 0;
- ops[0].data.send_initial_metadata.metadata = NULL;
+ grpc_op ops[1];
+ ops[0].op = GRPC_OP_RECV_CLOSE_ON_SERVER;
+ ops[0].data.recv_close_on_server.cancelled =
+ (&ctx->recv_close_on_server_cancelled);
ops[0].flags = 0;
- ops[1].op = GRPC_OP_RECV_CLOSE_ON_SERVER;
- ops[1].data.recv_close_on_server.cancelled =
- (&ctx->recv_close_on_server_cancelled);
- ops[1].flags = 0;
+ return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
+}
+
+GPR_EXPORT grpc_call_error GPR_CALLTYPE
+grpcsharp_call_send_initial_metadata(grpc_call *call,
+ grpcsharp_batch_context *ctx,
+ grpc_metadata_array *initial_metadata) {
+ /* TODO: don't use magic number */
+ grpc_op ops[1];
+ ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
+ grpcsharp_metadata_array_move(&(ctx->send_initial_metadata),
+ initial_metadata);
+ ops[0].data.send_initial_metadata.count = ctx->send_initial_metadata.count;
+ ops[0].data.send_initial_metadata.metadata =
+ ctx->send_initial_metadata.metadata;
+ ops[0].flags = 0;
return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx);
}
@@ -849,6 +876,11 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_redirect_log(grpcsharp_log_func func) {
typedef void(GPR_CALLTYPE *test_callback_funcptr)(gpr_int32 success);
+/* Version info */
+GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_version_string() {
+ return grpc_version_string();
+}
+
/* For testing */
GPR_EXPORT void GPR_CALLTYPE
grpcsharp_test_callback(test_callback_funcptr callback) {
diff --git a/src/node/binding.gyp b/src/node/binding.gyp
index 6ba233388a..734dc8410b 100644
--- a/src/node/binding.gyp
+++ b/src/node/binding.gyp
@@ -11,7 +11,8 @@
'-pedantic',
'-g',
'-zdefs',
- '-Werror'
+ '-Werror',
+ '-Wno-error=deprecated-declarations'
],
'ldflags': [
'-g'
diff --git a/src/node/examples/perf_test.js b/src/node/examples/perf_test.js
index 0f38725f72..214b9384d5 100644
--- a/src/node/examples/perf_test.js
+++ b/src/node/examples/perf_test.js
@@ -63,7 +63,7 @@ function runTest(iterations, callback) {
var timeDiff = process.hrtime(startTime);
intervals[i] = timeDiff[0] * 1000000 + timeDiff[1] / 1000;
next(i+1);
- }, {}, deadline);
+ }, {}, {deadline: deadline});
}
}
next(0);
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index fe585a0d4f..c5c8313385 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -510,10 +510,21 @@ NAN_METHOD(Call::New) {
NanUtf8String method(args[1]);
double deadline = args[2]->NumberValue();
grpc_channel *wrapped_channel = channel->GetWrappedChannel();
- grpc_call *wrapped_call = grpc_channel_create_call(
- wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
- CompletionQueueAsyncWorker::GetQueue(), *method, channel->GetHost(),
- MillisecondsToTimespec(deadline));
+ grpc_call *wrapped_call;
+ if (args[3]->IsString()) {
+ NanUtf8String host_override(args[3]);
+ wrapped_call = grpc_channel_create_call(
+ wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
+ CompletionQueueAsyncWorker::GetQueue(), *method,
+ *host_override, MillisecondsToTimespec(deadline));
+ } else if (args[3]->IsUndefined() || args[3]->IsNull()) {
+ wrapped_call = grpc_channel_create_call(
+ wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
+ CompletionQueueAsyncWorker::GetQueue(), *method,
+ NULL, MillisecondsToTimespec(deadline));
+ } else {
+ return NanThrowTypeError("Call's fourth argument must be a string");
+ }
call = new Call(wrapped_call);
args.This()->SetHiddenValue(NanNew("channel_"), channel_object);
}
diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc
index d02ed95672..457a58c057 100644
--- a/src/node/ext/channel.cc
+++ b/src/node/ext/channel.cc
@@ -59,14 +59,12 @@ using v8::Value;
NanCallback *Channel::constructor;
Persistent<FunctionTemplate> Channel::fun_tpl;
-Channel::Channel(grpc_channel *channel, NanUtf8String *host)
- : wrapped_channel(channel), host(host) {}
+Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {}
Channel::~Channel() {
if (wrapped_channel != NULL) {
grpc_channel_destroy(wrapped_channel);
}
- delete host;
}
void Channel::Init(Handle<Object> exports) {
@@ -91,8 +89,6 @@ bool Channel::HasInstance(Handle<Value> val) {
grpc_channel *Channel::GetWrappedChannel() { return this->wrapped_channel; }
-char *Channel::GetHost() { return **this->host; }
-
NAN_METHOD(Channel::New) {
NanScope();
@@ -103,8 +99,7 @@ NAN_METHOD(Channel::New) {
}
grpc_channel *wrapped_channel;
// Owned by the Channel object
- NanUtf8String *host = new NanUtf8String(args[0]);
- NanUtf8String *host_override = NULL;
+ NanUtf8String host(args[0]);
grpc_credentials *creds;
if (!Credentials::HasInstance(args[1])) {
return NanThrowTypeError(
@@ -116,12 +111,9 @@ NAN_METHOD(Channel::New) {
grpc_channel_args *channel_args_ptr;
if (args[2]->IsUndefined()) {
channel_args_ptr = NULL;
- wrapped_channel = grpc_insecure_channel_create(**host, NULL);
+ wrapped_channel = grpc_insecure_channel_create(*host, NULL);
} else if (args[2]->IsObject()) {
Handle<Object> args_hash(args[2]->ToObject()->Clone());
- if (args_hash->HasOwnProperty(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))) {
- host_override = new NanUtf8String(args_hash->Get(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG)));
- }
Handle<Array> keys(args_hash->GetOwnPropertyNames());
grpc_channel_args channel_args;
channel_args.num_args = keys->Length();
@@ -153,20 +145,15 @@ NAN_METHOD(Channel::New) {
return NanThrowTypeError("Channel expects a string and an object");
}
if (creds == NULL) {
- wrapped_channel = grpc_insecure_channel_create(**host, channel_args_ptr);
+ wrapped_channel = grpc_insecure_channel_create(*host, channel_args_ptr);
} else {
wrapped_channel =
- grpc_secure_channel_create(creds, **host, channel_args_ptr);
+ grpc_secure_channel_create(creds, *host, channel_args_ptr);
}
if (channel_args_ptr != NULL) {
free(channel_args_ptr->args);
}
- Channel *channel;
- if (host_override == NULL) {
- channel = new Channel(wrapped_channel, host);
- } else {
- channel = new Channel(wrapped_channel, host_override);
- }
+ Channel *channel = new Channel(wrapped_channel);
channel->Wrap(args.This());
NanReturnValue(args.This());
} else {
diff --git a/src/node/ext/channel.h b/src/node/ext/channel.h
index 6725ebb03f..e2182cb45c 100644
--- a/src/node/ext/channel.h
+++ b/src/node/ext/channel.h
@@ -53,11 +53,8 @@ class Channel : public ::node::ObjectWrap {
/* Returns the grpc_channel struct that this object wraps */
grpc_channel *GetWrappedChannel();
- /* Return the hostname that this channel connects to */
- char *GetHost();
-
private:
- explicit Channel(grpc_channel *channel, NanUtf8String *host);
+ explicit Channel(grpc_channel *channel);
~Channel();
// Prevent copying
@@ -71,7 +68,6 @@ class Channel : public ::node::ObjectWrap {
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
grpc_channel *wrapped_channel;
- NanUtf8String *host;
};
} // namespace node
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index 236b36616c..6d6f9a349e 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -67,11 +67,8 @@ function zeroBuffer(size) {
* primarily for use with mocha
*/
function emptyUnary(client, done) {
- var call = client.emptyCall({}, function(err, resp) {
+ client.emptyCall({}, function(err, resp) {
assert.ifError(err);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -92,13 +89,10 @@ function largeUnary(client, done) {
body: zeroBuffer(271828)
}
};
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -115,9 +109,6 @@ function clientStreaming(client, done) {
var call = client.streamingInputCall(function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.aggregated_payload_size, 74922);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -268,7 +259,7 @@ function cancelAfterFirstResponse(client, done) {
function timeoutOnSleepingServer(client, done) {
var deadline = new Date();
deadline.setMilliseconds(deadline.getMilliseconds() + 1);
- var call = client.fullDuplexCall(null, deadline);
+ var call = client.fullDuplexCall(null, {deadline: deadline});
call.write({
payload: {body: zeroBuffer(27182)}
});
@@ -302,15 +293,14 @@ function authTest(expected_user, scope, client, done) {
fill_username: true,
fill_oauth_scope: true
};
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
assert.strictEqual(resp.username, expected_user);
- assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
+ if (scope) {
+ assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+ }
if (done) {
done();
}
@@ -340,17 +330,14 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
};
var makeTestCall = function(error, client_metadata) {
assert.ifError(error);
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
- });
+ }, client_metadata);
};
if (per_rpc) {
updateMetadata('', {}, makeTestCall);
@@ -358,7 +345,6 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
client.updateMetadata = updateMetadata;
makeTestCall(null, {});
}
-
});
});
}
@@ -409,6 +395,7 @@ function runTest(address, host_override, test_case, tls, test_ca, done) {
creds = grpc.Credentials.createSsl(ca_data);
if (host_override) {
options['grpc.ssl_target_name_override'] = host_override;
+ options['grpc.default_authority'] = host_override;
}
} else {
creds = grpc.Credentials.createInsecure();
diff --git a/src/node/src/client.js b/src/node/src/client.js
index b2b4423707..5cde438572 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -208,6 +208,25 @@ ClientWritableStream.prototype.getPeer = getPeer;
ClientDuplexStream.prototype.getPeer = getPeer;
/**
+ * Get a call object built with the provided options. Keys for options are
+ * 'deadline', which takes a date or number, and 'host', which takes a string
+ * and overrides the hostname to connect to.
+ * @param {Object} options Options map.
+ */
+function getCall(channel, method, options) {
+ var deadline;
+ var host;
+ if (options) {
+ deadline = options.deadline;
+ host = options.host;
+ }
+ if (deadline === undefined) {
+ deadline = Infinity;
+ }
+ return new grpc.Call(channel, method, deadline, host);
+}
+
+/**
* Get a function that can make unary requests to the specified method.
* @param {string} method The name of the method to request
* @param {function(*):Buffer} serialize The serialization function for inputs
@@ -226,17 +245,13 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
* response is received
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object=} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeUnaryRequest(argument, callback, metadata, deadline) {
+ function makeUnaryRequest(argument, callback, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
var emitter = new EventEmitter();
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -300,16 +315,12 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
* response is received
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object=} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeClientStreamRequest(callback, metadata, deadline) {
+ function makeClientStreamRequest(callback, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -374,16 +385,12 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
* serialize
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeServerStreamRequest(argument, metadata, deadline) {
+ function makeServerStreamRequest(argument, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -446,16 +453,12 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
* @this {SurfaceClient} Client object. Must have a channel member.
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Options} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeBidiStreamRequest(metadata, deadline) {
+ function makeBidiStreamRequest(metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -523,7 +526,7 @@ var requester_makers = {
* requestSerialize: function to serialize request objects
* responseDeserialize: function to deserialize response objects
* @param {Object} methods An object mapping method names to method attributes
- * @param {string} serviceName The name of the service
+ * @param {string} serviceName The fully qualified name of the service
* @return {function(string, Object)} New client constructor
*/
exports.makeClientConstructor = function(methods, serviceName) {
@@ -548,8 +551,10 @@ exports.makeClientConstructor = function(methods, serviceName) {
}
options['grpc.primary_user_agent'] = 'grpc-node/' + version;
this.channel = new grpc.Channel(address, credentials, options);
- this.server_address = address.replace(/\/$/, '');
- this.auth_uri = this.server_address + '/' + serviceName;
+ // Remove the optional DNS scheme, trailing port, and trailing backslash
+ address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2');
+ this.server_address = address;
+ this.auth_uri = 'https://' + this.server_address + '/' + serviceName;
this.updateMetadata = updateMetadata;
}
@@ -587,7 +592,8 @@ exports.makeClientConstructor = function(methods, serviceName) {
*/
exports.makeProtobufClientConstructor = function(service) {
var method_attrs = common.getProtobufServiceAttrs(service, service.name);
- var Client = exports.makeClientConstructor(method_attrs);
+ var Client = exports.makeClientConstructor(
+ method_attrs, common.fullyQualifiedName(service));
Client.service = service;
return Client;
};
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index 48d859a8ec..8d0f20b074 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -84,6 +84,11 @@ describe('call', function() {
new grpc.Call(channel, 'method', 0);
});
});
+ it('should accept an optional fourth string parameter', function() {
+ assert.doesNotThrow(function() {
+ new grpc.Call(channel, 'method', new Date(), 'host_override');
+ });
+ });
it('should fail with a closed channel', function() {
var local_channel = new grpc.Channel('hostname', insecureCreds);
local_channel.close();
diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js
index ea41dfc28c..7574d98b8a 100644
--- a/src/node/test/end_to_end_test.js
+++ b/src/node/test/end_to_end_test.js
@@ -74,8 +74,6 @@ describe('end-to-end', function() {
});
it('should start and end a request without error', function(complete) {
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
@@ -126,8 +124,6 @@ describe('end-to-end', function() {
});
it('should successfully send and receive metadata', function(complete) {
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
@@ -184,8 +180,6 @@ describe('end-to-end', function() {
var req_text = 'client_request';
var reply_text = 'server_response';
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'success';
var call = new grpc.Call(channel,
'dummy_method',
@@ -241,8 +235,6 @@ describe('end-to-end', function() {
it('should send multiple messages', function(complete) {
var done = multiDone(complete, 2);
var requests = ['req1', 'req2'];
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
index ed39d4b0f7..83b0de18e3 100644
--- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
+++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m
@@ -40,7 +40,7 @@ static NSString * const kChallengeHeader = @"www-authenticate";
@implementation GRPCCall (OAuth2)
- (NSString *)oauth2AccessToken {
- NSString *headerValue = self.requestMetadata[kAuthorizationHeader];
+ NSString *headerValue = self.requestHeaders[kAuthorizationHeader];
if ([headerValue hasPrefix:kBearerPrefix]) {
return [headerValue substringFromIndex:kBearerPrefix.length];
} else {
@@ -50,14 +50,14 @@ static NSString * const kChallengeHeader = @"www-authenticate";
- (void)setOauth2AccessToken:(NSString *)token {
if (token) {
- self.requestMetadata[kAuthorizationHeader] = [kBearerPrefix stringByAppendingString:token];
+ self.requestHeaders[kAuthorizationHeader] = [kBearerPrefix stringByAppendingString:token];
} else {
- [self.requestMetadata removeObjectForKey:kAuthorizationHeader];
+ [self.requestHeaders removeObjectForKey:kAuthorizationHeader];
}
}
- (NSString *)oauth2ChallengeHeader {
- return self.responseMetadata[kChallengeHeader];
+ return self.responseHeaders[kChallengeHeader];
}
@end
diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h
index 4a8b7fff48..4eda499b1a 100644
--- a/src/objective-c/GRPCClient/GRPCCall.h
+++ b/src/objective-c/GRPCClient/GRPCCall.h
@@ -48,8 +48,10 @@
#import <Foundation/Foundation.h>
#import <RxLibrary/GRXWriter.h>
-// Key used in |NSError|'s |userInfo| dictionary to store the response metadata sent by the server.
-extern id const kGRPCStatusMetadataKey;
+// Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
+// the server.
+extern id const kGRPCHeadersKey;
+extern id const kGRPCTrailersKey;
// Represents a single gRPC remote call.
@interface GRPCCall : GRXWriter
@@ -57,43 +59,49 @@ extern id const kGRPCStatusMetadataKey;
// These HTTP headers will be passed to the server as part of this call. Each HTTP header is a
// name-value pair with string names and either string or binary values.
//
-// The passed dictionary has to use NSString keys, corresponding to the header names. The
-// value associated to each can be a NSString object or a NSData object. E.g.:
+// The passed dictionary has to use NSString keys, corresponding to the header names. The value
+// associated to each can be a NSString object or a NSData object. E.g.:
//
-// call.requestMetadata = @{@"Authorization": @"Bearer ..."};
+// call.requestHeaders = @{@"authorization": @"Bearer ..."};
//
-// call.requestMetadata[@"SomeBinaryHeader"] = someData;
+// call.requestHeaders[@"my-header-bin"] = someData;
//
-// After the call is started, modifying this won't have any effect.
+// After the call is started, trying to modify this property is an error.
//
// For convenience, the property is initialized to an empty NSMutableDictionary, and the setter
// accepts (and copies) both mutable and immutable dictionaries.
-- (NSMutableDictionary *)requestMetadata; // nonatomic
-- (void)setRequestMetadata:(NSDictionary *)requestMetadata; // nonatomic, copy
+- (NSMutableDictionary *)requestHeaders; // nonatomic
+- (void)setRequestHeaders:(NSDictionary *)requestHeaders; // nonatomic, copy
-// This dictionary is populated with the HTTP headers received from the server. When the RPC ends,
-// the HTTP trailers received are added to the dictionary too. It has the same structure as the
-// request metadata dictionary.
+// This dictionary is populated with the HTTP headers received from the server. This happens before
+// any response message is received from the server. It has the same structure as the request
+// headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a
+// NSData value; the others have a NSString value.
//
-// The first time this object calls |writeValue| on the writeable passed to |startWithWriteable|,
-// the |responseMetadata| dictionary already contains the response headers. When it calls
-// |writesFinishedWithError|, the dictionary contains both the response headers and trailers.
-@property(atomic, readonly) NSDictionary *responseMetadata;
+// The value of this property is nil until all response headers are received, and will change before
+// any of -writeValue: or -writesFinishedWithError: are sent to the writeable.
+@property(atomic, readonly) NSDictionary *responseHeaders;
+
+// Same as responseHeaders, but populated with the HTTP trailers received from the server before the
+// call finishes.
+//
+// The value of this property is nil until all response trailers are received, and will change
+// before -writesFinishedWithError: is sent to the writeable.
+@property(atomic, readonly) NSDictionary *responseTrailers;
// The request writer has to write NSData objects into the provided Writeable. The server will
-// receive each of those separately and in order.
-// A gRPC call might not complete until the request writer finishes. On the other hand, the
-// request finishing doesn't necessarily make the call to finish, as the server might continue
-// sending messages to the response side of the call indefinitely (depending on the semantics of
-// the specific remote method called).
+// receive each of those separately and in order as distinct messages.
+// A gRPC call might not complete until the request writer finishes. On the other hand, the request
+// finishing doesn't necessarily make the call to finish, as the server might continue sending
+// messages to the response side of the call indefinitely (depending on the semantics of the
+// specific remote method called).
// To finish a call right away, invoke cancel.
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER;
-// Finishes the request side of this call, notifies the server that the RPC
-// should be cancelled, and finishes the response side of the call with an error
-// of code CANCELED.
+// Finishes the request side of this call, notifies the server that the RPC should be cancelled, and
+// finishes the response side of the call with an error of code CANCELED.
- (void)cancel;
// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 5f7d74bca8..ff5d1c5aaf 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -42,9 +42,13 @@
#import "private/NSDictionary+GRPC.h"
#import "private/NSError+GRPC.h"
-NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
+NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey";
+NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
@interface GRPCCall () <GRXWriteable>
+// Make them read-write.
+@property(atomic, strong) NSDictionary *responseHeaders;
+@property(atomic, strong) NSDictionary *responseTrailers;
@end
// The following methods of a C gRPC call object aren't reentrant, and thus
@@ -74,14 +78,22 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
// all. This wrapper over our actual writeable ensures thread-safety and
// correct ordering.
GRXConcurrentWriteable *_responseWriteable;
+
+ // The network thread wants the requestWriter to resume (when the server is ready for more input),
+ // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
+ // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
+ // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
+ // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
+ // pause the writer immediately on writeValue:, so we need our locking to be recursive.
GRXWriter *_requestWriter;
// To create a retain cycle when a call is started, up until it finishes. See
- // |startWithWriteable:| and |finishWithError:|.
- GRPCCall *_self;
+ // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
+ // reference to the call object if all they're interested in is the handler being executed when
+ // the response arrives.
+ GRPCCall *_retainSelf;
- NSMutableDictionary *_requestMetadata;
- NSMutableDictionary *_responseMetadata;
+ NSMutableDictionary *_requestHeaders;
}
@synthesize state = _state;
@@ -112,35 +124,31 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
_requestWriter = requestWriter;
- _requestMetadata = [NSMutableDictionary dictionary];
- _responseMetadata = [NSMutableDictionary dictionary];
+ _requestHeaders = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark Metadata
-- (NSMutableDictionary *)requestMetadata {
- return _requestMetadata;
-}
-
-- (void)setRequestMetadata:(NSDictionary *)requestMetadata {
- _requestMetadata = [NSMutableDictionary dictionaryWithDictionary:requestMetadata];
+- (NSMutableDictionary *)requestHeaders {
+ return _requestHeaders;
}
-- (NSDictionary *)responseMetadata {
- return _responseMetadata;
+- (void)setRequestHeaders:(NSDictionary *)requestHeaders {
+ _requestHeaders = [NSMutableDictionary dictionaryWithDictionary:requestHeaders];
}
#pragma mark Finish
- (void)finishWithError:(NSError *)errorOrNil {
// If the call isn't retained anywhere else, it can be deallocated now.
- _self = nil;
+ _retainSelf = nil;
// If there were still request messages coming, stop them.
- _requestWriter.state = GRXWriterStateFinished;
- _requestWriter = nil;
+ @synchronized(_requestWriter) {
+ _requestWriter.state = GRXWriterStateFinished;
+ }
if (errorOrNil) {
[_responseWriteable cancelWithError:errorOrNil];
@@ -222,11 +230,10 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
#pragma mark Send headers
-// TODO(jcanizales): Rename to commitHeaders.
-- (void)sendHeaders:(NSDictionary *)metadata {
+- (void)sendHeaders:(NSDictionary *)headers {
// TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
- initWithMetadata:metadata ?: @{} handler:nil]]];
+ initWithMetadata:headers ?: @{} handler:nil]]];
}
#pragma mark GRXWriteable implementation
@@ -240,12 +247,14 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
// Resume the request writer.
GRPCCall *strongSelf = weakSelf;
if (strongSelf) {
- strongSelf->_requestWriter.state = GRXWriterStateStarted;
+ @synchronized(strongSelf->_requestWriter) {
+ strongSelf->_requestWriter.state = GRXWriterStateStarted;
+ }
}
};
- [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc]
- initWithMessage:message
- handler:resumingHandler]] errorHandler:errorHandler];
+ [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message
+ handler:resumingHandler]]
+ errorHandler:errorHandler];
}
- (void)writeValue:(id)value {
@@ -253,7 +262,9 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
// Pause the input and only resume it when the C layer notifies us that writes
// can proceed.
- _requestWriter.state = GRXWriterStatePaused;
+ @synchronized(_requestWriter) {
+ _requestWriter.state = GRXWriterStatePaused;
+ }
__weak GRPCCall *weakSelf = self;
dispatch_async(_callQueue, ^{
@@ -273,7 +284,6 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
}
- (void)writesFinishedWithError:(NSError *)errorOrNil {
- _requestWriter = nil;
if (errorOrNil) {
[self cancel];
} else {
@@ -292,42 +302,54 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
// Both handlers will eventually be called, from the network queue. Writes can start immediately
// after this.
-// The first one (metadataHandler), when the response headers are received.
+// The first one (headersHandler), when the response headers are received.
// The second one (completionHandler), whenever the RPC finishes for any reason.
-- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler
+- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler
completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler {
// TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc]
- initWithHandler:metadataHandler]]];
+ initWithHandler:headersHandler]]];
[_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc]
initWithHandler:completionHandler]]];
}
- (void)invokeCall {
__weak GRPCCall *weakSelf = self;
- [self invokeCallWithMetadataHandler:^(NSDictionary *headers) {
+ [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
// Response headers received.
GRPCCall *strongSelf = weakSelf;
if (strongSelf) {
- [strongSelf->_responseMetadata addEntriesFromDictionary:headers];
+ strongSelf.responseHeaders = headers;
[strongSelf startNextRead];
}
} completionHandler:^(NSError *error, NSDictionary *trailers) {
GRPCCall *strongSelf = weakSelf;
if (strongSelf) {
- [strongSelf->_responseMetadata addEntriesFromDictionary:trailers];
+ strongSelf.responseTrailers = trailers;
if (error) {
- NSMutableDictionary *userInfo =
- [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
- userInfo[kGRPCStatusMetadataKey] = strongSelf->_responseMetadata;
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ if (error.userInfo) {
+ [userInfo addEntriesFromDictionary:error.userInfo];
+ }
+ userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers;
+ // TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be
+ // called before this one, so an error might end up with trailers but no headers. We
+ // shouldn't call finishWithError until ater both blocks are called. It is also when this is
+ // done that we can provide a merged view of response headers and trailers in a thread-safe
+ // way.
+ if (strongSelf.responseHeaders) {
+ userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders;
+ }
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
}
[strongSelf finishWithError:error];
}
}];
// Now that the RPC has been initiated, request writes can start.
- [_requestWriter startWithWriteable:self];
+ @synchronized(_requestWriter) {
+ [_requestWriter startWithWriteable:self];
+ }
}
#pragma mark GRXWriter implementation
@@ -338,10 +360,10 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
// before being autoreleased).
// Care is taken not to retain self strongly in any of the blocks used in this implementation, so
// that the life of the instance is determined by this retain cycle.
- _self = self;
+ _retainSelf = self;
_responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable];
- [self sendHeaders:_requestMetadata];
+ [self sendHeaders:_requestHeaders];
[self invokeCall];
}
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
index 9b4b6768f8..0a54804bb2 100644
--- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m
@@ -38,15 +38,18 @@
// Returns NULL if the file at path couldn't be read. In that case, if errorPtr isn't NULL,
// *errorPtr will be an object describing what went wrong.
static grpc_credentials *CertificatesAtPath(NSString *path, NSError **errorPtr) {
- NSString *certsContent = [NSString stringWithContentsOfFile:path
- encoding:NSASCIIStringEncoding
+ // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
+ // issuer). Load them as UTF8 and produce an ASCII equivalent.
+ NSString *contentInUTF8 = [NSString stringWithContentsOfFile:path
+ encoding:NSUTF8StringEncoding
error:errorPtr];
- if (!certsContent) {
+ NSData *contentInASCII = [contentInUTF8 dataUsingEncoding:NSASCIIStringEncoding
+ allowLossyConversion:YES];
+ if (!contentInASCII.bytes) {
// Passing NULL to grpc_ssl_credentials_create produces behavior we don't want, so return.
return NULL;
}
- const char * asCString = [certsContent cStringUsingEncoding:NSASCIIStringEncoding];
- return grpc_ssl_credentials_create(asCString, NULL);
+ return grpc_ssl_credentials_create(contentInASCII.bytes, NULL);
}
@implementation GRPCSecureChannel
diff --git a/src/objective-c/RxLibrary/GRXBufferedPipe.h b/src/objective-c/RxLibrary/GRXBufferedPipe.h
index b6296e1ed7..ca94ce275f 100644
--- a/src/objective-c/RxLibrary/GRXBufferedPipe.h
+++ b/src/objective-c/RxLibrary/GRXBufferedPipe.h
@@ -36,13 +36,11 @@
#import "GRXWriteable.h"
#import "GRXWriter.h"
-// A buffered pipe is a Writeable that also acts as a Writer (to whichever other writeable is passed
-// to -startWithWriteable:).
+// A buffered pipe is a Writer that also acts as a Writeable.
// Once it is started, whatever values are written into it (via -writeValue:) will be propagated
// immediately, unless flow control prevents it.
// If it is throttled and keeps receiving values, as well as if it receives values before being
-// started, it will buffer them and propagate them in order as soon as its state becomes
-// GRXWriterStateStarted.
+// started, it will buffer them and propagate them in order as soon as its state becomes Started.
// If it receives an error (via -writesFinishedWithError:), it will drop any buffered values and
// propagate the error immediately.
//
@@ -51,6 +49,9 @@
// pipe will keep buffering all data written to it, your application could run out of memory and
// crash. If you want to react to flow control signals to prevent that, instead of using this class
// you can implement an object that conforms to GRXWriter.
+//
+// Thread-safety:
+// The methods of an object of this class should not be called concurrently from different threads.
@interface GRXBufferedPipe : GRXWriter<GRXWriteable>
// Convenience constructor.
diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.h b/src/objective-c/RxLibrary/GRXForwardingWriter.h
index d004333d2b..f310832284 100644
--- a/src/objective-c/RxLibrary/GRXForwardingWriter.h
+++ b/src/objective-c/RxLibrary/GRXForwardingWriter.h
@@ -33,11 +33,17 @@
#import "GRXWriter.h"
-// A "proxy" class that simply forwards values, completion, and errors from its
-// input writer to its writeable.
+// A "proxy" class that simply forwards values, completion, and errors from its input writer to its
+// writeable.
// It is useful as a superclass for pipes that act as a transformation of their
// input writer, and for classes that represent objects with input and
// output sequences of values, like an RPC.
+//
+// Thread-safety:
+// All messages sent to this object need to be serialized. When it is started, the writer it wraps
+// is started in the same thread. Manual state changes are propagated to the wrapped writer in the
+// same thread too. Importantly, all messages the wrapped writer sends to its writeable need to be
+// serialized with any message sent to this object.
@interface GRXForwardingWriter : GRXWriter
- (instancetype)initWithWriter:(GRXWriter *)writer NS_DESIGNATED_INITIALIZER;
@end
diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.m b/src/objective-c/RxLibrary/GRXForwardingWriter.m
index 2342f51ab3..a72be9ace2 100644
--- a/src/objective-c/RxLibrary/GRXForwardingWriter.m
+++ b/src/objective-c/RxLibrary/GRXForwardingWriter.m
@@ -48,7 +48,11 @@
// Designated initializer
- (instancetype)initWithWriter:(GRXWriter *)writer {
if (!writer) {
- [NSException raise:NSInvalidArgumentException format:@"writer can't be nil."];
+ return nil;
+ }
+ if (writer.state != GRXWriterStateNotStarted) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"The writer argument must not have already started."];
}
if ((self = [super init])) {
_writer = writer;
diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.h b/src/objective-c/RxLibrary/GRXImmediateWriter.h
index b171f0c760..3fcc259434 100644
--- a/src/objective-c/RxLibrary/GRXImmediateWriter.h
+++ b/src/objective-c/RxLibrary/GRXImmediateWriter.h
@@ -36,10 +36,17 @@
#import "GRXWriter.h"
// Utility to construct GRXWriter instances from values that are immediately available when
-// required. The returned writers all support pausing and early termination.
+// required.
//
-// Unless the writeable callback pauses them or stops them early, these writers will do all their
-// interactions with the writeable before the start method returns.
+// Thread-safety:
+//
+// An object of this class shouldn't be messaged concurrently by more than one thread. It will start
+// messaging the writeable before |startWithWriteable:| returns, in the same thread. That is the
+// only place where the writer can be paused or stopped prematurely.
+//
+// If a paused writer of this class is resumed, it will start messaging the writeable, in the same
+// thread, before |setState:| returns. Because the object can't be legally accessed concurrently,
+// that's the only place where it can be paused again (or stopped).
@interface GRXImmediateWriter : GRXWriter
// Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
diff --git a/src/objective-c/RxLibrary/GRXWriteable.h b/src/objective-c/RxLibrary/GRXWriteable.h
index 216de30735..45613d6dd0 100644
--- a/src/objective-c/RxLibrary/GRXWriteable.h
+++ b/src/objective-c/RxLibrary/GRXWriteable.h
@@ -48,15 +48,15 @@
typedef void (^GRXValueHandler)(id value);
typedef void (^GRXCompletionHandler)(NSError *errorOrNil);
-typedef void (^GRXSingleValueHandler)(id value, NSError *errorOrNil);
-typedef void (^GRXStreamHandler)(BOOL done, id value, NSError *error);
+typedef void (^GRXSingleHandler)(id value, NSError *errorOrNil);
+typedef void (^GRXEventHandler)(BOOL done, id value, NSError *error);
// Utility to create objects that conform to the GRXWriteable protocol, from
// blocks that handle each of the two methods of the protocol.
@interface GRXWriteable : NSObject<GRXWriteable>
-+ (instancetype)writeableWithSingleValueHandler:(GRXSingleValueHandler)handler;
-+ (instancetype)writeableWithStreamHandler:(GRXStreamHandler)handler;
++ (instancetype)writeableWithSingleHandler:(GRXSingleHandler)handler;
++ (instancetype)writeableWithEventHandler:(GRXEventHandler)handler;
- (instancetype)initWithValueHandler:(GRXValueHandler)valueHandler
completionHandler:(GRXCompletionHandler)completionHandler
diff --git a/src/objective-c/RxLibrary/GRXWriteable.m b/src/objective-c/RxLibrary/GRXWriteable.m
index 63f7c3e7f3..2729d62b72 100644
--- a/src/objective-c/RxLibrary/GRXWriteable.m
+++ b/src/objective-c/RxLibrary/GRXWriteable.m
@@ -38,7 +38,7 @@
GRXCompletionHandler _completionHandler;
}
-+ (instancetype)writeableWithSingleValueHandler:(GRXSingleValueHandler)handler {
++ (instancetype)writeableWithSingleHandler:(GRXSingleHandler)handler {
if (!handler) {
return [[self alloc] init];
}
@@ -51,7 +51,7 @@
}];
}
-+ (instancetype)writeableWithStreamHandler:(GRXStreamHandler)handler {
++ (instancetype)writeableWithEventHandler:(GRXEventHandler)handler {
if (!handler) {
return [[self alloc] init];
}
diff --git a/src/objective-c/RxLibrary/GRXWriter.h b/src/objective-c/RxLibrary/GRXWriter.h
index 5d6e1a472a..b1c994aa38 100644
--- a/src/objective-c/RxLibrary/GRXWriter.h
+++ b/src/objective-c/RxLibrary/GRXWriter.h
@@ -35,84 +35,73 @@
#import "GRXWriteable.h"
+// States of a writer.
typedef NS_ENUM(NSInteger, GRXWriterState) {
- // The writer has not yet been given a writeable to which it can push its
- // values. To have an writer transition to the Started state, send it a
- // startWithWriteable: message.
+ // The writer has not yet been given a writeable to which it can push its values. To have a writer
+ // transition to the Started state, send it a startWithWriteable: message.
//
- // An writer's state cannot be manually set to this value.
+ // A writer's state cannot be manually set to this value.
GRXWriterStateNotStarted,
// The writer might push values to the writeable at any moment.
GRXWriterStateStarted,
- // The writer is temporarily paused, and won't send any more values to the
- // writeable unless its state is set back to Started. The writer might still
- // transition to the Finished state at any moment, and is allowed to send
- // writesFinishedWithError: to its writeable.
- //
- // Not all implementations of writer have to support pausing, and thus
- // trying to set an writer's state to this value might have no effect.
+ // The writer is temporarily paused, and won't send any more values to the writeable unless its
+ // state is set back to Started. The writer might still transition to the Finished state at any
+ // moment, and is allowed to send writesFinishedWithError: to its writeable.
GRXWriterStatePaused,
// The writer has released its writeable and won't interact with it anymore.
//
- // One seldomly wants to set an writer's state to this value, as its
- // writeable isn't notified with a writesFinishedWithError: message. Instead, sending
- // finishWithError: to the writer will make it notify the writeable and then
- // transition to this state.
+ // One seldomly wants to set a writer's state to this value, as its writeable isn't notified with
+ // a writesFinishedWithError: message. Instead, sending finishWithError: to the writer will make
+ // it notify the writeable and then transition to this state.
GRXWriterStateFinished
};
-// An object that conforms to this protocol can produce, on demand, a sequence
-// of values. The sequence may be produced asynchronously, and it may consist of
-// any number of elements, including none or an infinite number.
+// An GRXWriter object can produce, on demand, a sequence of values. The sequence may be produced
+// asynchronously, and it may consist of any number of elements, including none or an infinite
+// number.
+//
+// GRXWriter is the active dual of NSEnumerator. The difference between them is thus whether the
+// object plays an active or passive role during usage: A user of NSEnumerator pulls values off it,
+// and passes the values to a writeable. A user of GRXWriter, though, just gives it a writeable, and
+// the GRXWriter instance pushes values to the writeable. This makes this protocol suitable to
+// represent a sequence of future values, as well as collections with internal iteration.
//
-// GRXWriter is the active dual of NSEnumerator. The difference between them
-// is thus whether the object plays an active or passive role during usage: A
-// user of NSEnumerator pulls values off it, and passes the values to a writeable.
-// A user of GRXWriter, though, just gives it a writeable, and the
-// GRXWriter instance pushes values to the writeable. This makes this protocol
-// suitable to represent a sequence of future values, as well as collections
-// with internal iteration.
+// An instance of GRXWriter can start producing values after a writeable is passed to it. It can
+// also be commanded to finish the sequence immediately (with an optional error). Finally, it can be
+// asked to pause, and resumed later. All GRXWriter objects support pausing and early termination.
//
-// An instance of GRXWriter can start producing values after a writeable is
-// passed to it. It can also be commanded to finish the sequence immediately
-// (with an optional error). Finally, it can be asked to pause, but the
-// conforming instance is not required to oblige.
+// Thread-safety:
//
-// Unless otherwise indicated by a conforming class, no messages should be sent
-// concurrently to a GRXWriter. I.e., conforming classes aren't required to
-// be thread-safe.
+// State transitions take immediate effect if the object is used from a single thread. Subclasses
+// might offer stronger guarantees.
+//
+// Unless otherwise indicated by a conforming subclass, no messages should be sent concurrently to a
+// GRXWriter. I.e., conforming classes aren't required to be thread-safe.
@interface GRXWriter : NSObject
-// This property can be used to query the current state of the writer, which
-// determines how it might currently use its writeable. Some state transitions can
-// be triggered by setting this property to the corresponding value, and that's
-// useful for advanced use cases like pausing an writer. For more details,
-// see the documentation of the enum.
+// This property can be used to query the current state of the writer, which determines how it might
+// currently use its writeable. Some state transitions can be triggered by setting this property to
+// the corresponding value, and that's useful for advanced use cases like pausing an writer. For
+// more details, see the documentation of the enum further down.
@property(nonatomic) GRXWriterState state;
-// Start sending messages to the writeable. Messages may be sent before the method
-// returns, or they may be sent later in the future. See GRXWriteable.h for the
-// different messages a writeable can receive.
+// Transition to the Started state, and start sending messages to the writeable (a reference to it
+// is retained). Messages to the writeable may be sent before the method returns, or they may be
+// sent later in the future. See GRXWriteable.h for the different messages a writeable can receive.
//
-// If this writer draws its values from an external source (e.g. from the
-// filesystem or from a server), calling this method will commonly trigger side
-// effects (like network connections).
+// If this writer draws its values from an external source (e.g. from the filesystem or from a
+// server), calling this method will commonly trigger side effects (like network connections).
//
// This method might only be called on writers in the NotStarted state.
- (void)startWithWriteable:(id<GRXWriteable>)writeable;
-// Send writesFinishedWithError:errorOrNil immediately to the writeable, and don't send
-// any more messages to it.
-//
-// This method might only be called on writers in the Started or Paused
-// state.
+// Send writesFinishedWithError:errorOrNil to the writeable. Then release the reference to it and
+// transition to the Finished state.
//
-// TODO(jcanizales): Consider adding some guarantee about the immediacy of that
-// stopping. I know I've relied on it in part of the code that uses this, but
-// can't remember the details in the presence of concurrency.
+// This method might only be called on writers in the Started or Paused state.
- (void)finishWithError:(NSError *)errorOrNil;
@end
diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m
index e85dd6e65c..06581e7599 100644
--- a/src/objective-c/tests/GRPCClientTests.m
+++ b/src/objective-c/tests/GRPCClientTests.m
@@ -114,7 +114,7 @@ static ProtoMethod *kUnaryCallMethod;
[call startWithWriteable:responsesWriteable];
- [self waitForExpectationsWithTimeout:4 handler:nil];
+ [self waitForExpectationsWithTimeout:8 handler:nil];
}
- (void)testSimpleProtoRPC {
@@ -146,7 +146,7 @@ static ProtoMethod *kUnaryCallMethod;
[call startWithWriteable:responsesWriteable];
- [self waitForExpectationsWithTimeout:4 handler:nil];
+ [self waitForExpectationsWithTimeout:8 handler:nil];
}
- (void)testMetadata {
@@ -168,11 +168,13 @@ static ProtoMethod *kUnaryCallMethod;
} completionHandler:^(NSError *errorOrNil) {
XCTAssertNotNil(errorOrNil, @"Finished without error!");
XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil);
- XCTAssertEqualObjects(call.responseMetadata, errorOrNil.userInfo[kGRPCStatusMetadataKey],
- @"Metadata in the NSError object and call object differ.");
+ XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey],
+ @"Headers in the NSError object and call object differ.");
+ XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey],
+ @"Trailers in the NSError object and call object differ.");
NSString *challengeHeader = call.oauth2ChallengeHeader;
XCTAssertGreaterThan(challengeHeader.length, 0,
- @"No challenge in response headers %@", call.responseMetadata);
+ @"No challenge in response headers %@", call.responseHeaders);
[expectation fulfill];
}];
diff --git a/src/objective-c/tests/InteropTests.h b/src/objective-c/tests/InteropTests.h
index 4eb97e9e06..1045c3d124 100644
--- a/src/objective-c/tests/InteropTests.h
+++ b/src/objective-c/tests/InteropTests.h
@@ -37,8 +37,7 @@
// https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
@interface InteropTests : XCTestCase
-// Returns @"localhost:5050".
+// Returns @"grpc-test.sandbox.google.com".
// Override in a subclass to perform the same tests against a different address.
-// For interop tests, use @"grpc-test.sandbox.google.com".
+ (NSString *)host;
@end
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index b61d567464..1b63fe2059 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -78,20 +78,17 @@
#pragma mark Tests
-static NSString * const kLocalCleartextHost = @"localhost:5050";
+static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
@implementation InteropTests {
RMTTestService *_service;
}
+ (NSString *)host {
- return kLocalCleartextHost;
+ return kRemoteSSLHost;
}
- (void)setUp {
- // Register test server as non-SSL.
- [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost];
-
_service = [[RMTTestService alloc] initWithHost:self.class.host];
}
@@ -131,7 +128,7 @@ static NSString * const kLocalCleartextHost = @"localhost:5050";
[expectation fulfill];
}];
- [self waitForExpectationsWithTimeout:8 handler:nil];
+ [self waitForExpectationsWithTimeout:16 handler:nil];
}
- (void)testClientStreamingRPC {
diff --git a/include/grpc++/auth_property_iterator.h b/src/objective-c/tests/InteropTestsLocalCleartext.m
index c7870c46be..2d7d3c4b2c 100644
--- a/include/grpc++/auth_property_iterator.h
+++ b/src/objective-c/tests/InteropTestsLocalCleartext.m
@@ -31,47 +31,29 @@
*
*/
-#ifndef GRPCXX_AUTH_PROPERTY_ITERATOR_H
-#define GRPCXX_AUTH_PROPERTY_ITERATOR_H
+// Repeat of the tests in InteropTests.m, but sending the RPCs to a local cleartext server instead
+// of the remote SSL one.
-#include <iterator>
-#include <vector>
+#import <GRPCClient/GRPCCall+Tests.h>
-#include <grpc++/config.h>
+#import "InteropTests.h"
-struct grpc_auth_context;
-struct grpc_auth_property;
-struct grpc_auth_property_iterator;
+static NSString * const kLocalCleartextHost = @"localhost:5050";
-namespace grpc {
-class SecureAuthContext;
+@interface InteropTestsLocalCleartext : InteropTests
+@end
-typedef std::pair<grpc::string, grpc::string> AuthProperty;
+@implementation InteropTestsLocalCleartext
-class AuthPropertyIterator
- : public std::iterator<std::input_iterator_tag, const AuthProperty> {
- public:
- ~AuthPropertyIterator();
- AuthPropertyIterator& operator++();
- AuthPropertyIterator operator++(int);
- bool operator==(const AuthPropertyIterator& rhs) const;
- bool operator!=(const AuthPropertyIterator& rhs) const;
- const AuthProperty operator*();
++ (NSString *)host {
+ return kLocalCleartextHost;
+}
- protected:
- AuthPropertyIterator();
- AuthPropertyIterator(const grpc_auth_property* property,
- const grpc_auth_property_iterator* iter);
- private:
- friend class SecureAuthContext;
- const grpc_auth_property* property_;
- // The following items form a grpc_auth_property_iterator.
- const grpc_auth_context* ctx_;
- size_t index_;
- const char* name_;
-};
+- (void)setUp {
+ // Register test server as non-SSL.
+ [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost];
-} // namespace grpc
-
- #endif // GRPCXX_AUTH_PROPERTY_ITERATOR_H
+ [super setUp];
+}
+@end
diff --git a/src/objective-c/tests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTestsLocalSSL.m
index 227ca79659..f69f806dcf 100644
--- a/src/objective-c/tests/InteropTestsLocalSSL.m
+++ b/src/objective-c/tests/InteropTestsLocalSSL.m
@@ -31,8 +31,8 @@
*
*/
-// Repeat of the tests in InteropTests.m, but using SSL to communicate with the local server instead
-// of cleartext.
+// Repeat of the tests in InteropTests.m, but sending the RPCs to a local SSL server instead of the
+// remote one.
#import <GRPCClient/GRPCCall+Tests.h>
diff --git a/src/objective-c/tests/RxLibraryUnitTests.m b/src/objective-c/tests/RxLibraryUnitTests.m
index 5e3162875a..a67a4c6cd9 100644
--- a/src/objective-c/tests/RxLibraryUnitTests.m
+++ b/src/objective-c/tests/RxLibraryUnitTests.m
@@ -55,7 +55,7 @@
return [[self alloc] init];
}
-- (GRXSingleValueHandler)block {
+- (GRXSingleHandler)block {
return ^(id value, NSError *errorOrNil) {
++_timesCalled;
_value = value;
@@ -71,13 +71,13 @@
#pragma mark Writeable
-- (void)testWriteableSingleValueHandlerIsCalledForValue {
+- (void)testWriteableSingleHandlerIsCalledForValue {
// Given:
CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler];
id anyValue = @7;
// If:
- id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block];
+ id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block];
[writeable writeValue:anyValue];
// Then:
@@ -86,13 +86,13 @@
XCTAssertEqualObjects(handler.errorOrNil, nil);
}
-- (void)testWriteableSingleValueHandlerIsCalledForError {
+- (void)testWriteableSingleHandlerIsCalledForError {
// Given:
CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler];
NSError *anyError = [NSError errorWithDomain:@"domain" code:7 userInfo:nil];
// If:
- id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block];
+ id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block];
[writeable writesFinishedWithError:anyError];
// Then:
@@ -106,7 +106,7 @@
- (void)testBufferedPipePropagatesValue {
// Given:
CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler];
- id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block];
+ id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block];
id anyValue = @7;
// If:
@@ -123,7 +123,7 @@
- (void)testBufferedPipePropagatesError {
// Given:
CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler];
- id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block];
+ id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block];
NSError *anyError = [NSError errorWithDomain:@"domain" code:7 userInfo:nil];
// If:
diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
index af98aba9c0..3a1c3d940a 100644
--- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
+++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
@@ -13,6 +13,7 @@
63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; };
635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; };
635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; };
+ 63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; };
63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; };
63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
7D8A186224D39101F90230F6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
@@ -51,6 +52,7 @@
635697CC1B14FC11007A7283 /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
635697D81B14FC11007A7283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTests.m; sourceTree = "<group>"; };
+ 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalCleartext.m; sourceTree = "<group>"; };
63E240CC1B6C4D3A005F3B0E /* InteropTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteropTests.h; sourceTree = "<group>"; };
63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; };
63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; };
@@ -117,14 +119,15 @@
635697C91B14FC11007A7283 /* Tests */ = {
isa = PBXGroup;
children = (
- 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */,
6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */,
- 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */,
+ 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */,
635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */,
+ 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */,
+ 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */,
63423F501B151B77006CF63C /* RxLibraryUnitTests.m */,
+ 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */,
635697CC1B14FC11007A7283 /* Tests.m */,
635697D71B14FC11007A7283 /* Supporting Files */,
- 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */,
);
name = Tests;
sourceTree = SOURCE_ROOT;
@@ -261,6 +264,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */,
63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */,
63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */,
63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */,
diff --git a/src/python/grpcio/grpc/_adapter/_c/module.c b/src/python/grpcio/grpc/_adapter/_c/module.c
index 1f3aedd9d8..9b93b051f6 100644
--- a/src/python/grpcio/grpc/_adapter/_c/module.c
+++ b/src/python/grpcio/grpc/_adapter/_c/module.c
@@ -53,6 +53,12 @@ PyMODINIT_FUNC init_c(void) {
return;
}
+ if (PyModule_AddStringConstant(
+ module, "PRIMARY_USER_AGENT_KEY",
+ GRPC_ARG_PRIMARY_USER_AGENT_STRING) < 0) {
+ return;
+ }
+
/* GRPC maintains an internal counter of how many times it has been
initialized and handles multiple pairs of grpc_init()/grpc_shutdown()
invocations accordingly. */
diff --git a/src/python/grpcio/grpc/_adapter/_c/types.h b/src/python/grpcio/grpc/_adapter/_c/types.h
index 4e0da4a28a..f646465c63 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types.h
+++ b/src/python/grpcio/grpc/_adapter/_c/types.h
@@ -113,6 +113,7 @@ Call *pygrpc_Call_new_empty(CompletionQueue *cq);
void pygrpc_Call_dealloc(Call *self);
PyObject *pygrpc_Call_start_batch(Call *self, PyObject *args, PyObject *kwargs);
PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_Call_peer(Call *self);
extern PyTypeObject pygrpc_Call_type;
@@ -129,6 +130,11 @@ Channel *pygrpc_Channel_new(
void pygrpc_Channel_dealloc(Channel *self);
Call *pygrpc_Channel_create_call(
Channel *self, PyObject *args, PyObject *kwargs);
+PyObject *pygrpc_Channel_check_connectivity_state(Channel *self, PyObject *args,
+ PyObject *kwargs);
+PyObject *pygrpc_Channel_watch_connectivity_state(Channel *self, PyObject *args,
+ PyObject *kwargs);
+PyObject *pygrpc_Channel_target(Channel *self);
extern PyTypeObject pygrpc_Channel_type;
@@ -181,6 +187,9 @@ pygrpc_tag *pygrpc_produce_request_tag(PyObject *user_tag, Call *empty_call);
/* Construct a tag associated with a server shutdown. */
pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag);
+/* Construct a tag associated with a channel state change. */
+pygrpc_tag *pygrpc_produce_channel_state_change_tag(PyObject *user_tag);
+
/* Frees all resources owned by the tag and the tag itself. */
void pygrpc_discard_tag(pygrpc_tag *tag);
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/call.c b/src/python/grpcio/grpc/_adapter/_c/types/call.c
index 0739070044..5e46605c45 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/call.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/call.c
@@ -42,6 +42,7 @@
PyMethodDef pygrpc_Call_methods[] = {
{"start_batch", (PyCFunction)pygrpc_Call_start_batch, METH_KEYWORDS, ""},
{"cancel", (PyCFunction)pygrpc_Call_cancel, METH_KEYWORDS, ""},
+ {"peer", (PyCFunction)pygrpc_Call_peer, METH_NOARGS, ""},
{NULL}
};
const char pygrpc_Call_doc[] = "See grpc._adapter._types.Call.";
@@ -161,3 +162,10 @@ PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs) {
}
return PyInt_FromLong(errcode);
}
+
+PyObject *pygrpc_Call_peer(Call *self) {
+ char *peer = grpc_call_get_peer(self->c_call);
+ PyObject *py_peer = PyString_FromString(peer);
+ gpr_free(peer);
+ return py_peer;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/channel.c b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
index 963104742f..eb9d43d154 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/channel.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/channel.c
@@ -36,10 +36,14 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
PyMethodDef pygrpc_Channel_methods[] = {
{"create_call", (PyCFunction)pygrpc_Channel_create_call, METH_KEYWORDS, ""},
+ {"check_connectivity_state", (PyCFunction)pygrpc_Channel_check_connectivity_state, METH_KEYWORDS, ""},
+ {"watch_connectivity_state", (PyCFunction)pygrpc_Channel_watch_connectivity_state, METH_KEYWORDS, ""},
+ {"target", (PyCFunction)pygrpc_Channel_target, METH_NOARGS, ""},
{NULL}
};
const char pygrpc_Channel_doc[] = "See grpc._adapter._types.Channel.";
@@ -122,7 +126,7 @@ Call *pygrpc_Channel_create_call(
const char *host;
double deadline;
char *keywords[] = {"cq", "method", "host", "deadline", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ssd:create_call", keywords,
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!szd:create_call", keywords,
&pygrpc_CompletionQueue_type, &cq, &method, &host, &deadline)) {
return NULL;
}
@@ -132,3 +136,51 @@ Call *pygrpc_Channel_create_call(
pygrpc_cast_double_to_gpr_timespec(deadline));
return call;
}
+
+PyObject *pygrpc_Channel_check_connectivity_state(
+ Channel *self, PyObject *args, PyObject *kwargs) {
+ PyObject *py_try_to_connect;
+ int try_to_connect;
+ char *keywords[] = {"try_to_connect", NULL};
+ grpc_connectivity_state state;
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:connectivity_state", keywords,
+ &py_try_to_connect)) {
+ return NULL;
+ }
+ if (!PyBool_Check(py_try_to_connect)) {
+ Py_XDECREF(py_try_to_connect);
+ return NULL;
+ }
+ try_to_connect = Py_True == py_try_to_connect;
+ Py_DECREF(py_try_to_connect);
+ state = grpc_channel_check_connectivity_state(self->c_chan, try_to_connect);
+ return PyInt_FromLong(state);
+}
+
+PyObject *pygrpc_Channel_watch_connectivity_state(
+ Channel *self, PyObject *args, PyObject *kwargs) {
+ PyObject *tag;
+ double deadline;
+ int last_observed_state;
+ CompletionQueue *completion_queue;
+ char *keywords[] = {"last_observed_state", "deadline",
+ "completion_queue", "tag"};
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "idO!O:watch_connectivity_state", keywords,
+ &last_observed_state, &deadline, &pygrpc_CompletionQueue_type,
+ &completion_queue, &tag)) {
+ return NULL;
+ }
+ grpc_channel_watch_connectivity_state(
+ self->c_chan, (grpc_connectivity_state)last_observed_state,
+ pygrpc_cast_double_to_gpr_timespec(deadline), completion_queue->c_cq,
+ pygrpc_produce_channel_state_change_tag(tag));
+ Py_RETURN_NONE;
+}
+
+PyObject *pygrpc_Channel_target(Channel *self) {
+ char *target = grpc_channel_get_target(self->c_chan);
+ PyObject *py_target = PyString_FromString(target);
+ gpr_free(target);
+ return py_target;
+}
diff --git a/src/python/grpcio/grpc/_adapter/_c/types/server.c b/src/python/grpcio/grpc/_adapter/_c/types/server.c
index c2190ea672..2a11d09d21 100644
--- a/src/python/grpcio/grpc/_adapter/_c/types/server.c
+++ b/src/python/grpcio/grpc/_adapter/_c/types/server.c
@@ -96,7 +96,7 @@ Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
PyObject *py_args;
grpc_channel_args c_args;
char *keywords[] = {"cq", "args", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Channel", keywords,
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Server", keywords,
&pygrpc_CompletionQueue_type, &cq, &py_args)) {
return NULL;
}
diff --git a/src/python/grpcio/grpc/_adapter/_c/utility.c b/src/python/grpcio/grpc/_adapter/_c/utility.c
index 51f3c9be01..2eea0e18ef 100644
--- a/src/python/grpcio/grpc/_adapter/_c/utility.c
+++ b/src/python/grpcio/grpc/_adapter/_c/utility.c
@@ -88,6 +88,19 @@ pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag) {
return tag;
}
+pygrpc_tag *pygrpc_produce_channel_state_change_tag(PyObject *user_tag) {
+ pygrpc_tag *tag = gpr_malloc(sizeof(pygrpc_tag));
+ tag->user_tag = user_tag;
+ Py_XINCREF(tag->user_tag);
+ tag->call = NULL;
+ tag->ops = NULL;
+ tag->nops = 0;
+ grpc_call_details_init(&tag->request_call_details);
+ grpc_metadata_array_init(&tag->request_metadata);
+ tag->is_new_call = 0;
+ return tag;
+}
+
void pygrpc_discard_tag(pygrpc_tag *tag) {
if (!tag) {
return;
@@ -139,7 +152,7 @@ PyObject *pygrpc_consume_event(grpc_event event) {
}
int pygrpc_produce_op(PyObject *op, grpc_op *result) {
- static const int OP_TUPLE_SIZE = 5;
+ static const int OP_TUPLE_SIZE = 6;
static const int STATUS_TUPLE_SIZE = 2;
static const int TYPE_INDEX = 0;
static const int INITIAL_METADATA_INDEX = 1;
@@ -148,6 +161,7 @@ int pygrpc_produce_op(PyObject *op, grpc_op *result) {
static const int STATUS_INDEX = 4;
static const int STATUS_CODE_INDEX = 0;
static const int STATUS_DETAILS_INDEX = 1;
+ static const int WRITE_FLAGS_INDEX = 5;
int type;
Py_ssize_t message_size;
char *message;
@@ -170,7 +184,10 @@ int pygrpc_produce_op(PyObject *op, grpc_op *result) {
return 0;
}
c_op.op = type;
- c_op.flags = 0;
+ c_op.flags = PyInt_AsLong(PyTuple_GET_ITEM(op, WRITE_FLAGS_INDEX));
+ if (PyErr_Occurred()) {
+ return 0;
+ }
switch (type) {
case GRPC_OP_SEND_INITIAL_METADATA:
if (!pygrpc_cast_pyseq_to_send_metadata(
diff --git a/src/python/grpcio/grpc/_adapter/_intermediary_low.py b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
index 3c7f0a2619..e7bf9dc462 100644
--- a/src/python/grpcio/grpc/_adapter/_intermediary_low.py
+++ b/src/python/grpcio/grpc/_adapter/_intermediary_low.py
@@ -127,7 +127,7 @@ class Call(object):
def write(self, message, tag):
return self._internal.start_batch([
- _types.OpArgs.send_message(message)
+ _types.OpArgs.send_message(message, 0)
], _TagAdapter(tag, Event.Kind.WRITE_ACCEPTED))
def complete(self, tag):
diff --git a/src/python/grpcio/grpc/_adapter/_low.py b/src/python/grpcio/grpc/_adapter/_low.py
index dcf67dbc11..147086e725 100644
--- a/src/python/grpcio/grpc/_adapter/_low.py
+++ b/src/python/grpcio/grpc/_adapter/_low.py
@@ -27,9 +27,12 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from grpc import _grpcio_metadata
from grpc._adapter import _c
from grpc._adapter import _types
+_USER_AGENT = 'Python-gRPC-{}'.format(_grpcio_metadata.__version__)
+
ClientCredentials = _c.ClientCredentials
ServerCredentials = _c.ServerCredentials
@@ -72,10 +75,14 @@ class Call(_types.Call):
else:
return self.call.cancel(code, details)
+ def peer(self):
+ return self.call.peer()
+
class Channel(_types.Channel):
def __init__(self, target, args, creds=None):
+ args = list(args) + [(_c.PRIMARY_USER_AGENT_KEY, _USER_AGENT)]
if creds is None:
self.channel = _c.Channel(target, args)
else:
@@ -84,6 +91,17 @@ class Channel(_types.Channel):
def create_call(self, completion_queue, method, host, deadline=None):
return Call(self.channel.create_call(completion_queue.completion_queue, method, host, deadline))
+ def check_connectivity_state(self, try_to_connect):
+ return self.channel.check_connectivity_state(try_to_connect)
+
+ def watch_connectivity_state(self, last_observed_state, deadline,
+ completion_queue, tag):
+ self.channel.watch_connectivity_state(
+ last_observed_state, deadline, completion_queue.completion_queue, tag)
+
+ def target(self):
+ return self.channel.target()
+
_NO_TAG = object()
diff --git a/src/python/grpcio/grpc/_adapter/_types.py b/src/python/grpcio/grpc/_adapter/_types.py
index 5ddb1774ea..5470d2de4a 100644
--- a/src/python/grpcio/grpc/_adapter/_types.py
+++ b/src/python/grpcio/grpc/_adapter/_types.py
@@ -31,13 +31,12 @@ import abc
import collections
import enum
-# TODO(atash): decide whether or not to move these enums to the _c module to
-# force build errors with upstream changes.
class GrpcChannelArgumentKeys(enum.Enum):
"""Mirrors keys used in grpc_channel_args for GRPC-specific arguments."""
SSL_TARGET_NAME_OVERRIDE = 'grpc.ssl_target_name_override'
+
@enum.unique
class CallError(enum.IntEnum):
"""Mirrors grpc_call_error in the C core."""
@@ -53,6 +52,7 @@ class CallError(enum.IntEnum):
ERROR_INVALID_FLAGS = 9
ERROR_INVALID_METADATA = 10
+
@enum.unique
class StatusCode(enum.IntEnum):
"""Mirrors grpc_status_code in the C core."""
@@ -74,6 +74,14 @@ class StatusCode(enum.IntEnum):
DATA_LOSS = 15
UNAUTHENTICATED = 16
+
+@enum.unique
+class OpWriteFlags(enum.IntEnum):
+ """Mirrors defined write-flag constants in the C core."""
+ WRITE_BUFFER_HINT = 1
+ WRITE_NO_COMPRESS = 2
+
+
@enum.unique
class OpType(enum.IntEnum):
"""Mirrors grpc_op_type in the C core."""
@@ -86,12 +94,24 @@ class OpType(enum.IntEnum):
RECV_STATUS_ON_CLIENT = 6
RECV_CLOSE_ON_SERVER = 7
+
@enum.unique
class EventType(enum.IntEnum):
"""Mirrors grpc_completion_type in the C core."""
- QUEUE_SHUTDOWN = 0
- QUEUE_TIMEOUT = 1 # if seen on the Python side, something went horridly wrong
- OP_COMPLETE = 2
+ QUEUE_SHUTDOWN = 0
+ QUEUE_TIMEOUT = 1 # if seen on the Python side, something went horridly wrong
+ OP_COMPLETE = 2
+
+
+@enum.unique
+class ConnectivityState(enum.IntEnum):
+ """Mirrors grpc_connectivity_state in the C core."""
+ IDLE = 0
+ CONNECTING = 1
+ READY = 2
+ TRANSIENT_FAILURE = 3
+ FATAL_FAILURE = 4
+
class Status(collections.namedtuple(
'Status', [
@@ -105,6 +125,7 @@ class Status(collections.namedtuple(
details (str): ...
"""
+
class CallDetails(collections.namedtuple(
'CallDetails', [
'method',
@@ -119,6 +140,7 @@ class CallDetails(collections.namedtuple(
deadline (float): ...
"""
+
class OpArgs(collections.namedtuple(
'OpArgs', [
'type',
@@ -126,6 +148,7 @@ class OpArgs(collections.namedtuple(
'trailing_metadata',
'message',
'status',
+ 'write_flags',
])):
"""Arguments passed into a GRPC operation.
@@ -138,39 +161,40 @@ class OpArgs(collections.namedtuple(
message (bytes): Only valid if type == OpType.SEND_MESSAGE, else is None.
status (Status): Only valid if type == OpType.SEND_STATUS_FROM_SERVER, else
is None.
+ write_flags (int): a bit OR'ing of 0 or more OpWriteFlags values.
"""
@staticmethod
def send_initial_metadata(initial_metadata):
- return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None)
+ return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None, 0)
@staticmethod
- def send_message(message):
- return OpArgs(OpType.SEND_MESSAGE, None, None, message, None)
+ def send_message(message, flags):
+ return OpArgs(OpType.SEND_MESSAGE, None, None, message, None, flags)
@staticmethod
def send_close_from_client():
- return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None)
+ return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None, 0)
@staticmethod
def send_status_from_server(trailing_metadata, status_code, status_details):
- return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details))
+ return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details), 0)
@staticmethod
def recv_initial_metadata():
- return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None);
+ return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None, 0);
@staticmethod
def recv_message():
- return OpArgs(OpType.RECV_MESSAGE, None, None, None, None)
+ return OpArgs(OpType.RECV_MESSAGE, None, None, None, None, 0)
@staticmethod
def recv_status_on_client():
- return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None)
+ return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None, 0)
@staticmethod
def recv_close_on_server():
- return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None)
+ return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None, 0)
class OpResult(collections.namedtuple(
@@ -290,6 +314,15 @@ class Call:
"""
return CallError.ERROR
+ @abc.abstractmethod
+ def peer(self):
+ """Get the peer of this call.
+
+ Returns:
+ str: the peer of this call.
+ """
+ return None
+
class Channel:
__metaclass__ = abc.ABCMeta
@@ -321,6 +354,40 @@ class Channel:
"""
return None
+ @abc.abstractmethod
+ def check_connectivity_state(self, try_to_connect):
+ """Check and optionally repair the connectivity state of the channel.
+
+ Args:
+ try_to_connect (bool): whether or not to try to connect the channel if
+ disconnected.
+
+ Returns:
+ ConnectivityState: state of the channel at the time of this invocation.
+ """
+ return None
+
+ @abc.abstractmethod
+ def watch_connectivity_state(self, last_observed_state, deadline,
+ completion_queue, tag):
+ """Watch for connectivity state changes from the last_observed_state.
+
+ Args:
+ last_observed_state (ConnectivityState): ...
+ deadline (float): ...
+ completion_queue (CompletionQueue): ...
+ tag (object) ...
+ """
+
+ @abc.abstractmethod
+ def target(self):
+ """Get the target of this channel.
+
+ Returns:
+ str: the target of this channel.
+ """
+ return None
+
class Server:
__metaclass__ = abc.ABCMeta
diff --git a/src/python/grpcio_test/grpc_interop/_interop_test_case.py b/src/python/grpcio_test/grpc_interop/_interop_test_case.py
index ed8f7ef009..b6d06b300d 100644
--- a/src/python/grpcio_test/grpc_interop/_interop_test_case.py
+++ b/src/python/grpcio_test/grpc_interop/_interop_test_case.py
@@ -59,3 +59,6 @@ class InteropTestCase(object):
def testCancelAfterFirstResponse(self):
methods.TestCase.CANCEL_AFTER_FIRST_RESPONSE.test_interoperability(self.stub, None)
+
+ def testTimeoutOnSleepingServer(self):
+ methods.TestCase.TIMEOUT_ON_SLEEPING_SERVER.test_interoperability(self.stub, None)
diff --git a/src/python/grpcio_test/grpc_interop/methods.py b/src/python/grpcio_test/grpc_interop/methods.py
index f4c94685ee..7a831f3cbd 100644
--- a/src/python/grpcio_test/grpc_interop/methods.py
+++ b/src/python/grpcio_test/grpc_interop/methods.py
@@ -33,10 +33,12 @@ import enum
import json
import os
import threading
+import time
from oauth2client import client as oauth2client_client
from grpc.framework.alpha import utilities
+from grpc.framework.alpha import exceptions
from grpc_interop import empty_pb2
from grpc_interop import messages_pb2
@@ -318,6 +320,24 @@ def _cancel_after_first_response(stub):
raise ValueError('expected call to be cancelled')
+def _timeout_on_sleeping_server(stub):
+ request_payload_size = 27182
+ with stub, _Pipe() as pipe:
+ response_iterator = stub.FullDuplexCall(pipe, 0.001)
+
+ request = messages_pb2.StreamingOutputCallRequest(
+ response_type=messages_pb2.COMPRESSABLE,
+ payload=messages_pb2.Payload(body=b'\x00' * request_payload_size))
+ pipe.add(request)
+ time.sleep(0.1)
+ try:
+ next(response_iterator)
+ except exceptions.ExpirationError:
+ pass
+ else:
+ raise ValueError('expected call to exceed deadline')
+
+
def _compute_engine_creds(stub, args):
response = _large_unary_common_behavior(stub, True, True)
if args.default_service_account != response.username:
@@ -351,6 +371,7 @@ class TestCase(enum.Enum):
CANCEL_AFTER_FIRST_RESPONSE = 'cancel_after_first_response'
COMPUTE_ENGINE_CREDS = 'compute_engine_creds'
SERVICE_ACCOUNT_CREDS = 'service_account_creds'
+ TIMEOUT_ON_SLEEPING_SERVER = 'timeout_on_sleeping_server'
def test_interoperability(self, stub, args):
if self is TestCase.EMPTY_UNARY:
@@ -367,6 +388,8 @@ class TestCase(enum.Enum):
_cancel_after_begin(stub)
elif self is TestCase.CANCEL_AFTER_FIRST_RESPONSE:
_cancel_after_first_response(stub)
+ elif self is TestCase.TIMEOUT_ON_SLEEPING_SERVER:
+ _timeout_on_sleeping_server(stub)
elif self is TestCase.COMPUTE_ENGINE_CREDS:
_compute_engine_creds(stub, args)
elif self is TestCase.SERVICE_ACCOUNT_CREDS:
diff --git a/src/python/grpcio_test/grpc_protoc_plugin/__init__.py b/src/python/grpcio_test/grpc_protoc_plugin/__init__.py
new file mode 100644
index 0000000000..7086519106
--- /dev/null
+++ b/src/python/grpcio_test/grpc_protoc_plugin/__init__.py
@@ -0,0 +1,30 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
diff --git a/test/compiler/python_plugin_test.py b/src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py
index 0e58d912b9..b200d129a9 100644
--- a/test/compiler/python_plugin_test.py
+++ b/src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py
@@ -29,9 +29,11 @@
import argparse
import contextlib
+import distutils.spawn
import errno
import itertools
import os
+import pkg_resources
import shutil
import subprocess
import sys
@@ -58,9 +60,6 @@ SHORT_TIMEOUT = 2
LONG_TIMEOUT = 600
NO_DELAY = 0
-# Build mode environment variable set by tools/run_tests/run_tests.py.
-_build_mode = os.environ['CONFIG']
-
class _ServicerMethods(object):
@@ -228,9 +227,13 @@ class PythonPluginTest(unittest.TestCase):
"""
def setUp(self):
- protoc_command = '../../bins/%s/protobuf/protoc' % _build_mode
- protoc_plugin_filename = '../../bins/%s/grpc_python_plugin' % _build_mode
- test_proto_filename = './test.proto'
+ # Assume that the appropriate protoc and grpc_python_plugins are on the
+ # path.
+ protoc_command = 'protoc'
+ protoc_plugin_filename = distutils.spawn.find_executable(
+ 'grpc_python_plugin')
+ test_proto_filename = pkg_resources.resource_filename(
+ 'grpc_protoc_plugin', 'test.proto')
if not os.path.isfile(protoc_command):
# Assume that if we haven't built protoc that it's on the system.
protoc_command = 'protoc'
@@ -242,12 +245,13 @@ class PythonPluginTest(unittest.TestCase):
cmd = [
protoc_command,
'--plugin=protoc-gen-python-grpc=%s' % protoc_plugin_filename,
- '-I %s' % os.path.dirname(test_proto_filename),
+ '-I .',
'--python_out=%s' % self.outdir,
'--python-grpc_out=%s' % self.outdir,
os.path.basename(test_proto_filename),
]
- subprocess.call(' '.join(cmd), shell=True)
+ subprocess.check_call(' '.join(cmd), shell=True, env=os.environ,
+ cwd=os.path.dirname(test_proto_filename))
sys.path.append(self.outdir)
def tearDown(self):
diff --git a/test/compiler/test.proto b/src/python/grpcio_test/grpc_protoc_plugin/test.proto
index ed7c6a7b79..ed7c6a7b79 100644
--- a/test/compiler/test.proto
+++ b/src/python/grpcio_test/grpc_protoc_plugin/test.proto
diff --git a/src/python/grpcio_test/grpc_test/_adapter/_low_test.py b/src/python/grpcio_test/grpc_test/_adapter/_low_test.py
index 9a8edfad0c..44fe760fbc 100644
--- a/src/python/grpcio_test/grpc_test/_adapter/_low_test.py
+++ b/src/python/grpcio_test/grpc_test/_adapter/_low_test.py
@@ -31,11 +31,12 @@ import threading
import time
import unittest
+from grpc import _grpcio_metadata
from grpc._adapter import _types
from grpc._adapter import _low
-def WaitForEvents(completion_queues, deadline):
+def wait_for_events(completion_queues, deadline):
"""
Args:
completion_queues: list of completion queues to wait for events on
@@ -62,6 +63,7 @@ def WaitForEvents(completion_queues, deadline):
thread.join()
return results
+
class InsecureServerInsecureClient(unittest.TestCase):
def setUp(self):
@@ -115,7 +117,7 @@ class InsecureServerInsecureClient(unittest.TestCase):
client_initial_metadata = [(CLIENT_METADATA_ASCII_KEY, CLIENT_METADATA_ASCII_VALUE), (CLIENT_METADATA_BIN_KEY, CLIENT_METADATA_BIN_VALUE)]
client_start_batch_result = client_call.start_batch([
_types.OpArgs.send_initial_metadata(client_initial_metadata),
- _types.OpArgs.send_message(REQUEST),
+ _types.OpArgs.send_message(REQUEST, 0),
_types.OpArgs.send_close_from_client(),
_types.OpArgs.recv_initial_metadata(),
_types.OpArgs.recv_message(),
@@ -123,20 +125,34 @@ class InsecureServerInsecureClient(unittest.TestCase):
], client_call_tag)
self.assertEquals(_types.CallError.OK, client_start_batch_result)
- client_no_event, request_event, = WaitForEvents([self.client_completion_queue, self.server_completion_queue], time.time() + 2)
+ client_no_event, request_event, = wait_for_events([self.client_completion_queue, self.server_completion_queue], time.time() + 2)
self.assertEquals(client_no_event, None)
self.assertEquals(_types.EventType.OP_COMPLETE, request_event.type)
self.assertIsInstance(request_event.call, _low.Call)
self.assertIs(server_request_tag, request_event.tag)
self.assertEquals(1, len(request_event.results))
- got_initial_metadata = dict(request_event.results[0].initial_metadata)
+ received_initial_metadata = dict(request_event.results[0].initial_metadata)
+ # Check that our metadata were transmitted
self.assertEquals(
dict(client_initial_metadata),
- dict((x, got_initial_metadata[x]) for x in zip(*client_initial_metadata)[0]))
+ dict((x, received_initial_metadata[x]) for x in zip(*client_initial_metadata)[0]))
+ # Check that Python's user agent string is a part of the full user agent
+ # string
+ self.assertIn('Python-gRPC-{}'.format(_grpcio_metadata.__version__),
+ received_initial_metadata['user-agent'])
self.assertEquals(METHOD, request_event.call_details.method)
self.assertEquals(HOST, request_event.call_details.host)
self.assertLess(abs(DEADLINE - request_event.call_details.deadline), DEADLINE_TOLERANCE)
+ # Check that the channel is connected, and that both it and the call have
+ # the proper target and peer; do this after the first flurry of messages to
+ # avoid the possibility that connection was delayed by the core until the
+ # first message was sent.
+ self.assertEqual(_types.ConnectivityState.READY,
+ self.client_channel.check_connectivity_state(False))
+ self.assertIsNotNone(self.client_channel.target())
+ self.assertIsNotNone(client_call.peer())
+
server_call_tag = object()
server_call = request_event.call
server_initial_metadata = [(SERVER_INITIAL_METADATA_KEY, SERVER_INITIAL_METADATA_VALUE)]
@@ -144,13 +160,13 @@ class InsecureServerInsecureClient(unittest.TestCase):
server_start_batch_result = server_call.start_batch([
_types.OpArgs.send_initial_metadata(server_initial_metadata),
_types.OpArgs.recv_message(),
- _types.OpArgs.send_message(RESPONSE),
+ _types.OpArgs.send_message(RESPONSE, 0),
_types.OpArgs.recv_close_on_server(),
_types.OpArgs.send_status_from_server(server_trailing_metadata, SERVER_STATUS_CODE, SERVER_STATUS_DETAILS)
], server_call_tag)
self.assertEquals(_types.CallError.OK, server_start_batch_result)
- client_event, server_event, = WaitForEvents([self.client_completion_queue, self.server_completion_queue], time.time() + 1)
+ client_event, server_event, = wait_for_events([self.client_completion_queue, self.server_completion_queue], time.time() + 1)
self.assertEquals(6, len(client_event.results))
found_client_op_types = set()
diff --git a/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py
index 7e1158f96b..251e1eb68e 100644
--- a/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py
+++ b/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py
@@ -34,15 +34,13 @@ import abc
import unittest # pylint: disable=unused-import
from grpc.framework.face import exceptions
+from grpc_test.framework.common import test_constants
from grpc_test.framework.face.testing import control
from grpc_test.framework.face.testing import coverage
from grpc_test.framework.face.testing import digest
from grpc_test.framework.face.testing import stock_service
from grpc_test.framework.face.testing import test_case
-_TIMEOUT = 3
-_LONG_TIMEOUT = 45
-
class BlockingInvocationInlineServiceTestCase(
test_case.FaceTestCase, coverage.BlockingCoverage):
@@ -79,7 +77,7 @@ class BlockingInvocationInlineServiceTestCase(
request = test_messages.request()
response = self.stub.blocking_value_in_value_out(
- name, request, _LONG_TIMEOUT)
+ name, request, test_constants.LONG_TIMEOUT)
test_messages.verify(request, response, self)
@@ -90,7 +88,7 @@ class BlockingInvocationInlineServiceTestCase(
request = test_messages.request()
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _LONG_TIMEOUT)
+ name, request, test_constants.LONG_TIMEOUT)
responses = list(response_iterator)
test_messages.verify(request, responses, self)
@@ -102,7 +100,7 @@ class BlockingInvocationInlineServiceTestCase(
requests = test_messages.requests()
response = self.stub.blocking_stream_in_value_out(
- name, iter(requests), _LONG_TIMEOUT)
+ name, iter(requests), test_constants.LONG_TIMEOUT)
test_messages.verify(requests, response, self)
@@ -113,7 +111,7 @@ class BlockingInvocationInlineServiceTestCase(
requests = test_messages.requests()
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _LONG_TIMEOUT)
+ name, iter(requests), test_constants.LONG_TIMEOUT)
responses = list(response_iterator)
test_messages.verify(requests, responses, self)
@@ -126,12 +124,12 @@ class BlockingInvocationInlineServiceTestCase(
second_request = test_messages.request()
first_response = self.stub.blocking_value_in_value_out(
- name, first_request, _TIMEOUT)
+ name, first_request, test_constants.SHORT_TIMEOUT)
test_messages.verify(first_request, first_response, self)
second_response = self.stub.blocking_value_in_value_out(
- name, second_request, _TIMEOUT)
+ name, second_request, test_constants.SHORT_TIMEOUT)
test_messages.verify(second_request, second_response, self)
@@ -144,7 +142,7 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.pause(), self.assertRaises(
exceptions.ExpirationError):
multi_callable = self.stub.unary_unary_multi_callable(name)
- multi_callable(request, _TIMEOUT)
+ multi_callable(request, test_constants.SHORT_TIMEOUT)
def testExpiredUnaryRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -155,7 +153,7 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.pause(), self.assertRaises(
exceptions.ExpirationError):
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
list(response_iterator)
def testExpiredStreamRequestUnaryResponse(self):
@@ -167,7 +165,7 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.pause(), self.assertRaises(
exceptions.ExpirationError):
multi_callable = self.stub.stream_unary_multi_callable(name)
- multi_callable(iter(requests), _TIMEOUT)
+ multi_callable(iter(requests), test_constants.SHORT_TIMEOUT)
def testExpiredStreamRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -178,7 +176,7 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.pause(), self.assertRaises(
exceptions.ExpirationError):
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
list(response_iterator)
def testFailedUnaryRequestUnaryResponse(self):
@@ -188,7 +186,8 @@ class BlockingInvocationInlineServiceTestCase(
request = test_messages.request()
with self.control.fail(), self.assertRaises(exceptions.ServicerError):
- self.stub.blocking_value_in_value_out(name, request, _TIMEOUT)
+ self.stub.blocking_value_in_value_out(name, request,
+ test_constants.SHORT_TIMEOUT)
def testFailedUnaryRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -198,7 +197,7 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.fail(), self.assertRaises(exceptions.ServicerError):
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
list(response_iterator)
def testFailedStreamRequestUnaryResponse(self):
@@ -208,7 +207,8 @@ class BlockingInvocationInlineServiceTestCase(
requests = test_messages.requests()
with self.control.fail(), self.assertRaises(exceptions.ServicerError):
- self.stub.blocking_stream_in_value_out(name, iter(requests), _TIMEOUT)
+ self.stub.blocking_stream_in_value_out(name, iter(requests),
+ test_constants.SHORT_TIMEOUT)
def testFailedStreamRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -218,5 +218,5 @@ class BlockingInvocationInlineServiceTestCase(
with self.control.fail(), self.assertRaises(exceptions.ServicerError):
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
list(response_iterator)
diff --git a/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py
index 18eed53d6e..9df77678eb 100644
--- a/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py
+++ b/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py
@@ -33,6 +33,7 @@ import abc
import unittest
from grpc.framework.face import interfaces
+from grpc_test.framework.common import test_constants
from grpc_test.framework.face.testing import callback as testing_callback
from grpc_test.framework.face.testing import control
from grpc_test.framework.face.testing import coverage
@@ -40,8 +41,6 @@ from grpc_test.framework.face.testing import digest
from grpc_test.framework.face.testing import stock_service
from grpc_test.framework.face.testing import test_case
-_TIMEOUT = 3
-
class EventInvocationSynchronousEventServiceTestCase(
test_case.FaceTestCase, coverage.FullCoverage):
@@ -79,7 +78,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
self.stub.event_value_in_value_out(
- name, request, callback.complete, callback.abort, _TIMEOUT)
+ name, request, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
response = callback.response()
@@ -93,7 +93,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
self.stub.event_value_in_stream_out(
- name, request, callback, callback.abort, _TIMEOUT)
+ name, request, callback, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
responses = callback.responses()
@@ -107,7 +108,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
unused_call, request_consumer = self.stub.event_stream_in_value_out(
- name, callback.complete, callback.abort, _TIMEOUT)
+ name, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
request_consumer.terminate()
@@ -124,7 +126,7 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
unused_call, request_consumer = self.stub.event_stream_in_stream_out(
- name, callback, callback.abort, _TIMEOUT)
+ name, callback, callback.abort, test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
request_consumer.terminate()
@@ -147,11 +149,11 @@ class EventInvocationSynchronousEventServiceTestCase(
first_callback.complete(first_response)
self.stub.event_value_in_value_out(
name, second_request, second_callback.complete,
- second_callback.abort, _TIMEOUT)
+ second_callback.abort, test_constants.SHORT_TIMEOUT)
self.stub.event_value_in_value_out(
name, first_request, make_second_invocation, first_callback.abort,
- _TIMEOUT)
+ test_constants.SHORT_TIMEOUT)
second_callback.block_until_terminated()
first_response = first_callback.response()
@@ -168,7 +170,8 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.pause():
self.stub.event_value_in_value_out(
- name, request, callback.complete, callback.abort, _TIMEOUT)
+ name, request, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
@@ -182,7 +185,8 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.pause():
self.stub.event_value_in_stream_out(
- name, request, callback, callback.abort, _TIMEOUT)
+ name, request, callback, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
@@ -194,7 +198,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
self.stub.event_stream_in_value_out(
- name, callback.complete, callback.abort, _TIMEOUT)
+ name, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion())
@@ -207,7 +212,7 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
unused_call, request_consumer = self.stub.event_stream_in_stream_out(
- name, callback, callback.abort, _TIMEOUT)
+ name, callback, callback.abort, test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
callback.block_until_terminated()
@@ -223,10 +228,12 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.fail():
self.stub.event_value_in_value_out(
- name, request, callback.complete, callback.abort, _TIMEOUT)
+ name, request, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
- self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
+ self.assertEqual(interfaces.Abortion.SERVICER_FAILURE,
+ callback.abortion())
def testFailedUnaryRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -237,10 +244,12 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.fail():
self.stub.event_value_in_stream_out(
- name, request, callback, callback.abort, _TIMEOUT)
+ name, request, callback, callback.abort,
+ test_constants.SHORT_TIMEOUT)
callback.block_until_terminated()
- self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
+ self.assertEqual(interfaces.Abortion.SERVICER_FAILURE,
+ callback.abortion())
def testFailedStreamRequestUnaryResponse(self):
for name, test_messages_sequence in (
@@ -251,13 +260,15 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.fail():
unused_call, request_consumer = self.stub.event_stream_in_value_out(
- name, callback.complete, callback.abort, _TIMEOUT)
+ name, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
request_consumer.terminate()
callback.block_until_terminated()
- self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion())
+ self.assertEqual(interfaces.Abortion.SERVICER_FAILURE,
+ callback.abortion())
def testFailedStreamRequestStreamResponse(self):
for name, test_messages_sequence in (
@@ -268,7 +279,7 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.fail():
unused_call, request_consumer = self.stub.event_stream_in_stream_out(
- name, callback, callback.abort, _TIMEOUT)
+ name, callback, callback.abort, test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
request_consumer.terminate()
@@ -287,10 +298,10 @@ class EventInvocationSynchronousEventServiceTestCase(
self.stub.event_value_in_value_out(
name, first_request, first_callback.complete, first_callback.abort,
- _TIMEOUT)
+ test_constants.SHORT_TIMEOUT)
self.stub.event_value_in_value_out(
name, second_request, second_callback.complete,
- second_callback.abort, _TIMEOUT)
+ second_callback.abort, test_constants.SHORT_TIMEOUT)
first_callback.block_until_terminated()
second_callback.block_until_terminated()
@@ -312,7 +323,8 @@ class EventInvocationSynchronousEventServiceTestCase(
with self.control.pause():
call = self.stub.event_value_in_value_out(
- name, request, callback.complete, callback.abort, _TIMEOUT)
+ name, request, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
call.cancel()
callback.block_until_terminated()
@@ -326,7 +338,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
call = self.stub.event_value_in_stream_out(
- name, request, callback, callback.abort, _TIMEOUT)
+ name, request, callback, callback.abort,
+ test_constants.SHORT_TIMEOUT)
call.cancel()
callback.block_until_terminated()
@@ -340,7 +353,8 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
call, request_consumer = self.stub.event_stream_in_value_out(
- name, callback.complete, callback.abort, _TIMEOUT)
+ name, callback.complete, callback.abort,
+ test_constants.SHORT_TIMEOUT)
for request in requests:
request_consumer.consume(request)
call.cancel()
@@ -355,7 +369,7 @@ class EventInvocationSynchronousEventServiceTestCase(
callback = testing_callback.Callback()
call, unused_request_consumer = self.stub.event_stream_in_stream_out(
- name, callback, callback.abort, _TIMEOUT)
+ name, callback, callback.abort, test_constants.SHORT_TIMEOUT)
call.cancel()
callback.block_until_terminated()
diff --git a/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
index 3b42914342..70d86a0422 100644
--- a/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
+++ b/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py
@@ -37,13 +37,13 @@ import unittest
from grpc.framework.face import exceptions
from grpc.framework.foundation import future
from grpc.framework.foundation import logging_pool
+from grpc_test.framework.common import test_constants
from grpc_test.framework.face.testing import control
from grpc_test.framework.face.testing import coverage
from grpc_test.framework.face.testing import digest
from grpc_test.framework.face.testing import stock_service
from grpc_test.framework.face.testing import test_case
-_TIMEOUT = 3
_MAXIMUM_POOL_SIZE = 10
@@ -110,7 +110,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
request = test_messages.request()
response_future = self.stub.future_value_in_value_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
response = response_future.result()
test_messages.verify(request, response, self)
@@ -122,7 +122,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
request = test_messages.request()
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
responses = list(response_iterator)
test_messages.verify(request, responses, self)
@@ -138,7 +138,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
# returned to calling code before the iterator yields any requests.
with request_iterator.pause():
response_future = self.stub.future_stream_in_value_out(
- name, request_iterator, _TIMEOUT)
+ name, request_iterator, test_constants.SHORT_TIMEOUT)
response = response_future.result()
test_messages.verify(requests, response, self)
@@ -154,7 +154,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
# returned to calling code before the iterator yields any requests.
with request_iterator.pause():
response_iterator = self.stub.inline_stream_in_stream_out(
- name, request_iterator, _TIMEOUT)
+ name, request_iterator, test_constants.SHORT_TIMEOUT)
responses = list(response_iterator)
test_messages.verify(requests, responses, self)
@@ -167,13 +167,13 @@ class FutureInvocationAsynchronousEventServiceTestCase(
second_request = test_messages.request()
first_response_future = self.stub.future_value_in_value_out(
- name, first_request, _TIMEOUT)
+ name, first_request, test_constants.SHORT_TIMEOUT)
first_response = first_response_future.result()
test_messages.verify(first_request, first_response, self)
second_response_future = self.stub.future_value_in_value_out(
- name, second_request, _TIMEOUT)
+ name, second_request, test_constants.SHORT_TIMEOUT)
second_response = second_response_future.result()
test_messages.verify(second_request, second_response, self)
@@ -186,7 +186,8 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
multi_callable = self.stub.unary_unary_multi_callable(name)
- response_future = multi_callable.future(request, _TIMEOUT)
+ response_future = multi_callable.future(request,
+ test_constants.SHORT_TIMEOUT)
self.assertIsInstance(
response_future.exception(), exceptions.ExpirationError)
with self.assertRaises(exceptions.ExpirationError):
@@ -200,7 +201,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
with self.assertRaises(exceptions.ExpirationError):
list(response_iterator)
@@ -212,7 +213,8 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
multi_callable = self.stub.stream_unary_multi_callable(name)
- response_future = multi_callable.future(iter(requests), _TIMEOUT)
+ response_future = multi_callable.future(iter(requests),
+ test_constants.SHORT_TIMEOUT)
self.assertIsInstance(
response_future.exception(), exceptions.ExpirationError)
with self.assertRaises(exceptions.ExpirationError):
@@ -226,7 +228,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
with self.assertRaises(exceptions.ExpirationError):
list(response_iterator)
@@ -238,7 +240,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.fail():
response_future = self.stub.future_value_in_value_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
# Because the servicer fails outside of the thread from which the
# servicer-side runtime called into it its failure is
@@ -261,7 +263,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
# expiration of the RPC.
with self.control.fail(), self.assertRaises(exceptions.ExpirationError):
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
list(response_iterator)
def testFailedStreamRequestUnaryResponse(self):
@@ -272,7 +274,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.fail():
response_future = self.stub.future_stream_in_value_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
# Because the servicer fails outside of the thread from which the
# servicer-side runtime called into it its failure is
@@ -295,7 +297,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
# expiration of the RPC.
with self.control.fail(), self.assertRaises(exceptions.ExpirationError):
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
list(response_iterator)
def testParallelInvocations(self):
@@ -305,10 +307,11 @@ class FutureInvocationAsynchronousEventServiceTestCase(
first_request = test_messages.request()
second_request = test_messages.request()
+ # TODO(bug 2039): use LONG_TIMEOUT instead
first_response_future = self.stub.future_value_in_value_out(
- name, first_request, _TIMEOUT)
+ name, first_request, test_constants.SHORT_TIMEOUT)
second_response_future = self.stub.future_value_in_value_out(
- name, second_request, _TIMEOUT)
+ name, second_request, test_constants.SHORT_TIMEOUT)
first_response = first_response_future.result()
second_response = second_response_future.result()
@@ -327,7 +330,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_future = self.stub.future_value_in_value_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
cancel_method_return_value = response_future.cancel()
self.assertFalse(cancel_method_return_value)
@@ -341,7 +344,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_iterator = self.stub.inline_value_in_stream_out(
- name, request, _TIMEOUT)
+ name, request, test_constants.SHORT_TIMEOUT)
response_iterator.cancel()
with self.assertRaises(future.CancelledError):
@@ -355,7 +358,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_future = self.stub.future_stream_in_value_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
cancel_method_return_value = response_future.cancel()
self.assertFalse(cancel_method_return_value)
@@ -369,7 +372,7 @@ class FutureInvocationAsynchronousEventServiceTestCase(
with self.control.pause():
response_iterator = self.stub.inline_stream_in_stream_out(
- name, iter(requests), _TIMEOUT)
+ name, iter(requests), test_constants.SHORT_TIMEOUT)
response_iterator.cancel()
with self.assertRaises(future.CancelledError):
diff --git a/src/python/grpcio_test/setup.py b/src/python/grpcio_test/setup.py
index 925c32720f..a6203cae2d 100644
--- a/src/python/grpcio_test/setup.py
+++ b/src/python/grpcio_test/setup.py
@@ -48,8 +48,13 @@ _PACKAGE_DIRECTORIES = {
_PACKAGE_DATA = {
'grpc_interop': [
- 'credentials/ca.pem', 'credentials/server1.key',
- 'credentials/server1.pem',]
+ 'credentials/ca.pem',
+ 'credentials/server1.key',
+ 'credentials/server1.pem',
+ ],
+ 'grpc_protoc_plugin': [
+ 'test.proto',
+ ],
}
_SETUP_REQUIRES = (
@@ -75,5 +80,5 @@ setuptools.setup(
package_data=_PACKAGE_DATA,
install_requires=_INSTALL_REQUIRES + _SETUP_REQUIRES,
setup_requires=_SETUP_REQUIRES,
- cmdclass=_COMMAND_CLASS
+ cmdclass=_COMMAND_CLASS,
)
diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c
index a7607a83a3..88659da535 100644
--- a/src/ruby/ext/grpc/rb_call.c
+++ b/src/ruby/ext/grpc/rb_call.c
@@ -179,6 +179,19 @@ static VALUE grpc_rb_call_cancel(VALUE self) {
return Qnil;
}
+/* Called to obtain the peer that this call is connected to. */
+static VALUE grpc_rb_call_get_peer(VALUE self) {
+ VALUE res = Qnil;
+ grpc_call *call = NULL;
+ char *peer = NULL;
+ TypedData_Get_Struct(self, grpc_call, &grpc_call_data_type, call);
+ peer = grpc_call_get_peer(call);
+ res = rb_str_new2(peer);
+ gpr_free(peer);
+
+ return res;
+}
+
/*
call-seq:
status = call.status
@@ -720,6 +733,7 @@ void Init_grpc_call() {
/* Add ruby analogues of the Call methods. */
rb_define_method(grpc_rb_cCall, "run_batch", grpc_rb_call_run_batch, 4);
rb_define_method(grpc_rb_cCall, "cancel", grpc_rb_call_cancel, 0);
+ rb_define_method(grpc_rb_cCall, "peer", grpc_rb_call_get_peer, 0);
rb_define_method(grpc_rb_cCall, "status", grpc_rb_call_get_status, 0);
rb_define_method(grpc_rb_cCall, "status=", grpc_rb_call_set_status, 1);
rb_define_method(grpc_rb_cCall, "metadata", grpc_rb_call_get_metadata, 0);
diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c
index a0663607c2..2129ba3485 100644
--- a/src/ruby/ext/grpc/rb_channel.c
+++ b/src/ruby/ext/grpc/rb_channel.c
@@ -37,6 +37,7 @@
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
+#include <grpc/support/alloc.h>
#include "rb_grpc.h"
#include "rb_call.h"
#include "rb_channel_args.h"
@@ -164,6 +165,65 @@ static VALUE grpc_rb_channel_init(int argc, VALUE *argv, VALUE self) {
return self;
}
+/*
+ call-seq:
+ insecure_channel = Channel:new("myhost:8080", {'arg1': 'value1'})
+ creds = ...
+ secure_channel = Channel:new("myhost:443", {'arg1': 'value1'}, creds)
+
+ Creates channel instances. */
+static VALUE grpc_rb_channel_get_connectivity_state(int argc, VALUE *argv,
+ VALUE self) {
+ VALUE try_to_connect = Qfalse;
+ grpc_rb_channel *wrapper = NULL;
+ grpc_channel *ch = NULL;
+
+ /* "01" == 0 mandatory args, 1 (try_to_connect) is optional */
+ rb_scan_args(argc, argv, "01", try_to_connect);
+
+ TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
+ ch = wrapper->wrapped;
+ if (ch == NULL) {
+ rb_raise(rb_eRuntimeError, "closed!");
+ return Qnil;
+ }
+ return NUM2LONG(
+ grpc_channel_check_connectivity_state(ch, (int)try_to_connect));
+}
+
+/* Watch for a change in connectivity state.
+
+ Once the channel connectivity state is different from the last observed
+ state, tag will be enqueued on cq with success=1
+
+ If deadline expires BEFORE the state is changed, tag will be enqueued on
+ the completion queue with success=0 */
+static VALUE grpc_rb_channel_watch_connectivity_state(VALUE self,
+ VALUE last_state,
+ VALUE cqueue,
+ VALUE deadline,
+ VALUE tag) {
+ grpc_rb_channel *wrapper = NULL;
+ grpc_channel *ch = NULL;
+ grpc_completion_queue *cq = NULL;
+
+ cq = grpc_rb_get_wrapped_completion_queue(cqueue);
+ TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
+ ch = wrapper->wrapped;
+ if (ch == NULL) {
+ rb_raise(rb_eRuntimeError, "closed!");
+ return Qnil;
+ }
+ grpc_channel_watch_connectivity_state(
+ ch,
+ NUM2LONG(last_state),
+ grpc_rb_time_timeval(deadline, /* absolute time */ 0),
+ cq,
+ ROBJECT(tag));
+
+ return Qnil;
+}
+
/* Clones Channel instances.
Gives Channel a consistent implementation of Ruby's object copy/dup
@@ -194,15 +254,28 @@ static VALUE grpc_rb_channel_init_copy(VALUE copy, VALUE orig) {
/* Create a call given a grpc_channel, in order to call method. The request
is not sent until grpc_call_invoke is called. */
-static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method,
- VALUE host, VALUE deadline) {
+static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue,
+ VALUE parent, VALUE mask,
+ VALUE method, VALUE host,
+ VALUE deadline) {
VALUE res = Qnil;
grpc_rb_channel *wrapper = NULL;
grpc_call *call = NULL;
+ grpc_call *parent_call = NULL;
grpc_channel *ch = NULL;
grpc_completion_queue *cq = NULL;
+ int flags = GRPC_PROPAGATE_DEFAULTS;
char *method_chars = StringValueCStr(method);
- char *host_chars = StringValueCStr(host);
+ char *host_chars = NULL;
+ if (host != Qnil) {
+ host_chars = StringValueCStr(host);
+ }
+ if (mask != Qnil) {
+ flags = NUM2UINT(mask);
+ }
+ if (parent != Qnil) {
+ parent_call = grpc_rb_get_wrapped_call(parent);
+ }
cq = grpc_rb_get_wrapped_completion_queue(cqueue);
TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
@@ -212,10 +285,10 @@ static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method,
return Qnil;
}
- call = grpc_channel_create_call(ch, NULL, GRPC_PROPAGATE_DEFAULTS, cq,
- method_chars, host_chars,
- grpc_rb_time_timeval(deadline,
- /* absolute time */ 0));
+ call = grpc_channel_create_call(ch, parent_call, flags, cq, method_chars,
+ host_chars, grpc_rb_time_timeval(
+ deadline,
+ /* absolute time */ 0));
if (call == NULL) {
rb_raise(rb_eRuntimeError, "cannot create call with method %s",
method_chars);
@@ -233,6 +306,7 @@ static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method,
return res;
}
+
/* Closes the channel, calling it's destroy method */
static VALUE grpc_rb_channel_destroy(VALUE self) {
grpc_rb_channel *wrapper = NULL;
@@ -249,6 +323,53 @@ static VALUE grpc_rb_channel_destroy(VALUE self) {
return Qnil;
}
+
+/* Called to obtain the target that this channel accesses. */
+static VALUE grpc_rb_channel_get_target(VALUE self) {
+ grpc_rb_channel *wrapper = NULL;
+ VALUE res = Qnil;
+ char* target = NULL;
+
+ TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper);
+ target = grpc_channel_get_target(wrapper->wrapped);
+ res = rb_str_new2(target);
+ gpr_free(target);
+
+ return res;
+}
+
+static void Init_grpc_propagate_masks() {
+ /* Constants representing call propagation masks in grpc.h */
+ VALUE grpc_rb_mPropagateMasks = rb_define_module_under(
+ grpc_rb_mGrpcCore, "PropagateMasks");
+ rb_define_const(grpc_rb_mPropagateMasks, "DEADLINE",
+ UINT2NUM(GRPC_PROPAGATE_DEADLINE));
+ rb_define_const(grpc_rb_mPropagateMasks, "CENSUS_STATS_CONTEXT",
+ UINT2NUM(GRPC_PROPAGATE_CENSUS_STATS_CONTEXT));
+ rb_define_const(grpc_rb_mPropagateMasks, "CENSUS_TRACING_CONTEXT",
+ UINT2NUM(GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT));
+ rb_define_const(grpc_rb_mPropagateMasks, "CANCELLATION",
+ UINT2NUM(GRPC_PROPAGATE_CANCELLATION));
+ rb_define_const(grpc_rb_mPropagateMasks, "DEFAULTS",
+ UINT2NUM(GRPC_PROPAGATE_DEFAULTS));
+}
+
+static void Init_grpc_connectivity_states() {
+ /* Constants representing call propagation masks in grpc.h */
+ VALUE grpc_rb_mConnectivityStates = rb_define_module_under(
+ grpc_rb_mGrpcCore, "ConnectivityStates");
+ rb_define_const(grpc_rb_mConnectivityStates, "IDLE",
+ LONG2NUM(GRPC_CHANNEL_IDLE));
+ rb_define_const(grpc_rb_mConnectivityStates, "CONNECTING",
+ LONG2NUM(GRPC_CHANNEL_CONNECTING));
+ rb_define_const(grpc_rb_mConnectivityStates, "READY",
+ LONG2NUM(GRPC_CHANNEL_READY));
+ rb_define_const(grpc_rb_mConnectivityStates, "TRANSIENT_FAILURE",
+ LONG2NUM(GRPC_CHANNEL_TRANSIENT_FAILURE));
+ rb_define_const(grpc_rb_mConnectivityStates, "FATAL_FAILURE",
+ LONG2NUM(GRPC_CHANNEL_FATAL_FAILURE));
+}
+
void Init_grpc_channel() {
grpc_rb_cChannelArgs = rb_define_class("TmpChannelArgs", rb_cObject);
grpc_rb_cChannel =
@@ -263,8 +384,14 @@ void Init_grpc_channel() {
grpc_rb_channel_init_copy, 1);
/* Add ruby analogues of the Channel methods. */
+ rb_define_method(grpc_rb_cChannel, "connectivity_state",
+ grpc_rb_channel_get_connectivity_state,
+ -1);
+ rb_define_method(grpc_rb_cChannel, "watch_connectivity_state",
+ grpc_rb_channel_watch_connectivity_state, 4);
rb_define_method(grpc_rb_cChannel, "create_call",
- grpc_rb_channel_create_call, 4);
+ grpc_rb_channel_create_call, 6);
+ rb_define_method(grpc_rb_cChannel, "target", grpc_rb_channel_get_target, 0);
rb_define_method(grpc_rb_cChannel, "destroy", grpc_rb_channel_destroy, 0);
rb_define_alias(grpc_rb_cChannel, "close", "destroy");
@@ -279,6 +406,8 @@ void Init_grpc_channel() {
ID2SYM(rb_intern(GRPC_ARG_MAX_CONCURRENT_STREAMS)));
rb_define_const(grpc_rb_cChannel, "MAX_MESSAGE_LENGTH",
ID2SYM(rb_intern(GRPC_ARG_MAX_MESSAGE_LENGTH)));
+ Init_grpc_propagate_masks();
+ Init_grpc_connectivity_states();
}
/* Gets the wrapped channel from the ruby wrapper */
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
index dd4e27df51..eb748458b9 100755
--- a/src/ruby/grpc.gemspec
+++ b/src/ruby/grpc.gemspec
@@ -16,12 +16,15 @@ Gem::Specification.new do |s|
s.required_ruby_version = '>= 2.0.0'
s.requirements << 'libgrpc ~> 0.10.0 needs to be installed'
- s.files = `git ls-files`.split("\n")
- s.test_files = `git ls-files -- spec/*`.split("\n")
- s.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f|
- File.basename(f)
+ s.files = %w( Rakefile )
+ s.files += Dir.glob('lib/**/*')
+ s.files += Dir.glob('ext/**/*')
+ s.files += Dir.glob('bin/**/*')
+ s.test_files = Dir.glob('spec/**/*')
+ %w(math noproto).each do |b|
+ s.executables += ["#{b}_client.rb", "#{b}_server.rb"]
end
- s.require_paths = ['lib']
+ s.require_paths = %w( bin lib )
s.platform = Gem::Platform::RUBY
s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
index 7b2c04aa22..cce718537c 100644
--- a/src/ruby/lib/grpc/generic/client_stub.rb
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -28,16 +28,19 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc/generic/active_call'
+require 'grpc/version'
# GRPC contains the General RPC module.
module GRPC
+ # rubocop:disable Metrics/ParameterLists
+
# ClientStub represents an endpoint used to send requests to GRPC servers.
class ClientStub
include Core::StatusCodes
include Core::TimeConsts
- # Default timeout is 5 seconds.
- DEFAULT_TIMEOUT = 5
+ # Default timeout is infinity.
+ DEFAULT_TIMEOUT = INFINITE_FUTURE
# setup_channel is used by #initialize to constuct a channel from its
# arguments.
@@ -46,6 +49,7 @@ module GRPC
fail(TypeError, '!Channel') unless alt_chan.is_a?(Core::Channel)
return alt_chan
end
+ kw['grpc.primary_user_agent'] = "grpc-ruby/#{VERSION}"
return Core::Channel.new(host, kw) if creds.nil?
fail(TypeError, '!Credentials') unless creds.is_a?(Core::Credentials)
Core::Channel.new(host, kw, creds)
@@ -66,6 +70,12 @@ module GRPC
update_metadata
end
+ # Allows users of the stub to modify the propagate mask.
+ #
+ # This is an advanced feature for use when making calls to another gRPC
+ # server whilst running in the handler of an existing one.
+ attr_writer :propagate_mask
+
# Creates a new ClientStub.
#
# Minimally, a stub is created with the just the host of the gRPC service
@@ -89,8 +99,8 @@ module GRPC
#
# - :update_metadata
# when present, this a func that takes a hash and returns a hash
- # it can be used to update metadata, i.e, remove, change or update
- # amend metadata values.
+ # it can be used to update metadata, i.e, remove, or amend
+ # metadata values.
#
# @param host [String] the host the stub connects to
# @param q [Core::CompletionQueue] used to wait for events
@@ -103,6 +113,7 @@ module GRPC
channel_override: nil,
timeout: nil,
creds: nil,
+ propagate_mask: nil,
update_metadata: nil,
**kw)
fail(TypeError, '!CompletionQueue') unless q.is_a?(Core::CompletionQueue)
@@ -111,6 +122,7 @@ module GRPC
@update_metadata = ClientStub.check_update_metadata(update_metadata)
alt_host = kw[Core::Channel::SSL_TARGET]
@host = alt_host.nil? ? host : alt_host
+ @propagate_mask = propagate_mask
@timeout = timeout.nil? ? DEFAULT_TIMEOUT : timeout
end
@@ -149,11 +161,15 @@ module GRPC
# @param marshal [Function] f(obj)->string that marshals requests
# @param unmarshal [Function] f(string)->obj that unmarshals responses
# @param timeout [Numeric] (optional) the max completion time in seconds
+ # @param parent [Core::Call] a prior call whose reserved metadata
+ # will be propagated by this one.
# @param return_op [true|false] return an Operation if true
# @return [Object] the response received from the server
def request_response(method, req, marshal, unmarshal, timeout = nil,
- return_op: false, **kw)
- c = new_active_call(method, marshal, unmarshal, timeout)
+ return_op: false,
+ parent: parent,
+ **kw)
+ c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
return c.request_response(req, **md) unless return_op
@@ -208,10 +224,14 @@ module GRPC
# @param unmarshal [Function] f(string)->obj that unmarshals responses
# @param timeout [Numeric] the max completion time in seconds
# @param return_op [true|false] return an Operation if true
+ # @param parent [Core::Call] a prior call whose reserved metadata
+ # will be propagated by this one.
# @return [Object|Operation] the response received from the server
def client_streamer(method, requests, marshal, unmarshal, timeout = nil,
- return_op: false, **kw)
- c = new_active_call(method, marshal, unmarshal, timeout)
+ return_op: false,
+ parent: nil,
+ **kw)
+ c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
return c.client_streamer(requests, **md) unless return_op
@@ -274,11 +294,16 @@ module GRPC
# @param unmarshal [Function] f(string)->obj that unmarshals responses
# @param timeout [Numeric] the max completion time in seconds
# @param return_op [true|false]return an Operation if true
+ # @param parent [Core::Call] a prior call whose reserved metadata
+ # will be propagated by this one.
# @param blk [Block] when provided, is executed for each response
# @return [Enumerator|Operation|nil] as discussed above
def server_streamer(method, req, marshal, unmarshal, timeout = nil,
- return_op: false, **kw, &blk)
- c = new_active_call(method, marshal, unmarshal, timeout)
+ return_op: false,
+ parent: nil,
+ **kw,
+ &blk)
+ c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
return c.server_streamer(req, **md, &blk) unless return_op
@@ -379,12 +404,17 @@ module GRPC
# @param marshal [Function] f(obj)->string that marshals requests
# @param unmarshal [Function] f(string)->obj that unmarshals responses
# @param timeout [Numeric] (optional) the max completion time in seconds
- # @param blk [Block] when provided, is executed for each response
+ # @param parent [Core::Call] a prior call whose reserved metadata
+ # will be propagated by this one.
# @param return_op [true|false] return an Operation if true
+ # @param blk [Block] when provided, is executed for each response
# @return [Enumerator|nil|Operation] as discussed above
def bidi_streamer(method, requests, marshal, unmarshal, timeout = nil,
- return_op: false, **kw, &blk)
- c = new_active_call(method, marshal, unmarshal, timeout)
+ return_op: false,
+ parent: nil,
+ **kw,
+ &blk)
+ c = new_active_call(method, marshal, unmarshal, timeout, parent: parent)
kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
return c.bidi_streamer(requests, **md, &blk) unless return_op
@@ -405,10 +435,17 @@ module GRPC
# @param method [string] the method being called.
# @param marshal [Function] f(obj)->string that marshals requests
# @param unmarshal [Function] f(string)->obj that unmarshals responses
+ # @param parent [Grpc::Call] a parent call, available when calls are
+ # made from server
# @param timeout [TimeConst]
- def new_active_call(method, marshal, unmarshal, timeout = nil)
+ def new_active_call(method, marshal, unmarshal, timeout = nil, parent: nil)
deadline = from_relative_time(timeout.nil? ? @timeout : timeout)
- call = @ch.create_call(@queue, method, @host, deadline)
+ call = @ch.create_call(@queue,
+ parent, # parent call
+ @propagate_mask, # propagation options
+ method,
+ nil, # host use nil,
+ deadline)
ActiveCall.new(call, @queue, marshal, unmarshal, deadline, started: false)
end
end
diff --git a/src/ruby/spec/call_spec.rb b/src/ruby/spec/call_spec.rb
index 4977c10a7e..3c5d33ffcd 100644
--- a/src/ruby/spec/call_spec.rb
+++ b/src/ruby/spec/call_spec.rb
@@ -137,7 +137,7 @@ describe GRPC::Core::Call do
end
def make_test_call
- @ch.create_call(client_queue, 'dummy_method', 'dummy_host', deadline)
+ @ch.create_call(client_queue, nil, nil, 'dummy_method', nil, deadline)
end
def deadline
diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb
index d471ff5db6..25cefcdfb7 100644
--- a/src/ruby/spec/channel_spec.rb
+++ b/src/ruby/spec/channel_spec.rb
@@ -117,7 +117,7 @@ describe GRPC::Core::Channel do
deadline = Time.now + 5
blk = proc do
- ch.create_call(cq, 'dummy_method', 'dummy_host', deadline)
+ ch.create_call(cq, nil, nil, 'dummy_method', nil, deadline)
end
expect(&blk).to_not raise_error
end
@@ -128,7 +128,7 @@ describe GRPC::Core::Channel do
deadline = Time.now + 5
blk = proc do
- ch.create_call(cq, 'dummy_method', 'dummy_host', deadline)
+ ch.create_call(cq, nil, nil, 'dummy_method', nil, deadline)
end
expect(&blk).to raise_error(RuntimeError)
end
diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb
index 0e85441209..2e673ff413 100644
--- a/src/ruby/spec/client_server_spec.rb
+++ b/src/ruby/spec/client_server_spec.rb
@@ -61,7 +61,7 @@ shared_context 'setup: tags' do
end
def new_client_call
- @ch.create_call(@client_queue, '/method', 'foo.test.google.fr', deadline)
+ @ch.create_call(@client_queue, nil, nil, '/method', nil, deadline)
end
end
@@ -69,6 +69,23 @@ shared_examples 'basic GRPC message delivery is OK' do
include GRPC::Core
include_context 'setup: tags'
+ context 'the test channel' do
+ it 'should have a target' do
+ expect(@ch.target).to be_a(String)
+ end
+ end
+
+ context 'a client call' do
+ it 'should have a peer' do
+ expect(new_client_call.peer).to be_a(String)
+ end
+ end
+
+ it 'calls have peer info' do
+ call = new_client_call
+ expect(call.peer).to be_a(String)
+ end
+
it 'servers receive requests from clients and can respond' do
call = new_client_call
server_call = nil
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
index bc3bee3d44..0bf65ba2e9 100644
--- a/src/ruby/spec/generic/active_call_spec.rb
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -338,7 +338,7 @@ describe GRPC::ActiveCall do
end
def make_test_call
- @ch.create_call(@client_queue, '/method', 'a.dummy.host', deadline)
+ @ch.create_call(@client_queue, nil, nil, '/method', nil, deadline)
end
def deadline
diff --git a/test/core/end2end/tests/bad_hostname.c b/test/core/end2end/tests/bad_hostname.c
index 198ba46cd2..501db89b7b 100644
--- a/test/core/end2end/tests/bad_hostname.c
+++ b/test/core/end2end/tests/bad_hostname.c
@@ -146,7 +146,7 @@ static void simple_request_body(grpc_end2end_test_fixture f) {
cq_expect_completion(cqv, tag(1), 1);
cq_verify(cqv);
- GPR_ASSERT(status == GRPC_STATUS_UNAUTHENTICATED);
+ GPR_ASSERT(status == GRPC_STATUS_INVALID_ARGUMENT);
gpr_free(details);
grpc_metadata_array_destroy(&initial_metadata_recv);
diff --git a/test/core/security/verify_jwt.c b/test/core/security/verify_jwt.c
index 9b334b3c3e..69bbc3cc0c 100644
--- a/test/core/security/verify_jwt.c
+++ b/test/core/security/verify_jwt.c
@@ -112,7 +112,7 @@ int main(int argc, char **argv) {
while (!sync.is_done) {
grpc_pollset_worker worker;
grpc_pollset_work(&sync.pollset, &worker,
- gpr_inf_future(GPR_CLOCK_REALTIME));
+ gpr_inf_future(GPR_CLOCK_MONOTONIC));
}
gpr_mu_unlock(GRPC_POLLSET_MU(&sync.pollset));
diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc
index 5f0749daa5..37669815c6 100644
--- a/test/cpp/end2end/end2end_test.cc
+++ b/test/cpp/end2end/end2end_test.cc
@@ -117,7 +117,7 @@ class Proxy : public ::grpc::cpp::test::util::TestService::Service {
}
private:
- std::unique_ptr<::grpc::cpp::test::util::TestService::Stub> stub_;
+ std::unique_ptr< ::grpc::cpp::test::util::TestService::Stub> stub_;
};
class TestServiceImpl : public ::grpc::cpp::test::util::TestService::Service {
diff --git a/test/cpp/qps/client.h b/test/cpp/qps/client.h
index 28cd32a197..1c4f46328f 100644
--- a/test/cpp/qps/client.h
+++ b/test/cpp/qps/client.h
@@ -41,6 +41,7 @@
#include <condition_variable>
#include <mutex>
+#include <grpc++/config.h>
namespace grpc {
@@ -67,10 +68,12 @@ typedef std::chrono::time_point<grpc_time_source> grpc_time;
class Client {
public:
explicit Client(const ClientConfig& config)
- : timer_(new Timer), interarrival_timer_() {
+ : channels_(config.client_channels()),
+ timer_(new Timer),
+ interarrival_timer_() {
for (int i = 0; i < config.client_channels(); i++) {
- channels_.push_back(ClientChannelInfo(
- config.server_targets(i % config.server_targets_size()), config));
+ channels_[i].init(config.server_targets(i % config.server_targets_size()),
+ config);
}
request_.set_response_type(grpc::testing::PayloadType::COMPRESSABLE);
request_.set_response_size(config.payload_size());
@@ -79,7 +82,8 @@ class Client {
ClientStats Mark() {
Histogram latencies;
- std::vector<Histogram> to_merge(threads_.size());
+ // avoid std::vector for old compilers that expect a copy constructor
+ Histogram* to_merge = new Histogram[threads_.size()];
for (size_t i = 0; i < threads_.size(); i++) {
threads_[i]->BeginSwap(&to_merge[i]);
}
@@ -89,6 +93,7 @@ class Client {
threads_[i]->EndSwap();
latencies.Merge(&to_merge[i]);
}
+ delete[] to_merge;
auto timer_result = timer->Mark();
@@ -106,9 +111,20 @@ class Client {
class ClientChannelInfo {
public:
- ClientChannelInfo(const grpc::string& target, const ClientConfig& config)
- : channel_(CreateTestChannel(target, config.enable_ssl())),
- stub_(TestService::NewStub(channel_)) {}
+ ClientChannelInfo() {}
+ ClientChannelInfo(const ClientChannelInfo& i) {
+ // The copy constructor is to satisfy old compilers
+ // that need it for using std::vector . It is only ever
+ // used for empty entries
+ GPR_ASSERT(!i.channel_ && !i.stub_);
+ }
+ void init(const grpc::string& target, const ClientConfig& config) {
+ // We have to use a 2-phase init like this with a default
+ // constructor followed by an initializer function to make
+ // old compilers happy with using this in std::vector
+ channel_ = CreateTestChannel(target, config.enable_ssl());
+ stub_ = TestService::NewStub(channel_);
+ }
ChannelInterface* get_channel() { return channel_.get(); }
TestService::Stub* get_stub() { return stub_.get(); }
@@ -189,27 +205,9 @@ class Client {
Thread(Client* client, size_t idx)
: done_(false),
new_(nullptr),
- impl_([this, idx, client]() {
- for (;;) {
- // run the loop body
- bool thread_still_ok = client->ThreadFunc(&histogram_, idx);
- // lock, see if we're done
- std::lock_guard<std::mutex> g(mu_);
- if (!thread_still_ok) {
- gpr_log(GPR_ERROR, "Finishing client thread due to RPC error");
- done_ = true;
- }
- if (done_) {
- return;
- }
- // check if we're marking, swap out the histogram if so
- if (new_) {
- new_->Swap(&histogram_);
- new_ = nullptr;
- cv_.notify_one();
- }
- }
- }) {}
+ client_(client),
+ idx_(idx),
+ impl_(&Thread::ThreadFunc, this) {}
~Thread() {
{
@@ -226,13 +224,37 @@ class Client {
void EndSwap() {
std::unique_lock<std::mutex> g(mu_);
- cv_.wait(g, [this]() { return new_ == nullptr; });
+ while (new_ != nullptr) {
+ cv_.wait(g);
+ };
}
private:
Thread(const Thread&);
Thread& operator=(const Thread&);
+ void ThreadFunc() {
+ for (;;) {
+ // run the loop body
+ const bool thread_still_ok = client_->ThreadFunc(&histogram_, idx_);
+ // lock, see if we're done
+ std::lock_guard<std::mutex> g(mu_);
+ if (!thread_still_ok) {
+ gpr_log(GPR_ERROR, "Finishing client thread due to RPC error");
+ done_ = true;
+ }
+ if (done_) {
+ return;
+ }
+ // check if we're marking, swap out the histogram if so
+ if (new_) {
+ new_->Swap(&histogram_);
+ new_ = nullptr;
+ cv_.notify_one();
+ }
+ }
+ }
+
TestService::Stub* stub_;
ClientConfig config_;
std::mutex mu_;
@@ -240,6 +262,8 @@ class Client {
bool done_;
Histogram* new_;
Histogram histogram_;
+ Client* client_;
+ size_t idx_;
std::thread impl_;
};
diff --git a/test/cpp/qps/client_async.cc b/test/cpp/qps/client_async.cc
index e1e44f9ac0..a337610cbf 100644
--- a/test/cpp/qps/client_async.cc
+++ b/test/cpp/qps/client_async.cc
@@ -156,7 +156,7 @@ class AsyncClient : public Client {
std::function<ClientRpcContext*(int, TestService::Stub*,
const SimpleRequest&)> setup_ctx)
: Client(config),
- channel_lock_(config.client_channels()),
+ channel_lock_(new std::mutex[config.client_channels()]),
contexts_(config.client_channels()),
max_outstanding_per_channel_(config.outstanding_rpcs_per_channel()),
channel_count_(config.client_channels()),
@@ -208,6 +208,7 @@ class AsyncClient : public Client {
delete ctx;
}
}
+ delete[] channel_lock_;
}
bool ThreadFunc(Histogram* histogram,
@@ -316,23 +317,28 @@ class AsyncClient : public Client {
}
private:
- class boolean { // exists only to avoid data-race on vector<bool>
+ class boolean { // exists only to avoid data-race on vector<bool>
public:
- boolean(): val_(false) {}
- boolean(bool b): val_(b) {}
- operator bool() const {return val_;}
- boolean& operator=(bool b) {val_=b; return *this;}
+ boolean() : val_(false) {}
+ boolean(bool b) : val_(b) {}
+ operator bool() const { return val_; }
+ boolean& operator=(bool b) {
+ val_ = b;
+ return *this;
+ }
+
private:
bool val_;
};
std::vector<std::unique_ptr<CompletionQueue>> cli_cqs_;
std::vector<deadline_list> rpc_deadlines_; // per thread deadlines
- std::vector<int> next_channel_; // per thread round-robin channel ctr
- std::vector<boolean> issue_allowed_; // may this thread attempt to issue
- std::vector<grpc_time> next_issue_; // when should it issue?
+ std::vector<int> next_channel_; // per thread round-robin channel ctr
+ std::vector<boolean> issue_allowed_; // may this thread attempt to issue
+ std::vector<grpc_time> next_issue_; // when should it issue?
- std::vector<std::mutex> channel_lock_;
+ std::mutex*
+ channel_lock_; // a vector, but avoid std::vector for old compilers
std::vector<context_list> contexts_; // per-channel list of idle contexts
int max_outstanding_per_channel_;
int channel_count_;
@@ -348,15 +354,17 @@ class AsyncUnaryClient GRPC_FINAL : public AsyncClient {
~AsyncUnaryClient() GRPC_OVERRIDE { EndThreads(); }
private:
+ static void CheckDone(grpc::Status s, SimpleResponse* response) {}
+ static std::unique_ptr<grpc::ClientAsyncResponseReader<SimpleResponse>>
+ StartReq(TestService::Stub* stub, grpc::ClientContext* ctx,
+ const SimpleRequest& request, CompletionQueue* cq) {
+ return stub->AsyncUnaryCall(ctx, request, cq);
+ };
static ClientRpcContext* SetupCtx(int channel_id, TestService::Stub* stub,
const SimpleRequest& req) {
- auto check_done = [](grpc::Status s, SimpleResponse* response) {};
- auto start_req = [](TestService::Stub* stub, grpc::ClientContext* ctx,
- const SimpleRequest& request, CompletionQueue* cq) {
- return stub->AsyncUnaryCall(ctx, request, cq);
- };
return new ClientRpcContextUnaryImpl<SimpleRequest, SimpleResponse>(
- channel_id, stub, req, start_req, check_done);
+ channel_id, stub, req, AsyncUnaryClient::StartReq,
+ AsyncUnaryClient::CheckDone);
}
};
@@ -442,16 +450,19 @@ class AsyncStreamingClient GRPC_FINAL : public AsyncClient {
~AsyncStreamingClient() GRPC_OVERRIDE { EndThreads(); }
private:
+ static void CheckDone(grpc::Status s, SimpleResponse* response) {}
+ static std::unique_ptr<
+ grpc::ClientAsyncReaderWriter<SimpleRequest, SimpleResponse>>
+ StartReq(TestService::Stub* stub, grpc::ClientContext* ctx,
+ CompletionQueue* cq, void* tag) {
+ auto stream = stub->AsyncStreamingCall(ctx, cq, tag);
+ return stream;
+ };
static ClientRpcContext* SetupCtx(int channel_id, TestService::Stub* stub,
const SimpleRequest& req) {
- auto check_done = [](grpc::Status s, SimpleResponse* response) {};
- auto start_req = [](TestService::Stub* stub, grpc::ClientContext* ctx,
- CompletionQueue* cq, void* tag) {
- auto stream = stub->AsyncStreamingCall(ctx, cq, tag);
- return stream;
- };
return new ClientRpcContextStreamingImpl<SimpleRequest, SimpleResponse>(
- channel_id, stub, req, start_req, check_done);
+ channel_id, stub, req, AsyncStreamingClient::StartReq,
+ AsyncStreamingClient::CheckDone);
}
};
diff --git a/test/cpp/qps/client_sync.cc b/test/cpp/qps/client_sync.cc
index 718698bfe1..db5416a707 100644
--- a/test/cpp/qps/client_sync.cc
+++ b/test/cpp/qps/client_sync.cc
@@ -45,8 +45,9 @@
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
#include <grpc/support/histogram.h>
-#include <grpc/support/log.h>
#include <grpc/support/host_port.h>
+#include <grpc/support/log.h>
+#include <grpc/support/time.h>
#include <gflags/gflags.h>
#include <grpc++/client_context.h>
#include <grpc++/server.h>
@@ -79,7 +80,9 @@ class SynchronousClient : public Client {
void WaitToIssue(int thread_idx) {
grpc_time next_time;
if (NextIssueTime(thread_idx, &next_time)) {
- std::this_thread::sleep_until(next_time);
+ gpr_timespec next_timespec;
+ TimepointHR2Timespec(next_time, &next_timespec);
+ gpr_sleep_until(next_timespec);
}
}
@@ -110,9 +113,10 @@ class SynchronousUnaryClient GRPC_FINAL : public SynchronousClient {
class SynchronousStreamingClient GRPC_FINAL : public SynchronousClient {
public:
SynchronousStreamingClient(const ClientConfig& config)
- : SynchronousClient(config),
- context_(num_threads_),
- stream_(num_threads_) {
+ : SynchronousClient(config) {
+ context_ = new grpc::ClientContext[num_threads_];
+ stream_ = new std::unique_ptr<
+ grpc::ClientReaderWriter<SimpleRequest, SimpleResponse>>[num_threads_];
for (size_t thread_idx = 0; thread_idx < num_threads_; thread_idx++) {
auto* stub = channels_[thread_idx % channels_.size()].get_stub();
stream_[thread_idx] = stub->StreamingCall(&context_[thread_idx]);
@@ -121,12 +125,15 @@ class SynchronousStreamingClient GRPC_FINAL : public SynchronousClient {
}
~SynchronousStreamingClient() {
EndThreads();
- for (auto stream = stream_.begin(); stream != stream_.end(); stream++) {
+ for (auto stream = &stream_[0]; stream != &stream_[num_threads_];
+ stream++) {
if (*stream) {
(*stream)->WritesDone();
EXPECT_TRUE((*stream)->Finish().ok());
}
}
+ delete[] stream_;
+ delete[] context_;
}
bool ThreadFunc(Histogram* histogram, size_t thread_idx) GRPC_OVERRIDE {
@@ -141,9 +148,11 @@ class SynchronousStreamingClient GRPC_FINAL : public SynchronousClient {
}
private:
- std::vector<grpc::ClientContext> context_;
- std::vector<std::unique_ptr<
- grpc::ClientReaderWriter<SimpleRequest, SimpleResponse>>> stream_;
+ // These are both conceptually std::vector but cannot be for old compilers
+ // that expect contained classes to support copy constructors
+ grpc::ClientContext* context_;
+ std::unique_ptr<grpc::ClientReaderWriter<SimpleRequest, SimpleResponse>>*
+ stream_;
};
std::unique_ptr<Client> CreateSynchronousUnaryClient(
diff --git a/test/cpp/qps/driver.cc b/test/cpp/qps/driver.cc
index a0360295e0..78e3720938 100644
--- a/test/cpp/qps/driver.cc
+++ b/test/cpp/qps/driver.cc
@@ -77,16 +77,34 @@ static deque<string> get_hosts(const string& name) {
}
}
+// Namespace for classes and functions used only in RunScenario
+// Using this rather than local definitions to workaround gcc-4.4 limitations
+// regarding using templates without linkage
+namespace runsc {
+
+// ClientContext allocator
+static ClientContext* AllocContext(list<ClientContext>* contexts) {
+ contexts->emplace_back();
+ return &contexts->back();
+}
+
+struct ServerData {
+ unique_ptr<Worker::Stub> stub;
+ unique_ptr<ClientReaderWriter<ServerArgs, ServerStatus>> stream;
+};
+
+struct ClientData {
+ unique_ptr<Worker::Stub> stub;
+ unique_ptr<ClientReaderWriter<ClientArgs, ClientStatus>> stream;
+};
+} // namespace runsc
+
std::unique_ptr<ScenarioResult> RunScenario(
const ClientConfig& initial_client_config, size_t num_clients,
const ServerConfig& server_config, size_t num_servers, int warmup_seconds,
int benchmark_seconds, int spawn_local_worker_count) {
- // ClientContext allocator (all are destroyed at scope exit)
+ // ClientContext allocations (all are destroyed at scope exit)
list<ClientContext> contexts;
- auto alloc_context = [&contexts]() {
- contexts.emplace_back();
- return &contexts.back();
- };
// To be added to the result, containing the final configuration used for
// client and config (incluiding host, etc.)
@@ -131,23 +149,22 @@ std::unique_ptr<ScenarioResult> RunScenario(
workers.resize(num_clients + num_servers);
// Start servers
- struct ServerData {
- unique_ptr<Worker::Stub> stub;
- unique_ptr<ClientReaderWriter<ServerArgs, ServerStatus>> stream;
- };
- vector<ServerData> servers;
+ using runsc::ServerData;
+ // servers is array rather than std::vector to avoid gcc-4.4 issues
+ // where class contained in std::vector must have a copy constructor
+ auto* servers = new ServerData[num_servers];
for (size_t i = 0; i < num_servers; i++) {
- ServerData sd;
- sd.stub = std::move(Worker::NewStub(
+ servers[i].stub = std::move(Worker::NewStub(
CreateChannel(workers[i], InsecureCredentials(), ChannelArguments())));
ServerArgs args;
result_server_config = server_config;
result_server_config.set_host(workers[i]);
*args.mutable_setup() = server_config;
- sd.stream = std::move(sd.stub->RunServer(alloc_context()));
- GPR_ASSERT(sd.stream->Write(args));
+ servers[i].stream =
+ std::move(servers[i].stub->RunServer(runsc::AllocContext(&contexts)));
+ GPR_ASSERT(servers[i].stream->Write(args));
ServerStatus init_status;
- GPR_ASSERT(sd.stream->Read(&init_status));
+ GPR_ASSERT(servers[i].stream->Read(&init_status));
char* host;
char* driver_port;
char* cli_target;
@@ -157,30 +174,25 @@ std::unique_ptr<ScenarioResult> RunScenario(
gpr_free(host);
gpr_free(driver_port);
gpr_free(cli_target);
-
- servers.push_back(std::move(sd));
}
// Start clients
- struct ClientData {
- unique_ptr<Worker::Stub> stub;
- unique_ptr<ClientReaderWriter<ClientArgs, ClientStatus>> stream;
- };
- vector<ClientData> clients;
+ using runsc::ClientData;
+ // clients is array rather than std::vector to avoid gcc-4.4 issues
+ // where class contained in std::vector must have a copy constructor
+ auto* clients = new ClientData[num_clients];
for (size_t i = 0; i < num_clients; i++) {
- ClientData cd;
- cd.stub = std::move(Worker::NewStub(CreateChannel(
+ clients[i].stub = std::move(Worker::NewStub(CreateChannel(
workers[i + num_servers], InsecureCredentials(), ChannelArguments())));
ClientArgs args;
result_client_config = client_config;
result_client_config.set_host(workers[i + num_servers]);
*args.mutable_setup() = client_config;
- cd.stream = std::move(cd.stub->RunTest(alloc_context()));
- GPR_ASSERT(cd.stream->Write(args));
+ clients[i].stream =
+ std::move(clients[i].stub->RunTest(runsc::AllocContext(&contexts)));
+ GPR_ASSERT(clients[i].stream->Write(args));
ClientStatus init_status;
- GPR_ASSERT(cd.stream->Read(&init_status));
-
- clients.push_back(std::move(cd));
+ GPR_ASSERT(clients[i].stream->Read(&init_status));
}
// Let everything warmup
@@ -195,23 +207,25 @@ std::unique_ptr<ScenarioResult> RunScenario(
server_mark.mutable_mark();
ClientArgs client_mark;
client_mark.mutable_mark();
- for (auto server = servers.begin(); server != servers.end(); server++) {
+ for (auto server = &servers[0]; server != &servers[num_servers]; server++) {
GPR_ASSERT(server->stream->Write(server_mark));
}
- for (auto client = clients.begin(); client != clients.end(); client++) {
+ for (auto client = &clients[0]; client != &clients[num_clients]; client++) {
GPR_ASSERT(client->stream->Write(client_mark));
}
ServerStatus server_status;
ClientStatus client_status;
- for (auto server = servers.begin(); server != servers.end(); server++) {
+ for (auto server = &servers[0]; server != &servers[num_servers]; server++) {
GPR_ASSERT(server->stream->Read(&server_status));
}
- for (auto client = clients.begin(); client != clients.end(); client++) {
+ for (auto client = &clients[0]; client != &clients[num_clients]; client++) {
GPR_ASSERT(client->stream->Read(&client_status));
}
// Wait some time
gpr_log(GPR_INFO, "Running");
+ // Use gpr_sleep_until rather than this_thread::sleep_until to support
+ // compilers that don't work with this_thread
gpr_sleep_until(gpr_time_add(
start, gpr_time_from_seconds(benchmark_seconds, GPR_TIMESPAN)));
@@ -220,34 +234,36 @@ std::unique_ptr<ScenarioResult> RunScenario(
result->client_config = result_client_config;
result->server_config = result_server_config;
gpr_log(GPR_INFO, "Finishing");
- for (auto server = servers.begin(); server != servers.end(); server++) {
+ for (auto server = &servers[0]; server != &servers[num_servers]; server++) {
GPR_ASSERT(server->stream->Write(server_mark));
}
- for (auto client = clients.begin(); client != clients.end(); client++) {
+ for (auto client = &clients[0]; client != &clients[num_clients]; client++) {
GPR_ASSERT(client->stream->Write(client_mark));
}
- for (auto server = servers.begin(); server != servers.end(); server++) {
+ for (auto server = &servers[0]; server != &servers[num_servers]; server++) {
GPR_ASSERT(server->stream->Read(&server_status));
const auto& stats = server_status.stats();
- result->server_resources.push_back(ResourceUsage{
- stats.time_elapsed(), stats.time_user(), stats.time_system()});
+ result->server_resources.emplace_back(
+ stats.time_elapsed(), stats.time_user(), stats.time_system());
}
- for (auto client = clients.begin(); client != clients.end(); client++) {
+ for (auto client = &clients[0]; client != &clients[num_clients]; client++) {
GPR_ASSERT(client->stream->Read(&client_status));
const auto& stats = client_status.stats();
result->latencies.MergeProto(stats.latencies());
- result->client_resources.push_back(ResourceUsage{
- stats.time_elapsed(), stats.time_user(), stats.time_system()});
+ result->client_resources.emplace_back(
+ stats.time_elapsed(), stats.time_user(), stats.time_system());
}
- for (auto client = clients.begin(); client != clients.end(); client++) {
+ for (auto client = &clients[0]; client != &clients[num_clients]; client++) {
GPR_ASSERT(client->stream->WritesDone());
GPR_ASSERT(client->stream->Finish().ok());
}
- for (auto server = servers.begin(); server != servers.end(); server++) {
+ for (auto server = &servers[0]; server != &servers[num_servers]; server++) {
GPR_ASSERT(server->stream->WritesDone());
GPR_ASSERT(server->stream->Finish().ok());
}
+ delete[] clients;
+ delete[] servers;
return result;
}
} // namespace testing
diff --git a/test/cpp/qps/driver.h b/test/cpp/qps/driver.h
index 5e9d4b3cb9..9a29df8d49 100644
--- a/test/cpp/qps/driver.h
+++ b/test/cpp/qps/driver.h
@@ -41,10 +41,18 @@
namespace grpc {
namespace testing {
-struct ResourceUsage {
- double wall_time;
- double user_time;
- double system_time;
+class ResourceUsage {
+ public:
+ ResourceUsage(double w, double u, double s)
+ : wall_time_(w), user_time_(u), system_time_(s) {}
+ double wall_time() const { return wall_time_; }
+ double user_time() const { return user_time_; }
+ double system_time() const { return system_time_; }
+
+ private:
+ double wall_time_;
+ double user_time_;
+ double system_time_;
};
struct ScenarioResult {
diff --git a/test/cpp/qps/interarrival.h b/test/cpp/qps/interarrival.h
index f90a17a894..04d14f689f 100644
--- a/test/cpp/qps/interarrival.h
+++ b/test/cpp/qps/interarrival.h
@@ -36,7 +36,8 @@
#include <chrono>
#include <cmath>
-#include <random>
+#include <cstdlib>
+#include <vector>
#include <grpc++/config.h>
@@ -141,17 +142,16 @@ class ParetoDist GRPC_FINAL : public RandomDist {
// in an efficient re-entrant way. The random table is built at construction
// time, and each call must include the thread id of the invoker
-typedef std::default_random_engine qps_random_engine;
-
class InterarrivalTimer {
public:
InterarrivalTimer() {}
void init(const RandomDist& r, int threads, int entries = 1000000) {
- qps_random_engine gen;
- std::uniform_real_distribution<double> uniform(0.0, 1.0);
for (int i = 0; i < entries; i++) {
- random_table_.push_back(std::chrono::nanoseconds(
- static_cast<int64_t>(1e9 * r(uniform(gen)))));
+ // rand is the only choice that is portable across POSIX and Windows
+ // and that supports new and old compilers
+ const double uniform_0_1 = rand() / RAND_MAX;
+ random_table_.push_back(
+ std::chrono::nanoseconds(static_cast<int64_t>(1e9 * r(uniform_0_1))));
}
// Now set up the thread positions
for (int i = 0; i < threads; i++) {
diff --git a/test/cpp/qps/qps_driver.cc b/test/cpp/qps/qps_driver.cc
index d534846365..b1463be8f6 100644
--- a/test/cpp/qps/qps_driver.cc
+++ b/test/cpp/qps/qps_driver.cc
@@ -33,6 +33,7 @@
#include <memory>
#include <set>
+#include <signal.h>
#include <gflags/gflags.h>
#include <grpc/support/log.h>
diff --git a/test/cpp/qps/report.cc b/test/cpp/qps/report.cc
index ff01ec1501..e03e8e1fb0 100644
--- a/test/cpp/qps/report.cc
+++ b/test/cpp/qps/report.cc
@@ -34,11 +34,16 @@
#include "test/cpp/qps/report.h"
#include <grpc/support/log.h>
+#include "test/cpp/qps/driver.h"
#include "test/cpp/qps/stats.h"
namespace grpc {
namespace testing {
+static double WallTime(ResourceUsage u) { return u.wall_time(); }
+static double UserTime(ResourceUsage u) { return u.user_time(); }
+static double SystemTime(ResourceUsage u) { return u.system_time(); }
+
void CompositeReporter::add(std::unique_ptr<Reporter> reporter) {
reporters_.emplace_back(std::move(reporter));
}
@@ -68,16 +73,14 @@ void CompositeReporter::ReportTimes(const ScenarioResult& result) {
}
void GprLogReporter::ReportQPS(const ScenarioResult& result) {
- gpr_log(GPR_INFO, "QPS: %.1f",
- result.latencies.Count() /
- average(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; }));
+ gpr_log(
+ GPR_INFO, "QPS: %.1f",
+ result.latencies.Count() / average(result.client_resources, WallTime));
}
void GprLogReporter::ReportQPSPerCore(const ScenarioResult& result) {
- auto qps = result.latencies.Count() /
- average(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; });
+ auto qps =
+ result.latencies.Count() / average(result.client_resources, WallTime);
gpr_log(GPR_INFO, "QPS: %.1f (%.1f/server core)", qps,
qps / result.server_config.threads());
@@ -95,40 +98,30 @@ void GprLogReporter::ReportLatency(const ScenarioResult& result) {
void GprLogReporter::ReportTimes(const ScenarioResult& result) {
gpr_log(GPR_INFO, "Server system time: %.2f%%",
- 100.0 * sum(result.server_resources,
- [](ResourceUsage u) { return u.system_time; }) /
- sum(result.server_resources,
- [](ResourceUsage u) { return u.wall_time; }));
+ 100.0 * sum(result.server_resources, SystemTime) /
+ sum(result.server_resources, WallTime));
gpr_log(GPR_INFO, "Server user time: %.2f%%",
- 100.0 * sum(result.server_resources,
- [](ResourceUsage u) { return u.user_time; }) /
- sum(result.server_resources,
- [](ResourceUsage u) { return u.wall_time; }));
+ 100.0 * sum(result.server_resources, UserTime) /
+ sum(result.server_resources, WallTime));
gpr_log(GPR_INFO, "Client system time: %.2f%%",
- 100.0 * sum(result.client_resources,
- [](ResourceUsage u) { return u.system_time; }) /
- sum(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; }));
+ 100.0 * sum(result.client_resources, SystemTime) /
+ sum(result.client_resources, WallTime));
gpr_log(GPR_INFO, "Client user time: %.2f%%",
- 100.0 * sum(result.client_resources,
- [](ResourceUsage u) { return u.user_time; }) /
- sum(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; }));
+ 100.0 * sum(result.client_resources, UserTime) /
+ sum(result.client_resources, WallTime));
}
void PerfDbReporter::ReportQPS(const ScenarioResult& result) {
- auto qps = result.latencies.Count() /
- average(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; });
+ auto qps =
+ result.latencies.Count() / average(result.client_resources, WallTime);
perf_db_client_.setQps(qps);
perf_db_client_.setConfigs(result.client_config, result.server_config);
}
void PerfDbReporter::ReportQPSPerCore(const ScenarioResult& result) {
- auto qps = result.latencies.Count() /
- average(result.client_resources,
- [](ResourceUsage u) { return u.wall_time; });
+ auto qps =
+ result.latencies.Count() / average(result.client_resources, WallTime);
auto qpsPerCore = qps / result.server_config.threads();
@@ -139,33 +132,29 @@ void PerfDbReporter::ReportQPSPerCore(const ScenarioResult& result) {
void PerfDbReporter::ReportLatency(const ScenarioResult& result) {
perf_db_client_.setLatencies(result.latencies.Percentile(50) / 1000,
- result.latencies.Percentile(90) / 1000,
- result.latencies.Percentile(95) / 1000,
- result.latencies.Percentile(99) / 1000,
- result.latencies.Percentile(99.9) / 1000);
+ result.latencies.Percentile(90) / 1000,
+ result.latencies.Percentile(95) / 1000,
+ result.latencies.Percentile(99) / 1000,
+ result.latencies.Percentile(99.9) / 1000);
perf_db_client_.setConfigs(result.client_config, result.server_config);
}
void PerfDbReporter::ReportTimes(const ScenarioResult& result) {
- double server_system_time =
- 100.0 * sum(result.server_resources,
- [](ResourceUsage u) { return u.system_time; }) /
- sum(result.server_resources, [](ResourceUsage u) { return u.wall_time; });
- double server_user_time =
- 100.0 * sum(result.server_resources,
- [](ResourceUsage u) { return u.user_time; }) /
- sum(result.server_resources, [](ResourceUsage u) { return u.wall_time; });
- double client_system_time =
- 100.0 * sum(result.client_resources,
- [](ResourceUsage u) { return u.system_time; }) /
- sum(result.client_resources, [](ResourceUsage u) { return u.wall_time; });
- double client_user_time =
- 100.0 * sum(result.client_resources,
- [](ResourceUsage u) { return u.user_time; }) /
- sum(result.client_resources, [](ResourceUsage u) { return u.wall_time; });
-
- perf_db_client_.setTimes(server_system_time, server_user_time, client_system_time,
- client_user_time);
+ const double server_system_time = 100.0 *
+ sum(result.server_resources, SystemTime) /
+ sum(result.server_resources, WallTime);
+ const double server_user_time = 100.0 *
+ sum(result.server_resources, UserTime) /
+ sum(result.server_resources, WallTime);
+ const double client_system_time = 100.0 *
+ sum(result.client_resources, SystemTime) /
+ sum(result.client_resources, WallTime);
+ const double client_user_time = 100.0 *
+ sum(result.client_resources, UserTime) /
+ sum(result.client_resources, WallTime);
+
+ perf_db_client_.setTimes(server_system_time, server_user_time,
+ client_system_time, client_user_time);
perf_db_client_.setConfigs(result.client_config, result.server_config);
}
diff --git a/test/cpp/qps/server_async.cc b/test/cpp/qps/server_async.cc
index 33b6fa55c3..b4fc49c31c 100644
--- a/test/cpp/qps/server_async.cc
+++ b/test/cpp/qps/server_async.cc
@@ -99,25 +99,7 @@ class AsyncQpsServerTest : public Server {
shutdown_state_.emplace_back(new PerThreadShutdownState());
}
for (int i = 0; i < config.threads(); i++) {
- threads_.push_back(std::thread([=]() {
- // Wait until work is available or we are shutting down
- bool ok;
- void *got_tag;
- while (srv_cqs_[i]->Next(&got_tag, &ok)) {
- ServerRpcContext *ctx = detag(got_tag);
- // The tag is a pointer to an RPC context to invoke
- bool still_going = ctx->RunNextState(ok);
- if (!shutdown_state_[i]->shutdown()) {
- // this RPC context is done, so refresh it
- if (!still_going) {
- ctx->Reset();
- }
- } else {
- return;
- }
- }
- return;
- }));
+ threads_.emplace_back(&AsyncQpsServerTest::ThreadFunc, this, i);
}
}
~AsyncQpsServerTest() {
@@ -142,6 +124,26 @@ class AsyncQpsServerTest : public Server {
}
private:
+ void ThreadFunc(int rank) {
+ // Wait until work is available or we are shutting down
+ bool ok;
+ void *got_tag;
+ while (srv_cqs_[rank]->Next(&got_tag, &ok)) {
+ ServerRpcContext *ctx = detag(got_tag);
+ // The tag is a pointer to an RPC context to invoke
+ const bool still_going = ctx->RunNextState(ok);
+ if (!shutdown_state_[rank]->shutdown()) {
+ // this RPC context is done, so refresh it
+ if (!still_going) {
+ ctx->Reset();
+ }
+ } else {
+ return;
+ }
+ }
+ return;
+ }
+
class ServerRpcContext {
public:
ServerRpcContext() {}
diff --git a/tools/dockerfile/grpc_go/Dockerfile b/tools/dockerfile/grpc_go/Dockerfile
index 06bb3e2d5e..7f3bd8719c 100644
--- a/tools/dockerfile/grpc_go/Dockerfile
+++ b/tools/dockerfile/grpc_go/Dockerfile
@@ -32,6 +32,8 @@ FROM golang:1.4
# Get the source from GitHub
RUN go get google.golang.org/grpc
+RUN go get golang.org/x/oauth2
+RUN go get google.golang.org/cloud
# Add a service_account directory containing the auth creds file
ADD service_account service_account
diff --git a/tools/dockerfile/grpc_go/build.sh b/tools/dockerfile/grpc_go/build.sh
new file mode 100755
index 0000000000..254942fe01
--- /dev/null
+++ b/tools/dockerfile/grpc_go/build.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+cp -R /var/local/git-clone/grpc-go/. /go/
+go get golang.org/x/oauth2
+go get google.golang.org/cloud
+cd src/google.golang.org/grpc/interop/client && go install
diff --git a/tools/doxygen/Doxyfile.c++ b/tools/doxygen/Doxyfile.c++
index eb14925fff..790e637b72 100644
--- a/tools/doxygen/Doxyfile.c++
+++ b/tools/doxygen/Doxyfile.c++
@@ -40,7 +40,7 @@ PROJECT_NAME = "GRPC C++"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.10.0.0
+PROJECT_NUMBER = 0.10.1.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -763,7 +763,6 @@ WARN_LOGFILE =
INPUT = include/grpc++/async_generic_service.h \
include/grpc++/async_unary_call.h \
include/grpc++/auth_context.h \
-include/grpc++/auth_property_iterator.h \
include/grpc++/byte_buffer.h \
include/grpc++/channel_arguments.h \
include/grpc++/channel_interface.h \
diff --git a/tools/doxygen/Doxyfile.c++.internal b/tools/doxygen/Doxyfile.c++.internal
index 3d6649bef6..cd1279e2a6 100644
--- a/tools/doxygen/Doxyfile.c++.internal
+++ b/tools/doxygen/Doxyfile.c++.internal
@@ -40,7 +40,7 @@ PROJECT_NAME = "GRPC C++"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.10.0.0
+PROJECT_NUMBER = 0.10.1.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -763,7 +763,6 @@ WARN_LOGFILE =
INPUT = include/grpc++/async_generic_service.h \
include/grpc++/async_unary_call.h \
include/grpc++/auth_context.h \
-include/grpc++/auth_property_iterator.h \
include/grpc++/byte_buffer.h \
include/grpc++/channel_arguments.h \
include/grpc++/channel_interface.h \
diff --git a/tools/doxygen/Doxyfile.core b/tools/doxygen/Doxyfile.core
index 2fb27efc9e..d219ca7ac0 100644
--- a/tools/doxygen/Doxyfile.core
+++ b/tools/doxygen/Doxyfile.core
@@ -40,7 +40,7 @@ PROJECT_NAME = "GRPC Core"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.10.0.0
+PROJECT_NUMBER = 0.10.1.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal
index cbf5c50a65..1f66fe1d35 100644
--- a/tools/doxygen/Doxyfile.core.internal
+++ b/tools/doxygen/Doxyfile.core.internal
@@ -40,7 +40,7 @@ PROJECT_NAME = "GRPC Core"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.10.0.0
+PROJECT_NUMBER = 0.10.1.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
diff --git a/tools/run_tests/run_interops.py b/tools/run_tests/run_interops.py
index 1cf268526d..4e6b5ce2f6 100755
--- a/tools/run_tests/run_interops.py
+++ b/tools/run_tests/run_interops.py
@@ -4,24 +4,20 @@ import jobset
argp = argparse.ArgumentParser(description='Run interop tests.')
argp.add_argument('-l', '--language',
- choices=['build_only', 'c++'],
- nargs='+',
- default=['build_only'])
+ default='c++')
args = argp.parse_args()
# build job
-build_steps = 'tools/run_tests/run_interops_build.sh'
-build_job = jobset.JobSpec(cmdline=build_steps, shortname='build')
+build_job = jobset.JobSpec(cmdline=['tools/run_tests/run_interops_build.sh', '%s' % args.language], shortname='build')
-# test jobs
+# test jobs, each test is a separate job to run in parallel
_TESTS = ['large_unary', 'empty_unary', 'ping_pong', 'client_streaming', 'server_streaming']
jobs = []
jobNumber = 0
-for lang in args.language:
- for test in _TESTS:
- test_job = jobset.JobSpec(cmdline=['tools/run_tests/run_interops_test.sh', '%s' % lang, '%s' % test], shortname=test)
- jobs.append(test_job)
- jobNumber+=1
+for test in _TESTS:
+ test_job = jobset.JobSpec(cmdline=['tools/run_tests/run_interops_test.sh', '%s' % args.language, '%s' % test], shortname=test)
+ jobs.append(test_job)
+ jobNumber+=1
root = ET.Element('testsuites')
testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
diff --git a/tools/run_tests/run_interops_build.sh b/tools/run_tests/run_interops_build.sh
index 23441a5300..ff1a26cf89 100755
--- a/tools/run_tests/run_interops_build.sh
+++ b/tools/run_tests/run_interops_build.sh
@@ -29,6 +29,8 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+language=$1
+
set -e
#clean up any old docker files and start mirroring repository if not started already
@@ -40,8 +42,34 @@ sudo docker run -d -e GCS_BUCKET=docker-interop-images -e STORAGE_PATH=/admin/d
#prepare building by pulling down base images and necessary files
sudo docker pull 0.0.0.0:5000/grpc/base
sudo docker tag -f 0.0.0.0:5000/grpc/base grpc/base
-gsutil cp -R gs://docker-interop-images/admin/service_account tools/dockerfile/grpc_cxx
-gsutil cp -R gs://docker-interop-images/admin/cacerts tools/dockerfile/grpc_cxx
-#build docker file, add more languages later
-sudo docker build --no-cache -t grpc/cxx tools/dockerfile/grpc_cxx
+if [ "$language" = "c++" ]
+then
+ gsutil cp -R gs://docker-interop-images/admin/service_account tools/dockerfile/grpc_cxx
+ gsutil cp -R gs://docker-interop-images/admin/cacerts tools/dockerfile/grpc_cxx
+ sudo docker build --no-cache -t grpc/cxx tools/dockerfile/grpc_cxx
+elif [ "$language" = "node" ]
+then
+ sudo docker pull 0.0.0.0:5000/grpc/node_base
+ sudo docker tag -f 0.0.0.0:5000/grpc/node_base grpc/node_base
+ gsutil cp -R gs://docker-interop-images/admin/service_account tools/dockerfile/grpc_node
+ gsutil cp -R gs://docker-interop-images/admin/cacerts tools/dockerfile/grpc_node
+ sudo docker build --no-cache -t grpc/node tools/dockerfile/grpc_node
+elif [ "$language" = "ruby" ]
+then
+ sudo docker pull 0.0.0.0:5000/grpc/ruby_base
+ sudo docker tag -f 0.0.0.0:5000/grpc/ruby_base grpc/ruby_base
+ gsutil cp -R gs://docker-interop-images/admin/service_account tools/dockerfile/grpc_ruby
+ gsutil cp -R gs://docker-interop-images/admin/cacerts tools/dockerfile/grpc_ruby
+ sudo docker build --no-cache -t grpc/ruby tools/dockerfile/grpc_ruby
+elif [ "$language" = "php" ]
+then
+ sudo docker pull 0.0.0.0:5000/grpc/php_base
+ sudo docker tag -f 0.0.0.0:5000/grpc/php_base grpc/php_base
+ gsutil cp -R gs://docker-interop-images/admin/service_account tools/dockerfile/grpc_php
+ gsutil cp -R gs://docker-interop-images/admin/cacerts tools/dockerfile/grpc_php
+ sudo docker build --no-cache -t grpc/php tools/dockerfile/grpc_php
+else
+ echo "interop testss not added for $language"
+ exit 1
+fi
diff --git a/tools/run_tests/run_interops_test.sh b/tools/run_tests/run_interops_test.sh
index 1d0eedad85..9be253af46 100755
--- a/tools/run_tests/run_interops_test.sh
+++ b/tools/run_tests/run_interops_test.sh
@@ -36,6 +36,17 @@ set -e
if [ "$language" = "c++" ]
then
sudo docker run grpc/cxx /var/local/git/grpc/bins/opt/interop_client --enable_ssl --use_prod_roots --server_host_override=grpc-test.sandbox.google.com --server_host=grpc-test.sandbox.google.com --server_port=443 --test_case=$test_case
+elif [ "$language" = "node" ]
+then
+ sudo docker run grpc/node /usr/bin/nodejs /var/local/git/grpc/src/node/interop/interop_client.js --use_tls=true --use_test_ca=true --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com --test_case=$test_case
+elif [ "$language" = "ruby" ]
+then
+ cmd_prefix="SSL_CERT_FILE=/cacerts/roots.pem ruby /var/local/git/grpc/src/ruby/bin/interop/interop_client.rb --use_tls --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com "
+ cmd="$cmd_prefix --test_case=$test_case"
+ sudo docker run grpc/ruby bin/bash -l -c '$cmd'
+elif [ "$language" = "php" ]
+then
+ sudo docker run -e SSL_CERT_FILE=/cacerts/roots.pem grpc/php /var/local/git/grpc/src/php/bin/interop_client.sh --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com --test_case=$test_case
else
echo "interop testss not added for $language"
exit 1
diff --git a/tools/run_tests/run_python.sh b/tools/run_tests/run_python.sh
index 5ffd4460b9..6f80219b0e 100755
--- a/tools/run_tests/run_python.sh
+++ b/tools/run_tests/run_python.sh
@@ -37,5 +37,6 @@ ROOT=`pwd`
GRPCIO_TEST=$ROOT/src/python/grpcio_test
export LD_LIBRARY_PATH=$ROOT/libs/$CONFIG
export DYLD_LIBRARY_PATH=$ROOT/libs/$CONFIG
+export PATH=$ROOT/bins/$CONFIG:$ROOT/bins/$CONFIG/protobuf:$PATH
source "python"$PYVER"_virtual_environment"/bin/activate
"python"$PYVER $GRPCIO_TEST/setup.py test -a "-n8 --cov=grpc --junitxml=./report.xml"
diff --git a/tools/run_tests/sources_and_headers.json b/tools/run_tests/sources_and_headers.json
index 5d23bf9e88..d2c3ec3add 100644
--- a/tools/run_tests/sources_and_headers.json
+++ b/tools/run_tests/sources_and_headers.json
@@ -13051,7 +13051,6 @@
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
@@ -13102,7 +13101,6 @@
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
@@ -13227,7 +13225,6 @@
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
@@ -13275,7 +13272,6 @@
"include/grpc++/async_generic_service.h",
"include/grpc++/async_unary_call.h",
"include/grpc++/auth_context.h",
- "include/grpc++/auth_property_iterator.h",
"include/grpc++/byte_buffer.h",
"include/grpc++/channel_arguments.h",
"include/grpc++/channel_interface.h",
diff --git a/vsprojects/grpc++/grpc++.vcxproj b/vsprojects/grpc++/grpc++.vcxproj
index 58474511fc..929bc1500e 100644
--- a/vsprojects/grpc++/grpc++.vcxproj
+++ b/vsprojects/grpc++/grpc++.vcxproj
@@ -216,7 +216,6 @@
<ClInclude Include="..\..\include\grpc++\async_generic_service.h" />
<ClInclude Include="..\..\include\grpc++\async_unary_call.h" />
<ClInclude Include="..\..\include\grpc++\auth_context.h" />
- <ClInclude Include="..\..\include\grpc++\auth_property_iterator.h" />
<ClInclude Include="..\..\include\grpc++\byte_buffer.h" />
<ClInclude Include="..\..\include\grpc++\channel_arguments.h" />
<ClInclude Include="..\..\include\grpc++\channel_interface.h" />
diff --git a/vsprojects/grpc++/grpc++.vcxproj.filters b/vsprojects/grpc++/grpc++.vcxproj.filters
index 2a8ee08b08..0408fb46a5 100644
--- a/vsprojects/grpc++/grpc++.vcxproj.filters
+++ b/vsprojects/grpc++/grpc++.vcxproj.filters
@@ -105,9 +105,6 @@
<ClInclude Include="..\..\include\grpc++\auth_context.h">
<Filter>include\grpc++</Filter>
</ClInclude>
- <ClInclude Include="..\..\include\grpc++\auth_property_iterator.h">
- <Filter>include\grpc++</Filter>
- </ClInclude>
<ClInclude Include="..\..\include\grpc++\byte_buffer.h">
<Filter>include\grpc++</Filter>
</ClInclude>
diff --git a/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj b/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj
index 0d989c4a93..2ff252e04e 100644
--- a/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj
+++ b/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj
@@ -216,7 +216,6 @@
<ClInclude Include="..\..\include\grpc++\async_generic_service.h" />
<ClInclude Include="..\..\include\grpc++\async_unary_call.h" />
<ClInclude Include="..\..\include\grpc++\auth_context.h" />
- <ClInclude Include="..\..\include\grpc++\auth_property_iterator.h" />
<ClInclude Include="..\..\include\grpc++\byte_buffer.h" />
<ClInclude Include="..\..\include\grpc++\channel_arguments.h" />
<ClInclude Include="..\..\include\grpc++\channel_interface.h" />
diff --git a/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj.filters b/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
index 71d42e5c6d..b4fae7741c 100644
--- a/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
+++ b/vsprojects/grpc++_unsecure/grpc++_unsecure.vcxproj.filters
@@ -90,9 +90,6 @@
<ClInclude Include="..\..\include\grpc++\auth_context.h">
<Filter>include\grpc++</Filter>
</ClInclude>
- <ClInclude Include="..\..\include\grpc++\auth_property_iterator.h">
- <Filter>include\grpc++</Filter>
- </ClInclude>
<ClInclude Include="..\..\include\grpc++\byte_buffer.h">
<Filter>include\grpc++</Filter>
</ClInclude>