Merge branch 'master' of github.com:grpc/grpc into head_vs_tails

pull/3010/head
David Garcia Quintas 9 years ago
commit bb88b9007f
  1. 23
      INSTALL
  2. 45
      Makefile
  3. 23
      build.json
  4. 6
      doc/connection-backoff-interop-test-description.md
  5. 9
      doc/connection-backoff.md
  6. 11
      include/grpc++/server.h
  7. 7
      include/grpc/grpc.h
  8. 11
      src/core/surface/server.c
  9. 37
      src/cpp/server/server.cc
  10. 1
      src/csharp/.gitignore
  11. 33
      src/csharp/Grpc.Core.Tests/ChannelTest.cs
  12. 15
      src/csharp/Grpc.Core.Tests/ClientServerTest.cs
  13. 8
      src/csharp/Grpc.Core.Tests/CompressionTest.cs
  14. 8
      src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
  15. 3
      src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
  16. 33
      src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
  17. 222
      src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
  18. 120
      src/csharp/Grpc.Core.Tests/MetadataTest.cs
  19. 79
      src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
  20. 5
      src/csharp/Grpc.Core.Tests/ServerTest.cs
  21. 77
      src/csharp/Grpc.Core.Tests/ShutdownTest.cs
  22. 8
      src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
  23. 15
      src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
  24. 16
      src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
  25. 16
      src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
  26. 15
      src/csharp/Grpc.Core/AsyncUnaryCall.cs
  27. 8
      src/csharp/Grpc.Core/Calls.cs
  28. 51
      src/csharp/Grpc.Core/Channel.cs
  29. 3
      src/csharp/Grpc.Core/ClientBase.cs
  30. 1
      src/csharp/Grpc.Core/ContextPropagationToken.cs
  31. 1
      src/csharp/Grpc.Core/Grpc.Core.csproj
  32. 44
      src/csharp/Grpc.Core/GrpcEnvironment.cs
  33. 149
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  34. 64
      src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
  35. 23
      src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
  36. 16
      src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
  37. 53
      src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
  38. 7
      src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
  39. 8
      src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
  40. 14
      src/csharp/Grpc.Core/Internal/DebugStats.cs
  41. 3
      src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
  42. 85
      src/csharp/Grpc.Core/Internal/INativeCall.cs
  43. 5
      src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
  44. 10
      src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
  45. 4
      src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
  46. 14
      src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
  47. 112
      src/csharp/Grpc.Core/Metadata.cs
  48. 57
      src/csharp/Grpc.Core/Server.cs
  49. 20
      src/csharp/Grpc.Examples.MathClient/MathClient.cs
  50. 1
      src/csharp/Grpc.Examples.MathServer/MathServer.cs
  51. 3
      src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
  52. 3
      src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
  53. 40
      src/csharp/Grpc.IntegrationTesting/InteropClient.cs
  54. 9
      src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
  55. 2
      src/csharp/Grpc.IntegrationTesting/InteropServer.cs
  56. 3
      src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
  57. 55
      src/csharp/ext/grpc_csharp_ext.c
  58. 32
      src/node/README.md
  59. 63
      src/node/ext/server_credentials.cc
  60. 10
      src/node/health_check/health.js
  61. 5
      src/node/health_check/health.proto
  62. 4
      src/node/interop/interop_server.js
  63. 8
      src/node/src/client.js
  64. 24
      src/node/test/health_test.js
  65. 4
      src/node/test/server_test.js
  66. 46
      src/php/README.md
  67. 17
      src/php/ext/grpc/call.c
  68. 21
      src/php/ext/grpc/channel.c
  69. 1
      src/php/ext/grpc/channel.h
  70. 8
      src/php/tests/generated_code/AbstractGeneratedCodeTest.php
  71. 4
      src/php/tests/generated_code/GeneratedCodeTest.php
  72. 4
      src/php/tests/generated_code/GeneratedCodeWithCallbackTest.php
  73. 14
      src/php/tests/unit_tests/SecureEndToEndTest.php
  74. 37
      src/python/README.md
  75. 3
      src/python/grpcio/README.rst
  76. 30
      src/python/grpcio/grpc/framework/core/__init__.py
  77. 59
      src/python/grpcio/grpc/framework/core/_constants.py
  78. 92
      src/python/grpcio/grpc/framework/core/_context.py
  79. 97
      src/python/grpcio/grpc/framework/core/_emission.py
  80. 251
      src/python/grpcio/grpc/framework/core/_end.py
  81. 152
      src/python/grpcio/grpc/framework/core/_expiration.py
  82. 410
      src/python/grpcio/grpc/framework/core/_ingestion.py
  83. 308
      src/python/grpcio/grpc/framework/core/_interfaces.py
  84. 192
      src/python/grpcio/grpc/framework/core/_operation.py
  85. 137
      src/python/grpcio/grpc/framework/core/_reception.py
  86. 212
      src/python/grpcio/grpc/framework/core/_termination.py
  87. 294
      src/python/grpcio/grpc/framework/core/_transmission.py
  88. 46
      src/python/grpcio/grpc/framework/core/_utilities.py
  89. 62
      src/python/grpcio/grpc/framework/core/implementations.py
  90. 39
      src/python/grpcio/grpc/framework/interfaces/base/base.py
  91. 30
      src/python/grpcio/grpc/framework/interfaces/face/__init__.py
  92. 933
      src/python/grpcio/grpc/framework/interfaces/face/face.py
  93. 178
      src/python/grpcio/grpc/framework/interfaces/face/utilities.py
  94. 2
      src/python/grpcio/grpc/framework/interfaces/links/links.py
  95. 165
      src/python/grpcio_test/grpc_test/_core_over_links_base_interface_test.py
  96. 30
      src/python/grpcio_test/grpc_test/framework/core/__init__.py
  97. 96
      src/python/grpcio_test/grpc_test/framework/core/_base_interface_test.py
  98. 6
      src/python/grpcio_test/grpc_test/framework/interfaces/base/test_cases.py
  99. 101
      src/python/grpcio_test/grpc_test/framework/interfaces/links/test_utilities.py
  100. 38
      src/ruby/README.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -9,25 +9,40 @@ wiki pages:
* If you are in a hurry * * If you are in a hurry *
************************* *************************
On Linux (Debian):
Note: you will need to add the Debian 'unstable' distribution to your sources
file first.
Add the following line to your `/etc/apt/sources.list` file:
deb http://ftp.us.debian.org/debian unstable main contrib non-free
Install the gRPC library:
$ [sudo] apt-get install libgrpc-dev
OR
$ git clone https://github.com/grpc/grpc.git $ git clone https://github.com/grpc/grpc.git
$ cd grpc $ cd grpc
$ git submodule update --init $ git submodule update --init
$ make $ make
$ sudo make install $ [sudo] make install
You don't need anything else than GNU Make, gcc and autotools. Under a Debian You don't need anything else than GNU Make, gcc and autotools. Under a Debian
or Ubuntu system, this should boil down to the following packages: or Ubuntu system, this should boil down to the following packages:
$ apt-get install build-essential autoconf libtool $ [sudo] apt-get install build-essential autoconf libtool
Building the python wrapper requires the following: Building the python wrapper requires the following:
# apt-get install python-all-dev python-virtualenv $ [sudo] apt-get install python-all-dev python-virtualenv
If you want to install in a different directory than the default /usr/lib, you can If you want to install in a different directory than the default /usr/lib, you can
override it on the command line: override it on the command line:
# make install prefix=/opt $ [sudo] make install prefix=/opt
******************************* *******************************

@ -889,6 +889,7 @@ reconnect_interop_server: $(BINDIR)/$(CONFIG)/reconnect_interop_server
secure_auth_context_test: $(BINDIR)/$(CONFIG)/secure_auth_context_test secure_auth_context_test: $(BINDIR)/$(CONFIG)/secure_auth_context_test
server_crash_test: $(BINDIR)/$(CONFIG)/server_crash_test server_crash_test: $(BINDIR)/$(CONFIG)/server_crash_test
server_crash_test_client: $(BINDIR)/$(CONFIG)/server_crash_test_client server_crash_test_client: $(BINDIR)/$(CONFIG)/server_crash_test_client
shutdown_test: $(BINDIR)/$(CONFIG)/shutdown_test
status_test: $(BINDIR)/$(CONFIG)/status_test status_test: $(BINDIR)/$(CONFIG)/status_test
sync_streaming_ping_pong_test: $(BINDIR)/$(CONFIG)/sync_streaming_ping_pong_test sync_streaming_ping_pong_test: $(BINDIR)/$(CONFIG)/sync_streaming_ping_pong_test
sync_unary_ping_pong_test: $(BINDIR)/$(CONFIG)/sync_unary_ping_pong_test sync_unary_ping_pong_test: $(BINDIR)/$(CONFIG)/sync_unary_ping_pong_test
@ -1735,7 +1736,7 @@ buildtests_c: privatelibs_c $(BINDIR)/$(CONFIG)/alarm_heap_test $(BINDIR)/$(CONF
buildtests_cxx: buildtests_zookeeper privatelibs_cxx $(BINDIR)/$(CONFIG)/async_end2end_test $(BINDIR)/$(CONFIG)/async_streaming_ping_pong_test $(BINDIR)/$(CONFIG)/async_unary_ping_pong_test $(BINDIR)/$(CONFIG)/auth_property_iterator_test $(BINDIR)/$(CONFIG)/channel_arguments_test $(BINDIR)/$(CONFIG)/cli_call_test $(BINDIR)/$(CONFIG)/client_crash_test $(BINDIR)/$(CONFIG)/client_crash_test_server $(BINDIR)/$(CONFIG)/credentials_test $(BINDIR)/$(CONFIG)/cxx_byte_buffer_test $(BINDIR)/$(CONFIG)/cxx_slice_test $(BINDIR)/$(CONFIG)/cxx_time_test $(BINDIR)/$(CONFIG)/dynamic_thread_pool_test $(BINDIR)/$(CONFIG)/end2end_test $(BINDIR)/$(CONFIG)/fixed_size_thread_pool_test $(BINDIR)/$(CONFIG)/generic_end2end_test $(BINDIR)/$(CONFIG)/grpc_cli $(BINDIR)/$(CONFIG)/interop_client $(BINDIR)/$(CONFIG)/interop_server $(BINDIR)/$(CONFIG)/interop_test $(BINDIR)/$(CONFIG)/mock_test $(BINDIR)/$(CONFIG)/qps_interarrival_test $(BINDIR)/$(CONFIG)/qps_openloop_test $(BINDIR)/$(CONFIG)/qps_test $(BINDIR)/$(CONFIG)/reconnect_interop_client $(BINDIR)/$(CONFIG)/reconnect_interop_server $(BINDIR)/$(CONFIG)/secure_auth_context_test $(BINDIR)/$(CONFIG)/server_crash_test $(BINDIR)/$(CONFIG)/server_crash_test_client $(BINDIR)/$(CONFIG)/status_test $(BINDIR)/$(CONFIG)/sync_streaming_ping_pong_test $(BINDIR)/$(CONFIG)/sync_unary_ping_pong_test $(BINDIR)/$(CONFIG)/thread_stress_test buildtests_cxx: buildtests_zookeeper privatelibs_cxx $(BINDIR)/$(CONFIG)/async_end2end_test $(BINDIR)/$(CONFIG)/async_streaming_ping_pong_test $(BINDIR)/$(CONFIG)/async_unary_ping_pong_test $(BINDIR)/$(CONFIG)/auth_property_iterator_test $(BINDIR)/$(CONFIG)/channel_arguments_test $(BINDIR)/$(CONFIG)/cli_call_test $(BINDIR)/$(CONFIG)/client_crash_test $(BINDIR)/$(CONFIG)/client_crash_test_server $(BINDIR)/$(CONFIG)/credentials_test $(BINDIR)/$(CONFIG)/cxx_byte_buffer_test $(BINDIR)/$(CONFIG)/cxx_slice_test $(BINDIR)/$(CONFIG)/cxx_time_test $(BINDIR)/$(CONFIG)/dynamic_thread_pool_test $(BINDIR)/$(CONFIG)/end2end_test $(BINDIR)/$(CONFIG)/fixed_size_thread_pool_test $(BINDIR)/$(CONFIG)/generic_end2end_test $(BINDIR)/$(CONFIG)/grpc_cli $(BINDIR)/$(CONFIG)/interop_client $(BINDIR)/$(CONFIG)/interop_server $(BINDIR)/$(CONFIG)/interop_test $(BINDIR)/$(CONFIG)/mock_test $(BINDIR)/$(CONFIG)/qps_interarrival_test $(BINDIR)/$(CONFIG)/qps_openloop_test $(BINDIR)/$(CONFIG)/qps_test $(BINDIR)/$(CONFIG)/reconnect_interop_client $(BINDIR)/$(CONFIG)/reconnect_interop_server $(BINDIR)/$(CONFIG)/secure_auth_context_test $(BINDIR)/$(CONFIG)/server_crash_test $(BINDIR)/$(CONFIG)/server_crash_test_client $(BINDIR)/$(CONFIG)/status_test $(BINDIR)/$(CONFIG)/sync_streaming_ping_pong_test $(BINDIR)/$(CONFIG)/sync_unary_ping_pong_test $(BINDIR)/$(CONFIG)/thread_stress_test
ifeq ($(HAS_ZOOKEEPER),true) ifeq ($(HAS_ZOOKEEPER),true)
buildtests_zookeeper: privatelibs_zookeeper $(BINDIR)/$(CONFIG)/zookeeper_test buildtests_zookeeper: privatelibs_zookeeper $(BINDIR)/$(CONFIG)/shutdown_test $(BINDIR)/$(CONFIG)/zookeeper_test
else else
buildtests_zookeeper: buildtests_zookeeper:
endif endif
@ -3363,6 +3364,8 @@ flaky_test_cxx: buildtests_cxx
ifeq ($(HAS_ZOOKEEPER),true) ifeq ($(HAS_ZOOKEEPER),true)
test_zookeeper: buildtests_zookeeper test_zookeeper: buildtests_zookeeper
$(E) "[RUN] Testing shutdown_test"
$(Q) $(BINDIR)/$(CONFIG)/shutdown_test || ( echo test shutdown_test failed ; exit 1 )
$(E) "[RUN] Testing zookeeper_test" $(E) "[RUN] Testing zookeeper_test"
$(Q) $(BINDIR)/$(CONFIG)/zookeeper_test || ( echo test zookeeper_test failed ; exit 1 ) $(Q) $(BINDIR)/$(CONFIG)/zookeeper_test || ( echo test zookeeper_test failed ; exit 1 )
@ -10262,6 +10265,46 @@ endif
endif endif
SHUTDOWN_TEST_SRC = \
test/cpp/end2end/shutdown_test.cc \
SHUTDOWN_TEST_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(SHUTDOWN_TEST_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/shutdown_test: openssl_dep_error
else
ifeq ($(NO_PROTOBUF),true)
# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
$(BINDIR)/$(CONFIG)/shutdown_test: protobuf_dep_error
else
$(BINDIR)/$(CONFIG)/shutdown_test: $(PROTOBUF_DEP) $(SHUTDOWN_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc_zookeeper.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LDXX) $(LDFLAGS) $(SHUTDOWN_TEST_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc_zookeeper.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a -lzookeeper_mt $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/shutdown_test
endif
endif
$(OBJDIR)/$(CONFIG)/test/cpp/end2end/shutdown_test.o: $(LIBDIR)/$(CONFIG)/libgrpc++_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc_zookeeper.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr_test_util.a $(LIBDIR)/$(CONFIG)/libgpr.a
deps_shutdown_test: $(SHUTDOWN_TEST_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(SHUTDOWN_TEST_OBJS:.o=.dep)
endif
endif
STATUS_TEST_SRC = \ STATUS_TEST_SRC = \
test/cpp/util/status_test.cc \ test/cpp/util/status_test.cc \

@ -2488,6 +2488,9 @@
"gpr", "gpr",
"grpc++_test_config" "grpc++_test_config"
], ],
"exclude_configs": [
"tsan"
],
"platforms": [ "platforms": [
"mac", "mac",
"linux", "linux",
@ -2610,6 +2613,26 @@
"gpr" "gpr"
] ]
}, },
{
"name": "shutdown_test",
"build": "test",
"language": "c++",
"src": [
"test/cpp/end2end/shutdown_test.cc"
],
"deps": [
"grpc++_test_util",
"grpc_test_util",
"grpc++",
"grpc_zookeeper",
"grpc",
"gpr_test_util",
"gpr"
],
"external_deps": [
"zookeeper"
]
},
{ {
"name": "status_test", "name": "status_test",
"build": "test", "build": "test",

@ -31,9 +31,9 @@ Clients should accept these arguments:
* --server_retry_port=PORT * --server_retry_port=PORT
* The server port to connect to for testing backoffs. For example, "8081" * The server port to connect to for testing backoffs. For example, "8081"
The client must connect to the control port without TLS. The client should The client must connect to the control port without TLS. The client must connect
either assert on the server returned backoff status or check the returned to the retry port with TLS. The client should either assert on the server
backoffs on its own. returned backoff status or check the returned backoffs on its own.
Procedure of client: Procedure of client:

@ -44,3 +44,12 @@ different jitter logic.
Alternate implementations must ensure that connection backoffs started at the Alternate implementations must ensure that connection backoffs started at the
same time disperse, and must not attempt connections substantially more often same time disperse, and must not attempt connections substantially more often
than the above algorithm. than the above algorithm.
## Reset Backoff
The back off should be reset to INITIAL_BACKOFF at some time point, so that the
reconnecting behavior is consistent no matter the connection is a newly started
one or a previously disconnected one.
We choose to reset the Backoff when the SETTINGS frame is received, at that time
point, we know for sure that this connection was accepted by the server.

@ -63,7 +63,14 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
~Server(); ~Server();
// Shutdown the server, block until all rpc processing finishes. // Shutdown the server, block until all rpc processing finishes.
void Shutdown(); // Forcefully terminate pending calls after deadline expires.
template <class T>
void Shutdown(const T& deadline) {
ShutdownInternal(TimePoint<T>(deadline).raw_time());
}
// Shutdown the server, waiting for all rpc processing to finish.
void Shutdown() { ShutdownInternal(gpr_inf_future(GPR_CLOCK_MONOTONIC)); }
// Block waiting for all work to complete (the server must either // Block waiting for all work to complete (the server must either
// be shutting down or some other thread must call Shutdown for this // be shutting down or some other thread must call Shutdown for this
@ -99,6 +106,8 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
void PerformOpsOnCall(CallOpSetInterface* ops, Call* call) GRPC_OVERRIDE; void PerformOpsOnCall(CallOpSetInterface* ops, Call* call) GRPC_OVERRIDE;
void ShutdownInternal(gpr_timespec deadline);
class BaseAsyncRequest : public CompletionQueueTag { class BaseAsyncRequest : public CompletionQueueTag {
public: public:
BaseAsyncRequest(Server* server, ServerContext* context, BaseAsyncRequest(Server* server, ServerContext* context,

@ -386,13 +386,6 @@ typedef struct grpc_op {
the reverse order they were initialized. */ the reverse order they were initialized. */
void grpc_register_plugin(void (*init)(void), void (*destroy)(void)); void grpc_register_plugin(void (*init)(void), void (*destroy)(void));
/** Frees the memory used by all the plugin information.
While grpc_init and grpc_shutdown can be called multiple times, the plugins
won't be unregistered and their memory cleaned up unless you call that
function. Using atexit(grpc_unregister_all_plugins) is a valid method. */
void grpc_unregister_all_plugins();
/* Propagation bits: this can be bitwise or-ed to form propagation_mask for /* Propagation bits: this can be bitwise or-ed to form propagation_mask for
* grpc_call */ * grpc_call */
/** Propagate deadline */ /** Propagate deadline */

@ -975,6 +975,11 @@ void grpc_server_setup_transport(grpc_server *s, grpc_transport *transport,
grpc_transport_perform_op(transport, &op); grpc_transport_perform_op(transport, &op);
} }
void done_published_shutdown(void *done_arg, grpc_cq_completion *storage) {
(void) done_arg;
gpr_free(storage);
}
void grpc_server_shutdown_and_notify(grpc_server *server, void grpc_server_shutdown_and_notify(grpc_server *server,
grpc_completion_queue *cq, void *tag) { grpc_completion_queue *cq, void *tag) {
listener *l; listener *l;
@ -986,6 +991,12 @@ void grpc_server_shutdown_and_notify(grpc_server *server,
/* lock, and gather up some stuff to do */ /* lock, and gather up some stuff to do */
gpr_mu_lock(&server->mu_global); gpr_mu_lock(&server->mu_global);
grpc_cq_begin_op(cq); grpc_cq_begin_op(cq);
if (server->shutdown_published) {
grpc_cq_end_op(cq, tag, 1, done_published_shutdown, NULL,
gpr_malloc(sizeof(grpc_cq_completion)));
gpr_mu_unlock(&server->mu_global);
return;
}
server->shutdown_tags = server->shutdown_tags =
gpr_realloc(server->shutdown_tags, gpr_realloc(server->shutdown_tags,
sizeof(shutdown_tag) * (server->num_shutdown_tags + 1)); sizeof(shutdown_tag) * (server->num_shutdown_tags + 1));

@ -90,6 +90,26 @@ class Server::SyncRequest GRPC_FINAL : public CompletionQueueTag {
return mrd; return mrd;
} }
static bool AsyncWait(CompletionQueue* cq, SyncRequest** req, bool* ok,
gpr_timespec deadline) {
void* tag = nullptr;
*ok = false;
switch (cq->AsyncNext(&tag, ok, deadline)) {
case CompletionQueue::TIMEOUT:
*req = nullptr;
return true;
case CompletionQueue::SHUTDOWN:
*req = nullptr;
return false;
case CompletionQueue::GOT_EVENT:
*req = static_cast<SyncRequest*>(tag);
GPR_ASSERT((*req)->in_flight_);
return true;
}
gpr_log(GPR_ERROR, "Should never reach here");
abort();
}
void SetupRequest() { cq_ = grpc_completion_queue_create(nullptr); } void SetupRequest() { cq_ = grpc_completion_queue_create(nullptr); }
void TeardownRequest() { void TeardownRequest() {
@ -303,12 +323,27 @@ bool Server::Start() {
return true; return true;
} }
void Server::Shutdown() { void Server::ShutdownInternal(gpr_timespec deadline) {
grpc::unique_lock<grpc::mutex> lock(mu_); grpc::unique_lock<grpc::mutex> lock(mu_);
if (started_ && !shutdown_) { if (started_ && !shutdown_) {
shutdown_ = true; shutdown_ = true;
grpc_server_shutdown_and_notify(server_, cq_.cq(), new ShutdownRequest()); grpc_server_shutdown_and_notify(server_, cq_.cq(), new ShutdownRequest());
cq_.Shutdown(); cq_.Shutdown();
// Spin, eating requests until the completion queue is completely shutdown.
// If the deadline expires then cancel anything that's pending and keep
// spinning forever until the work is actually drained.
// Since nothing else needs to touch state guarded by mu_, holding it
// through this loop is fine.
SyncRequest* request;
bool ok;
while (SyncRequest::AsyncWait(&cq_, &request, &ok, deadline)) {
if (request == NULL) { // deadline expired
grpc_server_cancel_all_calls(server_);
deadline = gpr_inf_future(GPR_CLOCK_MONOTONIC);
} else if (ok) {
SyncRequest::CallData call_data(this, request);
}
}
// Wait for running callbacks to finish. // Wait for running callbacks to finish.
while (num_running_cb_ != 0) { while (num_running_cb_ != 0) {

@ -5,4 +5,5 @@ test-results
packages packages
Grpc.v12.suo Grpc.v12.suo
TestResult.xml TestResult.xml
/TestResults
*.nupkg *.nupkg

@ -41,12 +41,6 @@ namespace Grpc.Core.Tests
{ {
public class ChannelTest public class ChannelTest
{ {
[TestFixtureTearDown]
public void CleanupClass()
{
GrpcEnvironment.Shutdown();
}
[Test] [Test]
public void Constructor_RejectsInvalidParams() public void Constructor_RejectsInvalidParams()
{ {
@ -56,36 +50,33 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void State_IdleAfterCreation() public void State_IdleAfterCreation()
{ {
using (var channel = new Channel("localhost", Credentials.Insecure)) var channel = new Channel("localhost", Credentials.Insecure);
{ Assert.AreEqual(ChannelState.Idle, channel.State);
Assert.AreEqual(ChannelState.Idle, channel.State); channel.ShutdownAsync().Wait();
}
} }
[Test] [Test]
public void WaitForStateChangedAsync_InvalidArgument() public void WaitForStateChangedAsync_InvalidArgument()
{ {
using (var channel = new Channel("localhost", Credentials.Insecure)) var channel = new Channel("localhost", Credentials.Insecure);
{ Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure));
Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure)); channel.ShutdownAsync().Wait();
}
} }
[Test] [Test]
public void ResolvedTarget() public void ResolvedTarget()
{ {
using (var channel = new Channel("127.0.0.1", Credentials.Insecure)) var channel = new Channel("127.0.0.1", Credentials.Insecure);
{ Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1"));
Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1")); channel.ShutdownAsync().Wait();
}
} }
[Test] [Test]
public void Dispose_IsIdempotent() public void Shutdown_AllowedOnlyOnce()
{ {
var channel = new Channel("localhost", Credentials.Insecure); var channel = new Channel("localhost", Credentials.Insecure);
channel.Dispose(); channel.ShutdownAsync().Wait();
channel.Dispose(); Assert.Throws(typeof(InvalidOperationException), () => channel.ShutdownAsync().GetAwaiter().GetResult());
} }
} }
} }

@ -63,16 +63,10 @@ namespace Grpc.Core.Tests
[TearDown] [TearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
} }
[TestFixtureTearDown]
public void CleanupClass()
{
GrpcEnvironment.Shutdown();
}
[Test] [Test]
public async Task UnaryCall() public async Task UnaryCall()
{ {
@ -207,13 +201,6 @@ namespace Grpc.Core.Tests
CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes); CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes);
} }
[Test]
public void UnaryCall_DisposedChannel()
{
channel.Dispose();
Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC"));
}
[Test] [Test]
public void UnaryCallPerformance() public void UnaryCallPerformance()
{ {

@ -62,16 +62,10 @@ namespace Grpc.Core.Tests
[TearDown] [TearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
} }
[TestFixtureTearDown]
public void CleanupClass()
{
GrpcEnvironment.Shutdown();
}
[Test] [Test]
public void WriteOptions_Unary() public void WriteOptions_Unary()
{ {

@ -62,16 +62,10 @@ namespace Grpc.Core.Tests
[TearDown] [TearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
} }
[TestFixtureTearDown]
public void CleanupClass()
{
GrpcEnvironment.Shutdown();
}
[Test] [Test]
public async Task PropagateCancellation() public async Task PropagateCancellation()
{ {

@ -64,6 +64,8 @@
<Link>Version.cs</Link> <Link>Version.cs</Link>
</Compile> </Compile>
<Compile Include="ClientBaseTest.cs" /> <Compile Include="ClientBaseTest.cs" />
<Compile Include="ShutdownTest.cs" />
<Compile Include="Internal\AsyncCallTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ClientServerTest.cs" /> <Compile Include="ClientServerTest.cs" />
<Compile Include="ServerTest.cs" /> <Compile Include="ServerTest.cs" />
@ -82,6 +84,7 @@
<Compile Include="ResponseHeadersTest.cs" /> <Compile Include="ResponseHeadersTest.cs" />
<Compile Include="CompressionTest.cs" /> <Compile Include="CompressionTest.cs" />
<Compile Include="ContextPropagationTest.cs" /> <Compile Include="ContextPropagationTest.cs" />
<Compile Include="MetadataTest.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup> <ItemGroup>

@ -43,31 +43,40 @@ namespace Grpc.Core.Tests
[Test] [Test]
public void InitializeAndShutdownGrpcEnvironment() public void InitializeAndShutdownGrpcEnvironment()
{ {
var env = GrpcEnvironment.GetInstance(); var env = GrpcEnvironment.AddRef();
Assert.IsNotNull(env.CompletionQueue); Assert.IsNotNull(env.CompletionQueue);
GrpcEnvironment.Shutdown(); GrpcEnvironment.Release();
} }
[Test] [Test]
public void SubsequentInvocations() public void SubsequentInvocations()
{ {
var env1 = GrpcEnvironment.GetInstance(); var env1 = GrpcEnvironment.AddRef();
var env2 = GrpcEnvironment.GetInstance(); var env2 = GrpcEnvironment.AddRef();
Assert.IsTrue(object.ReferenceEquals(env1, env2)); Assert.AreSame(env1, env2);
GrpcEnvironment.Shutdown(); GrpcEnvironment.Release();
GrpcEnvironment.Shutdown(); GrpcEnvironment.Release();
} }
[Test] [Test]
public void InitializeAfterShutdown() public void InitializeAfterShutdown()
{ {
var env1 = GrpcEnvironment.GetInstance(); Assert.AreEqual(0, GrpcEnvironment.GetRefCount());
GrpcEnvironment.Shutdown();
var env2 = GrpcEnvironment.GetInstance(); var env1 = GrpcEnvironment.AddRef();
GrpcEnvironment.Shutdown(); GrpcEnvironment.Release();
Assert.IsFalse(object.ReferenceEquals(env1, env2)); var env2 = GrpcEnvironment.AddRef();
GrpcEnvironment.Release();
Assert.AreNotSame(env1, env2);
}
[Test]
public void ReleaseWithoutAddRef()
{
Assert.AreEqual(0, GrpcEnvironment.GetRefCount());
Assert.Throws(typeof(InvalidOperationException), () => GrpcEnvironment.Release());
} }
[Test] [Test]

@ -0,0 +1,222 @@
#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.Runtime.InteropServices;
using System.Threading.Tasks;
using Grpc.Core.Internal;
using NUnit.Framework;
namespace Grpc.Core.Internal.Tests
{
public class AsyncCallTest
{
Channel channel;
FakeNativeCall fakeCall;
AsyncCall<string, string> asyncCall;
[SetUp]
public void Init()
{
channel = new Channel("localhost", Credentials.Insecure);
fakeCall = new FakeNativeCall();
var callDetails = new CallInvocationDetails<string, string>(channel, "someMethod", null, Marshallers.StringMarshaller, Marshallers.StringMarshaller, new CallOptions());
asyncCall = new AsyncCall<string, string>(callDetails, fakeCall);
}
[TearDown]
public void Cleanup()
{
channel.ShutdownAsync().Wait();
}
[Test]
public void AsyncUnary_CompletionSuccess()
{
var resultTask = asyncCall.UnaryCallAsync("abc");
fakeCall.UnaryResponseClientHandler(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata()), new byte[] { 1, 2, 3 }, new Metadata());
Assert.IsTrue(resultTask.IsCompleted);
Assert.IsTrue(fakeCall.IsDisposed);
Assert.AreEqual(Status.DefaultSuccess, asyncCall.GetStatus());
}
[Test]
public void AsyncUnary_CompletionFailure()
{
var resultTask = asyncCall.UnaryCallAsync("abc");
fakeCall.UnaryResponseClientHandler(false, new ClientSideStatus(new Status(StatusCode.Internal, ""), null), new byte[] { 1, 2, 3 }, new Metadata());
Assert.IsTrue(resultTask.IsCompleted);
Assert.IsTrue(fakeCall.IsDisposed);
Assert.AreEqual(StatusCode.Internal, asyncCall.GetStatus().StatusCode);
Assert.IsNull(asyncCall.GetTrailers());
var ex = Assert.Throws<RpcException>(() => resultTask.GetAwaiter().GetResult());
Assert.AreEqual(StatusCode.Internal, ex.Status.StatusCode);
}
internal class FakeNativeCall : INativeCall
{
public UnaryResponseClientHandler UnaryResponseClientHandler
{
get;
set;
}
public ReceivedStatusOnClientHandler ReceivedStatusOnClientHandler
{
get;
set;
}
public ReceivedMessageHandler ReceivedMessageHandler
{
get;
set;
}
public ReceivedResponseHeadersHandler ReceivedResponseHeadersHandler
{
get;
set;
}
public SendCompletionHandler SendCompletionHandler
{
get;
set;
}
public ReceivedCloseOnServerHandler ReceivedCloseOnServerHandler
{
get;
set;
}
public bool IsCancelled
{
get;
set;
}
public bool IsDisposed
{
get;
set;
}
public void Cancel()
{
IsCancelled = true;
}
public void CancelWithStatus(Status status)
{
IsCancelled = true;
}
public string GetPeer()
{
return "PEER";
}
public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
UnaryResponseClientHandler = callback;
}
public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
throw new NotImplementedException();
}
public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray)
{
UnaryResponseClientHandler = callback;
}
public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
ReceivedStatusOnClientHandler = callback;
}
public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray)
{
ReceivedStatusOnClientHandler = callback;
}
public void StartReceiveMessage(ReceivedMessageHandler callback)
{
ReceivedMessageHandler = callback;
}
public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
{
ReceivedResponseHeadersHandler = callback;
}
public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
{
SendCompletionHandler = callback;
}
public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
{
SendCompletionHandler = callback;
}
public void StartSendCloseFromClient(SendCompletionHandler callback)
{
SendCompletionHandler = callback;
}
public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
{
SendCompletionHandler = callback;
}
public void StartServerSide(ReceivedCloseOnServerHandler callback)
{
ReceivedCloseOnServerHandler = callback;
}
public void Dispose()
{
IsDisposed = true;
}
}
}
}

@ -0,0 +1,120 @@
#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.Runtime.InteropServices;
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 MetadataTest
{
[Test]
public void AsciiEntry()
{
var entry = new Metadata.Entry("ABC", "XYZ");
Assert.IsFalse(entry.IsBinary);
Assert.AreEqual("abc", entry.Key); // key is in lowercase.
Assert.AreEqual("XYZ", entry.Value);
CollectionAssert.AreEqual(new[] { (byte)'X', (byte)'Y', (byte)'Z' }, entry.ValueBytes);
Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc-bin", "xyz"));
Assert.AreEqual("[Entry: key=abc, value=XYZ]", entry.ToString());
}
[Test]
public void BinaryEntry()
{
var bytes = new byte[] { 1, 2, 3 };
var entry = new Metadata.Entry("ABC-BIN", bytes);
Assert.IsTrue(entry.IsBinary);
Assert.AreEqual("abc-bin", entry.Key); // key is in lowercase.
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc", bytes));
Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString());
}
[Test]
public void Entry_ConstructionPreconditions()
{
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry(null, "xyz"));
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc", (string)null));
Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry("abc-bin", (byte[])null));
}
[Test]
public void Entry_Immutable()
{
var origBytes = new byte[] { 1, 2, 3 };
var bytes = new byte[] { 1, 2, 3 };
var entry = new Metadata.Entry("ABC-BIN", bytes);
bytes[0] = 255; // changing the array passed to constructor should have any effect.
CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
entry.ValueBytes[0] = 255;
CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
}
[Test]
public void Entry_CreateUnsafe_Ascii()
{
var bytes = new byte[] { (byte)'X', (byte)'y' };
var entry = Metadata.Entry.CreateUnsafe("abc", bytes);
Assert.IsFalse(entry.IsBinary);
Assert.AreEqual("abc", entry.Key);
Assert.AreEqual("Xy", entry.Value);
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
}
[Test]
public void Entry_CreateUnsafe_Binary()
{
var bytes = new byte[] { 1, 2, 3 };
var entry = Metadata.Entry.CreateUnsafe("abc-bin", bytes);
Assert.IsTrue(entry.IsBinary);
Assert.AreEqual("abc-bin", entry.Key);
Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
CollectionAssert.AreEqual(bytes, entry.ValueBytes);
}
}
}

@ -32,13 +32,16 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Grpc.Core; using Grpc.Core;
using Grpc.Core.Internal; using Grpc.Core.Internal;
using Grpc.Core.Utils; using Grpc.Core.Utils;
using NUnit.Framework; using NUnit.Framework;
namespace Grpc.Core.Tests namespace Grpc.Core.Tests
@ -69,14 +72,82 @@ namespace Grpc.Core.Tests
[TearDown] [TearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
} }
[TestFixtureTearDown] [Test]
public void CleanupClass() public async Task ResponseHeadersAsync_UnaryCall()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
await context.WriteResponseHeadersAsync(headers);
return "PASS";
});
var call = Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "");
var responseHeaders = await call.ResponseHeadersAsync;
Assert.AreEqual(headers.Count, responseHeaders.Count);
Assert.AreEqual("ascii-header", responseHeaders[0].Key);
Assert.AreEqual("abcdefg", responseHeaders[0].Value);
Assert.AreEqual("PASS", await call.ResponseAsync);
}
[Test]
public async Task ResponseHeadersAsync_ClientStreamingCall()
{
helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) =>
{
await context.WriteResponseHeadersAsync(headers);
return "PASS";
});
var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall());
await call.RequestStream.CompleteAsync();
var responseHeaders = await call.ResponseHeadersAsync;
Assert.AreEqual("ascii-header", responseHeaders[0].Key);
Assert.AreEqual("PASS", await call.ResponseAsync);
}
[Test]
public async Task ResponseHeadersAsync_ServerStreamingCall()
{
helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
{
await context.WriteResponseHeadersAsync(headers);
await responseStream.WriteAsync("PASS");
});
var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
var responseHeaders = await call.ResponseHeadersAsync;
Assert.AreEqual("ascii-header", responseHeaders[0].Key);
CollectionAssert.AreEqual(new[] { "PASS" }, await call.ResponseStream.ToListAsync());
}
[Test]
public async Task ResponseHeadersAsync_DuplexStreamingCall()
{ {
GrpcEnvironment.Shutdown(); helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
{
await context.WriteResponseHeadersAsync(headers);
while (await requestStream.MoveNext())
{
await responseStream.WriteAsync(requestStream.Current);
}
});
var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall());
var responseHeaders = await call.ResponseHeadersAsync;
var messages = new[] { "PASS" };
await call.RequestStream.WriteAllAsync(messages);
Assert.AreEqual("ascii-header", responseHeaders[0].Key);
CollectionAssert.AreEqual(messages, await call.ResponseStream.ToListAsync());
} }
[Test] [Test]

@ -51,7 +51,6 @@ namespace Grpc.Core.Tests
}; };
server.Start(); server.Start();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]
@ -67,8 +66,7 @@ namespace Grpc.Core.Tests
Assert.Greater(boundPort.BoundPort, 0); Assert.Greater(boundPort.BoundPort, 0);
server.Start(); server.Start();
server.ShutdownAsync(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]
@ -83,7 +81,6 @@ namespace Grpc.Core.Tests
Assert.Throws(typeof(InvalidOperationException), () => server.Services.Add(ServerServiceDefinition.CreateBuilder("serviceName").Build())); Assert.Throws(typeof(InvalidOperationException), () => server.Services.Add(ServerServiceDefinition.CreateBuilder("serviceName").Build()));
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
} }
} }

@ -0,0 +1,77 @@
#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 ShutdownTest
{
const string Host = "127.0.0.1";
MockServiceHelper helper;
Server server;
Channel channel;
[SetUp]
public void Init()
{
helper = new MockServiceHelper(Host);
server = helper.GetServer();
server.Start();
channel = helper.GetChannel();
}
[Test]
public async Task AbandonedCall()
{
helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) =>
{
await requestStream.ToListAsync();
});
var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddMilliseconds(1))));
channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
}
}

@ -65,16 +65,10 @@ namespace Grpc.Core.Tests
[TearDown] [TearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
} }
[TestFixtureTearDown]
public void CleanupClass()
{
GrpcEnvironment.Shutdown();
}
[Test] [Test]
public void InfiniteDeadline() public void InfiniteDeadline()
{ {

@ -44,14 +44,16 @@ namespace Grpc.Core
{ {
readonly IClientStreamWriter<TRequest> requestStream; readonly IClientStreamWriter<TRequest> requestStream;
readonly Task<TResponse> responseAsync; readonly Task<TResponse> responseAsync;
readonly Task<Metadata> responseHeadersAsync;
readonly Func<Status> getStatusFunc; readonly Func<Status> getStatusFunc;
readonly Func<Metadata> getTrailersFunc; readonly Func<Metadata> getTrailersFunc;
readonly Action disposeAction; readonly Action disposeAction;
public AsyncClientStreamingCall(IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) public AsyncClientStreamingCall(IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction)
{ {
this.requestStream = requestStream; this.requestStream = requestStream;
this.responseAsync = responseAsync; this.responseAsync = responseAsync;
this.responseHeadersAsync = responseHeadersAsync;
this.getStatusFunc = getStatusFunc; this.getStatusFunc = getStatusFunc;
this.getTrailersFunc = getTrailersFunc; this.getTrailersFunc = getTrailersFunc;
this.disposeAction = disposeAction; this.disposeAction = disposeAction;
@ -68,6 +70,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.responseHeadersAsync;
}
}
/// <summary> /// <summary>
/// Async stream to send streaming requests. /// Async stream to send streaming requests.
/// </summary> /// </summary>

@ -32,6 +32,7 @@
#endregion #endregion
using System; using System;
using System.Threading.Tasks;
namespace Grpc.Core namespace Grpc.Core
{ {
@ -42,14 +43,16 @@ namespace Grpc.Core
{ {
readonly IClientStreamWriter<TRequest> requestStream; readonly IClientStreamWriter<TRequest> requestStream;
readonly IAsyncStreamReader<TResponse> responseStream; readonly IAsyncStreamReader<TResponse> responseStream;
readonly Task<Metadata> responseHeadersAsync;
readonly Func<Status> getStatusFunc; readonly Func<Status> getStatusFunc;
readonly Func<Metadata> getTrailersFunc; readonly Func<Metadata> getTrailersFunc;
readonly Action disposeAction; readonly Action disposeAction;
public AsyncDuplexStreamingCall(IClientStreamWriter<TRequest> requestStream, IAsyncStreamReader<TResponse> responseStream, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) public AsyncDuplexStreamingCall(IClientStreamWriter<TRequest> requestStream, IAsyncStreamReader<TResponse> responseStream, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction)
{ {
this.requestStream = requestStream; this.requestStream = requestStream;
this.responseStream = responseStream; this.responseStream = responseStream;
this.responseHeadersAsync = responseHeadersAsync;
this.getStatusFunc = getStatusFunc; this.getStatusFunc = getStatusFunc;
this.getTrailersFunc = getTrailersFunc; this.getTrailersFunc = getTrailersFunc;
this.disposeAction = disposeAction; this.disposeAction = disposeAction;
@ -77,6 +80,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.responseHeadersAsync;
}
}
/// <summary> /// <summary>
/// Gets the call status if the call has already finished. /// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise. /// Throws InvalidOperationException otherwise.

@ -32,6 +32,7 @@
#endregion #endregion
using System; using System;
using System.Threading.Tasks;
namespace Grpc.Core namespace Grpc.Core
{ {
@ -41,13 +42,15 @@ namespace Grpc.Core
public sealed class AsyncServerStreamingCall<TResponse> : IDisposable public sealed class AsyncServerStreamingCall<TResponse> : IDisposable
{ {
readonly IAsyncStreamReader<TResponse> responseStream; readonly IAsyncStreamReader<TResponse> responseStream;
readonly Task<Metadata> responseHeadersAsync;
readonly Func<Status> getStatusFunc; readonly Func<Status> getStatusFunc;
readonly Func<Metadata> getTrailersFunc; readonly Func<Metadata> getTrailersFunc;
readonly Action disposeAction; readonly Action disposeAction;
public AsyncServerStreamingCall(IAsyncStreamReader<TResponse> responseStream, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) public AsyncServerStreamingCall(IAsyncStreamReader<TResponse> responseStream, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction)
{ {
this.responseStream = responseStream; this.responseStream = responseStream;
this.responseHeadersAsync = responseHeadersAsync;
this.getStatusFunc = getStatusFunc; this.getStatusFunc = getStatusFunc;
this.getTrailersFunc = getTrailersFunc; this.getTrailersFunc = getTrailersFunc;
this.disposeAction = disposeAction; this.disposeAction = disposeAction;
@ -64,6 +67,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.responseHeadersAsync;
}
}
/// <summary> /// <summary>
/// Gets the call status if the call has already finished. /// Gets the call status if the call has already finished.
/// Throws InvalidOperationException otherwise. /// Throws InvalidOperationException otherwise.

@ -43,13 +43,15 @@ namespace Grpc.Core
public sealed class AsyncUnaryCall<TResponse> : IDisposable public sealed class AsyncUnaryCall<TResponse> : IDisposable
{ {
readonly Task<TResponse> responseAsync; readonly Task<TResponse> responseAsync;
readonly Task<Metadata> responseHeadersAsync;
readonly Func<Status> getStatusFunc; readonly Func<Status> getStatusFunc;
readonly Func<Metadata> getTrailersFunc; readonly Func<Metadata> getTrailersFunc;
readonly Action disposeAction; readonly Action disposeAction;
public AsyncUnaryCall(Task<TResponse> responseAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction) public AsyncUnaryCall(Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> getTrailersFunc, Action disposeAction)
{ {
this.responseAsync = responseAsync; this.responseAsync = responseAsync;
this.responseHeadersAsync = responseHeadersAsync;
this.getStatusFunc = getStatusFunc; this.getStatusFunc = getStatusFunc;
this.getTrailersFunc = getTrailersFunc; this.getTrailersFunc = getTrailersFunc;
this.disposeAction = disposeAction; this.disposeAction = disposeAction;
@ -66,6 +68,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Asynchronous access to response headers.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return this.responseHeadersAsync;
}
}
/// <summary> /// <summary>
/// Allows awaiting this object directly. /// Allows awaiting this object directly.
/// </summary> /// </summary>

@ -74,7 +74,7 @@ namespace Grpc.Core
{ {
var asyncCall = new AsyncCall<TRequest, TResponse>(call); var asyncCall = new AsyncCall<TRequest, TResponse>(call);
var asyncResult = asyncCall.UnaryCallAsync(req); var asyncResult = asyncCall.UnaryCallAsync(req);
return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
} }
/// <summary> /// <summary>
@ -93,7 +93,7 @@ namespace Grpc.Core
var asyncCall = new AsyncCall<TRequest, TResponse>(call); var asyncCall = new AsyncCall<TRequest, TResponse>(call);
asyncCall.StartServerStreamingCall(req); asyncCall.StartServerStreamingCall(req);
var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall);
return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
} }
/// <summary> /// <summary>
@ -110,7 +110,7 @@ namespace Grpc.Core
var asyncCall = new AsyncCall<TRequest, TResponse>(call); var asyncCall = new AsyncCall<TRequest, TResponse>(call);
var resultTask = asyncCall.ClientStreamingCallAsync(); var resultTask = asyncCall.ClientStreamingCallAsync();
var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall);
return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
} }
/// <summary> /// <summary>
@ -130,7 +130,7 @@ namespace Grpc.Core
asyncCall.StartDuplexStreamingCall(); asyncCall.StartDuplexStreamingCall();
var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall);
var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall);
return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.ResponseHeadersAsync, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel);
} }
} }
} }

@ -45,15 +45,19 @@ namespace Grpc.Core
/// <summary> /// <summary>
/// gRPC Channel /// gRPC Channel
/// </summary> /// </summary>
public class Channel : IDisposable public class Channel
{ {
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>(); static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>();
readonly object myLock = new object();
readonly AtomicCounter activeCallCounter = new AtomicCounter();
readonly string target; readonly string target;
readonly GrpcEnvironment environment; readonly GrpcEnvironment environment;
readonly ChannelSafeHandle handle; readonly ChannelSafeHandle handle;
readonly List<ChannelOption> options; readonly List<ChannelOption> options;
bool disposed;
bool shutdownRequested;
/// <summary> /// <summary>
/// Creates a channel that connects to a specific host. /// Creates a channel that connects to a specific host.
@ -65,7 +69,7 @@ namespace Grpc.Core
public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null) public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null)
{ {
this.target = Preconditions.CheckNotNull(target, "target"); this.target = Preconditions.CheckNotNull(target, "target");
this.environment = GrpcEnvironment.GetInstance(); this.environment = GrpcEnvironment.AddRef();
this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
EnsureUserAgentChannelOption(this.options); EnsureUserAgentChannelOption(this.options);
@ -172,12 +176,26 @@ namespace Grpc.Core
} }
/// <summary> /// <summary>
/// Destroys the underlying channel. /// Waits until there are no more active calls for this channel and then cleans up
/// resources used by this channel.
/// </summary> /// </summary>
public void Dispose() public async Task ShutdownAsync()
{ {
Dispose(true); lock (myLock)
GC.SuppressFinalize(this); {
Preconditions.CheckState(!shutdownRequested);
shutdownRequested = true;
}
var activeCallCount = activeCallCounter.Count;
if (activeCallCount > 0)
{
Logger.Warning("Channel shutdown was called but there are still {0} active calls for that channel.", activeCallCount);
}
handle.Dispose();
await Task.Run(() => GrpcEnvironment.Release());
} }
internal ChannelSafeHandle Handle internal ChannelSafeHandle Handle
@ -196,13 +214,20 @@ namespace Grpc.Core
} }
} }
protected virtual void Dispose(bool disposing) internal void AddCallReference(object call)
{ {
if (disposing && handle != null && !disposed) activeCallCounter.Increment();
{
disposed = true; bool success = false;
handle.Dispose(); handle.DangerousAddRef(ref success);
} Preconditions.CheckState(success);
}
internal void RemoveCallReference(object call)
{
handle.DangerousRelease();
activeCallCounter.Decrement();
} }
private static void EnsureUserAgentChannelOption(List<ChannelOption> options) private static void EnsureUserAgentChannelOption(List<ChannelOption> options)

@ -119,7 +119,8 @@ namespace Grpc.Core
internal static string GetAuthUriBase(string target) internal static string GetAuthUriBase(string target)
{ {
var match = ChannelTargetPattern.Match(target); var match = ChannelTargetPattern.Match(target);
if (!match.Success) { if (!match.Success)
{
return null; return null;
} }
return "https://" + match.Groups[2].Value + "/"; return "https://" + match.Groups[2].Value + "/";

@ -132,7 +132,6 @@ namespace Grpc.Core
bool propagateDeadline; bool propagateDeadline;
bool propagateCancellation; bool propagateCancellation;
/// <summary> /// <summary>
/// Creates new context propagation options. /// Creates new context propagation options.
/// </summary> /// </summary>

@ -49,6 +49,7 @@
<Compile Include="AsyncDuplexStreamingCall.cs" /> <Compile Include="AsyncDuplexStreamingCall.cs" />
<Compile Include="AsyncServerStreamingCall.cs" /> <Compile Include="AsyncServerStreamingCall.cs" />
<Compile Include="IClientStreamWriter.cs" /> <Compile Include="IClientStreamWriter.cs" />
<Compile Include="Internal\INativeCall.cs" />
<Compile Include="IServerStreamWriter.cs" /> <Compile Include="IServerStreamWriter.cs" />
<Compile Include="IAsyncStreamWriter.cs" /> <Compile Include="IAsyncStreamWriter.cs" />
<Compile Include="IAsyncStreamReader.cs" /> <Compile Include="IAsyncStreamReader.cs" />

@ -58,6 +58,7 @@ namespace Grpc.Core
static object staticLock = new object(); static object staticLock = new object();
static GrpcEnvironment instance; static GrpcEnvironment instance;
static int refCount;
static ILogger logger = new ConsoleLogger(); static ILogger logger = new ConsoleLogger();
@ -67,13 +68,14 @@ namespace Grpc.Core
bool isClosed; bool isClosed;
/// <summary> /// <summary>
/// Returns an instance of initialized gRPC environment. /// Returns a reference-counted instance of initialized gRPC environment.
/// Subsequent invocations return the same instance unless Shutdown has been called first. /// Subsequent invocations return the same instance unless reference count has dropped to zero previously.
/// </summary> /// </summary>
internal static GrpcEnvironment GetInstance() internal static GrpcEnvironment AddRef()
{ {
lock (staticLock) lock (staticLock)
{ {
refCount++;
if (instance == null) if (instance == null)
{ {
instance = new GrpcEnvironment(); instance = new GrpcEnvironment();
@ -83,14 +85,16 @@ namespace Grpc.Core
} }
/// <summary> /// <summary>
/// Shuts down the gRPC environment if it was initialized before. /// Decrements the reference count for currently active environment and shuts down the gRPC environment if reference count drops to zero.
/// Blocks until the environment has been fully shutdown. /// (and blocks until the environment has been fully shutdown).
/// </summary> /// </summary>
public static void Shutdown() internal static void Release()
{ {
lock (staticLock) lock (staticLock)
{ {
if (instance != null) Preconditions.CheckState(refCount > 0);
refCount--;
if (refCount == 0)
{ {
instance.Close(); instance.Close();
instance = null; instance = null;
@ -98,6 +102,14 @@ namespace Grpc.Core
} }
} }
internal static int GetRefCount()
{
lock (staticLock)
{
return refCount;
}
}
/// <summary> /// <summary>
/// Gets application-wide logger used by gRPC. /// Gets application-wide logger used by gRPC.
/// </summary> /// </summary>
@ -125,12 +137,10 @@ namespace Grpc.Core
private GrpcEnvironment() private GrpcEnvironment()
{ {
NativeLogRedirector.Redirect(); NativeLogRedirector.Redirect();
grpcsharp_init(); GrpcNativeInit();
completionRegistry = new CompletionRegistry(this); completionRegistry = new CompletionRegistry(this);
threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE); threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE);
threadPool.Start(); threadPool.Start();
// TODO: use proper logging here
Logger.Info("gRPC initialized.");
} }
/// <summary> /// <summary>
@ -175,6 +185,16 @@ namespace Grpc.Core
return Marshal.PtrToStringAnsi(ptr); return Marshal.PtrToStringAnsi(ptr);
} }
internal static void GrpcNativeInit()
{
grpcsharp_init();
}
internal static void GrpcNativeShutdown()
{
grpcsharp_shutdown();
}
/// <summary> /// <summary>
/// Shuts down this environment. /// Shuts down this environment.
/// </summary> /// </summary>
@ -185,12 +205,10 @@ namespace Grpc.Core
throw new InvalidOperationException("Close has already been called"); throw new InvalidOperationException("Close has already been called");
} }
threadPool.Stop(); threadPool.Stop();
grpcsharp_shutdown(); GrpcNativeShutdown();
isClosed = true; isClosed = true;
debugStats.CheckOK(); debugStats.CheckOK();
Logger.Info("gRPC shutdown.");
} }
} }
} }

@ -51,22 +51,35 @@ namespace Grpc.Core.Internal
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>(); static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>();
readonly CallInvocationDetails<TRequest, TResponse> details; readonly CallInvocationDetails<TRequest, TResponse> details;
readonly INativeCall injectedNativeCall; // for testing
// Completion of a pending unary response if not null. // Completion of a pending unary response if not null.
TaskCompletionSource<TResponse> unaryResponseTcs; TaskCompletionSource<TResponse> unaryResponseTcs;
// Indicates that steaming call has finished.
TaskCompletionSource<object> streamingCallFinishedTcs = new TaskCompletionSource<object>();
// Response headers set here once received.
TaskCompletionSource<Metadata> responseHeadersTcs = new TaskCompletionSource<Metadata>();
// Set after status is received. Used for both unary and streaming response calls. // Set after status is received. Used for both unary and streaming response calls.
ClientSideStatus? finishedStatus; ClientSideStatus? finishedStatus;
bool readObserverCompleted; // True if readObserver has already been completed.
public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails) public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails)
: base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer) : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer, callDetails.Channel.Environment)
{ {
this.details = callDetails.WithOptions(callDetails.Options.Normalize()); this.details = callDetails.WithOptions(callDetails.Options.Normalize());
this.initialMetadataSent = true; // we always send metadata at the very beginning of the call. this.initialMetadataSent = true; // we always send metadata at the very beginning of the call.
} }
/// <summary>
/// This constructor should only be used for testing.
/// </summary>
public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails, INativeCall injectedNativeCall) : this(callDetails)
{
this.injectedNativeCall = injectedNativeCall;
}
// TODO: this method is not Async, so it shouldn't be in AsyncCall class, but // TODO: this method is not Async, so it shouldn't be in AsyncCall class, but
// it is reusing fair amount of code in this class, so we are leaving it here. // it is reusing fair amount of code in this class, so we are leaving it here.
/// <summary> /// <summary>
@ -100,7 +113,7 @@ namespace Grpc.Core.Internal
bool success = (ev.success != 0); bool success = (ev.success != 0);
try try
{ {
HandleUnaryResponse(success, ctx); HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata());
} }
catch (Exception e) catch (Exception e)
{ {
@ -125,7 +138,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started); Preconditions.CheckState(!started);
started = true; started = true;
Initialize(details.Channel.Environment.CompletionQueue); Initialize(environment.CompletionQueue);
halfcloseRequested = true; halfcloseRequested = true;
readingDone = true; readingDone = true;
@ -152,7 +165,7 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started); Preconditions.CheckState(!started);
started = true; started = true;
Initialize(details.Channel.Environment.CompletionQueue); Initialize(environment.CompletionQueue);
readingDone = true; readingDone = true;
@ -176,10 +189,9 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started); Preconditions.CheckState(!started);
started = true; started = true;
Initialize(details.Channel.Environment.CompletionQueue); Initialize(environment.CompletionQueue);
halfcloseRequested = true; halfcloseRequested = true;
halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called.
byte[] payload = UnsafeSerialize(msg); byte[] payload = UnsafeSerialize(msg);
@ -187,6 +199,7 @@ namespace Grpc.Core.Internal
{ {
call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall()); call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall());
} }
call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
} }
} }
@ -201,12 +214,13 @@ namespace Grpc.Core.Internal
Preconditions.CheckState(!started); Preconditions.CheckState(!started);
started = true; started = true;
Initialize(details.Channel.Environment.CompletionQueue); Initialize(environment.CompletionQueue);
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{ {
call.StartDuplexStreaming(HandleFinished, metadataArray); call.StartDuplexStreaming(HandleFinished, metadataArray);
} }
call.StartReceiveInitialMetadata(HandleReceivedResponseHeaders);
} }
} }
@ -247,6 +261,28 @@ namespace Grpc.Core.Internal
} }
} }
/// <summary>
/// Get the task that completes once if streaming call finishes with ok status and throws RpcException with given status otherwise.
/// </summary>
public Task StreamingCallFinishedTask
{
get
{
return streamingCallFinishedTcs.Task;
}
}
/// <summary>
/// Get the task that completes once response headers are received.
/// </summary>
public Task<Metadata> ResponseHeadersAsync
{
get
{
return responseHeadersTcs.Task;
}
}
/// <summary> /// <summary>
/// Gets the resulting status if the call has already finished. /// Gets the resulting status if the call has already finished.
/// Throws InvalidOperationException otherwise. /// Throws InvalidOperationException otherwise.
@ -281,51 +317,31 @@ namespace Grpc.Core.Internal
} }
} }
/// <summary> protected override void OnAfterReleaseResources()
/// On client-side, we only fire readCompletionDelegate once all messages have been read
/// and status has been received.
/// </summary>
protected override void ProcessLastRead(AsyncCompletionDelegate<TResponse> completionDelegate)
{ {
if (completionDelegate != null && readingDone && finishedStatus.HasValue) details.Channel.RemoveCallReference(this);
{
bool shouldComplete;
lock (myLock)
{
shouldComplete = !readObserverCompleted;
readObserverCompleted = true;
}
if (shouldComplete)
{
var status = finishedStatus.Value.Status;
if (status.StatusCode != StatusCode.OK)
{
FireCompletion(completionDelegate, default(TResponse), new RpcException(status));
}
else
{
FireCompletion(completionDelegate, default(TResponse), null);
}
}
}
} }
protected override void OnReleaseResources() private void Initialize(CompletionQueueSafeHandle cq)
{ {
details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement(); var call = CreateNativeCall(cq);
details.Channel.AddCallReference(this);
InitializeInternal(call);
RegisterCancellationCallback();
} }
private void Initialize(CompletionQueueSafeHandle cq) private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq)
{ {
if (injectedNativeCall != null)
{
return injectedNativeCall; // allows injecting a mock INativeCall in tests.
}
var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance;
var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry, return details.Channel.Handle.CreateCall(environment.CompletionRegistry,
parentCall, ContextPropagationToken.DefaultMask, cq, parentCall, ContextPropagationToken.DefaultMask, cq,
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value));
details.Channel.Environment.DebugStats.ActiveClientCalls.Increment();
InitializeInternal(call);
RegisterCancellationCallback();
} }
// Make sure that once cancellationToken for this call is cancelled, Cancel() will be called. // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called.
@ -348,31 +364,31 @@ namespace Grpc.Core.Internal
} }
/// <summary> /// <summary>
/// Handler for unary response completion. /// Handles receive status completion for calls with streaming response.
/// </summary> /// </summary>
private void HandleUnaryResponse(bool success, BatchContextSafeHandle ctx) private void HandleReceivedResponseHeaders(bool success, Metadata responseHeaders)
{ {
var fullStatus = ctx.GetReceivedStatusOnClient(); responseHeadersTcs.SetResult(responseHeaders);
}
/// <summary>
/// Handler for unary response completion.
/// </summary>
private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
{
lock (myLock) lock (myLock)
{ {
finished = true; finished = true;
finishedStatus = fullStatus; finishedStatus = receivedStatus;
halfclosed = true;
ReleaseResourcesIfPossible(); ReleaseResourcesIfPossible();
} }
if (!success) responseHeadersTcs.SetResult(responseHeaders);
{
unaryResponseTcs.SetException(new RpcException(new Status(StatusCode.Internal, "Internal error occured.")));
return;
}
var status = fullStatus.Status; var status = receivedStatus.Status;
if (status.StatusCode != StatusCode.OK) if (!success || status.StatusCode != StatusCode.OK)
{ {
unaryResponseTcs.SetException(new RpcException(status)); unaryResponseTcs.SetException(new RpcException(status));
return; return;
@ -380,7 +396,7 @@ namespace Grpc.Core.Internal
// TODO: handle deserialization error // TODO: handle deserialization error
TResponse msg; TResponse msg;
TryDeserialize(ctx.GetReceivedMessage(), out msg); TryDeserialize(receivedMessage, out msg);
unaryResponseTcs.SetResult(msg); unaryResponseTcs.SetResult(msg);
} }
@ -388,22 +404,25 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// Handles receive status completion for calls with streaming response. /// Handles receive status completion for calls with streaming response.
/// </summary> /// </summary>
private void HandleFinished(bool success, BatchContextSafeHandle ctx) private void HandleFinished(bool success, ClientSideStatus receivedStatus)
{ {
var fullStatus = ctx.GetReceivedStatusOnClient();
AsyncCompletionDelegate<TResponse> origReadCompletionDelegate = null;
lock (myLock) lock (myLock)
{ {
finished = true; finished = true;
finishedStatus = fullStatus; finishedStatus = receivedStatus;
origReadCompletionDelegate = readCompletionDelegate;
ReleaseResourcesIfPossible(); ReleaseResourcesIfPossible();
} }
ProcessLastRead(origReadCompletionDelegate); var status = receivedStatus.Status;
if (!success || status.StatusCode != StatusCode.OK)
{
streamingCallFinishedTcs.SetException(new RpcException(status));
return;
}
streamingCallFinishedTcs.SetResult(null);
} }
} }
} }

@ -54,30 +54,30 @@ namespace Grpc.Core.Internal
readonly Func<TWrite, byte[]> serializer; readonly Func<TWrite, byte[]> serializer;
readonly Func<byte[], TRead> deserializer; readonly Func<byte[], TRead> deserializer;
protected readonly GrpcEnvironment environment;
protected readonly object myLock = new object(); protected readonly object myLock = new object();
protected CallSafeHandle call; protected INativeCall call;
protected bool disposed; protected bool disposed;
protected bool started; protected bool started;
protected bool errorOccured;
protected bool cancelRequested; protected bool cancelRequested;
protected AsyncCompletionDelegate<object> sendCompletionDelegate; // Completion of a pending send or sendclose if not null. protected AsyncCompletionDelegate<object> sendCompletionDelegate; // Completion of a pending send or sendclose if not null.
protected AsyncCompletionDelegate<TRead> readCompletionDelegate; // Completion of a pending send or sendclose if not null. protected AsyncCompletionDelegate<TRead> readCompletionDelegate; // Completion of a pending send or sendclose if not null.
protected bool readingDone; protected bool readingDone; // True if last read (i.e. read with null payload) was already received.
protected bool halfcloseRequested; protected bool halfcloseRequested; // True if send close have been initiated.
protected bool halfclosed;
protected bool finished; // True if close has been received from the peer. protected bool finished; // True if close has been received from the peer.
protected bool initialMetadataSent; protected bool initialMetadataSent;
protected long streamingWritesCounter; protected long streamingWritesCounter; // Number of streaming send operations started so far.
public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer) public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer, GrpcEnvironment environment)
{ {
this.serializer = Preconditions.CheckNotNull(serializer); this.serializer = Preconditions.CheckNotNull(serializer);
this.deserializer = Preconditions.CheckNotNull(deserializer); this.deserializer = Preconditions.CheckNotNull(deserializer);
this.environment = Preconditions.CheckNotNull(environment);
} }
/// <summary> /// <summary>
@ -114,7 +114,7 @@ namespace Grpc.Core.Internal
} }
} }
protected void InitializeInternal(CallSafeHandle call) protected void InitializeInternal(INativeCall call)
{ {
lock (myLock) lock (myLock)
{ {
@ -159,16 +159,6 @@ namespace Grpc.Core.Internal
} }
} }
// TODO(jtattermusch): find more fitting name for this method.
/// <summary>
/// Default behavior just completes the read observer, but more sofisticated behavior might be required
/// by subclasses.
/// </summary>
protected virtual void ProcessLastRead(AsyncCompletionDelegate<TRead> completionDelegate)
{
FireCompletion(completionDelegate, default(TRead), null);
}
/// <summary> /// <summary>
/// If there are no more pending actions and no new actions can be started, releases /// If there are no more pending actions and no new actions can be started, releases
/// the underlying native resources. /// the underlying native resources.
@ -177,7 +167,7 @@ namespace Grpc.Core.Internal
{ {
if (!disposed && call != null) if (!disposed && call != null)
{ {
bool noMoreSendCompletions = halfclosed || (cancelRequested && sendCompletionDelegate == null); bool noMoreSendCompletions = sendCompletionDelegate == null && (halfcloseRequested || cancelRequested || finished);
if (noMoreSendCompletions && readingDone && finished) if (noMoreSendCompletions && readingDone && finished)
{ {
ReleaseResources(); ReleaseResources();
@ -189,34 +179,33 @@ namespace Grpc.Core.Internal
private void ReleaseResources() private void ReleaseResources()
{ {
OnReleaseResources();
if (call != null) if (call != null)
{ {
call.Dispose(); call.Dispose();
} }
disposed = true; disposed = true;
OnAfterReleaseResources();
} }
protected virtual void OnReleaseResources() protected virtual void OnAfterReleaseResources()
{ {
} }
protected void CheckSendingAllowed() protected void CheckSendingAllowed()
{ {
Preconditions.CheckState(started); Preconditions.CheckState(started);
Preconditions.CheckState(!errorOccured);
CheckNotCancelled(); CheckNotCancelled();
Preconditions.CheckState(!disposed); Preconditions.CheckState(!disposed);
Preconditions.CheckState(!halfcloseRequested, "Already halfclosed."); Preconditions.CheckState(!halfcloseRequested, "Already halfclosed.");
Preconditions.CheckState(!finished, "Already finished.");
Preconditions.CheckState(sendCompletionDelegate == null, "Only one write can be pending at a time"); Preconditions.CheckState(sendCompletionDelegate == null, "Only one write can be pending at a time");
} }
protected void CheckReadingAllowed() protected virtual void CheckReadingAllowed()
{ {
Preconditions.CheckState(started); Preconditions.CheckState(started);
Preconditions.CheckState(!disposed); Preconditions.CheckState(!disposed);
Preconditions.CheckState(!errorOccured);
Preconditions.CheckState(!readingDone, "Stream has already been closed."); Preconditions.CheckState(!readingDone, "Stream has already been closed.");
Preconditions.CheckState(readCompletionDelegate == null, "Only one read can be pending at a time"); Preconditions.CheckState(readCompletionDelegate == null, "Only one read can be pending at a time");
@ -280,7 +269,7 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// Handles send completion. /// Handles send completion.
/// </summary> /// </summary>
protected void HandleSendFinished(bool success, BatchContextSafeHandle ctx) protected void HandleSendFinished(bool success)
{ {
AsyncCompletionDelegate<object> origCompletionDelegate = null; AsyncCompletionDelegate<object> origCompletionDelegate = null;
lock (myLock) lock (myLock)
@ -304,12 +293,11 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// Handles halfclose completion. /// Handles halfclose completion.
/// </summary> /// </summary>
protected void HandleHalfclosed(bool success, BatchContextSafeHandle ctx) protected void HandleHalfclosed(bool success)
{ {
AsyncCompletionDelegate<object> origCompletionDelegate = null; AsyncCompletionDelegate<object> origCompletionDelegate = null;
lock (myLock) lock (myLock)
{ {
halfclosed = true;
origCompletionDelegate = sendCompletionDelegate; origCompletionDelegate = sendCompletionDelegate;
sendCompletionDelegate = null; sendCompletionDelegate = null;
@ -329,23 +317,17 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// Handles streaming read completion. /// Handles streaming read completion.
/// </summary> /// </summary>
protected void HandleReadFinished(bool success, BatchContextSafeHandle ctx) protected void HandleReadFinished(bool success, byte[] receivedMessage)
{ {
var payload = ctx.GetReceivedMessage();
AsyncCompletionDelegate<TRead> origCompletionDelegate = null; AsyncCompletionDelegate<TRead> origCompletionDelegate = null;
lock (myLock) lock (myLock)
{ {
origCompletionDelegate = readCompletionDelegate; origCompletionDelegate = readCompletionDelegate;
if (payload != null) readCompletionDelegate = null;
{
readCompletionDelegate = null; if (receivedMessage == null)
}
else
{ {
// This was the last read. Keeping the readCompletionDelegate // This was the last read.
// to be either fired by this handler or by client-side finished
// handler.
readingDone = true; readingDone = true;
} }
@ -354,17 +336,17 @@ namespace Grpc.Core.Internal
// TODO: handle the case when error occured... // TODO: handle the case when error occured...
if (payload != null) if (receivedMessage != null)
{ {
// TODO: handle deserialization error // TODO: handle deserialization error
TRead msg; TRead msg;
TryDeserialize(payload, out msg); TryDeserialize(receivedMessage, out msg);
FireCompletion(origCompletionDelegate, msg, null); FireCompletion(origCompletionDelegate, msg, null);
} }
else else
{ {
ProcessLastRead(origCompletionDelegate); FireCompletion(origCompletionDelegate, default(TRead), null);
} }
} }
} }

@ -49,17 +49,18 @@ namespace Grpc.Core.Internal
{ {
readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>(); readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>();
readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
readonly GrpcEnvironment environment; readonly Server server;
public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment) : base(serializer, deserializer) public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment, Server server) : base(serializer, deserializer, environment)
{ {
this.environment = Preconditions.CheckNotNull(environment); this.server = Preconditions.CheckNotNull(server);
} }
public void Initialize(CallSafeHandle call) public void Initialize(CallSafeHandle call)
{ {
call.SetCompletionRegistry(environment.CompletionRegistry); call.SetCompletionRegistry(environment.CompletionRegistry);
environment.DebugStats.ActiveServerCalls.Increment();
server.AddCallReference(this);
InitializeInternal(call); InitializeInternal(call);
} }
@ -168,18 +169,22 @@ namespace Grpc.Core.Internal
} }
} }
protected override void OnReleaseResources() protected override void CheckReadingAllowed()
{ {
environment.DebugStats.ActiveServerCalls.Decrement(); base.CheckReadingAllowed();
Preconditions.CheckArgument(!cancelRequested);
}
protected override void OnAfterReleaseResources()
{
server.RemoveCallReference(this);
} }
/// <summary> /// <summary>
/// Handles the server side close completion. /// Handles the server side close completion.
/// </summary> /// </summary>
private void HandleFinishedServerside(bool success, BatchContextSafeHandle ctx) private void HandleFinishedServerside(bool success, bool cancelled)
{ {
bool cancelled = ctx.GetReceivedCloseOnServerCancelled();
lock (myLock) lock (myLock)
{ {
finished = true; finished = true;

@ -134,7 +134,7 @@ namespace Grpc.Core.Internal
} }
// Gets data of server_rpc_new completion. // Gets data of server_rpc_new completion.
public ServerRpcNew GetServerRpcNew() public ServerRpcNew GetServerRpcNew(Server server)
{ {
var call = grpcsharp_batch_context_server_rpc_new_call(this); var call = grpcsharp_batch_context_server_rpc_new_call(this);
@ -145,7 +145,7 @@ namespace Grpc.Core.Internal
IntPtr metadataArrayPtr = grpcsharp_batch_context_server_rpc_new_request_metadata(this); IntPtr metadataArrayPtr = grpcsharp_batch_context_server_rpc_new_request_metadata(this);
var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr); var metadata = MetadataArraySafeHandle.ReadMetadataFromPtrUnsafe(metadataArrayPtr);
return new ServerRpcNew(call, method, host, deadline, metadata); return new ServerRpcNew(server, call, method, host, deadline, metadata);
} }
// Gets data of receive_close_on_server completion. // Gets data of receive_close_on_server completion.
@ -198,14 +198,16 @@ namespace Grpc.Core.Internal
/// </summary> /// </summary>
internal struct ServerRpcNew internal struct ServerRpcNew
{ {
readonly Server server;
readonly CallSafeHandle call; readonly CallSafeHandle call;
readonly string method; readonly string method;
readonly string host; readonly string host;
readonly Timespec deadline; readonly Timespec deadline;
readonly Metadata requestMetadata; readonly Metadata requestMetadata;
public ServerRpcNew(CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata) public ServerRpcNew(Server server, CallSafeHandle call, string method, string host, Timespec deadline, Metadata requestMetadata)
{ {
this.server = server;
this.call = call; this.call = call;
this.method = method; this.method = method;
this.host = host; this.host = host;
@ -213,6 +215,14 @@ namespace Grpc.Core.Internal
this.requestMetadata = requestMetadata; this.requestMetadata = requestMetadata;
} }
public Server Server
{
get
{
return this.server;
}
}
public CallSafeHandle Call public CallSafeHandle Call
{ {
get get

@ -40,7 +40,7 @@ namespace Grpc.Core.Internal
/// <summary> /// <summary>
/// grpc_call from <grpc/grpc.h> /// grpc_call from <grpc/grpc.h>
/// </summary> /// </summary>
internal class CallSafeHandle : SafeHandleZeroIsInvalid internal class CallSafeHandle : SafeHandleZeroIsInvalid, INativeCall
{ {
public static readonly CallSafeHandle NullInstance = new CallSafeHandle(); public static readonly CallSafeHandle NullInstance = new CallSafeHandle();
@ -86,6 +86,10 @@ namespace Grpc.Core.Internal
static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call, static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call,
BatchContextSafeHandle ctx); BatchContextSafeHandle ctx);
[DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_recv_initial_metadata(CallSafeHandle call,
BatchContextSafeHandle ctx);
[DllImport("grpc_csharp_ext.dll")] [DllImport("grpc_csharp_ext.dll")]
static extern GRPCCallError grpcsharp_call_start_serverside(CallSafeHandle call, static extern GRPCCallError grpcsharp_call_start_serverside(CallSafeHandle call,
BatchContextSafeHandle ctx); BatchContextSafeHandle ctx);
@ -109,10 +113,10 @@ namespace Grpc.Core.Internal
this.completionRegistry = completionRegistry; this.completionRegistry = completionRegistry;
} }
public void StartUnary(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) public void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata()));
grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags) grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
.CheckOk(); .CheckOk();
} }
@ -123,66 +127,73 @@ namespace Grpc.Core.Internal
.CheckOk(); .CheckOk();
} }
public void StartClientStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata()));
grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk(); grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk();
} }
public void StartServerStreaming(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) public void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient()));
grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk(); grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk();
} }
public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) public void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedStatusOnClient()));
grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk(); grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk();
} }
public void StartSendMessage(BatchCompletionDelegate callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata) public void StartSendMessage(SendCompletionHandler callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk(); grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk();
} }
public void StartSendCloseFromClient(BatchCompletionDelegate callback) public void StartSendCloseFromClient(SendCompletionHandler callback)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
grpcsharp_call_send_close_from_client(this, ctx).CheckOk(); grpcsharp_call_send_close_from_client(this, ctx).CheckOk();
} }
public void StartSendStatusFromServer(BatchCompletionDelegate callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) public void StartSendStatusFromServer(SendCompletionHandler callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk(); grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk();
} }
public void StartReceiveMessage(BatchCompletionDelegate callback) public void StartReceiveMessage(ReceivedMessageHandler callback)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedMessage()));
grpcsharp_call_recv_message(this, ctx).CheckOk(); grpcsharp_call_recv_message(this, ctx).CheckOk();
} }
public void StartServerSide(BatchCompletionDelegate callback) public void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback)
{
var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedInitialMetadata()));
grpcsharp_call_recv_initial_metadata(this, ctx).CheckOk();
}
public void StartServerSide(ReceivedCloseOnServerHandler callback)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success, context.GetReceivedCloseOnServerCancelled()));
grpcsharp_call_start_serverside(this, ctx).CheckOk(); grpcsharp_call_start_serverside(this, ctx).CheckOk();
} }
public void StartSendInitialMetadata(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) public void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray)
{ {
var ctx = BatchContextSafeHandle.Create(); var ctx = BatchContextSafeHandle.Create();
completionRegistry.RegisterBatchCompletion(ctx, callback); completionRegistry.RegisterBatchCompletion(ctx, (success, context) => callback(success));
grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk(); grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk();
} }

@ -68,11 +68,17 @@ namespace Grpc.Core.Internal
public static ChannelSafeHandle CreateInsecure(string target, ChannelArgsSafeHandle channelArgs) public static ChannelSafeHandle CreateInsecure(string target, ChannelArgsSafeHandle channelArgs)
{ {
// Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
// Doing so would make object finalizer crash if we end up abandoning the handle.
GrpcEnvironment.GrpcNativeInit();
return grpcsharp_insecure_channel_create(target, channelArgs); return grpcsharp_insecure_channel_create(target, channelArgs);
} }
public static ChannelSafeHandle CreateSecure(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs) public static ChannelSafeHandle CreateSecure(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs)
{ {
// Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
// Doing so would make object finalizer crash if we end up abandoning the handle.
GrpcEnvironment.GrpcNativeInit();
return grpcsharp_secure_channel_create(credentials, target, channelArgs); return grpcsharp_secure_channel_create(credentials, target, channelArgs);
} }
@ -107,6 +113,7 @@ namespace Grpc.Core.Internal
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
grpcsharp_channel_destroy(handle); grpcsharp_channel_destroy(handle);
GrpcEnvironment.GrpcNativeShutdown();
return true; return true;
} }
} }

@ -72,7 +72,13 @@ namespace Grpc.Core.Internal
call.StartReadMessage(taskSource.CompletionDelegate); call.StartReadMessage(taskSource.CompletionDelegate);
var result = await taskSource.Task; var result = await taskSource.Task;
this.current = result; this.current = result;
return result != null;
if (result == null)
{
await call.StreamingCallFinishedTask;
return false;
}
return true;
} }
public void Dispose() public void Dispose()

@ -38,10 +38,6 @@ namespace Grpc.Core.Internal
{ {
internal class DebugStats internal class DebugStats
{ {
public readonly AtomicCounter ActiveClientCalls = new AtomicCounter();
public readonly AtomicCounter ActiveServerCalls = new AtomicCounter();
public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter(); public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter();
/// <summary> /// <summary>
@ -49,16 +45,6 @@ namespace Grpc.Core.Internal
/// </summary> /// </summary>
public void CheckOK() public void CheckOK()
{ {
var remainingClientCalls = ActiveClientCalls.Count;
if (remainingClientCalls != 0)
{
DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls));
}
var remainingServerCalls = ActiveServerCalls.Count;
if (remainingServerCalls != 0)
{
DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls));
}
var pendingBatchCompletions = PendingBatchCompletions.Count; var pendingBatchCompletions = PendingBatchCompletions.Count;
if (pendingBatchCompletions != 0) if (pendingBatchCompletions != 0)
{ {

@ -83,8 +83,6 @@ namespace Grpc.Core.Internal
lock (myLock) lock (myLock)
{ {
cq.Shutdown(); cq.Shutdown();
Logger.Info("Waiting for GRPC threads to finish.");
foreach (var thread in threads) foreach (var thread in threads)
{ {
thread.Join(); thread.Join();
@ -136,7 +134,6 @@ namespace Grpc.Core.Internal
} }
} }
while (ev.type != GRPCCompletionType.Shutdown); while (ev.type != GRPCCompletionType.Shutdown);
Logger.Info("Completion queue has shutdown successfully, thread {0} exiting.", Thread.CurrentThread.Name);
} }
} }
} }

@ -0,0 +1,85 @@
#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;
namespace Grpc.Core.Internal
{
internal delegate void UnaryResponseClientHandler(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders);
// Received status for streaming response calls.
internal delegate void ReceivedStatusOnClientHandler(bool success, ClientSideStatus receivedStatus);
internal delegate void ReceivedMessageHandler(bool success, byte[] receivedMessage);
internal delegate void ReceivedResponseHeadersHandler(bool success, Metadata responseHeaders);
internal delegate void SendCompletionHandler(bool success);
internal delegate void ReceivedCloseOnServerHandler(bool success, bool cancelled);
/// <summary>
/// Abstraction of a native call object.
/// </summary>
internal interface INativeCall : IDisposable
{
void Cancel();
void CancelWithStatus(Grpc.Core.Status status);
string GetPeer();
void StartUnary(UnaryResponseClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags);
void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags);
void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray);
void StartServerStreaming(ReceivedStatusOnClientHandler callback, byte[] payload, MetadataArraySafeHandle metadataArray, Grpc.Core.WriteFlags writeFlags);
void StartDuplexStreaming(ReceivedStatusOnClientHandler callback, MetadataArraySafeHandle metadataArray);
void StartReceiveMessage(ReceivedMessageHandler callback);
void StartReceiveInitialMetadata(ReceivedResponseHeadersHandler callback);
void StartSendInitialMetadata(SendCompletionHandler callback, MetadataArraySafeHandle metadataArray);
void StartSendMessage(SendCompletionHandler callback, byte[] payload, Grpc.Core.WriteFlags writeFlags, bool sendEmptyInitialMetadata);
void StartSendCloseFromClient(SendCompletionHandler callback);
void StartSendStatusFromServer(SendCompletionHandler callback, Grpc.Core.Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata);
void StartServerSide(ReceivedCloseOnServerHandler callback);
}
}

@ -70,7 +70,8 @@ namespace Grpc.Core.Internal
var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count)); var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count));
for (int i = 0; i < metadata.Count; i++) for (int i = 0; i < metadata.Count; i++)
{ {
grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, metadata[i].ValueBytes, new UIntPtr((ulong)metadata[i].ValueBytes.Length)); var valueBytes = metadata[i].GetSerializedValueUnsafe();
grpcsharp_metadata_array_add(metadataArray, metadata[i].Key, valueBytes, new UIntPtr((ulong)valueBytes.Length));
} }
return metadataArray; return metadataArray;
} }
@ -94,7 +95,7 @@ namespace Grpc.Core.Internal
string key = Marshal.PtrToStringAnsi(grpcsharp_metadata_array_get_key(metadataArray, index)); string key = Marshal.PtrToStringAnsi(grpcsharp_metadata_array_get_key(metadataArray, index));
var bytes = new byte[grpcsharp_metadata_array_get_value_length(metadataArray, index).ToUInt64()]; var bytes = new byte[grpcsharp_metadata_array_get_value_length(metadataArray, index).ToUInt64()];
Marshal.Copy(grpcsharp_metadata_array_get_value(metadataArray, index), bytes, 0, bytes.Length); Marshal.Copy(grpcsharp_metadata_array_get_value(metadataArray, index), bytes, 0, bytes.Length);
metadata.Add(new Metadata.Entry(key, bytes)); metadata.Add(Metadata.Entry.CreateUnsafe(key, bytes));
} }
return metadata; return metadata;
} }

@ -67,7 +67,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>( var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer, method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer, method.RequestMarshaller.Deserializer,
environment); environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call); asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync(); var finishedTask = asyncCall.ServerSideCallAsync();
@ -123,7 +123,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>( var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer, method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer, method.RequestMarshaller.Deserializer,
environment); environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call); asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync(); var finishedTask = asyncCall.ServerSideCallAsync();
@ -179,7 +179,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>( var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer, method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer, method.RequestMarshaller.Deserializer,
environment); environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call); asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync(); var finishedTask = asyncCall.ServerSideCallAsync();
@ -239,7 +239,7 @@ namespace Grpc.Core.Internal
var asyncCall = new AsyncCallServer<TRequest, TResponse>( var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer, method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer, method.RequestMarshaller.Deserializer,
environment); environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call); asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync(); var finishedTask = asyncCall.ServerSideCallAsync();
@ -278,7 +278,7 @@ namespace Grpc.Core.Internal
{ {
// We don't care about the payload type here. // We don't care about the payload type here.
var asyncCall = new AsyncCallServer<byte[], byte[]>( var asyncCall = new AsyncCallServer<byte[], byte[]>(
(payload) => payload, (payload) => payload, environment); (payload) => payload, (payload) => payload, environment, newRpc.Server);
asyncCall.Initialize(newRpc.Call); asyncCall.Initialize(newRpc.Call);
var finishedTask = asyncCall.ServerSideCallAsync(); var finishedTask = asyncCall.ServerSideCallAsync();

@ -74,6 +74,9 @@ namespace Grpc.Core.Internal
public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, ChannelArgsSafeHandle args) public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, ChannelArgsSafeHandle args)
{ {
// Increment reference count for the native gRPC environment to make sure we don't do grpc_shutdown() before destroying the server handle.
// Doing so would make object finalizer crash if we end up abandoning the handle.
GrpcEnvironment.GrpcNativeInit();
return grpcsharp_server_create(cq, args); return grpcsharp_server_create(cq, args);
} }
@ -109,6 +112,7 @@ namespace Grpc.Core.Internal
protected override bool ReleaseHandle() protected override bool ReleaseHandle()
{ {
grpcsharp_server_destroy(handle); grpcsharp_server_destroy(handle);
GrpcEnvironment.GrpcNativeShutdown();
return true; return true;
} }

@ -51,7 +51,19 @@ namespace Grpc.Core.Logging
private ConsoleLogger(Type forType) private ConsoleLogger(Type forType)
{ {
this.forType = forType; this.forType = forType;
this.forTypeString = forType != null ? forType.FullName + " " : ""; if (forType != null)
{
var namespaceStr = forType.Namespace ?? "";
if (namespaceStr.Length > 0)
{
namespaceStr += ".";
}
this.forTypeString = namespaceStr + forType.Name + " ";
}
else
{
this.forTypeString = "";
}
} }
/// <summary> /// <summary>

@ -45,6 +45,11 @@ namespace Grpc.Core
/// </summary> /// </summary>
public sealed class Metadata : IList<Metadata.Entry> public sealed class Metadata : IList<Metadata.Entry>
{ {
/// <summary>
/// All binary headers should have this suffix.
/// </summary>
public const string BinaryHeaderSuffix = "-bin";
/// <summary> /// <summary>
/// An read-only instance of metadata containing no entries. /// An read-only instance of metadata containing no entries.
/// </summary> /// </summary>
@ -181,23 +186,49 @@ namespace Grpc.Core
private static readonly Encoding Encoding = Encoding.ASCII; private static readonly Encoding Encoding = Encoding.ASCII;
readonly string key; readonly string key;
string value; readonly string value;
byte[] valueBytes; readonly byte[] valueBytes;
private Entry(string key, string value, byte[] valueBytes)
{
this.key = key;
this.value = value;
this.valueBytes = valueBytes;
}
/// <summary>
/// Initializes a new instance of the <see cref="Grpc.Core.Metadata+Entry"/> struct with a binary value.
/// </summary>
/// <param name="key">Metadata key, needs to have suffix indicating a binary valued metadata entry.</param>
/// <param name="valueBytes">Value bytes.</param>
public Entry(string key, byte[] valueBytes) public Entry(string key, byte[] valueBytes)
{ {
this.key = Preconditions.CheckNotNull(key, "key"); this.key = NormalizeKey(key);
Preconditions.CheckArgument(this.key.EndsWith(BinaryHeaderSuffix),
"Key for binary valued metadata entry needs to have suffix indicating binary value.");
this.value = null; this.value = null;
this.valueBytes = Preconditions.CheckNotNull(valueBytes, "valueBytes"); Preconditions.CheckNotNull(valueBytes, "valueBytes");
this.valueBytes = new byte[valueBytes.Length];
Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length); // defensive copy to guarantee immutability
} }
/// <summary>
/// Initializes a new instance of the <see cref="Grpc.Core.Metadata+Entry"/> struct holding an ASCII value.
/// </summary>
/// <param name="key">Metadata key, must not use suffix indicating a binary valued metadata entry.</param>
/// <param name="value">Value string. Only ASCII characters are allowed.</param>
public Entry(string key, string value) public Entry(string key, string value)
{ {
this.key = Preconditions.CheckNotNull(key, "key"); this.key = NormalizeKey(key);
Preconditions.CheckArgument(!this.key.EndsWith(BinaryHeaderSuffix),
"Key for ASCII valued metadata entry cannot have suffix indicating binary value.");
this.value = Preconditions.CheckNotNull(value, "value"); this.value = Preconditions.CheckNotNull(value, "value");
this.valueBytes = null; this.valueBytes = null;
} }
/// <summary>
/// Gets the metadata entry key.
/// </summary>
public string Key public string Key
{ {
get get
@ -206,33 +237,86 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// Gets the binary value of this metadata entry.
/// </summary>
public byte[] ValueBytes public byte[] ValueBytes
{ {
get get
{ {
if (valueBytes == null) if (valueBytes == null)
{ {
valueBytes = Encoding.GetBytes(value); return Encoding.GetBytes(value);
} }
return valueBytes;
// defensive copy to guarantee immutability
var bytes = new byte[valueBytes.Length];
Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length);
return bytes;
} }
} }
/// <summary>
/// Gets the string value of this metadata entry.
/// </summary>
public string Value public string Value
{ {
get get
{ {
if (value == null) Preconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");
{ return value ?? Encoding.GetString(valueBytes);
value = Encoding.GetString(valueBytes);
}
return value;
} }
} }
/// <summary>
/// Returns <c>true</c> if this entry is a binary-value entry.
/// </summary>
public bool IsBinary
{
get
{
return value == null;
}
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Metadata+Entry"/>.
/// </summary>
public override string ToString() public override string ToString()
{ {
return string.Format("[Entry: key={0}, value={1}]", Key, Value); if (IsBinary)
{
return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes);
}
return string.Format("[Entry: key={0}, value={1}]", key, value);
}
/// <summary>
/// Gets the serialized value for this entry. For binary metadata entries, this leaks
/// the internal <c>valueBytes</c> byte array and caller must not change contents of it.
/// </summary>
internal byte[] GetSerializedValueUnsafe()
{
return valueBytes ?? Encoding.GetBytes(value);
}
/// <summary>
/// Creates a binary value or ascii value metadata entry from data received from the native layer.
/// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
/// </summary>
internal static Entry CreateUnsafe(string key, byte[] valueBytes)
{
if (key.EndsWith(BinaryHeaderSuffix))
{
return new Entry(key, null, valueBytes);
}
return new Entry(key, Encoding.GetString(valueBytes), null);
}
private static string NormalizeKey(string key)
{
return Preconditions.CheckNotNull(key, "key").ToLower();
} }
} }
} }

@ -50,6 +50,8 @@ namespace Grpc.Core
{ {
static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>(); static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>();
readonly AtomicCounter activeCallCounter = new AtomicCounter();
readonly ServiceDefinitionCollection serviceDefinitions; readonly ServiceDefinitionCollection serviceDefinitions;
readonly ServerPortCollection ports; readonly ServerPortCollection ports;
readonly GrpcEnvironment environment; readonly GrpcEnvironment environment;
@ -73,7 +75,7 @@ namespace Grpc.Core
{ {
this.serviceDefinitions = new ServiceDefinitionCollection(this); this.serviceDefinitions = new ServiceDefinitionCollection(this);
this.ports = new ServerPortCollection(this); this.ports = new ServerPortCollection(this);
this.environment = GrpcEnvironment.GetInstance(); this.environment = GrpcEnvironment.AddRef();
this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>();
using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options)) using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options))
{ {
@ -105,6 +107,17 @@ namespace Grpc.Core
} }
} }
/// <summary>
/// To allow awaiting termination of the server.
/// </summary>
public Task ShutdownTask
{
get
{
return shutdownTcs.Task;
}
}
/// <summary> /// <summary>
/// Starts the server. /// Starts the server.
/// </summary> /// </summary>
@ -136,18 +149,9 @@ namespace Grpc.Core
handle.ShutdownAndNotify(HandleServerShutdown, environment); handle.ShutdownAndNotify(HandleServerShutdown, environment);
await shutdownTcs.Task; await shutdownTcs.Task;
handle.Dispose(); DisposeHandle();
}
/// <summary> await Task.Run(() => GrpcEnvironment.Release());
/// To allow awaiting termination of the server.
/// </summary>
public Task ShutdownTask
{
get
{
return shutdownTcs.Task;
}
} }
/// <summary> /// <summary>
@ -166,7 +170,22 @@ namespace Grpc.Core
handle.ShutdownAndNotify(HandleServerShutdown, environment); handle.ShutdownAndNotify(HandleServerShutdown, environment);
handle.CancelAllCalls(); handle.CancelAllCalls();
await shutdownTcs.Task; await shutdownTcs.Task;
handle.Dispose(); DisposeHandle();
}
internal void AddCallReference(object call)
{
activeCallCounter.Increment();
bool success = false;
handle.DangerousAddRef(ref success);
Preconditions.CheckState(success);
}
internal void RemoveCallReference(object call)
{
handle.DangerousRelease();
activeCallCounter.Decrement();
} }
/// <summary> /// <summary>
@ -227,6 +246,16 @@ namespace Grpc.Core
} }
} }
private void DisposeHandle()
{
var activeCallCount = activeCallCounter.Count;
if (activeCallCount > 0)
{
Logger.Warning("Server shutdown has finished but there are still {0} active calls for that server.", activeCallCount);
}
handle.Dispose();
}
/// <summary> /// <summary>
/// Selects corresponding handler for given call and handles the call. /// Selects corresponding handler for given call and handles the call.
/// </summary> /// </summary>
@ -254,7 +283,7 @@ namespace Grpc.Core
{ {
if (success) if (success)
{ {
ServerRpcNew newRpc = ctx.GetServerRpcNew(); ServerRpcNew newRpc = ctx.GetServerRpcNew(this);
// after server shutdown, the callback returns with null call // after server shutdown, the callback returns with null call
if (!newRpc.Call.IsInvalid) if (!newRpc.Call.IsInvalid)

@ -39,23 +39,21 @@ namespace math
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
using (Channel channel = new Channel("127.0.0.1", 23456, Credentials.Insecure)) var channel = new Channel("127.0.0.1", 23456, Credentials.Insecure);
{ Math.IMathClient client = new Math.MathClient(channel);
Math.IMathClient client = new Math.MathClient(channel); MathExamples.DivExample(client);
MathExamples.DivExample(client);
MathExamples.DivAsyncExample(client).Wait(); MathExamples.DivAsyncExample(client).Wait();
MathExamples.FibExample(client).Wait(); MathExamples.FibExample(client).Wait();
MathExamples.SumExample(client).Wait(); MathExamples.SumExample(client).Wait();
MathExamples.DivManyExample(client).Wait(); MathExamples.DivManyExample(client).Wait();
MathExamples.DependendRequestsExample(client).Wait(); MathExamples.DependendRequestsExample(client).Wait();
}
GrpcEnvironment.Shutdown(); channel.ShutdownAsync().Wait();
} }
} }
} }

@ -56,7 +56,6 @@ namespace math
Console.ReadKey(); Console.ReadKey();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
} }
} }

@ -68,9 +68,8 @@ namespace math.Tests
[TestFixtureTearDown] [TestFixtureTearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]

@ -71,10 +71,9 @@ namespace Grpc.HealthCheck.Tests
[TestFixtureTearDown] [TestFixtureTearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]

@ -37,13 +37,15 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.ProtocolBuffers; using Google.ProtocolBuffers;
using grpc.testing; using grpc.testing;
using Grpc.Auth; using Grpc.Auth;
using Grpc.Core; using Grpc.Core;
using Grpc.Core.Utils; using Grpc.Core.Utils;
using NUnit.Framework; using NUnit.Framework;
using Google.Apis.Auth.OAuth2;
namespace Grpc.IntegrationTesting namespace Grpc.IntegrationTesting
{ {
@ -118,12 +120,10 @@ namespace Grpc.IntegrationTesting
}; };
} }
using (Channel channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions)) var channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions);
{ TestService.TestServiceClient client = new TestService.TestServiceClient(channel);
TestService.TestServiceClient client = new TestService.TestServiceClient(channel); await RunTestCaseAsync(options.testCase, client);
await RunTestCaseAsync(options.testCase, client); channel.ShutdownAsync().Wait();
}
GrpcEnvironment.Shutdown();
} }
private async Task RunTestCaseAsync(string testCase, TestService.TestServiceClient client) private async Task RunTestCaseAsync(string testCase, TestService.TestServiceClient client)
@ -169,6 +169,9 @@ namespace Grpc.IntegrationTesting
case "cancel_after_first_response": case "cancel_after_first_response":
await RunCancelAfterFirstResponseAsync(client); await RunCancelAfterFirstResponseAsync(client);
break; break;
case "timeout_on_sleeping_server":
await RunTimeoutOnSleepingServerAsync(client);
break;
case "benchmark_empty_unary": case "benchmark_empty_unary":
RunBenchmarkEmptyUnary(client); RunBenchmarkEmptyUnary(client);
break; break;
@ -458,6 +461,29 @@ namespace Grpc.IntegrationTesting
Console.WriteLine("Passed!"); Console.WriteLine("Passed!");
} }
public static async Task RunTimeoutOnSleepingServerAsync(TestService.ITestServiceClient client)
{
Console.WriteLine("running timeout_on_sleeping_server");
var deadline = DateTime.UtcNow.AddMilliseconds(1);
using (var call = client.FullDuplexCall(deadline: deadline))
{
try
{
await call.RequestStream.WriteAsync(StreamingOutputCallRequest.CreateBuilder()
.SetPayload(CreateZerosPayload(27182)).Build());
}
catch (InvalidOperationException)
{
// Deadline was reached before write has started. Eat the exception and continue.
}
var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext());
Assert.AreEqual(StatusCode.DeadlineExceeded, ex.Status.StatusCode);
}
Console.WriteLine("Passed!");
}
// This is not an official interop test, but it's useful. // This is not an official interop test, but it's useful.
public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client) public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client)
{ {

@ -75,9 +75,8 @@ namespace Grpc.IntegrationTesting
[TestFixtureTearDown] [TestFixtureTearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]
@ -127,5 +126,11 @@ namespace Grpc.IntegrationTesting
{ {
await InteropClient.RunCancelAfterFirstResponseAsync(client); await InteropClient.RunCancelAfterFirstResponseAsync(client);
} }
[Test]
public async Task TimeoutOnSleepingServerAsync()
{
await InteropClient.RunTimeoutOnSleepingServerAsync(client);
}
} }
} }

@ -107,8 +107,6 @@ namespace Grpc.IntegrationTesting
server.Start(); server.Start();
server.ShutdownTask.Wait(); server.ShutdownTask.Wait();
GrpcEnvironment.Shutdown();
} }
private static ServerOptions ParseArguments(string[] args) private static ServerOptions ParseArguments(string[] args)

@ -85,9 +85,8 @@ namespace Grpc.IntegrationTesting
[TestFixtureTearDown] [TestFixtureTearDown]
public void Cleanup() public void Cleanup()
{ {
channel.Dispose(); channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait(); server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
} }
[Test] [Test]

@ -595,7 +595,7 @@ GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer, grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer,
size_t send_buffer_len, grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) { size_t send_buffer_len, grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) {
/* TODO: don't use magic number */ /* TODO: don't use magic number */
grpc_op ops[5]; grpc_op ops[4];
ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
grpcsharp_metadata_array_move(&(ctx->send_initial_metadata), grpcsharp_metadata_array_move(&(ctx->send_initial_metadata),
initial_metadata); initial_metadata);
@ -615,23 +615,18 @@ GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming(
ops[2].flags = 0; ops[2].flags = 0;
ops[2].reserved = NULL; ops[2].reserved = NULL;
ops[3].op = GRPC_OP_RECV_INITIAL_METADATA; ops[3].op = GRPC_OP_RECV_STATUS_ON_CLIENT;
ops[3].data.recv_initial_metadata = &(ctx->recv_initial_metadata); ops[3].data.recv_status_on_client.trailing_metadata =
ops[3].flags = 0;
ops[3].reserved = NULL;
ops[4].op = GRPC_OP_RECV_STATUS_ON_CLIENT;
ops[4].data.recv_status_on_client.trailing_metadata =
&(ctx->recv_status_on_client.trailing_metadata); &(ctx->recv_status_on_client.trailing_metadata);
ops[4].data.recv_status_on_client.status = ops[3].data.recv_status_on_client.status =
&(ctx->recv_status_on_client.status); &(ctx->recv_status_on_client.status);
/* not using preallocation for status_details */ /* not using preallocation for status_details */
ops[4].data.recv_status_on_client.status_details = ops[3].data.recv_status_on_client.status_details =
&(ctx->recv_status_on_client.status_details); &(ctx->recv_status_on_client.status_details);
ops[4].data.recv_status_on_client.status_details_capacity = ops[3].data.recv_status_on_client.status_details_capacity =
&(ctx->recv_status_on_client.status_details_capacity); &(ctx->recv_status_on_client.status_details_capacity);
ops[4].flags = 0; ops[3].flags = 0;
ops[4].reserved = NULL; ops[3].reserved = NULL;
return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx, return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
NULL); NULL);
@ -642,7 +637,7 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call,
grpcsharp_batch_context *ctx, grpcsharp_batch_context *ctx,
grpc_metadata_array *initial_metadata) { grpc_metadata_array *initial_metadata) {
/* TODO: don't use magic number */ /* TODO: don't use magic number */
grpc_op ops[3]; grpc_op ops[2];
ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; ops[0].op = GRPC_OP_SEND_INITIAL_METADATA;
grpcsharp_metadata_array_move(&(ctx->send_initial_metadata), grpcsharp_metadata_array_move(&(ctx->send_initial_metadata),
initial_metadata); initial_metadata);
@ -652,28 +647,36 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call,
ops[0].flags = 0; ops[0].flags = 0;
ops[0].reserved = NULL; ops[0].reserved = NULL;
ops[1].op = GRPC_OP_RECV_INITIAL_METADATA; ops[1].op = GRPC_OP_RECV_STATUS_ON_CLIENT;
ops[1].data.recv_initial_metadata = &(ctx->recv_initial_metadata); ops[1].data.recv_status_on_client.trailing_metadata =
ops[1].flags = 0;
ops[1].reserved = NULL;
ops[2].op = GRPC_OP_RECV_STATUS_ON_CLIENT;
ops[2].data.recv_status_on_client.trailing_metadata =
&(ctx->recv_status_on_client.trailing_metadata); &(ctx->recv_status_on_client.trailing_metadata);
ops[2].data.recv_status_on_client.status = ops[1].data.recv_status_on_client.status =
&(ctx->recv_status_on_client.status); &(ctx->recv_status_on_client.status);
/* not using preallocation for status_details */ /* not using preallocation for status_details */
ops[2].data.recv_status_on_client.status_details = ops[1].data.recv_status_on_client.status_details =
&(ctx->recv_status_on_client.status_details); &(ctx->recv_status_on_client.status_details);
ops[2].data.recv_status_on_client.status_details_capacity = ops[1].data.recv_status_on_client.status_details_capacity =
&(ctx->recv_status_on_client.status_details_capacity); &(ctx->recv_status_on_client.status_details_capacity);
ops[2].flags = 0; ops[1].flags = 0;
ops[2].reserved = NULL; ops[1].reserved = NULL;
return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx, return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
NULL); NULL);
} }
GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_recv_initial_metadata(
grpc_call *call, grpcsharp_batch_context *ctx) {
/* TODO: don't use magic number */
grpc_op ops[1];
ops[0].op = GRPC_OP_RECV_INITIAL_METADATA;
ops[0].data.recv_initial_metadata = &(ctx->recv_initial_metadata);
ops[0].flags = 0;
ops[0].reserved = NULL;
return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx,
NULL);
}
GPR_EXPORT grpc_call_error GPR_CALLTYPE GPR_EXPORT grpc_call_error GPR_CALLTYPE
grpcsharp_call_send_message(grpc_call *call, grpcsharp_batch_context *ctx, 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,

@ -5,11 +5,35 @@ Alpha : Ready for early adopters
## PREREQUISITES ## PREREQUISITES
- `node`: This requires `node` to be installed. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package. - `node`: This requires `node` to be installed. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
- [homebrew][] on Mac OS X, [linuxbrew][] on Linux. These simplify the installation of the gRPC C core. - [homebrew][] on Mac OS X. These simplify the installation of the gRPC C core.
## INSTALLATION ## INSTALLATION
On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][].
Run the following command to install gRPC Node.js. **Linux (Debian):**
Add [Debian unstable][] to your `sources.list` file. Example:
```sh
echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" | \
sudo tee -a /etc/apt/sources.list
```
Install the gRPC Debian package
```sh
sudo apt-get update
sudo apt-get install libgrpc-dev
```
Install the gRPC NPM package
```sh
npm install grpc
```
**Mac OS X**
Install [homebrew][]. Run the following command to install gRPC Node.js.
```sh ```sh
$ curl -fsSL https://goo.gl/getgrpc | bash -s nodejs $ curl -fsSL https://goo.gl/getgrpc | bash -s nodejs
``` ```
@ -88,5 +112,5 @@ ServerCredentials
An object with factory methods for creating credential objects for servers. An object with factory methods for creating credential objects for servers.
[homebrew]:http://brew.sh [homebrew]:http://brew.sh
[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
[Debian unstable]:https://www.debian.org/releases/sid/

@ -41,6 +41,7 @@
namespace grpc { namespace grpc {
namespace node { namespace node {
using v8::Array;
using v8::Exception; using v8::Exception;
using v8::External; using v8::External;
using v8::Function; using v8::Function;
@ -52,6 +53,7 @@ using v8::Local;
using v8::Object; using v8::Object;
using v8::ObjectTemplate; using v8::ObjectTemplate;
using v8::Persistent; using v8::Persistent;
using v8::String;
using v8::Value; using v8::Value;
NanCallback *ServerCredentials::constructor; NanCallback *ServerCredentials::constructor;
@ -122,25 +124,66 @@ NAN_METHOD(ServerCredentials::CreateSsl) {
// TODO: have the node API support multiple key/cert pairs. // TODO: have the node API support multiple key/cert pairs.
NanScope(); NanScope();
char *root_certs = NULL; char *root_certs = NULL;
grpc_ssl_pem_key_cert_pair key_cert_pair;
if (::node::Buffer::HasInstance(args[0])) { if (::node::Buffer::HasInstance(args[0])) {
root_certs = ::node::Buffer::Data(args[0]); root_certs = ::node::Buffer::Data(args[0]);
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) { } else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
return NanThrowTypeError( return NanThrowTypeError(
"createSSl's first argument must be a Buffer if provided"); "createSSl's first argument must be a Buffer if provided");
} }
if (!::node::Buffer::HasInstance(args[1])) { if (!args[1]->IsArray()) {
return NanThrowTypeError("createSsl's second argument must be a Buffer"); return NanThrowTypeError(
"createSsl's second argument must be a list of objects");
}
int force_client_auth = 0;
if (args[2]->IsBoolean()) {
force_client_auth = (int)args[2]->BooleanValue();
} else if (!(args[2]->IsUndefined() || args[2]->IsNull())) {
return NanThrowTypeError(
"createSsl's third argument must be a boolean if provided");
} }
key_cert_pair.private_key = ::node::Buffer::Data(args[1]); Handle<Array> pair_list = Local<Array>::Cast(args[1]);
if (!::node::Buffer::HasInstance(args[2])) { uint32_t key_cert_pair_count = pair_list->Length();
return NanThrowTypeError("createSsl's third argument must be a Buffer"); grpc_ssl_pem_key_cert_pair *key_cert_pairs = new grpc_ssl_pem_key_cert_pair[
key_cert_pair_count];
Handle<String> key_key = NanNew("private_key");
Handle<String> cert_key = NanNew("cert_chain");
for(uint32_t i = 0; i < key_cert_pair_count; i++) {
if (!pair_list->Get(i)->IsObject()) {
delete key_cert_pairs;
return NanThrowTypeError("Key/cert pairs must be objects");
}
Handle<Object> pair_obj = pair_list->Get(i)->ToObject();
if (!pair_obj->HasOwnProperty(key_key)) {
delete key_cert_pairs;
return NanThrowTypeError(
"Key/cert pairs must have a private_key and a cert_chain");
}
if (!pair_obj->HasOwnProperty(cert_key)) {
delete key_cert_pairs;
return NanThrowTypeError(
"Key/cert pairs must have a private_key and a cert_chain");
}
if (!::node::Buffer::HasInstance(pair_obj->Get(key_key))) {
delete key_cert_pairs;
return NanThrowTypeError("private_key must be a Buffer");
}
if (!::node::Buffer::HasInstance(pair_obj->Get(cert_key))) {
delete key_cert_pairs;
return NanThrowTypeError("cert_chain must be a Buffer");
}
key_cert_pairs[i].private_key = ::node::Buffer::Data(
pair_obj->Get(key_key));
key_cert_pairs[i].cert_chain = ::node::Buffer::Data(
pair_obj->Get(cert_key));
} }
key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]);
// TODO Add a force_client_auth parameter and pass it as the last parameter
// here.
grpc_server_credentials *creds = grpc_server_credentials *creds =
grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1, 0); grpc_ssl_server_credentials_create(root_certs,
key_cert_pairs,
key_cert_pair_count,
force_client_auth);
delete key_cert_pairs;
if (creds == NULL) { if (creds == NULL) {
NanReturnNull(); NanReturnNull();
} }

@ -45,17 +45,13 @@ function HealthImplementation(statusMap) {
this.statusMap = _.clone(statusMap); this.statusMap = _.clone(statusMap);
} }
HealthImplementation.prototype.setStatus = function(host, service, status) { HealthImplementation.prototype.setStatus = function(service, status) {
if (!this.statusMap[host]) { this.statusMap[service] = status;
this.statusMap[host] = {};
}
this.statusMap[host][service] = status;
}; };
HealthImplementation.prototype.check = function(call, callback){ HealthImplementation.prototype.check = function(call, callback){
var host = call.request.host;
var service = call.request.service; var service = call.request.service;
var status = _.get(this.statusMap, [host, service], null); var status = _.get(this.statusMap, service, null);
if (status === null) { if (status === null) {
callback({code:grpc.status.NOT_FOUND}); callback({code:grpc.status.NOT_FOUND});
} else { } else {

@ -32,8 +32,7 @@ syntax = "proto3";
package grpc.health.v1alpha; package grpc.health.v1alpha;
message HealthCheckRequest { message HealthCheckRequest {
string host = 1; string service = 1;
string service = 2;
} }
message HealthCheckResponse { message HealthCheckResponse {
@ -47,4 +46,4 @@ message HealthCheckResponse {
service Health { service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse); rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
} }

@ -169,8 +169,8 @@ function getServer(port, tls) {
var key_data = fs.readFileSync(key_path); var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path); var pem_data = fs.readFileSync(pem_path);
server_creds = grpc.ServerCredentials.createSsl(null, server_creds = grpc.ServerCredentials.createSsl(null,
key_data, [{private_key: key_data,
pem_data); cert_chain: pem_data}]);
} else { } else {
server_creds = grpc.ServerCredentials.createInsecure(); server_creds = grpc.ServerCredentials.createInsecure();
} }

@ -280,7 +280,9 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
} }
var client_batch = {}; var client_batch = {};
var message = serialize(argument); var message = serialize(argument);
message.grpcWriteFlags = options.flags; if (options) {
message.grpcWriteFlags = options.flags;
}
client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
client_batch[grpc.opType.SEND_MESSAGE] = message; client_batch[grpc.opType.SEND_MESSAGE] = message;
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
@ -416,7 +418,9 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
} }
var start_batch = {}; var start_batch = {};
var message = serialize(argument); var message = serialize(argument);
message.grpcWriteFlags = options.flags; if (options) {
message.grpcWriteFlags = options.flags;
}
start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
start_batch[grpc.opType.SEND_MESSAGE] = message; start_batch[grpc.opType.SEND_MESSAGE] = message;

@ -41,13 +41,9 @@ var grpc = require('../');
describe('Health Checking', function() { describe('Health Checking', function() {
var statusMap = { var statusMap = {
'': { '': 'SERVING',
'': 'SERVING', 'grpc.test.TestServiceNotServing': 'NOT_SERVING',
'grpc.test.TestService': 'NOT_SERVING', 'grpc.test.TestServiceServing': 'SERVING'
},
virtual_host: {
'grpc.test.TestService': 'SERVING'
}
}; };
var healthServer = new grpc.Server(); var healthServer = new grpc.Server();
healthServer.addProtoService(health.service, healthServer.addProtoService(health.service,
@ -71,15 +67,15 @@ describe('Health Checking', function() {
}); });
}); });
it('should say that a disabled service is NOT_SERVING', function(done) { it('should say that a disabled service is NOT_SERVING', function(done) {
healthClient.check({service: 'grpc.test.TestService'}, healthClient.check({service: 'grpc.test.TestServiceNotServing'},
function(err, response) { function(err, response) {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(response.status, 'NOT_SERVING'); assert.strictEqual(response.status, 'NOT_SERVING');
done(); done();
}); });
}); });
it('should say that a service on another host is SERVING', function(done) { it('should say that an enabled service is SERVING', function(done) {
healthClient.check({host: 'virtual_host', service: 'grpc.test.TestService'}, healthClient.check({service: 'grpc.test.TestServiceServing'},
function(err, response) { function(err, response) {
assert.ifError(err); assert.ifError(err);
assert.strictEqual(response.status, 'SERVING'); assert.strictEqual(response.status, 'SERVING');
@ -93,12 +89,4 @@ describe('Health Checking', function() {
done(); done();
}); });
}); });
it('should get NOT_FOUND if the host is not registered', function(done) {
healthClient.check({host: 'wrong_host', service: 'grpc.test.TestService'},
function(err, response) {
assert(err);
assert.strictEqual(err.code, grpc.status.NOT_FOUND);
done();
});
});
}); });

@ -70,7 +70,9 @@ describe('server', function() {
var pem_path = path.join(__dirname, '../test/data/server1.pem'); var pem_path = path.join(__dirname, '../test/data/server1.pem');
var key_data = fs.readFileSync(key_path); var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path); var pem_data = fs.readFileSync(pem_path);
var creds = grpc.ServerCredentials.createSsl(null, key_data, pem_data); var creds = grpc.ServerCredentials.createSsl(null,
[{private_key: key_data,
cert_chain: pem_data}]);
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
port = server.addHttp2Port('0.0.0.0:0', creds); port = server.addHttp2Port('0.0.0.0:0', creds);
}); });

@ -7,17 +7,17 @@ This directory contains source code for PHP implementation of gRPC layered on sh
Alpha : Ready for early adopters Alpha : Ready for early adopters
## ENVIRONMENT ## Environment
Prerequisite: PHP 5.5 or later, `phpunit`, `pecl` Prerequisite: PHP 5.5 or later, `phpunit`, `pecl`
Linux: **Linux:**
```sh ```sh
$ sudo apt-get install php5 php5-dev phpunit php-pear $ sudo apt-get install php5 php5-dev phpunit php-pear
``` ```
OS X: **Mac OS X:**
```sh ```sh
$ curl https://phar.phpunit.de/phpunit.phar -o phpunit.phar $ curl https://phar.phpunit.de/phpunit.phar -o phpunit.phar
@ -28,10 +28,39 @@ $ curl -O http://pear.php.net/go-pear.phar
$ sudo php -d detect_unicode=0 go-pear.phar $ sudo php -d detect_unicode=0 go-pear.phar
``` ```
## Build from Homebrew ## Quick Install
On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][]. Run the following command to **Linux (Debian):**
install gRPC.
Add [Debian unstable][] to your `sources.list` file. Example:
```sh
echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" | \
sudo tee -a /etc/apt/sources.list
```
Install the gRPC Debian package
```sh
sudo apt-get update
sudo apt-get install libgrpc-dev
```
Install the gRPC PHP extension
```sh
sudo pecl install grpc-alpha
```
**Mac OS X:**
Install [homebrew][]. Example:
```sh
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```
Install the gRPC core library and the PHP extension in one step
```sh ```sh
$ curl -fsSL https://goo.gl/getgrpc | bash -s php $ curl -fsSL https://goo.gl/getgrpc | bash -s php
@ -39,6 +68,7 @@ $ curl -fsSL https://goo.gl/getgrpc | bash -s php
This will download and run the [gRPC install script][] and compile the gRPC PHP extension. This will download and run the [gRPC install script][] and compile the gRPC PHP extension.
## Build from Source ## Build from Source
Clone this repository Clone this repository
@ -71,7 +101,7 @@ $ sudo make install
Install the gRPC PHP extension Install the gRPC PHP extension
```sh ```sh
$ sudo pecl install grpc $ sudo pecl install grpc-alpha
``` ```
OR OR
@ -140,6 +170,6 @@ $ ./bin/run_gen_code_test.sh
``` ```
[homebrew]:http://brew.sh [homebrew]:http://brew.sh
[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
[Node]:https://github.com/grpc/grpc/tree/master/src/node/examples [Node]:https://github.com/grpc/grpc/tree/master/src/node/examples
[Debian unstable]:https://www.debian.org/releases/sid/

@ -216,13 +216,18 @@ PHP_METHOD(Call, __construct) {
char *method; char *method;
int method_len; int method_len;
zval *deadline_obj; zval *deadline_obj;
/* "OsO" == 1 Object, 1 string, 1 Object */ char *host_override = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO", &channel_obj, int host_override_len = 0;
grpc_ce_channel, &method, &method_len, /* "OsO|s" == 1 Object, 1 string, 1 Object, 1 optional string */
&deadline_obj, grpc_ce_timeval) == FAILURE) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO|s",
&channel_obj, grpc_ce_channel,
&method, &method_len,
&deadline_obj, grpc_ce_timeval,
&host_override, &host_override_len)
== FAILURE) {
zend_throw_exception( zend_throw_exception(
spl_ce_InvalidArgumentException, spl_ce_InvalidArgumentException,
"Call expects a Channel, a String, and a Timeval", "Call expects a Channel, a String, a Timeval and an optional String",
1 TSRMLS_CC); 1 TSRMLS_CC);
return; return;
} }
@ -241,7 +246,7 @@ PHP_METHOD(Call, __construct) {
deadline_obj TSRMLS_CC); deadline_obj TSRMLS_CC);
call->wrapped = grpc_channel_create_call( call->wrapped = grpc_channel_create_call(
channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, method, channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, method,
channel->target, deadline->wrapped, NULL); host_override, deadline->wrapped, NULL);
} }
/** /**

@ -64,7 +64,6 @@ void free_wrapped_grpc_channel(void *object TSRMLS_DC) {
if (channel->wrapped != NULL) { if (channel->wrapped != NULL) {
grpc_channel_destroy(channel->wrapped); grpc_channel_destroy(channel->wrapped);
} }
efree(channel->target);
efree(channel); efree(channel);
} }
@ -141,9 +140,6 @@ PHP_METHOD(Channel, __construct) {
HashTable *array_hash; HashTable *array_hash;
zval **creds_obj = NULL; zval **creds_obj = NULL;
wrapped_grpc_credentials *creds = NULL; wrapped_grpc_credentials *creds = NULL;
zval **override_obj;
char *override;
int override_len;
/* "s|a" == 1 string, 1 optional array */ /* "s|a" == 1 string, 1 optional array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &target, if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &target,
&target_length, &args_array) == FAILURE) { &target_length, &args_array) == FAILURE) {
@ -151,8 +147,6 @@ PHP_METHOD(Channel, __construct) {
"Channel expects a string and an array", 1 TSRMLS_CC); "Channel expects a string and an array", 1 TSRMLS_CC);
return; return;
} }
override = target;
override_len = target_length;
if (args_array == NULL) { if (args_array == NULL) {
channel->wrapped = grpc_insecure_channel_create(target, NULL, NULL); channel->wrapped = grpc_insecure_channel_create(target, NULL, NULL);
} else { } else {
@ -169,19 +163,6 @@ PHP_METHOD(Channel, __construct) {
*creds_obj TSRMLS_CC); *creds_obj TSRMLS_CC);
zend_hash_del(array_hash, "credentials", 12); zend_hash_del(array_hash, "credentials", 12);
} }
if (zend_hash_find(array_hash, GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
sizeof(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG),
(void **)&override_obj) == SUCCESS) {
if (Z_TYPE_PP(override_obj) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
GRPC_SSL_TARGET_NAME_OVERRIDE_ARG
" must be a string",
1 TSRMLS_CC);
return;
}
override = Z_STRVAL_PP(override_obj);
override_len = Z_STRLEN_PP(override_obj);
}
php_grpc_read_args_array(args_array, &args); php_grpc_read_args_array(args_array, &args);
if (creds == NULL) { if (creds == NULL) {
channel->wrapped = grpc_insecure_channel_create(target, &args, NULL); channel->wrapped = grpc_insecure_channel_create(target, &args, NULL);
@ -192,8 +173,6 @@ PHP_METHOD(Channel, __construct) {
} }
efree(args.args); efree(args.args);
} }
channel->target = ecalloc(override_len + 1, sizeof(char));
memcpy(channel->target, override, override_len);
} }
/** /**

@ -53,7 +53,6 @@ typedef struct wrapped_grpc_channel {
zend_object std; zend_object std;
grpc_channel *wrapped; grpc_channel *wrapped;
char *target;
} wrapped_grpc_channel; } wrapped_grpc_channel;
/* Initializes the Channel class */ /* Initializes the Channel class */

@ -39,6 +39,14 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase {
protected static $client; protected static $client;
protected static $timeout; protected static $timeout;
public function testWaitForNotReady() {
$this->assertFalse(self::$client->waitForReady(1));
}
public function testWaitForReady() {
$this->assertTrue(self::$client->waitForReady(250000));
}
public function testSimpleRequest() { public function testSimpleRequest() {
$div_arg = new math\DivArgs(); $div_arg = new math\DivArgs();
$div_arg->setDividend(7); $div_arg->setDividend(7);

@ -35,7 +35,7 @@ require 'AbstractGeneratedCodeTest.php';
class GeneratedCodeTest extends AbstractGeneratedCodeTest { class GeneratedCodeTest extends AbstractGeneratedCodeTest {
public static function setUpBeforeClass() { public static function setUpBeforeClass() {
self::$client = new math\MathClient(new Grpc\BaseStub( self::$client = new math\MathClient(
getenv('GRPC_TEST_HOST'), [])); getenv('GRPC_TEST_HOST'), []);
} }
} }

@ -35,13 +35,13 @@ require 'AbstractGeneratedCodeTest.php';
class GeneratedCodeWithCallbackTest extends AbstractGeneratedCodeTest { class GeneratedCodeWithCallbackTest extends AbstractGeneratedCodeTest {
public static function setUpBeforeClass() { public static function setUpBeforeClass() {
self::$client = new math\MathClient(new Grpc\BaseStub( self::$client = new math\MathClient(
getenv('GRPC_TEST_HOST'), ['update_metadata' => getenv('GRPC_TEST_HOST'), ['update_metadata' =>
function($a_hash, function($a_hash,
$client = array()) { $client = array()) {
$a_copy = $a_hash; $a_copy = $a_hash;
$a_copy['foo'] = ['bar']; $a_copy['foo'] = ['bar'];
return $a_copy; return $a_copy;
}])); }]);
} }
} }

@ -40,13 +40,15 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
file_get_contents(dirname(__FILE__) . '/../data/server1.key'), file_get_contents(dirname(__FILE__) . '/../data/server1.key'),
file_get_contents(dirname(__FILE__) . '/../data/server1.pem')); file_get_contents(dirname(__FILE__) . '/../data/server1.pem'));
$this->server = new Grpc\Server(); $this->server = new Grpc\Server();
$port = $this->server->addSecureHttp2Port('0.0.0.0:0', $this->port = $this->server->addSecureHttp2Port('0.0.0.0:0',
$server_credentials); $server_credentials);
$this->server->start(); $this->server->start();
$this->host_override = 'foo.test.google.fr';
$this->channel = new Grpc\Channel( $this->channel = new Grpc\Channel(
'localhost:' . $port, 'localhost:' . $this->port,
[ [
'grpc.ssl_target_name_override' => 'foo.test.google.fr', 'grpc.ssl_target_name_override' => $this->host_override,
'grpc.default_authority' => $this->host_override,
'credentials' => $credentials 'credentials' => $credentials
]); ]);
} }
@ -61,7 +63,8 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
$status_text = 'xyz'; $status_text = 'xyz';
$call = new Grpc\Call($this->channel, $call = new Grpc\Call($this->channel,
'dummy_method', 'dummy_method',
$deadline); $deadline,
$this->host_override);
$event = $call->startBatch([ $event = $call->startBatch([
Grpc\OP_SEND_INITIAL_METADATA => [], Grpc\OP_SEND_INITIAL_METADATA => [],
@ -112,7 +115,8 @@ class SecureEndToEndTest extends PHPUnit_Framework_TestCase{
$call = new Grpc\Call($this->channel, $call = new Grpc\Call($this->channel,
'dummy_method', 'dummy_method',
$deadline); $deadline,
$this->host_override);
$event = $call->startBatch([ $event = $call->startBatch([
Grpc\OP_SEND_INITIAL_METADATA => [], Grpc\OP_SEND_INITIAL_METADATA => [],

@ -9,12 +9,36 @@ Alpha : Ready for early adopters
PREREQUISITES PREREQUISITES
------------- -------------
- Python 2.7, virtualenv, pip - Python 2.7, virtualenv, pip
- [homebrew][] on Mac OS X, [linuxbrew][] on Linux. These simplify the installation of the gRPC C core. - [homebrew][] on Mac OS X. These simplify the installation of the gRPC C core.
INSTALLATION INSTALLATION
------------- -------------
On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][].
Run the following command to install gRPC Python. **Linux (Debian):**
Add [Debian unstable][] to your `sources.list` file. Example:
```sh
echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" | \
sudo tee -a /etc/apt/sources.list
```
Install the gRPC Debian package
```sh
sudo apt-get update
sudo apt-get install libgrpc-dev
```
Install the gRPC Python module
```sh
sudo pip install grpcio
```
**Mac OS X**
Install [homebrew][]. Run the following command to install gRPC Python.
```sh ```sh
$ curl -fsSL https://goo.gl/getgrpc | bash -s python $ curl -fsSL https://goo.gl/getgrpc | bash -s python
``` ```
@ -27,11 +51,6 @@ Please read our online documentation for a [Quick Start][] and a [detailed examp
BUILDING FROM SOURCE BUILDING FROM SOURCE
--------------------- ---------------------
- Clone this repository - Clone this repository
- Build the gRPC core from the root of the
[gRPC Git repository](https://github.com/grpc/grpc)
```
$ make shared_c static_c
```
- Use build_python.sh to build the Python code and install it into a virtual environment - Use build_python.sh to build the Python code and install it into a virtual environment
``` ```
@ -60,7 +79,7 @@ $ ../../tools/distrib/python/submit.py
``` ```
[homebrew]:http://brew.sh [homebrew]:http://brew.sh
[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
[Quick Start]:http://www.grpc.io/docs/tutorials/basic/python.html [Quick Start]:http://www.grpc.io/docs/tutorials/basic/python.html
[detailed example]:http://www.grpc.io/docs/installation/python.html [detailed example]:http://www.grpc.io/docs/installation/python.html
[Debian unstable]:https://www.debian.org/releases/sid/

@ -6,7 +6,7 @@ Package for GRPC Python.
Dependencies Dependencies
------------ ------------
Ensure you have installed the gRPC core. On Mac OS X, install homebrew_. On Linux, install linuxbrew_. Ensure you have installed the gRPC core. On Mac OS X, install homebrew_.
Run the following command to install gRPC Python. Run the following command to install gRPC Python.
:: ::
@ -19,5 +19,4 @@ Otherwise, `install from source`_
.. _`install from source`: https://github.com/grpc/grpc/blob/master/src/python/README.md#building-from-source .. _`install from source`: https://github.com/grpc/grpc/blob/master/src/python/README.md#building-from-source
.. _homebrew: http://brew.sh .. _homebrew: http://brew.sh
.. _linuxbrew: https://github.com/Homebrew/linuxbrew#installation
.. _`gRPC install script`: https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install .. _`gRPC install script`: https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install

@ -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.

@ -0,0 +1,59 @@
# 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.
"""Private constants for the package."""
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
TICKET_SUBSCRIPTION_FOR_BASE_SUBSCRIPTION_KIND = {
base.Subscription.Kind.NONE: links.Ticket.Subscription.NONE,
base.Subscription.Kind.TERMINATION_ONLY:
links.Ticket.Subscription.TERMINATION,
base.Subscription.Kind.FULL: links.Ticket.Subscription.FULL,
}
# Mapping from abortive operation outcome to ticket termination to be
# sent to the other side of the operation, or None to indicate that no
# ticket should be sent to the other side in the event of such an
# outcome.
ABORTION_OUTCOME_TO_TICKET_TERMINATION = {
base.Outcome.CANCELLED: links.Ticket.Termination.CANCELLATION,
base.Outcome.EXPIRED: links.Ticket.Termination.EXPIRATION,
base.Outcome.LOCAL_SHUTDOWN: links.Ticket.Termination.SHUTDOWN,
base.Outcome.REMOTE_SHUTDOWN: None,
base.Outcome.RECEPTION_FAILURE: links.Ticket.Termination.RECEPTION_FAILURE,
base.Outcome.TRANSMISSION_FAILURE: None,
base.Outcome.LOCAL_FAILURE: links.Ticket.Termination.LOCAL_FAILURE,
base.Outcome.REMOTE_FAILURE: links.Ticket.Termination.REMOTE_FAILURE,
}
INTERNAL_ERROR_LOG_MESSAGE = ':-( RPC Framework (Core) internal error! )-:'
TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE = (
'Exception calling termination callback!')

@ -0,0 +1,92 @@
# 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.
"""State and behavior for operation context."""
import time
# _interfaces is referenced from specification in this module.
from grpc.framework.core import _interfaces # pylint: disable=unused-import
from grpc.framework.interfaces.base import base
class OperationContext(base.OperationContext):
"""An implementation of interfaces.OperationContext."""
def __init__(
self, lock, termination_manager, transmission_manager,
expiration_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
def _abort(self, outcome):
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def outcome(self):
"""See base.OperationContext.outcome for specification."""
with self._lock:
return self._termination_manager.outcome
def add_termination_callback(self, callback):
"""See base.OperationContext.add_termination_callback."""
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.add_callback(callback)
return None
else:
return self._termination_manager.outcome
def time_remaining(self):
"""See base.OperationContext.time_remaining for specification."""
with self._lock:
deadline = self._expiration_manager.deadline()
return max(0.0, deadline - time.time())
def cancel(self):
"""See base.OperationContext.cancel for specification."""
self._abort(base.Outcome.CANCELLED)
def fail(self, exception):
"""See base.OperationContext.fail for specification."""
self._abort(base.Outcome.LOCAL_FAILURE)

@ -0,0 +1,97 @@
# 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.
"""State and behavior for handling emitted values."""
from grpc.framework.core import _interfaces
from grpc.framework.interfaces.base import base
class EmissionManager(_interfaces.EmissionManager):
"""An EmissionManager implementation."""
def __init__(
self, lock, termination_manager, transmission_manager,
expiration_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._ingestion_manager = None
self._initial_metadata_seen = False
self._payload_seen = False
self._completion_seen = False
def set_ingestion_manager(self, ingestion_manager):
"""Sets the ingestion manager with which this manager will cooperate.
Args:
ingestion_manager: The _interfaces.IngestionManager for the operation.
"""
self._ingestion_manager = ingestion_manager
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
initial_metadata_present = initial_metadata is not None
payload_present = payload is not None
completion_present = completion is not None
allowance_present = allowance is not None
with self._lock:
if self._termination_manager.outcome is None:
if (initial_metadata_present and (
self._initial_metadata_seen or self._payload_seen or
self._completion_seen) or
payload_present and self._completion_seen or
completion_present and self._completion_seen or
allowance_present and allowance <= 0):
self._termination_manager.abort(base.Outcome.LOCAL_FAILURE)
self._transmission_manager.abort(base.Outcome.LOCAL_FAILURE)
self._expiration_manager.terminate()
else:
self._initial_metadata_seen |= initial_metadata_present
self._payload_seen |= payload_present
self._completion_seen |= completion_present
if completion_present:
self._termination_manager.emission_complete()
self._ingestion_manager.local_emissions_done()
self._transmission_manager.advance(
initial_metadata, payload, completion, allowance)
if allowance_present:
self._ingestion_manager.add_local_allowance(allowance)

@ -0,0 +1,251 @@
# 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.
"""Implementation of base.End."""
import abc
import enum
import threading
import uuid
from grpc.framework.core import _operation
from grpc.framework.core import _utilities
from grpc.framework.foundation import callable_util
from grpc.framework.foundation import later
from grpc.framework.foundation import logging_pool
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import utilities
_IDLE_ACTION_EXCEPTION_LOG_MESSAGE = 'Exception calling idle action!'
class End(base.End, links.Link):
"""A bridge between base.End and links.Link.
Implementations of this interface translate arriving tickets into
calls on application objects implementing base interfaces and
translate calls from application objects implementing base interfaces
into tickets sent to a joined link.
"""
__metaclass__ = abc.ABCMeta
class _Cycle(object):
"""State for a single start-stop End lifecycle."""
def __init__(self, pool):
self.pool = pool
self.grace = False
self.futures = []
self.operations = {}
self.idle_actions = []
def _abort(operations):
for operation in operations:
operation.abort(base.Outcome.LOCAL_SHUTDOWN)
def _cancel_futures(futures):
for future in futures:
futures.cancel()
def _future_shutdown(lock, cycle, event):
def in_future():
with lock:
_abort(cycle.operations.values())
_cancel_futures(cycle.futures)
pool = cycle.pool
cycle.pool.shutdown(wait=True)
return in_future
def _termination_action(lock, stats, operation_id, cycle):
"""Constructs the termination action for a single operation.
Args:
lock: A lock to hold during the termination action.
states: A mapping from base.Outcome values to integers to increment with
the outcome given to the termination action.
operation_id: The operation ID for the termination action.
cycle: A _Cycle value to be updated during the termination action.
Returns:
A callable that takes an operation outcome as its sole parameter and that
should be used as the termination action for the operation associated
with the given operation ID.
"""
def termination_action(outcome):
with lock:
stats[outcome] += 1
cycle.operations.pop(operation_id, None)
if not cycle.operations:
for action in cycle.idle_actions:
cycle.pool.submit(action)
cycle.idle_actions = []
if cycle.grace:
_cancel_futures(cycle.futures)
return termination_action
class _End(End):
"""An End implementation."""
def __init__(self, servicer_package):
"""Constructor.
Args:
servicer_package: A _ServicerPackage for servicing operations or None if
this end will not be used to service operations.
"""
self._lock = threading.Condition()
self._servicer_package = servicer_package
self._stats = {outcome: 0 for outcome in base.Outcome}
self._mate = None
self._cycle = None
def start(self):
"""See base.End.start for specification."""
with self._lock:
if self._cycle is not None:
raise ValueError('Tried to start a not-stopped End!')
else:
self._cycle = _Cycle(logging_pool.pool(1))
def stop(self, grace):
"""See base.End.stop for specification."""
with self._lock:
if self._cycle is None:
event = threading.Event()
event.set()
return event
elif not self._cycle.operations:
event = threading.Event()
self._cycle.pool.submit(event.set)
self._cycle.pool.shutdown(wait=False)
self._cycle = None
return event
else:
self._cycle.grace = True
event = threading.Event()
self._cycle.idle_actions.append(event.set)
if 0 < grace:
future = later.later(
grace, _future_shutdown(self._lock, self._cycle, event))
self._cycle.futures.append(future)
else:
_abort(self._cycle.operations.values())
return event
def operate(
self, group, method, subscription, timeout, initial_metadata=None,
payload=None, completion=None):
"""See base.End.operate for specification."""
operation_id = uuid.uuid4()
with self._lock:
if self._cycle is None or self._cycle.grace:
raise ValueError('Can\'t operate on stopped or stopping End!')
termination_action = _termination_action(
self._lock, self._stats, operation_id, self._cycle)
operation = _operation.invocation_operate(
operation_id, group, method, subscription, timeout, initial_metadata,
payload, completion, self._mate.accept_ticket, termination_action,
self._cycle.pool)
self._cycle.operations[operation_id] = operation
return operation.context, operation.operator
def operation_stats(self):
"""See base.End.operation_stats for specification."""
with self._lock:
return dict(self._stats)
def add_idle_action(self, action):
"""See base.End.add_idle_action for specification."""
with self._lock:
if self._cycle is None:
raise ValueError('Can\'t add idle action to stopped End!')
action_with_exceptions_logged = callable_util.with_exceptions_logged(
action, _IDLE_ACTION_EXCEPTION_LOG_MESSAGE)
if self._cycle.operations:
self._cycle.idle_actions.append(action_with_exceptions_logged)
else:
self._cycle.pool.submit(action_with_exceptions_logged)
def accept_ticket(self, ticket):
"""See links.Link.accept_ticket for specification."""
with self._lock:
if self._cycle is not None and not self._cycle.grace:
operation = self._cycle.operations.get(ticket.operation_id)
if operation is not None:
operation.handle_ticket(ticket)
elif self._servicer_package is not None:
termination_action = _termination_action(
self._lock, self._stats, ticket.operation_id, self._cycle)
operation = _operation.service_operate(
self._servicer_package, ticket, self._mate.accept_ticket,
termination_action, self._cycle.pool)
if operation is not None:
self._cycle.operations[ticket.operation_id] = operation
def join_link(self, link):
"""See links.Link.join_link for specification."""
with self._lock:
self._mate = utilities.NULL_LINK if link is None else link
def serviceless_end_link():
"""Constructs an End usable only for invoking operations.
Returns:
An End usable for translating operations into ticket exchange.
"""
return _End(None)
def serviceful_end_link(servicer, default_timeout, maximum_timeout):
"""Constructs an End capable of servicing operations.
Args:
servicer: An interfaces.Servicer for servicing operations.
default_timeout: A length of time in seconds to be used as the default
time alloted for a single operation.
maximum_timeout: A length of time in seconds to be used as the maximum
time alloted for a single operation.
Returns:
An End capable of servicing the operations requested of it through ticket
exchange.
"""
return _End(
_utilities.ServicerPackage(servicer, default_timeout, maximum_timeout))

@ -0,0 +1,152 @@
# 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.
"""State and behavior for operation expiration."""
import time
from grpc.framework.core import _interfaces
from grpc.framework.foundation import later
from grpc.framework.interfaces.base import base
class _ExpirationManager(_interfaces.ExpirationManager):
"""An implementation of _interfaces.ExpirationManager."""
def __init__(
self, commencement, timeout, maximum_timeout, lock, termination_manager,
transmission_manager):
"""Constructor.
Args:
commencement: The time in seconds since the epoch at which the operation
began.
timeout: A length of time in seconds to allow for the operation to run.
maximum_timeout: The maximum length of time in seconds to allow for the
operation to run despite what is requested via this object's
change_timout method.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._commencement = commencement
self._maximum_timeout = maximum_timeout
self._timeout = timeout
self._deadline = commencement + timeout
self._index = None
self._future = None
def _expire(self, index):
def expire():
with self._lock:
if self._future is not None and index == self._index:
self._future = None
self._termination_manager.expire()
self._transmission_manager.abort(base.Outcome.EXPIRED)
return expire
def start(self):
self._index = 0
self._future = later.later(self._timeout, self._expire(0))
def change_timeout(self, timeout):
if self._future is not None and timeout != self._timeout:
self._future.cancel()
new_timeout = min(timeout, self._maximum_timeout)
new_index = self._index + 1
self._timeout = new_timeout
self._deadline = self._commencement + new_timeout
self._index = new_index
delay = self._deadline - time.time()
self._future = later.later(delay, self._expire(new_index))
if new_timeout != timeout:
self._transmission_manager.timeout(new_timeout)
def deadline(self):
return self._deadline
def terminate(self):
if self._future:
self._future.cancel()
self._future = None
self._deadline_index = None
def invocation_expiration_manager(
timeout, lock, termination_manager, transmission_manager):
"""Creates an _interfaces.ExpirationManager appropriate for front-side use.
Args:
timeout: A length of time in seconds to allow for the operation to run.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
Returns:
An _interfaces.ExpirationManager appropriate for invocation-side use.
"""
expiration_manager = _ExpirationManager(
time.time(), timeout, timeout, lock, termination_manager,
transmission_manager)
expiration_manager.start()
return expiration_manager
def service_expiration_manager(
timeout, default_timeout, maximum_timeout, lock, termination_manager,
transmission_manager):
"""Creates an _interfaces.ExpirationManager appropriate for back-side use.
Args:
timeout: A length of time in seconds to allow for the operation to run. May
be None in which case default_timeout will be used.
default_timeout: The default length of time in seconds to allow for the
operation to run if the front-side customer has not specified such a value
(or if the value they specified is not yet known).
maximum_timeout: The maximum length of time in seconds to allow for the
operation to run.
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
Returns:
An _interfaces.ExpirationManager appropriate for service-side use.
"""
expiration_manager = _ExpirationManager(
time.time(), default_timeout if timeout is None else timeout,
maximum_timeout, lock, termination_manager, transmission_manager)
expiration_manager.start()
return expiration_manager

@ -0,0 +1,410 @@
# 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.
"""State and behavior for ingestion during an operation."""
import abc
import collections
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.foundation import abandonment
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
_CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE = 'Exception initializing ingestion!'
_INGESTION_EXCEPTION_LOG_MESSAGE = 'Exception during ingestion!'
class _SubscriptionCreation(collections.namedtuple(
'_SubscriptionCreation', ('subscription', 'remote_error', 'abandoned'))):
"""A sum type for the outcome of ingestion initialization.
Either subscription will be non-None, remote_error will be True, or abandoned
will be True.
Attributes:
subscription: A base.Subscription describing the customer's interest in
operation values from the other side.
remote_error: A boolean indicating that the subscription could not be
created due to an error on the remote side of the operation.
abandoned: A boolean indicating that subscription creation was abandoned.
"""
class _SubscriptionCreator(object):
"""Common specification of subscription-creating behavior."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def create(self, group, method):
"""Creates the base.Subscription of the local customer.
Any exceptions raised by this method should be attributed to and treated as
defects in the customer code called by this method.
Args:
group: The group identifier of the operation.
method: The method identifier of the operation.
Returns:
A _SubscriptionCreation describing the result of subscription creation.
"""
raise NotImplementedError()
class _ServiceSubscriptionCreator(_SubscriptionCreator):
"""A _SubscriptionCreator appropriate for service-side use."""
def __init__(self, servicer, operation_context, output_operator):
"""Constructor.
Args:
servicer: The base.Servicer that will service the operation.
operation_context: A base.OperationContext for the operation to be passed
to the customer.
output_operator: A base.Operator for the operation to be passed to the
customer and to be called by the customer to accept operation data
emitted by the customer.
"""
self._servicer = servicer
self._operation_context = operation_context
self._output_operator = output_operator
def create(self, group, method):
try:
subscription = self._servicer.service(
group, method, self._operation_context, self._output_operator)
except base.NoSuchMethodError:
return _SubscriptionCreation(None, True, False)
except abandonment.Abandoned:
return _SubscriptionCreation(None, False, True)
else:
return _SubscriptionCreation(subscription, False, False)
def _wrap(behavior):
def wrapped(*args, **kwargs):
try:
behavior(*args, **kwargs)
except abandonment.Abandoned:
return False
else:
return True
return wrapped
class _IngestionManager(_interfaces.IngestionManager):
"""An implementation of _interfaces.IngestionManager."""
def __init__(
self, lock, pool, subscription, subscription_creator, termination_manager,
transmission_manager, expiration_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
subscription: A base.Subscription describing the customer's interest in
operation values from the other side. May be None if
subscription_creator is not None.
subscription_creator: A _SubscriptionCreator wrapping the portion of
customer code that when called returns the base.Subscription describing
the customer's interest in operation values from the other side. May be
None if subscription is not None.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
"""
self._lock = lock
self._pool = pool
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
if subscription is None:
self._subscription_creator = subscription_creator
self._wrapped_operator = None
elif subscription.kind is base.Subscription.Kind.FULL:
self._subscription_creator = None
self._wrapped_operator = _wrap(subscription.operator.advance)
else:
# TODO(nathaniel): Support other subscriptions.
raise ValueError('Unsupported subscription "%s"!' % subscription.kind)
self._pending_initial_metadata = None
self._pending_payloads = []
self._pending_completion = None
self._local_allowance = 1
# A nonnegative integer or None, with None indicating that the local
# customer is done emitting anyway so there's no need to bother it by
# informing it that the remote customer has granted it further permission to
# emit.
self._remote_allowance = 0
self._processing = False
def _abort_internal_only(self):
self._subscription_creator = None
self._wrapped_operator = None
self._pending_initial_metadata = None
self._pending_payloads = None
self._pending_completion = None
def _abort_and_notify(self, outcome):
self._abort_internal_only()
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def _operator_next(self):
"""Computes the next step for full-subscription ingestion.
Returns:
An initial_metadata, payload, completion, allowance, continue quintet
indicating what operation values (if any) are available to pass into
customer code and whether or not there is anything immediately
actionable to call customer code to do.
"""
if self._wrapped_operator is None:
return None, None, None, None, False
else:
initial_metadata, payload, completion, allowance, action = [None] * 5
if self._pending_initial_metadata is not None:
initial_metadata = self._pending_initial_metadata
self._pending_initial_metadata = None
action = True
if self._pending_payloads and 0 < self._local_allowance:
payload = self._pending_payloads.pop(0)
self._local_allowance -= 1
action = True
if not self._pending_payloads and self._pending_completion is not None:
completion = self._pending_completion
self._pending_completion = None
action = True
if self._remote_allowance is not None and 0 < self._remote_allowance:
allowance = self._remote_allowance
self._remote_allowance = 0
action = True
return initial_metadata, payload, completion, allowance, bool(action)
def _operator_process(
self, wrapped_operator, initial_metadata, payload,
completion, allowance):
while True:
advance_outcome = callable_util.call_logging_exceptions(
wrapped_operator, _INGESTION_EXCEPTION_LOG_MESSAGE,
initial_metadata=initial_metadata, payload=payload,
completion=completion, allowance=allowance)
if advance_outcome.exception is None:
if advance_outcome.return_value:
with self._lock:
if self._termination_manager.outcome is not None:
return
if completion is not None:
self._termination_manager.ingestion_complete()
initial_metadata, payload, completion, allowance, moar = (
self._operator_next())
if not moar:
self._processing = False
return
else:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
return
else:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
return
def _operator_post_create(self, subscription):
wrapped_operator = _wrap(subscription.operator.advance)
with self._lock:
if self._termination_manager.outcome is not None:
return
self._wrapped_operator = wrapped_operator
self._subscription_creator = None
metadata, payload, completion, allowance, moar = self._operator_next()
if not moar:
self._processing = False
return
self._operator_process(
wrapped_operator, metadata, payload, completion, allowance)
def _create(self, subscription_creator, group, name):
outcome = callable_util.call_logging_exceptions(
subscription_creator.create, _CREATE_SUBSCRIPTION_EXCEPTION_LOG_MESSAGE,
group, name)
if outcome.return_value is None:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
elif outcome.return_value.abandoned:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.LOCAL_FAILURE)
elif outcome.return_value.remote_error:
with self._lock:
if self._termination_manager.outcome is None:
self._abort_and_notify(base.Outcome.REMOTE_FAILURE)
elif outcome.return_value.subscription.kind is base.Subscription.Kind.FULL:
self._operator_post_create(outcome.return_value.subscription)
else:
# TODO(nathaniel): Support other subscriptions.
raise ValueError(
'Unsupported "%s"!' % outcome.return_value.subscription.kind)
def _store_advance(self, initial_metadata, payload, completion, allowance):
if initial_metadata is not None:
self._pending_initial_metadata = initial_metadata
if payload is not None:
self._pending_payloads.append(payload)
if completion is not None:
self._pending_completion = completion
if allowance is not None and self._remote_allowance is not None:
self._remote_allowance += allowance
def _operator_advance(self, initial_metadata, payload, completion, allowance):
if self._processing:
self._store_advance(initial_metadata, payload, completion, allowance)
else:
action = False
if initial_metadata is not None:
action = True
if payload is not None:
if 0 < self._local_allowance:
self._local_allowance -= 1
action = True
else:
self._pending_payloads.append(payload)
payload = False
if completion is not None:
if self._pending_payloads:
self._pending_completion = completion
else:
action = True
if allowance is not None and self._remote_allowance is not None:
allowance += self._remote_allowance
self._remote_allowance = 0
action = True
if action:
self._pool.submit(
callable_util.with_exceptions_logged(
self._operator_process, _constants.INTERNAL_ERROR_LOG_MESSAGE),
self._wrapped_operator, initial_metadata, payload, completion,
allowance)
def set_group_and_method(self, group, method):
"""See _interfaces.IngestionManager.set_group_and_method for spec."""
if self._subscription_creator is not None and not self._processing:
self._pool.submit(
callable_util.with_exceptions_logged(
self._create, _constants.INTERNAL_ERROR_LOG_MESSAGE),
self._subscription_creator, group, method)
self._processing = True
def add_local_allowance(self, allowance):
"""See _interfaces.IngestionManager.add_local_allowance for spec."""
if any((self._subscription_creator, self._wrapped_operator,)):
self._local_allowance += allowance
if not self._processing:
initial_metadata, payload, completion, allowance, moar = (
self._operator_next())
if moar:
self._pool.submit(
callable_util.with_exceptions_logged(
self._operator_process,
_constants.INTERNAL_ERROR_LOG_MESSAGE),
initial_metadata, payload, completion, allowance)
def local_emissions_done(self):
self._remote_allowance = None
def advance(self, initial_metadata, payload, completion, allowance):
"""See _interfaces.IngestionManager.advance for specification."""
if self._subscription_creator is not None:
self._store_advance(initial_metadata, payload, completion, allowance)
elif self._wrapped_operator is not None:
self._operator_advance(initial_metadata, payload, completion, allowance)
def invocation_ingestion_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager):
"""Creates an IngestionManager appropriate for invocation-side use.
Args:
subscription: A base.Subscription indicating the customer's interest in the
data and results from the service-side of the operation.
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
Returns:
An IngestionManager appropriate for invocation-side use.
"""
return _IngestionManager(
lock, pool, subscription, None, termination_manager, transmission_manager,
expiration_manager)
def service_ingestion_manager(
servicer, operation_context, output_operator, lock, pool,
termination_manager, transmission_manager, expiration_manager):
"""Creates an IngestionManager appropriate for service-side use.
The returned IngestionManager will require its set_group_and_name method to be
called before its advance method may be called.
Args:
servicer: A base.Servicer for servicing the operation.
operation_context: A base.OperationContext for the operation to be passed to
the customer.
output_operator: A base.Operator for the operation to be passed to the
customer and to be called by the customer to accept operation data output
by the customer.
lock: The operation-wide lock.
pool: A thread pool in which to execute customer code.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
Returns:
An IngestionManager appropriate for service-side use.
"""
subscription_creator = _ServiceSubscriptionCreator(
servicer, operation_context, output_operator)
return _IngestionManager(
lock, pool, None, subscription_creator, termination_manager,
transmission_manager, expiration_manager)

@ -0,0 +1,308 @@
# 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.
"""Package-internal interfaces."""
import abc
from grpc.framework.interfaces.base import base
class TerminationManager(object):
"""An object responsible for handling the termination of an operation.
Attributes:
outcome: None if the operation is active or a base.Outcome value if it has
terminated.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def add_callback(self, callback):
"""Registers a callback to be called on operation termination.
If the operation has already terminated the callback will not be called.
Args:
callback: A callable that will be passed an interfaces.Outcome value.
Returns:
None if the operation has not yet terminated and the passed callback will
be called when it does, or a base.Outcome value describing the operation
termination if the operation has terminated and the callback will not be
called as a result of this method call.
"""
raise NotImplementedError()
@abc.abstractmethod
def emission_complete(self):
"""Indicates that emissions from customer code have completed."""
raise NotImplementedError()
@abc.abstractmethod
def transmission_complete(self):
"""Indicates that transmissions to the remote end are complete.
Returns:
True if the operation has terminated or False if the operation remains
ongoing.
"""
raise NotImplementedError()
@abc.abstractmethod
def reception_complete(self):
"""Indicates that reception from the other side is complete."""
raise NotImplementedError()
@abc.abstractmethod
def ingestion_complete(self):
"""Indicates that customer code ingestion of received values is complete."""
raise NotImplementedError()
@abc.abstractmethod
def expire(self):
"""Indicates that the operation must abort because it has taken too long."""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome):
"""Indicates that the operation must abort for the indicated reason.
Args:
outcome: An interfaces.Outcome indicating operation abortion.
"""
raise NotImplementedError()
class TransmissionManager(object):
"""A manager responsible for transmitting to the other end of an operation."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def kick_off(
self, group, method, timeout, initial_metadata, payload, completion,
allowance):
"""Transmits the values associated with operation invocation."""
raise NotImplementedError()
@abc.abstractmethod
def advance(self, initial_metadata, payload, completion, allowance):
"""Accepts values for transmission to the other end of the operation.
Args:
initial_metadata: An initial metadata value to be transmitted to the other
side of the operation. May only ever be non-None once.
payload: A payload value.
completion: A base.Completion value. May only ever be non-None in the last
transmission to be made to the other side.
allowance: A positive integer communicating the number of additional
payloads allowed to be transmitted from the other side to this side of
the operation, or None if no additional allowance is being granted in
this call.
"""
raise NotImplementedError()
@abc.abstractmethod
def timeout(self, timeout):
"""Accepts for transmission to the other side a new timeout value.
Args:
timeout: A positive float used as the new timeout value for the operation
to be transmitted to the other side.
"""
raise NotImplementedError()
@abc.abstractmethod
def allowance(self, allowance):
"""Indicates to this manager that the remote customer is allowing payloads.
Args:
allowance: A positive integer indicating the number of additional payloads
the remote customer is allowing to be transmitted from this side of the
operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def remote_complete(self):
"""Indicates to this manager that data from the remote side is complete."""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome):
"""Indicates that the operation has aborted.
Args:
outcome: An interfaces.Outcome for the operation. If None, indicates that
the operation abortion should not be communicated to the other side of
the operation.
"""
raise NotImplementedError()
class ExpirationManager(object):
"""A manager responsible for aborting the operation if it runs out of time."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def change_timeout(self, timeout):
"""Changes the timeout allotted for the operation.
Operation duration is always measure from the beginning of the operation;
calling this method changes the operation's allotted time to timeout total
seconds, not timeout seconds from the time of this method call.
Args:
timeout: A length of time in seconds to allow for the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def deadline(self):
"""Returns the time until which the operation is allowed to run.
Returns:
The time (seconds since the epoch) at which the operation will expire.
"""
raise NotImplementedError()
@abc.abstractmethod
def terminate(self):
"""Indicates to this manager that the operation has terminated."""
raise NotImplementedError()
class EmissionManager(base.Operator):
"""A manager of values emitted by customer code."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def advance(
self, initial_metadata=None, payload=None, completion=None,
allowance=None):
"""Accepts a value emitted by customer code.
This method should only be called by customer code.
Args:
initial_metadata: An initial metadata value emitted by the local customer
to be sent to the other side of the operation.
payload: A payload value emitted by the local customer to be sent to the
other side of the operation.
completion: A Completion value emitted by the local customer to be sent to
the other side of the operation.
allowance: A positive integer indicating an additional number of payloads
that the local customer is willing to accept from the other side of the
operation.
"""
raise NotImplementedError()
class IngestionManager(object):
"""A manager responsible for executing customer code.
This name of this manager comes from its responsibility to pass successive
values from the other side of the operation into the code of the local
customer.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def set_group_and_method(self, group, method):
"""Communicates to this IngestionManager the operation group and method.
Args:
group: The group identifier of the operation.
method: The method identifier of the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def add_local_allowance(self, allowance):
"""Communicates to this IngestionManager that more payloads may be ingested.
Args:
allowance: A positive integer indicating an additional number of payloads
that the local customer is willing to ingest.
"""
raise NotImplementedError()
@abc.abstractmethod
def local_emissions_done(self):
"""Indicates to this manager that local emissions are done."""
raise NotImplementedError()
@abc.abstractmethod
def advance(self, initial_metadata, payload, completion, allowance):
"""Advances the operation by passing values to the local customer."""
raise NotImplementedError()
class ReceptionManager(object):
"""A manager responsible for receiving tickets from the other end."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def receive_ticket(self, ticket):
"""Handle a ticket from the other side of the operation.
Args:
ticket: An interfaces.BackToFrontTicket or interfaces.FrontToBackTicket
appropriate to this end of the operation and this object.
"""
raise NotImplementedError()
class Operation(object):
"""An ongoing operation.
Attributes:
context: A base.OperationContext object for the operation.
operator: A base.Operator object for the operation for use by the customer
of the operation.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def handle_ticket(self, ticket):
"""Handle a ticket from the other side of the operation.
Args:
ticket: A links.Ticket from the other side of the operation.
"""
raise NotImplementedError()
@abc.abstractmethod
def abort(self, outcome):
"""Aborts the operation.
Args:
outcome: A base.Outcome value indicating operation abortion.
"""
raise NotImplementedError()

@ -0,0 +1,192 @@
# 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.
"""Implementation of operations."""
import threading
# _utilities is referenced from specification in this module.
from grpc.framework.core import _context
from grpc.framework.core import _emission
from grpc.framework.core import _expiration
from grpc.framework.core import _ingestion
from grpc.framework.core import _interfaces
from grpc.framework.core import _reception
from grpc.framework.core import _termination
from grpc.framework.core import _transmission
from grpc.framework.core import _utilities # pylint: disable=unused-import
class _EasyOperation(_interfaces.Operation):
"""A trivial implementation of interfaces.Operation."""
def __init__(
self, lock, termination_manager, transmission_manager, expiration_manager,
context, operator, reception_manager):
"""Constructor.
Args:
lock: The operation-wide lock.
termination_manager: The _interfaces.TerminationManager for the operation.
transmission_manager: The _interfaces.TransmissionManager for the
operation.
expiration_manager: The _interfaces.ExpirationManager for the operation.
context: A base.OperationContext for use by the customer during the
operation.
operator: A base.Operator for use by the customer during the operation.
reception_manager: The _interfaces.ReceptionManager for the operation.
"""
self._lock = lock
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._reception_manager = reception_manager
self.context = context
self.operator = operator
def handle_ticket(self, ticket):
with self._lock:
self._reception_manager.receive_ticket(ticket)
def abort(self, outcome):
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def invocation_operate(
operation_id, group, method, subscription, timeout, initial_metadata,
payload, completion, ticket_sink, termination_action, pool):
"""Constructs objects necessary for front-side operation management.
Args:
operation_id: An object identifying the operation.
group: The group identifier of the operation.
method: The method identifier of the operation.
subscription: A base.Subscription describing the customer's interest in the
results of the operation.
timeout: A length of time in seconds to allow for the operation.
initial_metadata: An initial metadata value to be sent to the other side of
the operation. May be None if the initial metadata will be passed later or
if there will be no initial metadata passed at all.
payload: The first payload value to be transmitted to the other side. May be
None if there is no such value or if the customer chose not to pass it at
operation invocation.
completion: A base.Completion value indicating the end of values passed to
the other side of the operation.
ticket_sink: A callable that accepts links.Tickets and delivers them to the
other side of the operation.
termination_action: A callable that accepts the outcome of the operation as
a base.Outcome value to be called on operation completion.
pool: A thread pool with which to do the work of the operation.
Returns:
An _interfaces.Operation for the operation.
"""
lock = threading.Lock()
with lock:
termination_manager = _termination.invocation_termination_manager(
termination_action, pool)
transmission_manager = _transmission.TransmissionManager(
operation_id, ticket_sink, lock, pool, termination_manager)
expiration_manager = _expiration.invocation_expiration_manager(
timeout, lock, termination_manager, transmission_manager)
operation_context = _context.OperationContext(
lock, termination_manager, transmission_manager, expiration_manager)
emission_manager = _emission.EmissionManager(
lock, termination_manager, transmission_manager, expiration_manager)
ingestion_manager = _ingestion.invocation_ingestion_manager(
subscription, lock, pool, termination_manager, transmission_manager,
expiration_manager)
reception_manager = _reception.ReceptionManager(
termination_manager, transmission_manager, expiration_manager,
ingestion_manager)
termination_manager.set_expiration_manager(expiration_manager)
transmission_manager.set_expiration_manager(expiration_manager)
emission_manager.set_ingestion_manager(ingestion_manager)
transmission_manager.kick_off(
group, method, timeout, initial_metadata, payload, completion, None)
return _EasyOperation(
lock, termination_manager, transmission_manager, expiration_manager,
operation_context, emission_manager, reception_manager)
def service_operate(
servicer_package, ticket, ticket_sink, termination_action, pool):
"""Constructs an Operation for service of an operation.
Args:
servicer_package: A _utilities.ServicerPackage to be used servicing the
operation.
ticket: The first links.Ticket received for the operation.
ticket_sink: A callable that accepts links.Tickets and delivers them to the
other side of the operation.
termination_action: A callable that accepts the outcome of the operation as
a base.Outcome value to be called on operation completion.
pool: A thread pool with which to do the work of the operation.
Returns:
An _interfaces.Operation for the operation.
"""
lock = threading.Lock()
with lock:
termination_manager = _termination.service_termination_manager(
termination_action, pool)
transmission_manager = _transmission.TransmissionManager(
ticket.operation_id, ticket_sink, lock, pool, termination_manager)
expiration_manager = _expiration.service_expiration_manager(
ticket.timeout, servicer_package.default_timeout,
servicer_package.maximum_timeout, lock, termination_manager,
transmission_manager)
operation_context = _context.OperationContext(
lock, termination_manager, transmission_manager, expiration_manager)
emission_manager = _emission.EmissionManager(
lock, termination_manager, transmission_manager, expiration_manager)
ingestion_manager = _ingestion.service_ingestion_manager(
servicer_package.servicer, operation_context, emission_manager, lock,
pool, termination_manager, transmission_manager, expiration_manager)
reception_manager = _reception.ReceptionManager(
termination_manager, transmission_manager, expiration_manager,
ingestion_manager)
termination_manager.set_expiration_manager(expiration_manager)
transmission_manager.set_expiration_manager(expiration_manager)
emission_manager.set_ingestion_manager(ingestion_manager)
reception_manager.receive_ticket(ticket)
return _EasyOperation(
lock, termination_manager, transmission_manager, expiration_manager,
operation_context, emission_manager, reception_manager)

@ -0,0 +1,137 @@
# 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.
"""State and behavior for ticket reception."""
from grpc.framework.core import _interfaces
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.base import utilities
from grpc.framework.interfaces.links import links
_REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME = {
links.Ticket.Termination.CANCELLATION: base.Outcome.CANCELLED,
links.Ticket.Termination.EXPIRATION: base.Outcome.EXPIRED,
links.Ticket.Termination.SHUTDOWN: base.Outcome.REMOTE_SHUTDOWN,
links.Ticket.Termination.RECEPTION_FAILURE: base.Outcome.RECEPTION_FAILURE,
links.Ticket.Termination.TRANSMISSION_FAILURE:
base.Outcome.TRANSMISSION_FAILURE,
links.Ticket.Termination.LOCAL_FAILURE: base.Outcome.REMOTE_FAILURE,
}
class ReceptionManager(_interfaces.ReceptionManager):
"""A ReceptionManager based around a _Receiver passed to it."""
def __init__(
self, termination_manager, transmission_manager, expiration_manager,
ingestion_manager):
"""Constructor.
Args:
termination_manager: The operation's _interfaces.TerminationManager.
transmission_manager: The operation's _interfaces.TransmissionManager.
expiration_manager: The operation's _interfaces.ExpirationManager.
ingestion_manager: The operation's _interfaces.IngestionManager.
"""
self._termination_manager = termination_manager
self._transmission_manager = transmission_manager
self._expiration_manager = expiration_manager
self._ingestion_manager = ingestion_manager
self._lowest_unseen_sequence_number = 0
self._out_of_sequence_tickets = {}
self._aborted = False
def _abort(self, outcome):
self._aborted = True
self._termination_manager.abort(outcome)
self._transmission_manager.abort(outcome)
self._expiration_manager.terminate()
def _sequence_failure(self, ticket):
"""Determines a just-arrived ticket's sequential legitimacy.
Args:
ticket: A just-arrived ticket.
Returns:
True if the ticket is sequentially legitimate; False otherwise.
"""
if ticket.sequence_number < self._lowest_unseen_sequence_number:
return True
elif ticket.sequence_number in self._out_of_sequence_tickets:
return True
else:
return False
def _process_one(self, ticket):
if ticket.sequence_number == 0:
self._ingestion_manager.set_group_and_method(ticket.group, ticket.method)
if ticket.timeout is not None:
self._expiration_manager.change_timeout(ticket.timeout)
if ticket.termination is None:
completion = None
else:
completion = utilities.completion(
ticket.terminal_metadata, ticket.code, ticket.message)
self._ingestion_manager.advance(
ticket.initial_metadata, ticket.payload, completion, ticket.allowance)
if ticket.allowance is not None:
self._transmission_manager.allowance(ticket.allowance)
def _process(self, ticket):
"""Process those tickets ready to be processed.
Args:
ticket: A just-arrived ticket the sequence number of which matches this
_ReceptionManager's _lowest_unseen_sequence_number field.
"""
while True:
self._process_one(ticket)
next_ticket = self._out_of_sequence_tickets.pop(
ticket.sequence_number + 1, None)
if next_ticket is None:
self._lowest_unseen_sequence_number = ticket.sequence_number + 1
return
else:
ticket = next_ticket
def receive_ticket(self, ticket):
"""See _interfaces.ReceptionManager.receive_ticket for specification."""
if self._aborted:
return
elif self._sequence_failure(ticket):
self._abort(base.Outcome.RECEPTION_FAILURE)
elif ticket.termination not in (None, links.Ticket.Termination.COMPLETION):
outcome = _REMOTE_TICKET_TERMINATION_TO_LOCAL_OUTCOME[ticket.termination]
self._abort(outcome)
elif ticket.sequence_number == self._lowest_unseen_sequence_number:
self._process(ticket)
else:
self._out_of_sequence_tickets[ticket.sequence_number] = ticket

@ -0,0 +1,212 @@
# 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.
"""State and behavior for operation termination."""
import abc
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
def _invocation_completion_predicate(
unused_emission_complete, unused_transmission_complete,
unused_reception_complete, ingestion_complete):
return ingestion_complete
def _service_completion_predicate(
unused_emission_complete, transmission_complete, unused_reception_complete,
unused_ingestion_complete):
return transmission_complete
class TerminationManager(_interfaces.TerminationManager):
"""A _interfaces.TransmissionManager on which another manager may be set."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def set_expiration_manager(self, expiration_manager):
"""Sets the expiration manager with which this manager will interact.
Args:
expiration_manager: The _interfaces.ExpirationManager associated with the
current operation.
"""
raise NotImplementedError()
class _TerminationManager(TerminationManager):
"""An implementation of TerminationManager."""
def __init__(self, predicate, action, pool):
"""Constructor.
Args:
predicate: One of _invocation_completion_predicate or
_service_completion_predicate to be used to determine when the operation
has completed.
action: A behavior to pass the operation outcome on operation termination.
pool: A thread pool.
"""
self._predicate = predicate
self._action = action
self._pool = pool
self._expiration_manager = None
self.outcome = None
self._callbacks = []
self._emission_complete = False
self._transmission_complete = False
self._reception_complete = False
self._ingestion_complete = False
def set_expiration_manager(self, expiration_manager):
self._expiration_manager = expiration_manager
def _terminate_internal_only(self, outcome):
"""Terminates the operation.
Args:
outcome: A base.Outcome describing the outcome of the operation.
"""
self.outcome = outcome
callbacks = list(self._callbacks)
self._callbacks = None
act = callable_util.with_exceptions_logged(
self._action, _constants.INTERNAL_ERROR_LOG_MESSAGE)
if outcome is base.Outcome.LOCAL_FAILURE:
self._pool.submit(act, outcome)
else:
def call_callbacks_and_act(callbacks, outcome):
for callback in callbacks:
callback_outcome = callable_util.call_logging_exceptions(
callback, _constants.TERMINATION_CALLBACK_EXCEPTION_LOG_MESSAGE,
outcome)
if callback_outcome.exception is not None:
outcome = base.Outcome.LOCAL_FAILURE
break
act(outcome)
self._pool.submit(
callable_util.with_exceptions_logged(
call_callbacks_and_act, _constants.INTERNAL_ERROR_LOG_MESSAGE),
callbacks, outcome)
def _terminate_and_notify(self, outcome):
self._terminate_internal_only(outcome)
self._expiration_manager.terminate()
def _perhaps_complete(self):
if self._predicate(
self._emission_complete, self._transmission_complete,
self._reception_complete, self._ingestion_complete):
self._terminate_and_notify(base.Outcome.COMPLETED)
return True
else:
return False
def is_active(self):
"""See _interfaces.TerminationManager.is_active for specification."""
return self.outcome is None
def add_callback(self, callback):
"""See _interfaces.TerminationManager.add_callback for specification."""
if self.outcome is None:
self._callbacks.append(callback)
return None
else:
return self.outcome
def emission_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._emission_complete = True
self._perhaps_complete()
def transmission_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._transmission_complete = True
return self._perhaps_complete()
else:
return False
def reception_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._reception_complete = True
self._perhaps_complete()
def ingestion_complete(self):
"""See superclass method for specification."""
if self.outcome is None:
self._ingestion_complete = True
self._perhaps_complete()
def expire(self):
"""See _interfaces.TerminationManager.expire for specification."""
self._terminate_internal_only(base.Outcome.EXPIRED)
def abort(self, outcome):
"""See _interfaces.TerminationManager.abort for specification."""
self._terminate_and_notify(outcome)
def invocation_termination_manager(action, pool):
"""Creates a TerminationManager appropriate for invocation-side use.
Args:
action: An action to call on operation termination.
pool: A thread pool in which to execute the passed action and any
termination callbacks that are registered during the operation.
Returns:
A TerminationManager appropriate for invocation-side use.
"""
return _TerminationManager(_invocation_completion_predicate, action, pool)
def service_termination_manager(action, pool):
"""Creates a TerminationManager appropriate for service-side use.
Args:
action: An action to call on operation termination.
pool: A thread pool in which to execute the passed action and any
termination callbacks that are registered during the operation.
Returns:
A TerminationManager appropriate for service-side use.
"""
return _TerminationManager(_service_completion_predicate, action, pool)

@ -0,0 +1,294 @@
# 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.
"""State and behavior for ticket transmission during an operation."""
from grpc.framework.core import _constants
from grpc.framework.core import _interfaces
from grpc.framework.foundation import callable_util
from grpc.framework.interfaces.base import base
from grpc.framework.interfaces.links import links
_TRANSMISSION_EXCEPTION_LOG_MESSAGE = 'Exception during transmission!'
def _explode_completion(completion):
if completion is None:
return None, None, None, None
else:
return (
completion.terminal_metadata, completion.code, completion.message,
links.Ticket.Termination.COMPLETION)
class TransmissionManager(_interfaces.TransmissionManager):
"""An _interfaces.TransmissionManager that sends links.Tickets."""
def __init__(
self, operation_id, ticket_sink, lock, pool, termination_manager):
"""Constructor.
Args:
operation_id: The operation's ID.
ticket_sink: A callable that accepts tickets and sends them to the other
side of the operation.
lock: The operation-servicing-wide lock object.
pool: A thread pool in which the work of transmitting tickets will be
performed.
termination_manager: The _interfaces.TerminationManager associated with
this operation.
"""
self._lock = lock
self._pool = pool
self._ticket_sink = ticket_sink
self._operation_id = operation_id
self._termination_manager = termination_manager
self._expiration_manager = None
self._lowest_unused_sequence_number = 0
self._remote_allowance = 1
self._remote_complete = False
self._timeout = None
self._local_allowance = 0
self._initial_metadata = None
self._payloads = []
self._completion = None
self._aborted = False
self._abortion_outcome = None
self._transmitting = False
def set_expiration_manager(self, expiration_manager):
"""Sets the ExpirationManager with which this manager will cooperate."""
self._expiration_manager = expiration_manager
def _next_ticket(self):
"""Creates the next ticket to be transmitted.
Returns:
A links.Ticket to be sent to the other side of the operation or None if
there is nothing to be sent at this time.
"""
if self._aborted:
if self._abortion_outcome is None:
return None
else:
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION[
self._abortion_outcome]
if termination is None:
return None
else:
self._abortion_outcome = None
return links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None,
None, None, None, None, None, None, None, None, None,
termination)
action = False
# TODO(nathaniel): Support other subscriptions.
local_subscription = links.Ticket.Subscription.FULL
timeout = self._timeout
if timeout is not None:
self._timeout = None
action = True
if self._local_allowance <= 0:
allowance = None
else:
allowance = self._local_allowance
self._local_allowance = 0
action = True
initial_metadata = self._initial_metadata
if initial_metadata is not None:
self._initial_metadata = None
action = True
if not self._payloads or self._remote_allowance <= 0:
payload = None
else:
payload = self._payloads.pop(0)
self._remote_allowance -= 1
action = True
if self._completion is None or self._payloads:
terminal_metadata, code, message, termination = None, None, None, None
else:
terminal_metadata, code, message, termination = _explode_completion(
self._completion)
self._completion = None
action = True
if action:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
local_subscription, timeout, allowance, initial_metadata, payload,
terminal_metadata, code, message, termination)
self._lowest_unused_sequence_number += 1
return ticket
else:
return None
def _transmit(self, ticket):
"""Commences the transmission loop sending tickets.
Args:
ticket: A links.Ticket to be sent to the other side of the operation.
"""
def transmit(ticket):
while True:
transmission_outcome = callable_util.call_logging_exceptions(
self._ticket_sink, _TRANSMISSION_EXCEPTION_LOG_MESSAGE, ticket)
if transmission_outcome.exception is None:
with self._lock:
if ticket.termination is links.Ticket.Termination.COMPLETION:
self._termination_manager.transmission_complete()
ticket = self._next_ticket()
if ticket is None:
self._transmitting = False
return
else:
with self._lock:
if self._termination_manager.outcome is None:
self._termination_manager.abort(base.Outcome.TRANSMISSION_FAILURE)
self._expiration_manager.terminate()
return
self._pool.submit(callable_util.with_exceptions_logged(
transmit, _constants.INTERNAL_ERROR_LOG_MESSAGE), ticket)
self._transmitting = True
def kick_off(
self, group, method, timeout, initial_metadata, payload, completion,
allowance):
"""See _interfaces.TransmissionManager.kickoff for specification."""
# TODO(nathaniel): Support other subscriptions.
subscription = links.Ticket.Subscription.FULL
terminal_metadata, code, message, termination = _explode_completion(
completion)
self._remote_allowance = 1 if payload is None else 0
ticket = links.Ticket(
self._operation_id, 0, group, method, subscription, timeout, allowance,
initial_metadata, payload, terminal_metadata, code, message,
termination)
self._lowest_unused_sequence_number = 1
self._transmit(ticket)
def advance(self, initial_metadata, payload, completion, allowance):
"""See _interfaces.TransmissionManager.advance for specification."""
effective_initial_metadata = initial_metadata
effective_payload = payload
effective_completion = completion
if allowance is not None and not self._remote_complete:
effective_allowance = allowance
else:
effective_allowance = None
if self._transmitting:
if effective_initial_metadata is not None:
self._initial_metadata = effective_initial_metadata
if effective_payload is not None:
self._payloads.append(effective_payload)
if effective_completion is not None:
self._completion = effective_completion
if effective_allowance is not None:
self._local_allowance += effective_allowance
else:
if effective_payload is not None:
if 0 < self._remote_allowance:
ticket_payload = effective_payload
self._remote_allowance -= 1
else:
self._payloads.append(effective_payload)
ticket_payload = None
else:
ticket_payload = None
if effective_completion is not None and not self._payloads:
ticket_completion = effective_completion
else:
self._completion = effective_completion
ticket_completion = None
if any(
(effective_initial_metadata, ticket_payload, ticket_completion,
effective_allowance)):
terminal_metadata, code, message, termination = _explode_completion(
completion)
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, None, allowance, effective_initial_metadata, ticket_payload,
terminal_metadata, code, message, termination)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def timeout(self, timeout):
"""See _interfaces.TransmissionManager.timeout for specification."""
if self._transmitting:
self._timeout = timeout
else:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, timeout, None, None, None, None, None, None, None)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def allowance(self, allowance):
"""See _interfaces.TransmissionManager.allowance for specification."""
if self._transmitting or not self._payloads:
self._remote_allowance += allowance
else:
self._remote_allowance += allowance - 1
payload = self._payloads.pop(0)
if self._payloads:
completion = None
else:
completion = self._completion
self._completion = None
terminal_metadata, code, message, termination = _explode_completion(
completion)
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None, None,
None, None, None, None, payload, terminal_metadata, code, message,
termination)
self._lowest_unused_sequence_number += 1
self._transmit(ticket)
def remote_complete(self):
"""See _interfaces.TransmissionManager.remote_complete for specification."""
self._remote_complete = True
self._local_allowance = 0
def abort(self, outcome):
"""See _interfaces.TransmissionManager.abort for specification."""
if self._transmitting:
self._aborted, self._abortion_outcome = True, outcome
else:
self._aborted = True
if outcome is not None:
termination = _constants.ABORTION_OUTCOME_TO_TICKET_TERMINATION[
outcome]
if termination is not None:
ticket = links.Ticket(
self._operation_id, self._lowest_unused_sequence_number, None,
None, None, None, None, None, None, None, None, None,
termination)
self._transmit(ticket)

@ -0,0 +1,46 @@
# 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.
"""Package-internal utilities."""
import collections
class ServicerPackage(
collections.namedtuple(
'ServicerPackage', ('servicer', 'default_timeout', 'maximum_timeout'))):
"""A trivial bundle class.
Attributes:
servicer: A base.Servicer.
default_timeout: A float indicating the length of time in seconds to allow
for an operation invoked without a timeout.
maximum_timeout: A float indicating the maximum length of time in seconds to
allow for an operation.
"""

@ -0,0 +1,62 @@
# 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.
"""Entry points into the ticket-exchange-based base layer implementation."""
# base and links are referenced from specification in this module.
from grpc.framework.core import _end
from grpc.framework.interfaces.base import base # pylint: disable=unused-import
from grpc.framework.interfaces.links import links # pylint: disable=unused-import
def invocation_end_link():
"""Creates a base.End-links.Link suitable for operation invocation.
Returns:
An object that is both a base.End and a links.Link, that supports operation
invocation, and that translates operation invocation into ticket exchange.
"""
return _end.serviceless_end_link()
def service_end_link(servicer, default_timeout, maximum_timeout):
"""Creates a base.End-links.Link suitable for operation service.
Args:
servicer: A base.Servicer for servicing operations.
default_timeout: A length of time in seconds to be used as the default
time alloted for a single operation.
maximum_timeout: A length of time in seconds to be used as the maximum
time alloted for a single operation.
Returns:
An object that is both a base.End and a links.Link and that services
operations that arrive at it through ticket exchange.
"""
return _end.serviceful_end_link(servicer, default_timeout, maximum_timeout)

@ -27,10 +27,20 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""The base interface of RPC Framework.""" """The base interface of RPC Framework.
Implementations of this interface support the conduct of "operations":
exchanges between two distinct ends of an arbitrary number of data payloads
and metadata such as a name for the operation, initial and terminal metadata
in each direction, and flow control. These operations may be used for transfers
of data, remote procedure calls, status indication, or anything else
applications choose.
"""
# threading is referenced from specification in this module.
import abc import abc
import enum import enum
import threading
# abandonment is referenced from specification in this module. # abandonment is referenced from specification in this module.
from grpc.framework.foundation import abandonment # pylint: disable=unused-import from grpc.framework.foundation import abandonment # pylint: disable=unused-import
@ -208,19 +218,26 @@ class End(object):
raise NotImplementedError() raise NotImplementedError()
@abc.abstractmethod @abc.abstractmethod
def stop_gracefully(self): def stop(self, grace):
"""Gracefully stops this object's service of operations. """Stops this object's service of operations.
Operations in progress will be allowed to complete, and this method blocks This object will refuse service of new operations as soon as this method is
until all of them have. called but operations under way at the time of the call may be given a
""" grace period during which they are allowed to finish.
raise NotImplementedError()
@abc.abstractmethod Args:
def stop_immediately(self): grace: A duration of time in seconds to allow ongoing operations to
"""Immediately stops this object's service of operations. terminate before being forcefully terminated by the stopping of this
End. May be zero to terminate all ongoing operations and immediately
stop.
Operations in progress will not be allowed to complete. Returns:
A threading.Event that will be set to indicate all operations having
terminated and this End having completely stopped. The returned event
may not be set until after the full grace period (if some ongoing
operation continues for the full length of the period) or it may be set
much sooner (if for example this End had no operations in progress at
the time its stop method was called).
""" """
raise NotImplementedError() raise NotImplementedError()

@ -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.

@ -0,0 +1,933 @@
# 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.
"""Interfaces defining the Face layer of RPC Framework."""
import abc
import collections
import enum
# cardinality, style, abandonment, future, and stream are
# referenced from specification in this module.
from grpc.framework.common import cardinality # pylint: disable=unused-import
from grpc.framework.common import style # pylint: disable=unused-import
from grpc.framework.foundation import abandonment # pylint: disable=unused-import
from grpc.framework.foundation import future # pylint: disable=unused-import
from grpc.framework.foundation import stream # pylint: disable=unused-import
class NoSuchMethodError(Exception):
"""Raised by customer code to indicate an unrecognized method.
Attributes:
group: The group of the unrecognized method.
name: The name of the unrecognized method.
"""
def __init__(self, group, method):
"""Constructor.
Args:
group: The group identifier of the unrecognized RPC name.
method: The method identifier of the unrecognized RPC name.
"""
super(NoSuchMethodError, self).__init__()
self.group = group
self.method = method
def __repr__(self):
return 'face.NoSuchMethodError(%s, %s)' % (self.group, self.method,)
class Abortion(
collections.namedtuple(
'Abortion',
('kind', 'initial_metadata', 'terminal_metadata', 'code', 'details',))):
"""A value describing RPC abortion.
Attributes:
kind: A Kind value identifying how the RPC failed.
initial_metadata: The initial metadata from the other side of the RPC or
None if no initial metadata value was received.
terminal_metadata: The terminal metadata from the other side of the RPC or
None if no terminal metadata value was received.
code: The code value from the other side of the RPC or None if no code value
was received.
details: The details value from the other side of the RPC or None if no
details value was received.
"""
@enum.unique
class Kind(enum.Enum):
"""Types of RPC abortion."""
CANCELLED = 'cancelled'
EXPIRED = 'expired'
LOCAL_SHUTDOWN = 'local shutdown'
REMOTE_SHUTDOWN = 'remote shutdown'
NETWORK_FAILURE = 'network failure'
LOCAL_FAILURE = 'local failure'
REMOTE_FAILURE = 'remote failure'
class AbortionError(Exception):
"""Common super type for exceptions indicating RPC abortion.
initial_metadata: The initial metadata from the other side of the RPC or
None if no initial metadata value was received.
terminal_metadata: The terminal metadata from the other side of the RPC or
None if no terminal metadata value was received.
code: The code value from the other side of the RPC or None if no code value
was received.
details: The details value from the other side of the RPC or None if no
details value was received.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, initial_metadata, terminal_metadata, code, details):
super(AbortionError, self).__init__()
self.initial_metadata = initial_metadata
self.terminal_metadata = terminal_metadata
self.code = code
self.details = details
class CancellationError(AbortionError):
"""Indicates that an RPC has been cancelled."""
class ExpirationError(AbortionError):
"""Indicates that an RPC has expired ("timed out")."""
class LocalShutdownError(AbortionError):
"""Indicates that an RPC has terminated due to local shutdown of RPCs."""
class RemoteShutdownError(AbortionError):
"""Indicates that an RPC has terminated due to remote shutdown of RPCs."""
class NetworkError(AbortionError):
"""Indicates that some error occurred on the network."""
class LocalError(AbortionError):
"""Indicates that an RPC has terminated due to a local defect."""
class RemoteError(AbortionError):
"""Indicates that an RPC has terminated due to a remote defect."""
class RpcContext(object):
"""Provides RPC-related information and action."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def is_active(self):
"""Describes whether the RPC is active or has terminated."""
raise NotImplementedError()
@abc.abstractmethod
def time_remaining(self):
"""Describes the length of allowed time remaining for the RPC.
Returns:
A nonnegative float indicating the length of allowed time in seconds
remaining for the RPC to complete before it is considered to have timed
out.
"""
raise NotImplementedError()
@abc.abstractmethod
def add_abortion_callback(self, abortion_callback):
"""Registers a callback to be called if the RPC is aborted.
Args:
abortion_callback: A callable to be called and passed an Abortion value
in the event of RPC abortion.
"""
raise NotImplementedError()
@abc.abstractmethod
def cancel(self):
"""Cancels the RPC.
Idempotent and has no effect if the RPC has already terminated.
"""
raise NotImplementedError()
class Call(RpcContext):
"""Invocation-side utility object for an RPC."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def initial_metadata(self):
"""Accesses the initial metadata from the service-side of the RPC.
This method blocks until the value is available or is known not to have been
emitted from the service-side of the RPC.
Returns:
The initial metadata object emitted by the service-side of the RPC, or
None if there was no such value.
"""
raise NotImplementedError()
@abc.abstractmethod
def terminal_metadata(self):
"""Accesses the terminal metadata from the service-side of the RPC.
This method blocks until the value is available or is known not to have been
emitted from the service-side of the RPC.
Returns:
The terminal metadata object emitted by the service-side of the RPC, or
None if there was no such value.
"""
raise NotImplementedError()
@abc.abstractmethod
def code(self):
"""Accesses the code emitted by the service-side of the RPC.
This method blocks until the value is available or is known not to have been
emitted from the service-side of the RPC.
Returns:
The code object emitted by the service-side of the RPC, or None if there
was no such value.
"""
raise NotImplementedError()
@abc.abstractmethod
def details(self):
"""Accesses the details value emitted by the service-side of the RPC.
This method blocks until the value is available or is known not to have been
emitted from the service-side of the RPC.
Returns:
The details value emitted by the service-side of the RPC, or None if there
was no such value.
"""
raise NotImplementedError()
class ServicerContext(RpcContext):
"""A context object passed to method implementations."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def invocation_metadata(self):
"""Accesses the metadata from the invocation-side of the RPC.
This method blocks until the value is available or is known not to have been
emitted from the invocation-side of the RPC.
Returns:
The metadata object emitted by the invocation-side of the RPC, or None if
there was no such value.
"""
raise NotImplementedError()
@abc.abstractmethod
def initial_metadata(self, initial_metadata):
"""Accepts the service-side initial metadata value of the RPC.
This method need not be called by method implementations if they have no
service-side initial metadata to transmit.
Args:
initial_metadata: The service-side initial metadata value of the RPC to
be transmitted to the invocation side of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def terminal_metadata(self, terminal_metadata):
"""Accepts the service-side terminal metadata value of the RPC.
This method need not be called by method implementations if they have no
service-side terminal metadata to transmit.
Args:
terminal_metadata: The service-side terminal metadata value of the RPC to
be transmitted to the invocation side of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def code(self, code):
"""Accepts the service-side code of the RPC.
This method need not be called by method implementations if they have no
code to transmit.
Args:
code: The code of the RPC to be transmitted to the invocation side of the
RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def details(self, details):
"""Accepts the service-side details of the RPC.
This method need not be called by method implementations if they have no
service-side details to transmit.
Args:
details: The service-side details value of the RPC to be transmitted to
the invocation side of the RPC.
"""
raise NotImplementedError()
class ResponseReceiver(object):
"""Invocation-side object used to accept the output of an RPC."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def initial_metadata(self, initial_metadata):
"""Receives the initial metadata from the service-side of the RPC.
Args:
initial_metadata: The initial metadata object emitted from the
service-side of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def response(self, response):
"""Receives a response from the service-side of the RPC.
Args:
response: A response object emitted from the service-side of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def complete(self, terminal_metadata, code, details):
"""Receives the completion values emitted from the service-side of the RPC.
Args:
terminal_metadata: The terminal metadata object emitted from the
service-side of the RPC.
code: The code object emitted from the service-side of the RPC.
details: The details object emitted from the service-side of the RPC.
"""
raise NotImplementedError()
class UnaryUnaryMultiCallable(object):
"""Affords invoking a unary-unary RPC in any call style."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def __call__(
self, request, timeout, metadata=None, with_call=False):
"""Synchronously invokes the underlying RPC.
Args:
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
with_call: Whether or not to include return a Call for the RPC in addition
to the reponse.
Returns:
The response value for the RPC, and a Call for the RPC if with_call was
set to True at invocation.
Raises:
AbortionError: Indicating that the RPC was aborted.
"""
raise NotImplementedError()
@abc.abstractmethod
def future(self, request, timeout, metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
An object that is both a Call for the RPC and a future.Future. In the
event of RPC completion, the return Future's result value will be the
response value of the RPC. In the event of RPC abortion, the returned
Future's exception value will be an AbortionError.
"""
raise NotImplementedError()
@abc.abstractmethod
def event(
self, request, receiver, abortion_callback, timeout,
metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
request: The request value for the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
A Call for the RPC.
"""
raise NotImplementedError()
class UnaryStreamMultiCallable(object):
"""Affords invoking a unary-stream RPC in any call style."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def __call__(self, request, timeout, metadata=None):
"""Invokes the underlying RPC.
Args:
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
An object that is both a Call for the RPC and an iterator of response
values. Drawing response values from the returned iterator may raise
AbortionError indicating abortion of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def event(
self, request, receiver, abortion_callback, timeout,
metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
request: The request value for the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
A Call object for the RPC.
"""
raise NotImplementedError()
class StreamUnaryMultiCallable(object):
"""Affords invoking a stream-unary RPC in any call style."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def __call__(
self, request_iterator, timeout, metadata=None,
with_call=False):
"""Synchronously invokes the underlying RPC.
Args:
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
with_call: Whether or not to include return a Call for the RPC in addition
to the reponse.
Returns:
The response value for the RPC, and a Call for the RPC if with_call was
set to True at invocation.
Raises:
AbortionError: Indicating that the RPC was aborted.
"""
raise NotImplementedError()
@abc.abstractmethod
def future(self, request_iterator, timeout, metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
An object that is both a Call for the RPC and a future.Future. In the
event of RPC completion, the return Future's result value will be the
response value of the RPC. In the event of RPC abortion, the returned
Future's exception value will be an AbortionError.
"""
raise NotImplementedError()
@abc.abstractmethod
def event(
self, receiver, abortion_callback, timeout, metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
A single object that is both a Call object for the RPC and a
stream.Consumer to which the request values of the RPC should be passed.
"""
raise NotImplementedError()
class StreamStreamMultiCallable(object):
"""Affords invoking a stream-stream RPC in any call style."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def __call__(self, request_iterator, timeout, metadata=None):
"""Invokes the underlying RPC.
Args:
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
An object that is both a Call for the RPC and an iterator of response
values. Drawing response values from the returned iterator may raise
AbortionError indicating abortion of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def event(
self, receiver, abortion_callback, timeout, metadata=None):
"""Asynchronously invokes the underlying RPC.
Args:
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of
the RPC.
Returns:
A single object that is both a Call object for the RPC and a
stream.Consumer to which the request values of the RPC should be passed.
"""
raise NotImplementedError()
class MethodImplementation(object):
"""A sum type that describes a method implementation.
Attributes:
cardinality: A cardinality.Cardinality value.
style: A style.Service value.
unary_unary_inline: The implementation of the method as a callable value
that takes a request value and a ServicerContext object and returns a
response value. Only non-None if cardinality is
cardinality.Cardinality.UNARY_UNARY and style is style.Service.INLINE.
unary_stream_inline: The implementation of the method as a callable value
that takes a request value and a ServicerContext object and returns an
iterator of response values. Only non-None if cardinality is
cardinality.Cardinality.UNARY_STREAM and style is style.Service.INLINE.
stream_unary_inline: The implementation of the method as a callable value
that takes an iterator of request values and a ServicerContext object and
returns a response value. Only non-None if cardinality is
cardinality.Cardinality.STREAM_UNARY and style is style.Service.INLINE.
stream_stream_inline: The implementation of the method as a callable value
that takes an iterator of request values and a ServicerContext object and
returns an iterator of response values. Only non-None if cardinality is
cardinality.Cardinality.STREAM_STREAM and style is style.Service.INLINE.
unary_unary_event: The implementation of the method as a callable value that
takes a request value, a response callback to which to pass the response
value of the RPC, and a ServicerContext. Only non-None if cardinality is
cardinality.Cardinality.UNARY_UNARY and style is style.Service.EVENT.
unary_stream_event: The implementation of the method as a callable value
that takes a request value, a stream.Consumer to which to pass the
response values of the RPC, and a ServicerContext. Only non-None if
cardinality is cardinality.Cardinality.UNARY_STREAM and style is
style.Service.EVENT.
stream_unary_event: The implementation of the method as a callable value
that takes a response callback to which to pass the response value of the
RPC and a ServicerContext and returns a stream.Consumer to which the
request values of the RPC should be passed. Only non-None if cardinality
is cardinality.Cardinality.STREAM_UNARY and style is style.Service.EVENT.
stream_stream_event: The implementation of the method as a callable value
that takes a stream.Consumer to which to pass the response values of the
RPC and a ServicerContext and returns a stream.Consumer to which the
request values of the RPC should be passed. Only non-None if cardinality
is cardinality.Cardinality.STREAM_STREAM and style is
style.Service.EVENT.
"""
__metaclass__ = abc.ABCMeta
class MultiMethodImplementation(object):
"""A general type able to service many methods."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def service(self, group, method, response_consumer, context):
"""Services an RPC.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
response_consumer: A stream.Consumer to be called to accept the response
values of the RPC.
context: a ServicerContext object.
Returns:
A stream.Consumer with which to accept the request values of the RPC. The
consumer returned from this method may or may not be invoked to
completion: in the case of RPC abortion, RPC Framework will simply stop
passing values to this object. Implementations must not assume that this
object will be called to completion of the request stream or even called
at all.
Raises:
abandonment.Abandoned: May or may not be raised when the RPC has been
aborted.
NoSuchMethodError: If this MultiMethod does not recognize the given group
and name for the RPC and is not able to service the RPC.
"""
raise NotImplementedError()
class GenericStub(object):
"""Affords RPC invocation via generic methods."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def blocking_unary_unary(
self, group, method, request, timeout, metadata=None,
with_call=False):
"""Invokes a unary-request-unary-response method.
This method blocks until either returning the response value of the RPC
(in the event of RPC completion) or raising an exception (in the event of
RPC abortion).
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
with_call: Whether or not to include return a Call for the RPC in addition
to the reponse.
Returns:
The response value for the RPC, and a Call for the RPC if with_call was
set to True at invocation.
Raises:
AbortionError: Indicating that the RPC was aborted.
"""
raise NotImplementedError()
@abc.abstractmethod
def future_unary_unary(
self, group, method, request, timeout, metadata=None):
"""Invokes a unary-request-unary-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
An object that is both a Call for the RPC and a future.Future. In the
event of RPC completion, the return Future's result value will be the
response value of the RPC. In the event of RPC abortion, the returned
Future's exception value will be an AbortionError.
"""
raise NotImplementedError()
@abc.abstractmethod
def inline_unary_stream(
self, group, method, request, timeout, metadata=None):
"""Invokes a unary-request-stream-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request: The request value for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
An object that is both a Call for the RPC and an iterator of response
values. Drawing response values from the returned iterator may raise
AbortionError indicating abortion of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def blocking_stream_unary(
self, group, method, request_iterator, timeout, metadata=None,
with_call=False):
"""Invokes a stream-request-unary-response method.
This method blocks until either returning the response value of the RPC
(in the event of RPC completion) or raising an exception (in the event of
RPC abortion).
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
with_call: Whether or not to include return a Call for the RPC in addition
to the reponse.
Returns:
The response value for the RPC, and a Call for the RPC if with_call was
set to True at invocation.
Raises:
AbortionError: Indicating that the RPC was aborted.
"""
raise NotImplementedError()
@abc.abstractmethod
def future_stream_unary(
self, group, method, request_iterator, timeout, metadata=None):
"""Invokes a stream-request-unary-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
An object that is both a Call for the RPC and a future.Future. In the
event of RPC completion, the return Future's result value will be the
response value of the RPC. In the event of RPC abortion, the returned
Future's exception value will be an AbortionError.
"""
raise NotImplementedError()
@abc.abstractmethod
def inline_stream_stream(
self, group, method, request_iterator, timeout, metadata=None):
"""Invokes a stream-request-stream-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request_iterator: An iterator that yields request values for the RPC.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
An object that is both a Call for the RPC and an iterator of response
values. Drawing response values from the returned iterator may raise
AbortionError indicating abortion of the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def event_unary_unary(
self, group, method, request, receiver, abortion_callback, timeout,
metadata=None):
"""Event-driven invocation of a unary-request-unary-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request: The request value for the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
A Call for the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def event_unary_stream(
self, group, method, request, receiver, abortion_callback, timeout,
metadata=None):
"""Event-driven invocation of a unary-request-stream-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
request: The request value for the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
A Call for the RPC.
"""
raise NotImplementedError()
@abc.abstractmethod
def event_stream_unary(
self, group, method, receiver, abortion_callback, timeout,
metadata=None):
"""Event-driven invocation of a unary-request-unary-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
A pair of a Call object for the RPC and a stream.Consumer to which the
request values of the RPC should be passed.
"""
raise NotImplementedError()
@abc.abstractmethod
def event_stream_stream(
self, group, method, receiver, abortion_callback, timeout,
metadata=None):
"""Event-driven invocation of a unary-request-stream-response method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
receiver: A ResponseReceiver to be passed the response data of the RPC.
abortion_callback: A callback to be called and passed an Abortion value
in the event of RPC abortion.
timeout: A duration of time in seconds to allow for the RPC.
metadata: A metadata value to be passed to the service-side of the RPC.
Returns:
A pair of a Call object for the RPC and a stream.Consumer to which the
request values of the RPC should be passed.
"""
raise NotImplementedError()
@abc.abstractmethod
def unary_unary(self, group, method):
"""Creates a UnaryUnaryMultiCallable for a unary-unary method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
Returns:
A UnaryUnaryMultiCallable value for the named unary-unary method.
"""
raise NotImplementedError()
@abc.abstractmethod
def unary_stream(self, group, method):
"""Creates a UnaryStreamMultiCallable for a unary-stream method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
Returns:
A UnaryStreamMultiCallable value for the name unary-stream method.
"""
raise NotImplementedError()
@abc.abstractmethod
def stream_unary(self, group, method):
"""Creates a StreamUnaryMultiCallable for a stream-unary method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
Returns:
A StreamUnaryMultiCallable value for the named stream-unary method.
"""
raise NotImplementedError()
@abc.abstractmethod
def stream_stream(self, group, method):
"""Creates a StreamStreamMultiCallable for a stream-stream method.
Args:
group: The group identifier of the RPC.
method: The method identifier of the RPC.
Returns:
A StreamStreamMultiCallable value for the named stream-stream method.
"""
raise NotImplementedError()
class DynamicStub(object):
"""Affords RPC invocation via attributes corresponding to afforded methods.
Instances of this type may be scoped to a single group so that attribute
access is unambiguous.
Instances of this type respond to attribute access as follows: if the
requested attribute is the name of a unary-unary method, the value of the
attribute will be a UnaryUnaryMultiCallable with which to invoke an RPC; if
the requested attribute is the name of a unary-stream method, the value of the
attribute will be a UnaryStreamMultiCallable with which to invoke an RPC; if
the requested attribute is the name of a stream-unary method, the value of the
attribute will be a StreamUnaryMultiCallable with which to invoke an RPC; and
if the requested attribute is the name of a stream-stream method, the value of
the attribute will be a StreamStreamMultiCallable with which to invoke an RPC.
"""
__metaclass__ = abc.ABCMeta

@ -0,0 +1,178 @@
# 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.
"""Utilities for RPC Framework's Face interface."""
import collections
# stream is referenced from specification in this module.
from grpc.framework.common import cardinality
from grpc.framework.common import style
from grpc.framework.foundation import stream # pylint: disable=unused-import
from grpc.framework.interfaces.face import face
class _MethodImplementation(
face.MethodImplementation,
collections.namedtuple(
'_MethodImplementation',
['cardinality', 'style', 'unary_unary_inline', 'unary_stream_inline',
'stream_unary_inline', 'stream_stream_inline', 'unary_unary_event',
'unary_stream_event', 'stream_unary_event', 'stream_stream_event',])):
pass
def unary_unary_inline(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a unary-unary RPC method as a callable value
that takes a request value and an face.ServicerContext object and
returns a response value.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.UNARY_UNARY, style.Service.INLINE, behavior,
None, None, None, None, None, None, None)
def unary_stream_inline(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a unary-stream RPC method as a callable
value that takes a request value and an face.ServicerContext object and
returns an iterator of response values.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.UNARY_STREAM, style.Service.INLINE, None,
behavior, None, None, None, None, None, None)
def stream_unary_inline(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a stream-unary RPC method as a callable
value that takes an iterator of request values and an
face.ServicerContext object and returns a response value.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.STREAM_UNARY, style.Service.INLINE, None, None,
behavior, None, None, None, None, None)
def stream_stream_inline(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a stream-stream RPC method as a callable
value that takes an iterator of request values and an
face.ServicerContext object and returns an iterator of response values.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.STREAM_STREAM, style.Service.INLINE, None, None,
None, behavior, None, None, None, None)
def unary_unary_event(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a unary-unary RPC method as a callable
value that takes a request value, a response callback to which to pass
the response value of the RPC, and an face.ServicerContext.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.UNARY_UNARY, style.Service.EVENT, None, None,
None, None, behavior, None, None, None)
def unary_stream_event(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a unary-stream RPC method as a callable
value that takes a request value, a stream.Consumer to which to pass the
the response values of the RPC, and an face.ServicerContext.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.UNARY_STREAM, style.Service.EVENT, None, None,
None, None, None, behavior, None, None)
def stream_unary_event(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a stream-unary RPC method as a callable
value that takes a response callback to which to pass the response value
of the RPC and an face.ServicerContext and returns a stream.Consumer to
which the request values of the RPC should be passed.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.STREAM_UNARY, style.Service.EVENT, None, None,
None, None, None, None, behavior, None)
def stream_stream_event(behavior):
"""Creates an face.MethodImplementation for the given behavior.
Args:
behavior: The implementation of a stream-stream RPC method as a callable
value that takes a stream.Consumer to which to pass the response values
of the RPC and an face.ServicerContext and returns a stream.Consumer to
which the request values of the RPC should be passed.
Returns:
An face.MethodImplementation derived from the given behavior.
"""
return _MethodImplementation(
cardinality.Cardinality.STREAM_STREAM, style.Service.EVENT, None, None,
None, None, None, None, None, behavior)

@ -98,7 +98,7 @@ class Ticket(
COMPLETION = 'completion' COMPLETION = 'completion'
CANCELLATION = 'cancellation' CANCELLATION = 'cancellation'
EXPIRATION = 'expiration' EXPIRATION = 'expiration'
LOCAL_SHUTDOWN = 'local shutdown' SHUTDOWN = 'shutdown'
RECEPTION_FAILURE = 'reception failure' RECEPTION_FAILURE = 'reception failure'
TRANSMISSION_FAILURE = 'transmission failure' TRANSMISSION_FAILURE = 'transmission failure'
LOCAL_FAILURE = 'local failure' LOCAL_FAILURE = 'local failure'

@ -0,0 +1,165 @@
# 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.
"""Tests the RPC Framework Core's implementation of the Base interface."""
import collections
import logging
import random
import time
import unittest
from grpc._adapter import _intermediary_low
from grpc._links import invocation
from grpc._links import service
from grpc.framework.core import implementations
from grpc.framework.interfaces.base import utilities
from grpc_test import test_common as grpc_test_common
from grpc_test.framework.common import test_constants
from grpc_test.framework.interfaces.base import test_cases
from grpc_test.framework.interfaces.base import test_interfaces
_INVOCATION_INITIAL_METADATA = ((b'0', b'abc'), (b'1', b'def'), (b'2', b'ghi'),)
_SERVICE_INITIAL_METADATA = ((b'3', b'jkl'), (b'4', b'mno'), (b'5', b'pqr'),)
_SERVICE_TERMINAL_METADATA = ((b'6', b'stu'), (b'7', b'vwx'), (b'8', b'yza'),)
_CODE = _intermediary_low.Code.OK
_MESSAGE = b'test message'
class _SerializationBehaviors(
collections.namedtuple(
'_SerializationBehaviors',
('request_serializers', 'request_deserializers', 'response_serializers',
'response_deserializers',))):
pass
class _Links(
collections.namedtuple(
'_Links',
('invocation_end_link', 'invocation_grpc_link', 'service_grpc_link',
'service_end_link'))):
pass
def _serialization_behaviors_from_serializations(serializations):
request_serializers = {}
request_deserializers = {}
response_serializers = {}
response_deserializers = {}
for (group, method), serialization in serializations.iteritems():
request_serializers[group, method] = serialization.serialize_request
request_deserializers[group, method] = serialization.deserialize_request
response_serializers[group, method] = serialization.serialize_response
response_deserializers[group, method] = serialization.deserialize_response
return _SerializationBehaviors(
request_serializers, request_deserializers, response_serializers,
response_deserializers)
class _Implementation(test_interfaces.Implementation):
def instantiate(self, serializations, servicer):
serialization_behaviors = _serialization_behaviors_from_serializations(
serializations)
invocation_end_link = implementations.invocation_end_link()
service_end_link = implementations.service_end_link(
servicer, test_constants.DEFAULT_TIMEOUT,
test_constants.MAXIMUM_TIMEOUT)
service_grpc_link = service.service_link(
serialization_behaviors.request_deserializers,
serialization_behaviors.response_serializers)
port = service_grpc_link.add_port(0, None)
channel = _intermediary_low.Channel('localhost:%d' % port, None)
invocation_grpc_link = invocation.invocation_link(
channel, b'localhost',
serialization_behaviors.request_serializers,
serialization_behaviors.response_deserializers)
invocation_end_link.join_link(invocation_grpc_link)
invocation_grpc_link.join_link(invocation_end_link)
service_end_link.join_link(service_grpc_link)
service_grpc_link.join_link(service_end_link)
invocation_grpc_link.start()
service_grpc_link.start()
return invocation_end_link, service_end_link, (
invocation_grpc_link, service_grpc_link)
def destantiate(self, memo):
invocation_grpc_link, service_grpc_link = memo
invocation_grpc_link.stop()
service_grpc_link.stop_gracefully()
def invocation_initial_metadata(self):
return _INVOCATION_INITIAL_METADATA
def service_initial_metadata(self):
return _SERVICE_INITIAL_METADATA
def invocation_completion(self):
return utilities.completion(None, None, None)
def service_completion(self):
return utilities.completion(_SERVICE_TERMINAL_METADATA, _CODE, _MESSAGE)
def metadata_transmitted(self, original_metadata, transmitted_metadata):
return original_metadata is None or grpc_test_common.metadata_transmitted(
original_metadata, transmitted_metadata)
def completion_transmitted(self, original_completion, transmitted_completion):
if (original_completion.terminal_metadata is not None and
not grpc_test_common.metadata_transmitted(
original_completion.terminal_metadata,
transmitted_completion.terminal_metadata)):
return False
elif original_completion.code is not transmitted_completion.code:
return False
elif original_completion.message != transmitted_completion.message:
return False
else:
return True
def setUpModule():
logging.warn('setUpModule!')
def tearDownModule():
logging.warn('tearDownModule!')
def load_tests(loader, tests, pattern):
return unittest.TestSuite(
tests=tuple(
loader.loadTestsFromTestCase(test_case_class)
for test_case_class in test_cases.test_cases(_Implementation())))
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -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.

@ -0,0 +1,96 @@
# 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.
"""Tests the RPC Framework Core's implementation of the Base interface."""
import logging
import random
import time
import unittest
from grpc.framework.core import implementations
from grpc.framework.interfaces.base import utilities
from grpc_test.framework.common import test_constants
from grpc_test.framework.interfaces.base import test_cases
from grpc_test.framework.interfaces.base import test_interfaces
class _Implementation(test_interfaces.Implementation):
def __init__(self):
self._invocation_initial_metadata = object()
self._service_initial_metadata = object()
self._invocation_terminal_metadata = object()
self._service_terminal_metadata = object()
def instantiate(self, serializations, servicer):
invocation = implementations.invocation_end_link()
service = implementations.service_end_link(
servicer, test_constants.DEFAULT_TIMEOUT,
test_constants.MAXIMUM_TIMEOUT)
invocation.join_link(service)
service.join_link(invocation)
return invocation, service, None
def destantiate(self, memo):
pass
def invocation_initial_metadata(self):
return self._invocation_initial_metadata
def service_initial_metadata(self):
return self._service_initial_metadata
def invocation_completion(self):
return utilities.completion(self._invocation_terminal_metadata, None, None)
def service_completion(self):
return utilities.completion(self._service_terminal_metadata, None, None)
def metadata_transmitted(self, original_metadata, transmitted_metadata):
return transmitted_metadata is original_metadata
def completion_transmitted(self, original_completion, transmitted_completion):
return (
(original_completion.terminal_metadata is
transmitted_completion.terminal_metadata) and
original_completion.code is transmitted_completion.code and
original_completion.message is transmitted_completion.message
)
def load_tests(loader, tests, pattern):
return unittest.TestSuite(
tests=tuple(
loader.loadTestsFromTestCase(test_case_class)
for test_case_class in test_cases.test_cases(_Implementation())))
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -211,8 +211,10 @@ class _OperationTest(unittest.TestCase):
elif instruction.kind is _control.Instruction.Kind.CONCLUDE: elif instruction.kind is _control.Instruction.Kind.CONCLUDE:
break break
invocation_end.stop_gracefully() invocation_stop_event = invocation_end.stop(0)
service_end.stop_gracefully() service_stop_event = service_end.stop(0)
invocation_stop_event.wait()
service_stop_event.wait()
invocation_stats = invocation_end.operation_stats() invocation_stats = invocation_end.operation_stats()
service_stats = service_end.operation_stats() service_stats = service_end.operation_stats()

@ -29,9 +29,42 @@
"""State and behavior appropriate for use in tests.""" """State and behavior appropriate for use in tests."""
import logging
import threading import threading
import time
from grpc.framework.interfaces.links import links from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import utilities
# A more-or-less arbitrary limit on the length of raw data values to be logged.
_UNCOMFORTABLY_LONG = 48
def _safe_for_log_ticket(ticket):
"""Creates a safe-for-printing-to-the-log ticket for a given ticket.
Args:
ticket: Any links.Ticket.
Returns:
A links.Ticket that is as much as can be equal to the given ticket but
possibly features values like the string "<payload of length 972321>" in
place of the actual values of the given ticket.
"""
if isinstance(ticket.payload, (basestring,)):
payload_length = len(ticket.payload)
else:
payload_length = -1
if payload_length < _UNCOMFORTABLY_LONG:
return ticket
else:
return links.Ticket(
ticket.operation_id, ticket.sequence_number,
ticket.group, ticket.method, ticket.subscription, ticket.timeout,
ticket.allowance, ticket.initial_metadata,
'<payload of length {}>'.format(payload_length),
ticket.terminal_metadata, ticket.code, ticket.message,
ticket.termination)
class RecordingLink(links.Link): class RecordingLink(links.Link):
@ -64,3 +97,71 @@ class RecordingLink(links.Link):
"""Returns a copy of the list of all tickets received by this Link.""" """Returns a copy of the list of all tickets received by this Link."""
with self._condition: with self._condition:
return tuple(self._tickets) return tuple(self._tickets)
class _Pipe(object):
"""A conduit that logs all tickets passed through it."""
def __init__(self, name):
self._lock = threading.Lock()
self._name = name
self._left_mate = utilities.NULL_LINK
self._right_mate = utilities.NULL_LINK
def accept_left_to_right_ticket(self, ticket):
with self._lock:
logging.warning(
'%s: moving left to right through %s: %s', time.time(), self._name,
_safe_for_log_ticket(ticket))
try:
self._right_mate.accept_ticket(ticket)
except Exception as e: # pylint: disable=broad-except
logging.exception(e)
def accept_right_to_left_ticket(self, ticket):
with self._lock:
logging.warning(
'%s: moving right to left through %s: %s', time.time(), self._name,
_safe_for_log_ticket(ticket))
try:
self._left_mate.accept_ticket(ticket)
except Exception as e: # pylint: disable=broad-except
logging.exception(e)
def join_left_mate(self, left_mate):
with self._lock:
self._left_mate = utilities.NULL_LINK if left_mate is None else left_mate
def join_right_mate(self, right_mate):
with self._lock:
self._right_mate = (
utilities.NULL_LINK if right_mate is None else right_mate)
class _Facade(links.Link):
def __init__(self, accept, join):
self._accept = accept
self._join = join
def accept_ticket(self, ticket):
self._accept(ticket)
def join_link(self, link):
self._join(link)
def logging_links(name):
"""Creates a conduit that logs all tickets passed through it.
Args:
name: A name to use for the conduit to identify itself in logging output.
Returns:
Two links.Links, the first of which is the "left" side of the conduit
and the second of which is the "right" side of the conduit.
"""
pipe = _Pipe(name)
left_facade = _Facade(pipe.accept_left_to_right_ticket, pipe.join_left_mate)
right_facade = _Facade(pipe.accept_right_to_left_ticket, pipe.join_right_mate)
return left_facade, right_facade

@ -12,12 +12,36 @@ PREREQUISITES
------------- -------------
- Ruby 2.x. The gRPC API uses keyword args. - Ruby 2.x. The gRPC API uses keyword args.
- [homebrew][] on Mac OS X, [linuxbrew][] on Linux. These simplify the installation of the gRPC C core. - [homebrew][] on Mac OS X. These simplify the installation of the gRPC C core.
INSTALLATION INSTALLATION
--------------- ---------------
On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][].
Run the following command to install gRPC Ruby. **Linux (Debian):**
Add [Debian unstable][] to your `sources.list` file. Example:
```sh
echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" | \
sudo tee -a /etc/apt/sources.list
```
Install the gRPC Debian package
```sh
sudo apt-get update
sudo apt-get install libgrpc-dev
```
Install the gRPC Ruby package
```sh
gem install grpc
```
**Mac OS X**
Install [homebrew][]. Run the following command to install gRPC Ruby.
```sh ```sh
$ curl -fsSL https://goo.gl/getgrpc | bash -s ruby $ curl -fsSL https://goo.gl/getgrpc | bash -s ruby
``` ```
@ -26,12 +50,6 @@ This will download and run the [gRPC install script][], then install the latest
BUILD FROM SOURCE BUILD FROM SOURCE
--------------------- ---------------------
- Clone this repository - Clone this repository
- Build the gRPC C core
E.g, from the root of the gRPC [Git repository](https://github.com/google/grpc)
```sh
$ cd ../..
$ make && sudo make install
```
- Install Ruby 2.x. Consider doing this with [RVM](http://rvm.io), it's a nice way of controlling - Install Ruby 2.x. Consider doing this with [RVM](http://rvm.io), it's a nice way of controlling
the exact ruby version that's used. the exact ruby version that's used.
@ -77,8 +95,8 @@ Directory structure is the layout for [ruby extensions][]
GRPC.logger.info("Answer: #{resp.inspect}") GRPC.logger.info("Answer: #{resp.inspect}")
``` ```
[homebrew]:http://brew.sh [homebrew]:http://brew.sh
[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install [gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
[ruby extensions]:http://guides.rubygems.org/gems-with-extensions/ [ruby extensions]:http://guides.rubygems.org/gems-with-extensions/
[rubydoc]: http://www.rubydoc.info/gems/grpc [rubydoc]: http://www.rubydoc.info/gems/grpc
[grpc.io]: http://www.grpc.io/docs/installation/ruby.html [grpc.io]: http://www.grpc.io/docs/installation/ruby.html
[Debian unstable]:https://www.debian.org/releases/sid/

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save