merge with head and resolve conflicts

pull/3008/head
yang-g 9 years ago
commit b1f2407833
  1. 12
      BUILD
  2. 140
      Makefile
  3. 56
      build.json
  4. 6
      doc/connection-backoff-interop-test-description.md
  5. 9
      doc/connection-backoff.md
  6. 3
      include/grpc++/impl/call.h
  7. 16
      include/grpc++/impl/rpc_service_method.h
  8. 17
      include/grpc++/server.h
  9. 8
      include/grpc++/server_builder.h
  10. 2
      include/grpc++/stream.h
  11. 26
      include/grpc/compression.h
  12. 14
      include/grpc/grpc_security.h
  13. 63
      src/core/channel/channel_args.c
  14. 20
      src/core/channel/channel_args.h
  15. 7
      src/core/iomgr/pollset.h
  16. 2
      src/core/iomgr/pollset_multipoller_with_epoll.c
  17. 2
      src/core/iomgr/pollset_multipoller_with_poll_posix.c
  18. 15
      src/core/iomgr/pollset_posix.c
  19. 6
      src/core/iomgr/pollset_posix.h
  20. 10
      src/core/iomgr/pollset_windows.c
  21. 2
      src/core/security/google_default_credentials.c
  22. 28
      src/core/security/server_auth_filter.c
  23. 16
      src/core/surface/completion_queue.c
  24. 2
      src/cpp/server/create_default_thread_pool.cc
  25. 2
      src/cpp/server/dynamic_thread_pool.cc
  26. 9
      src/cpp/server/dynamic_thread_pool.h
  27. 2
      src/cpp/server/fixed_size_thread_pool.cc
  28. 9
      src/cpp/server/fixed_size_thread_pool.h
  29. 101
      src/cpp/server/server.cc
  30. 16
      src/cpp/server/server_builder.cc
  31. 6
      src/cpp/server/thread_pool_interface.h
  32. 1
      src/csharp/.gitignore
  33. 33
      src/csharp/Grpc.Core.Tests/ChannelTest.cs
  34. 15
      src/csharp/Grpc.Core.Tests/ClientServerTest.cs
  35. 8
      src/csharp/Grpc.Core.Tests/CompressionTest.cs
  36. 8
      src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs
  37. 2
      src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
  38. 33
      src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
  39. 222
      src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs
  40. 79
      src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs
  41. 5
      src/csharp/Grpc.Core.Tests/ServerTest.cs
  42. 77
      src/csharp/Grpc.Core.Tests/ShutdownTest.cs
  43. 8
      src/csharp/Grpc.Core.Tests/TimeoutsTest.cs
  44. 15
      src/csharp/Grpc.Core/AsyncClientStreamingCall.cs
  45. 16
      src/csharp/Grpc.Core/AsyncDuplexStreamingCall.cs
  46. 16
      src/csharp/Grpc.Core/AsyncServerStreamingCall.cs
  47. 15
      src/csharp/Grpc.Core/AsyncUnaryCall.cs
  48. 8
      src/csharp/Grpc.Core/Calls.cs
  49. 51
      src/csharp/Grpc.Core/Channel.cs
  50. 1
      src/csharp/Grpc.Core/Grpc.Core.csproj
  51. 44
      src/csharp/Grpc.Core/GrpcEnvironment.cs
  52. 149
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  53. 64
      src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
  54. 23
      src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
  55. 16
      src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs
  56. 53
      src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
  57. 7
      src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
  58. 8
      src/csharp/Grpc.Core/Internal/ClientResponseStream.cs
  59. 14
      src/csharp/Grpc.Core/Internal/DebugStats.cs
  60. 3
      src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
  61. 85
      src/csharp/Grpc.Core/Internal/INativeCall.cs
  62. 10
      src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
  63. 4
      src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
  64. 14
      src/csharp/Grpc.Core/Logging/ConsoleLogger.cs
  65. 57
      src/csharp/Grpc.Core/Server.cs
  66. 20
      src/csharp/Grpc.Examples.MathClient/MathClient.cs
  67. 1
      src/csharp/Grpc.Examples.MathServer/MathServer.cs
  68. 3
      src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
  69. 3
      src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs
  70. 36
      src/csharp/Grpc.IntegrationTesting/InteropClient.cs
  71. 9
      src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
  72. 2
      src/csharp/Grpc.IntegrationTesting/InteropServer.cs
  73. 3
      src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
  74. 55
      src/csharp/ext/grpc_csharp_ext.c
  75. 63
      src/node/ext/server_credentials.cc
  76. 10
      src/node/health_check/health.js
  77. 5
      src/node/health_check/health.proto
  78. 4
      src/node/interop/interop_server.js
  79. 24
      src/node/test/health_test.js
  80. 4
      src/node/test/server_test.js
  81. 8
      src/php/tests/generated_code/AbstractGeneratedCodeTest.php
  82. 4
      src/php/tests/generated_code/GeneratedCodeTest.php
  83. 4
      src/php/tests/generated_code/GeneratedCodeWithCallbackTest.php
  84. 2
      src/python/grpcio/grpc/_adapter/_c/types.h
  85. 16
      src/python/grpcio/grpc/_adapter/_c/types/server.c
  86. 3
      src/python/grpcio/grpc/_adapter/_low.py
  87. 30
      src/python/grpcio/grpc/framework/core/__init__.py
  88. 59
      src/python/grpcio/grpc/framework/core/_constants.py
  89. 92
      src/python/grpcio/grpc/framework/core/_context.py
  90. 97
      src/python/grpcio/grpc/framework/core/_emission.py
  91. 251
      src/python/grpcio/grpc/framework/core/_end.py
  92. 152
      src/python/grpcio/grpc/framework/core/_expiration.py
  93. 410
      src/python/grpcio/grpc/framework/core/_ingestion.py
  94. 308
      src/python/grpcio/grpc/framework/core/_interfaces.py
  95. 192
      src/python/grpcio/grpc/framework/core/_operation.py
  96. 137
      src/python/grpcio/grpc/framework/core/_reception.py
  97. 212
      src/python/grpcio/grpc/framework/core/_termination.py
  98. 294
      src/python/grpcio/grpc/framework/core/_transmission.py
  99. 46
      src/python/grpcio/grpc/framework/core/_utilities.py
  100. 62
      src/python/grpcio/grpc/framework/core/implementations.py
  101. Some files were not shown because too many files have changed in this diff Show More

12
BUILD

@ -677,6 +677,9 @@ cc_library(
"src/cpp/server/secure_server_credentials.h", "src/cpp/server/secure_server_credentials.h",
"src/cpp/client/create_channel_internal.h", "src/cpp/client/create_channel_internal.h",
"src/cpp/common/create_auth_context.h", "src/cpp/common/create_auth_context.h",
"src/cpp/server/dynamic_thread_pool.h",
"src/cpp/server/fixed_size_thread_pool.h",
"src/cpp/server/thread_pool_interface.h",
"src/cpp/client/secure_channel_arguments.cc", "src/cpp/client/secure_channel_arguments.cc",
"src/cpp/client/secure_credentials.cc", "src/cpp/client/secure_credentials.cc",
"src/cpp/common/auth_property_iterator.cc", "src/cpp/common/auth_property_iterator.cc",
@ -722,8 +725,6 @@ cc_library(
"include/grpc++/config_protobuf.h", "include/grpc++/config_protobuf.h",
"include/grpc++/create_channel.h", "include/grpc++/create_channel.h",
"include/grpc++/credentials.h", "include/grpc++/credentials.h",
"include/grpc++/dynamic_thread_pool.h",
"include/grpc++/fixed_size_thread_pool.h",
"include/grpc++/generic_stub.h", "include/grpc++/generic_stub.h",
"include/grpc++/impl/call.h", "include/grpc++/impl/call.h",
"include/grpc++/impl/client_unary_call.h", "include/grpc++/impl/client_unary_call.h",
@ -748,7 +749,6 @@ cc_library(
"include/grpc++/status_code_enum.h", "include/grpc++/status_code_enum.h",
"include/grpc++/stream.h", "include/grpc++/stream.h",
"include/grpc++/stub_options.h", "include/grpc++/stub_options.h",
"include/grpc++/thread_pool_interface.h",
"include/grpc++/time.h", "include/grpc++/time.h",
], ],
includes = [ includes = [
@ -768,6 +768,9 @@ cc_library(
srcs = [ srcs = [
"src/cpp/client/create_channel_internal.h", "src/cpp/client/create_channel_internal.h",
"src/cpp/common/create_auth_context.h", "src/cpp/common/create_auth_context.h",
"src/cpp/server/dynamic_thread_pool.h",
"src/cpp/server/fixed_size_thread_pool.h",
"src/cpp/server/thread_pool_interface.h",
"src/cpp/common/insecure_create_auth_context.cc", "src/cpp/common/insecure_create_auth_context.cc",
"src/cpp/client/channel.cc", "src/cpp/client/channel.cc",
"src/cpp/client/channel_arguments.cc", "src/cpp/client/channel_arguments.cc",
@ -808,8 +811,6 @@ cc_library(
"include/grpc++/config_protobuf.h", "include/grpc++/config_protobuf.h",
"include/grpc++/create_channel.h", "include/grpc++/create_channel.h",
"include/grpc++/credentials.h", "include/grpc++/credentials.h",
"include/grpc++/dynamic_thread_pool.h",
"include/grpc++/fixed_size_thread_pool.h",
"include/grpc++/generic_stub.h", "include/grpc++/generic_stub.h",
"include/grpc++/impl/call.h", "include/grpc++/impl/call.h",
"include/grpc++/impl/client_unary_call.h", "include/grpc++/impl/client_unary_call.h",
@ -834,7 +835,6 @@ cc_library(
"include/grpc++/status_code_enum.h", "include/grpc++/status_code_enum.h",
"include/grpc++/stream.h", "include/grpc++/stream.h",
"include/grpc++/stub_options.h", "include/grpc++/stub_options.h",
"include/grpc++/thread_pool_interface.h",
"include/grpc++/time.h", "include/grpc++/time.h",
], ],
includes = [ includes = [

File diff suppressed because one or more lines are too long

@ -42,8 +42,6 @@
"include/grpc++/config_protobuf.h", "include/grpc++/config_protobuf.h",
"include/grpc++/create_channel.h", "include/grpc++/create_channel.h",
"include/grpc++/credentials.h", "include/grpc++/credentials.h",
"include/grpc++/dynamic_thread_pool.h",
"include/grpc++/fixed_size_thread_pool.h",
"include/grpc++/generic_stub.h", "include/grpc++/generic_stub.h",
"include/grpc++/impl/call.h", "include/grpc++/impl/call.h",
"include/grpc++/impl/client_unary_call.h", "include/grpc++/impl/client_unary_call.h",
@ -68,12 +66,14 @@
"include/grpc++/status_code_enum.h", "include/grpc++/status_code_enum.h",
"include/grpc++/stream.h", "include/grpc++/stream.h",
"include/grpc++/stub_options.h", "include/grpc++/stub_options.h",
"include/grpc++/thread_pool_interface.h",
"include/grpc++/time.h" "include/grpc++/time.h"
], ],
"headers": [ "headers": [
"src/cpp/client/create_channel_internal.h", "src/cpp/client/create_channel_internal.h",
"src/cpp/common/create_auth_context.h" "src/cpp/common/create_auth_context.h",
"src/cpp/server/dynamic_thread_pool.h",
"src/cpp/server/fixed_size_thread_pool.h",
"src/cpp/server/thread_pool_interface.h"
], ],
"src": [ "src": [
"src/cpp/client/channel.cc", "src/cpp/client/channel.cc",
@ -1364,6 +1364,20 @@
"gpr" "gpr"
] ]
}, },
{
"name": "grpc_channel_args_test",
"build": "test",
"language": "c",
"src": [
"test/core/channel/channel_args_test.c"
],
"deps": [
"grpc_test_util",
"grpc",
"gpr_test_util",
"gpr"
]
},
{ {
"name": "grpc_channel_stack_test", "name": "grpc_channel_stack_test",
"build": "test", "build": "test",
@ -2128,21 +2142,6 @@
"gpr" "gpr"
] ]
}, },
{
"name": "dynamic_thread_pool_test",
"build": "test",
"language": "c++",
"src": [
"test/cpp/server/dynamic_thread_pool_test.cc"
],
"deps": [
"grpc_test_util",
"grpc++",
"grpc",
"gpr_test_util",
"gpr"
]
},
{ {
"name": "end2end_test", "name": "end2end_test",
"build": "test", "build": "test",
@ -2159,21 +2158,6 @@
"gpr" "gpr"
] ]
}, },
{
"name": "fixed_size_thread_pool_test",
"build": "test",
"language": "c++",
"src": [
"test/cpp/server/fixed_size_thread_pool_test.cc"
],
"deps": [
"grpc_test_util",
"grpc++",
"grpc",
"gpr_test_util",
"gpr"
]
},
{ {
"name": "generic_end2end_test", "name": "generic_end2end_test",
"build": "test", "build": "test",
@ -2623,13 +2607,9 @@
"grpc++_test_util", "grpc++_test_util",
"grpc_test_util", "grpc_test_util",
"grpc++", "grpc++",
"grpc_zookeeper",
"grpc", "grpc",
"gpr_test_util", "gpr_test_util",
"gpr" "gpr"
],
"external_deps": [
"zookeeper"
] ]
}, },
{ {

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

@ -541,8 +541,7 @@ class CallOpSet : public CallOpSetInterface,
template <class Op1 = CallNoOp<1>, class Op2 = CallNoOp<2>, template <class Op1 = CallNoOp<1>, class Op2 = CallNoOp<2>,
class Op3 = CallNoOp<3>, class Op4 = CallNoOp<4>, class Op3 = CallNoOp<3>, class Op4 = CallNoOp<4>,
class Op5 = CallNoOp<5>, class Op6 = CallNoOp<6>> class Op5 = CallNoOp<5>, class Op6 = CallNoOp<6>>
class SneakyCallOpSet GRPC_FINAL class SneakyCallOpSet : public CallOpSet<Op1, Op2, Op3, Op4, Op5, Op6> {
: public CallOpSet<Op1, Op2, Op3, Op4, Op5, Op6> {
public: public:
bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE { bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE {
typedef CallOpSet<Op1, Op2, Op3, Op4, Op5, Op6> Base; typedef CallOpSet<Op1, Op2, Op3, Op4, Op5, Op6> Base;

@ -211,13 +211,19 @@ class BidiStreamingHandler : public MethodHandler {
// Handle unknown method by returning UNIMPLEMENTED error. // Handle unknown method by returning UNIMPLEMENTED error.
class UnknownMethodHandler : public MethodHandler { class UnknownMethodHandler : public MethodHandler {
public: public:
void RunHandler(const HandlerParameter& param) GRPC_FINAL { template <class T>
static void FillOps(ServerContext* context, T* ops) {
Status status(StatusCode::UNIMPLEMENTED, ""); Status status(StatusCode::UNIMPLEMENTED, "");
CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> ops; if (!context->sent_initial_metadata_) {
if (!param.server_context->sent_initial_metadata_) { ops->SendInitialMetadata(context->initial_metadata_);
ops.SendInitialMetadata(param.server_context->initial_metadata_); context->sent_initial_metadata_ = true;
} }
ops.ServerSendStatus(param.server_context->trailing_metadata_, status); ops->ServerSendStatus(context->trailing_metadata_, status);
}
void RunHandler(const HandlerParameter& param) GRPC_FINAL {
CallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus> ops;
FillOps(param.server_context, &ops);
param.call->PerformOps(&ops); param.call->PerformOps(&ops);
param.call->cq()->Pluck(&ops); param.call->cq()->Pluck(&ops);
} }

@ -98,7 +98,7 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
// Add a listening port. Can be called multiple times. // Add a listening port. Can be called multiple times.
int AddListeningPort(const grpc::string& addr, ServerCredentials* creds); int AddListeningPort(const grpc::string& addr, ServerCredentials* creds);
// Start the server. // Start the server.
bool Start(); bool Start(ServerCompletionQueue** cqs, size_t num_cqs);
void HandleQueueClosed(); void HandleQueueClosed();
void RunRpc(); void RunRpc();
@ -112,7 +112,8 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
public: public:
BaseAsyncRequest(Server* server, ServerContext* context, BaseAsyncRequest(Server* server, ServerContext* context,
ServerAsyncStreamingInterface* stream, ServerAsyncStreamingInterface* stream,
CompletionQueue* call_cq, void* tag); CompletionQueue* call_cq, void* tag,
bool delete_on_finalize);
virtual ~BaseAsyncRequest(); virtual ~BaseAsyncRequest();
bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE; bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE;
@ -123,6 +124,7 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
ServerAsyncStreamingInterface* const stream_; ServerAsyncStreamingInterface* const stream_;
CompletionQueue* const call_cq_; CompletionQueue* const call_cq_;
void* const tag_; void* const tag_;
const bool delete_on_finalize_;
grpc_call* call_; grpc_call* call_;
grpc_metadata_array initial_metadata_array_; grpc_metadata_array initial_metadata_array_;
}; };
@ -184,12 +186,13 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
Message* const request_; Message* const request_;
}; };
class GenericAsyncRequest GRPC_FINAL : public BaseAsyncRequest { class GenericAsyncRequest : public BaseAsyncRequest {
public: public:
GenericAsyncRequest(Server* server, GenericServerContext* context, GenericAsyncRequest(Server* server, GenericServerContext* context,
ServerAsyncStreamingInterface* stream, ServerAsyncStreamingInterface* stream,
CompletionQueue* call_cq, CompletionQueue* call_cq,
ServerCompletionQueue* notification_cq, void* tag); ServerCompletionQueue* notification_cq, void* tag,
bool delete_on_finalize);
bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE; bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE;
@ -197,6 +200,10 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
grpc_call_details call_details_; grpc_call_details call_details_;
}; };
class UnimplementedAsyncRequestContext;
class UnimplementedAsyncRequest;
class UnimplementedAsyncResponse;
template <class Message> template <class Message>
void RequestAsyncCall(void* registered_method, ServerContext* context, void RequestAsyncCall(void* registered_method, ServerContext* context,
ServerAsyncStreamingInterface* stream, ServerAsyncStreamingInterface* stream,
@ -221,7 +228,7 @@ class Server GRPC_FINAL : public GrpcLibrary, private CallHook {
ServerCompletionQueue* notification_cq, ServerCompletionQueue* notification_cq,
void* tag) { void* tag) {
new GenericAsyncRequest(this, context, stream, call_cq, notification_cq, new GenericAsyncRequest(this, context, stream, call_cq, notification_cq,
tag); tag, true);
} }
const int max_message_size_; const int max_message_size_;

@ -96,13 +96,9 @@ class ServerBuilder {
std::shared_ptr<ServerCredentials> creds, std::shared_ptr<ServerCredentials> creds,
int* selected_port = nullptr); int* selected_port = nullptr);
// Set the thread pool used for running appliation rpc handlers.
// Does not take ownership.
void SetThreadPool(ThreadPoolInterface* thread_pool);
// Add a completion queue for handling asynchronous services // Add a completion queue for handling asynchronous services
// Caller is required to keep this completion queue live until calling // Caller is required to keep this completion queue live until
// BuildAndStart() // the server is destroyed.
std::unique_ptr<ServerCompletionQueue> AddCompletionQueue(); std::unique_ptr<ServerCompletionQueue> AddCompletionQueue();
// Return a running server which is ready for processing rpcs. // Return a running server which is ready for processing rpcs.

@ -761,6 +761,8 @@ class ServerAsyncReaderWriter GRPC_FINAL : public ServerAsyncStreamingInterface,
} }
private: private:
friend class ::grpc::Server;
void BindCall(Call* call) GRPC_OVERRIDE { call_ = *call; } void BindCall(Call* call) GRPC_OVERRIDE { call_ = *call; }
Call call_; Call call_;

@ -36,12 +36,15 @@
#include <stdlib.h> #include <stdlib.h>
#include <grpc/support/port_platform.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/** To be used in channel arguments */ /** To be used in channel arguments */
#define GRPC_COMPRESSION_ALGORITHM_ARG "grpc.compression_algorithm" #define GRPC_COMPRESSION_ALGORITHM_ARG "grpc.compression_algorithm"
#define GRPC_COMPRESSION_ALGORITHM_STATE_ARG "grpc.compression_algorithm_state"
/* The various compression algorithms supported by GRPC */ /* The various compression algorithms supported by GRPC */
typedef enum { typedef enum {
@ -60,6 +63,11 @@ typedef enum {
GRPC_COMPRESS_LEVEL_COUNT GRPC_COMPRESS_LEVEL_COUNT
} grpc_compression_level; } grpc_compression_level;
typedef struct grpc_compression_options {
gpr_uint32 enabled_algorithms_bitset; /**< All algs are enabled by default */
grpc_compression_algorithm default_compression_algorithm; /**< for channel */
} grpc_compression_options;
/** Parses the first \a name_length bytes of \a name as a /** Parses the first \a name_length bytes of \a name as a
* grpc_compression_algorithm instance, updating \a algorithm. Returns 1 upon * grpc_compression_algorithm instance, updating \a algorithm. Returns 1 upon
* success, 0 otherwise. */ * success, 0 otherwise. */
@ -67,9 +75,7 @@ int grpc_compression_algorithm_parse(const char *name, size_t name_length,
grpc_compression_algorithm *algorithm); grpc_compression_algorithm *algorithm);
/** Updates \a name with the encoding name corresponding to a valid \a /** Updates \a name with the encoding name corresponding to a valid \a
* algorithm. Note that the string returned through \a name upon success is * algorithm. Returns 1 upon success, 0 otherwise. */
* statically allocated and shouldn't be freed. Returns 1 upon success, 0
* otherwise. */
int grpc_compression_algorithm_name(grpc_compression_algorithm algorithm, int grpc_compression_algorithm_name(grpc_compression_algorithm algorithm,
char **name); char **name);
@ -85,6 +91,20 @@ grpc_compression_level grpc_compression_level_for_algorithm(
grpc_compression_algorithm grpc_compression_algorithm_for_level( grpc_compression_algorithm grpc_compression_algorithm_for_level(
grpc_compression_level level); grpc_compression_level level);
void grpc_compression_options_init(grpc_compression_options *opts);
/** Mark \a algorithm as enabled in \a opts. */
void grpc_compression_options_enable_algorithm(
grpc_compression_options *opts, grpc_compression_algorithm algorithm);
/** Mark \a algorithm as disabled in \a opts. */
void grpc_compression_options_disable_algorithm(
grpc_compression_options *opts, grpc_compression_algorithm algorithm);
/** Returns true if \a algorithm is marked as enabled in \a opts. */
int grpc_compression_options_is_algorithm_enabled(
const grpc_compression_options *opts, grpc_compression_algorithm algorithm);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

@ -275,12 +275,18 @@ int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx,
/* --- Auth Metadata Processing --- */ /* --- Auth Metadata Processing --- */
/* Callback function that is called when the metadata processing is done. /* Callback function that is called when the metadata processing is done.
success is 1 if processing succeeded, 0 otherwise. - Consumed metadata will be removed from the set of metadata available on the
Consumed metadata will be removed from the set of metadata available on the call. consumed_md may be NULL if no metadata has been consumed.
call. */ - Response metadata will be set on the response. response_md may be NULL.
- status is GRPC_STATUS_OK for success or a specific status for an error.
Common error status for auth metadata processing is either
GRPC_STATUS_UNAUTHENTICATED in case of an authentication failure or
GRPC_STATUS PERMISSION_DENIED in case of an authorization failure.
- error_details gives details about the error. May be NULL. */
typedef void (*grpc_process_auth_metadata_done_cb)( typedef void (*grpc_process_auth_metadata_done_cb)(
void *user_data, const grpc_metadata *consumed_md, size_t num_consumed_md, void *user_data, const grpc_metadata *consumed_md, size_t num_consumed_md,
int success); const grpc_metadata *response_md, size_t num_response_md,
grpc_status_code status, const char *error_details);
/* Pluggable server-side metadata processor object. */ /* Pluggable server-side metadata processor object. */
typedef struct { typedef struct {

@ -37,6 +37,7 @@
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
#include <grpc/support/string_util.h> #include <grpc/support/string_util.h>
#include <grpc/support/useful.h>
#include <string.h> #include <string.h>
@ -146,3 +147,65 @@ grpc_channel_args *grpc_channel_args_set_compression_algorithm(
tmp.value.integer = algorithm; tmp.value.integer = algorithm;
return grpc_channel_args_copy_and_add(a, &tmp, 1); return grpc_channel_args_copy_and_add(a, &tmp, 1);
} }
/** Returns 1 if the argument for compression algorithm's enabled states bitset
* was found in \a a, returning the arg's value in \a states. Otherwise, returns
* 0. */
static int find_compression_algorithm_states_bitset(
const grpc_channel_args *a, int **states_arg) {
if (a != NULL) {
size_t i;
for (i = 0; i < a->num_args; ++i) {
if (a->args[i].type == GRPC_ARG_INTEGER &&
!strcmp(GRPC_COMPRESSION_ALGORITHM_STATE_ARG, a->args[i].key)) {
*states_arg = &a->args[i].value.integer;
return 1; /* GPR_TRUE */
}
}
}
return 0; /* GPR_FALSE */
}
grpc_channel_args *grpc_channel_args_compression_algorithm_set_state(
grpc_channel_args **a,
grpc_compression_algorithm algorithm,
int state) {
int *states_arg;
grpc_channel_args *result = *a;
const int states_arg_found =
find_compression_algorithm_states_bitset(*a, &states_arg);
if (states_arg_found) {
if (state != 0) {
GPR_BITSET(states_arg, algorithm);
} else {
GPR_BITCLEAR(states_arg, algorithm);
}
} else {
/* create a new arg */
grpc_arg tmp;
tmp.type = GRPC_ARG_INTEGER;
tmp.key = GRPC_COMPRESSION_ALGORITHM_STATE_ARG;
/* all enabled by default */
tmp.value.integer = (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1;
if (state != 0) {
GPR_BITSET(&tmp.value.integer, algorithm);
} else {
GPR_BITCLEAR(&tmp.value.integer, algorithm);
}
result = grpc_channel_args_copy_and_add(*a, &tmp, 1);
grpc_channel_args_destroy(*a);
*a = result;
}
return result;
}
int grpc_channel_args_compression_algorithm_get_states(
const grpc_channel_args *a) {
int *states_arg;
if (find_compression_algorithm_states_bitset(a, &states_arg)) {
return *states_arg;
} else {
return (1u << GRPC_COMPRESS_ALGORITHMS_COUNT) - 1; /* All algs. enabled */
}
}

@ -67,4 +67,24 @@ grpc_compression_algorithm grpc_channel_args_get_compression_algorithm(
grpc_channel_args *grpc_channel_args_set_compression_algorithm( grpc_channel_args *grpc_channel_args_set_compression_algorithm(
grpc_channel_args *a, grpc_compression_algorithm algorithm); grpc_channel_args *a, grpc_compression_algorithm algorithm);
/** Sets the support for the given compression algorithm. By default, all
* compression algorithms are enabled. It's an error to disable an algorithm set
* by grpc_channel_args_set_compression_algorithm.
*
* Returns an instance will the updated algorithm states. The \a a pointer is
* modified to point to the returned instance (which may be different from the
* input value of \a a). */
grpc_channel_args *grpc_channel_args_compression_algorithm_set_state(
grpc_channel_args **a,
grpc_compression_algorithm algorithm,
int enabled);
/** Returns the bitset representing the support state (true for enabled, false
* for disabled) for compression algorithms.
*
* The i-th bit of the returned bitset corresponds to the i-th entry in the
* grpc_compression_algorithm enum. */
int grpc_channel_args_compression_algorithm_get_states(
const grpc_channel_args *a);
#endif /* GRPC_INTERNAL_CORE_CHANNEL_CHANNEL_ARGS_H */ #endif /* GRPC_INTERNAL_CORE_CHANNEL_CHANNEL_ARGS_H */

@ -74,10 +74,9 @@ void grpc_pollset_destroy(grpc_pollset *pollset);
grpc_pollset_work, and it is guaranteed that GRPC_POLLSET_MU(pollset) will grpc_pollset_work, and it is guaranteed that GRPC_POLLSET_MU(pollset) will
not be released by grpc_pollset_work AFTER worker has been destroyed. not be released by grpc_pollset_work AFTER worker has been destroyed.
Returns true if some work has been done, and false if the deadline Tries not to block past deadline. */
expired. */ void grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker,
int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, gpr_timespec now, gpr_timespec deadline);
gpr_timespec deadline);
/* Break one polling thread out of polling work for this pollset. /* Break one polling thread out of polling work for this pollset.
If specific_worker is GRPC_POLLSET_KICK_BROADCAST, kick ALL the workers. If specific_worker is GRPC_POLLSET_KICK_BROADCAST, kick ALL the workers.

@ -181,7 +181,7 @@ static void multipoll_with_epoll_pollset_maybe_work(
pfds[1].events = POLLIN; pfds[1].events = POLLIN;
pfds[1].revents = 0; pfds[1].revents = 0;
poll_rv = poll(pfds, 2, timeout_ms); poll_rv = grpc_poll_function(pfds, 2, timeout_ms);
if (poll_rv < 0) { if (poll_rv < 0) {
if (errno != EINTR) { if (errno != EINTR) {

@ -144,7 +144,7 @@ static void multipoll_with_poll_pollset_maybe_work(
POLLOUT, &watchers[i]); POLLOUT, &watchers[i]);
} }
r = poll(pfds, pfd_count, timeout); r = grpc_poll_function(pfds, pfd_count, timeout);
for (i = 1; i < pfd_count; i++) { for (i = 1; i < pfd_count; i++) {
grpc_fd_end_poll(&watchers[i], pfds[i].revents & POLLIN, grpc_fd_end_poll(&watchers[i], pfds[i].revents & POLLIN,

@ -38,7 +38,6 @@
#include "src/core/iomgr/pollset_posix.h" #include "src/core/iomgr/pollset_posix.h"
#include <errno.h> #include <errno.h>
#include <poll.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
@ -57,6 +56,8 @@
GPR_TLS_DECL(g_current_thread_poller); GPR_TLS_DECL(g_current_thread_poller);
GPR_TLS_DECL(g_current_thread_worker); GPR_TLS_DECL(g_current_thread_worker);
grpc_poll_function_type grpc_poll_function = poll;
static void remove_worker(grpc_pollset *p, grpc_pollset_worker *worker) { static void remove_worker(grpc_pollset *p, grpc_pollset_worker *worker) {
worker->prev->next = worker->next; worker->prev->next = worker->next;
worker->next->prev = worker->prev; worker->next->prev = worker->prev;
@ -89,6 +90,7 @@ static void push_front_worker(grpc_pollset *p, grpc_pollset_worker *worker) {
} }
void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) {
/* pollset->mu already held */
if (specific_worker != NULL) { if (specific_worker != NULL) {
if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) {
for (specific_worker = p->root_worker.next; for (specific_worker = p->root_worker.next;
@ -168,14 +170,10 @@ static void finish_shutdown(grpc_pollset *pollset) {
pollset->shutdown_done_cb(pollset->shutdown_done_arg); pollset->shutdown_done_cb(pollset->shutdown_done_arg);
} }
int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, void grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker,
gpr_timespec deadline) { gpr_timespec now, gpr_timespec deadline) {
/* pollset->mu already held */ /* pollset->mu already held */
gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC);
int added_worker = 0; int added_worker = 0;
if (gpr_time_cmp(now, deadline) > 0) {
return 0;
}
/* this must happen before we (potentially) drop pollset->mu */ /* this must happen before we (potentially) drop pollset->mu */
worker->next = worker->prev = NULL; worker->next = worker->prev = NULL;
/* TODO(ctiller): pool these */ /* TODO(ctiller): pool these */
@ -217,7 +215,6 @@ done:
gpr_mu_lock(&pollset->mu); gpr_mu_lock(&pollset->mu);
} }
} }
return 1;
} }
void grpc_pollset_shutdown(grpc_pollset *pollset, void grpc_pollset_shutdown(grpc_pollset *pollset,
@ -456,7 +453,7 @@ static void basic_pollset_maybe_work(grpc_pollset *pollset,
/* poll fd count (argument 2) is shortened by one if we have no events /* poll fd count (argument 2) is shortened by one if we have no events
to poll on - such that it only includes the kicker */ to poll on - such that it only includes the kicker */
r = poll(pfd, nfds, timeout); r = grpc_poll_function(pfd, nfds, timeout);
GRPC_TIMER_MARK(GRPC_PTAG_POLL_FINISHED, r); GRPC_TIMER_MARK(GRPC_PTAG_POLL_FINISHED, r);
if (fd) { if (fd) {

@ -34,6 +34,8 @@
#ifndef GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H #ifndef GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H
#define GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H #define GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H
#include <poll.h>
#include <grpc/support/sync.h> #include <grpc/support/sync.h>
#include "src/core/iomgr/wakeup_fd_posix.h" #include "src/core/iomgr/wakeup_fd_posix.h"
@ -118,4 +120,8 @@ void grpc_poll_become_multipoller(grpc_pollset *pollset, struct grpc_fd **fds,
* be locked) */ * be locked) */
int grpc_pollset_has_workers(grpc_pollset *pollset); int grpc_pollset_has_workers(grpc_pollset *pollset);
/* override to allow tests to hook poll() usage */
typedef int (*grpc_poll_function_type)(struct pollfd *, nfds_t, int);
extern grpc_poll_function_type grpc_poll_function;
#endif /* GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H */ #endif /* GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H */

@ -99,14 +99,9 @@ void grpc_pollset_destroy(grpc_pollset *pollset) {
gpr_mu_destroy(&pollset->mu); gpr_mu_destroy(&pollset->mu);
} }
int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, void grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker,
gpr_timespec deadline) { gpr_timespec now, gpr_timespec deadline) {
gpr_timespec now;
int added_worker = 0; int added_worker = 0;
now = gpr_now(GPR_CLOCK_MONOTONIC);
if (gpr_time_cmp(now, deadline) > 0) {
return 0 /* GPR_FALSE */;
}
worker->next = worker->prev = NULL; worker->next = worker->prev = NULL;
gpr_cv_init(&worker->cv); gpr_cv_init(&worker->cv);
if (grpc_maybe_call_delayed_callbacks(&pollset->mu, 1 /* GPR_TRUE */)) { if (grpc_maybe_call_delayed_callbacks(&pollset->mu, 1 /* GPR_TRUE */)) {
@ -127,7 +122,6 @@ done:
if (added_worker) { if (added_worker) {
remove_worker(pollset, worker); remove_worker(pollset, worker);
} }
return 1 /* GPR_TRUE */;
} }
void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) {

@ -115,7 +115,7 @@ static int is_stack_running_on_compute_engine(void) {
gpr_mu_lock(GRPC_POLLSET_MU(&detector.pollset)); gpr_mu_lock(GRPC_POLLSET_MU(&detector.pollset));
while (!detector.is_done) { while (!detector.is_done) {
grpc_pollset_worker worker; grpc_pollset_worker worker;
grpc_pollset_work(&detector.pollset, &worker, grpc_pollset_work(&detector.pollset, &worker, gpr_now(GPR_CLOCK_MONOTONIC),
gpr_inf_future(GPR_CLOCK_MONOTONIC)); gpr_inf_future(GPR_CLOCK_MONOTONIC));
} }
gpr_mu_unlock(GRPC_POLLSET_MU(&detector.pollset)); gpr_mu_unlock(GRPC_POLLSET_MU(&detector.pollset));

@ -104,24 +104,34 @@ static grpc_mdelem *remove_consumed_md(void *user_data, grpc_mdelem *md) {
return md; return md;
} }
static void on_md_processing_done(void *user_data, static void on_md_processing_done(
const grpc_metadata *consumed_md, void *user_data, const grpc_metadata *consumed_md, size_t num_consumed_md,
size_t num_consumed_md, int success) { const grpc_metadata *response_md, size_t num_response_md,
grpc_status_code status, const char *error_details) {
grpc_call_element *elem = user_data; grpc_call_element *elem = user_data;
call_data *calld = elem->call_data; call_data *calld = elem->call_data;
if (success) { /* TODO(jboeuf): Implement support for response_md. */
if (response_md != NULL && num_response_md > 0) {
gpr_log(GPR_INFO,
"response_md in auth metadata processing not supported for now. "
"Ignoring...");
}
if (status == GRPC_STATUS_OK) {
calld->consumed_md = consumed_md; calld->consumed_md = consumed_md;
calld->num_consumed_md = num_consumed_md; calld->num_consumed_md = num_consumed_md;
grpc_metadata_batch_filter(&calld->md_op->data.metadata, remove_consumed_md, grpc_metadata_batch_filter(&calld->md_op->data.metadata, remove_consumed_md,
elem); elem);
calld->on_done_recv->cb(calld->on_done_recv->cb_arg, success); calld->on_done_recv->cb(calld->on_done_recv->cb_arg, 1);
} else { } else {
gpr_slice message = gpr_slice_from_copied_string( gpr_slice message;
"Authentication metadata processing failed."); error_details = error_details != NULL
? error_details
: "Authentication metadata processing failed.";
message = gpr_slice_from_copied_string(error_details);
grpc_sopb_reset(calld->recv_ops); grpc_sopb_reset(calld->recv_ops);
grpc_transport_stream_op_add_close(&calld->transport_op, grpc_transport_stream_op_add_close(&calld->transport_op, status, &message);
GRPC_STATUS_UNAUTHENTICATED, &message);
grpc_call_next_op(elem, &calld->transport_op); grpc_call_next_op(elem, &calld->transport_op);
} }
} }

@ -170,6 +170,9 @@ grpc_event grpc_completion_queue_next(grpc_completion_queue *cc,
gpr_timespec deadline, void *reserved) { gpr_timespec deadline, void *reserved) {
grpc_event ret; grpc_event ret;
grpc_pollset_worker worker; grpc_pollset_worker worker;
int first_loop = 1;
gpr_timespec now;
GPR_ASSERT(!reserved); GPR_ASSERT(!reserved);
deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC);
@ -196,12 +199,15 @@ grpc_event grpc_completion_queue_next(grpc_completion_queue *cc,
ret.type = GRPC_QUEUE_SHUTDOWN; ret.type = GRPC_QUEUE_SHUTDOWN;
break; break;
} }
if (!grpc_pollset_work(&cc->pollset, &worker, deadline)) { now = gpr_now(GPR_CLOCK_MONOTONIC);
if (!first_loop && gpr_time_cmp(now, deadline) >= 0) {
gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
memset(&ret, 0, sizeof(ret)); memset(&ret, 0, sizeof(ret));
ret.type = GRPC_QUEUE_TIMEOUT; ret.type = GRPC_QUEUE_TIMEOUT;
break; break;
} }
first_loop = 0;
grpc_pollset_work(&cc->pollset, &worker, now, deadline);
} }
GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret); GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret);
GRPC_CQ_INTERNAL_UNREF(cc, "next"); GRPC_CQ_INTERNAL_UNREF(cc, "next");
@ -239,6 +245,9 @@ grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag,
grpc_cq_completion *c; grpc_cq_completion *c;
grpc_cq_completion *prev; grpc_cq_completion *prev;
grpc_pollset_worker worker; grpc_pollset_worker worker;
gpr_timespec now;
int first_loop = 1;
GPR_ASSERT(!reserved); GPR_ASSERT(!reserved);
deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC);
@ -281,13 +290,16 @@ grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag,
ret.type = GRPC_QUEUE_TIMEOUT; ret.type = GRPC_QUEUE_TIMEOUT;
break; break;
} }
if (!grpc_pollset_work(&cc->pollset, &worker, deadline)) { now = gpr_now(GPR_CLOCK_MONOTONIC);
if (!first_loop && gpr_time_cmp(now, deadline) >= 0) {
del_plucker(cc, tag, &worker); del_plucker(cc, tag, &worker);
gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset));
memset(&ret, 0, sizeof(ret)); memset(&ret, 0, sizeof(ret));
ret.type = GRPC_QUEUE_TIMEOUT; ret.type = GRPC_QUEUE_TIMEOUT;
break; break;
} }
first_loop = 0;
grpc_pollset_work(&cc->pollset, &worker, now, deadline);
del_plucker(cc, tag, &worker); del_plucker(cc, tag, &worker);
} }
done: done:

@ -32,7 +32,7 @@
*/ */
#include <grpc/support/cpu.h> #include <grpc/support/cpu.h>
#include <grpc++/dynamic_thread_pool.h> #include "src/cpp/server/dynamic_thread_pool.h"
#ifndef GRPC_CUSTOM_DEFAULT_THREAD_POOL #ifndef GRPC_CUSTOM_DEFAULT_THREAD_POOL

@ -33,7 +33,7 @@
#include <grpc++/impl/sync.h> #include <grpc++/impl/sync.h>
#include <grpc++/impl/thd.h> #include <grpc++/impl/thd.h>
#include <grpc++/dynamic_thread_pool.h> #include "src/cpp/server/dynamic_thread_pool.h"
namespace grpc { namespace grpc {
DynamicThreadPool::DynamicThread::DynamicThread(DynamicThreadPool* pool) DynamicThreadPool::DynamicThread::DynamicThread(DynamicThreadPool* pool)

@ -31,19 +31,20 @@
* *
*/ */
#ifndef GRPCXX_DYNAMIC_THREAD_POOL_H #ifndef GRPC_INTERNAL_CPP_DYNAMIC_THREAD_POOL_H
#define GRPCXX_DYNAMIC_THREAD_POOL_H #define GRPC_INTERNAL_CPP_DYNAMIC_THREAD_POOL_H
#include <grpc++/config.h> #include <grpc++/config.h>
#include <grpc++/impl/sync.h> #include <grpc++/impl/sync.h>
#include <grpc++/impl/thd.h> #include <grpc++/impl/thd.h>
#include <grpc++/thread_pool_interface.h>
#include <list> #include <list>
#include <memory> #include <memory>
#include <queue> #include <queue>
#include "src/cpp/server/thread_pool_interface.h"
namespace grpc { namespace grpc {
class DynamicThreadPool GRPC_FINAL : public ThreadPoolInterface { class DynamicThreadPool GRPC_FINAL : public ThreadPoolInterface {
@ -80,4 +81,4 @@ class DynamicThreadPool GRPC_FINAL : public ThreadPoolInterface {
} // namespace grpc } // namespace grpc
#endif // GRPCXX_DYNAMIC_THREAD_POOL_H #endif // GRPC_INTERNAL_CPP_DYNAMIC_THREAD_POOL_H

@ -33,7 +33,7 @@
#include <grpc++/impl/sync.h> #include <grpc++/impl/sync.h>
#include <grpc++/impl/thd.h> #include <grpc++/impl/thd.h>
#include <grpc++/fixed_size_thread_pool.h> #include "src/cpp/server/fixed_size_thread_pool.h"
namespace grpc { namespace grpc {

@ -31,18 +31,19 @@
* *
*/ */
#ifndef GRPCXX_FIXED_SIZE_THREAD_POOL_H #ifndef GRPC_INTERNAL_CPP_FIXED_SIZE_THREAD_POOL_H
#define GRPCXX_FIXED_SIZE_THREAD_POOL_H #define GRPC_INTERNAL_CPP_FIXED_SIZE_THREAD_POOL_H
#include <grpc++/config.h> #include <grpc++/config.h>
#include <grpc++/impl/sync.h> #include <grpc++/impl/sync.h>
#include <grpc++/impl/thd.h> #include <grpc++/impl/thd.h>
#include <grpc++/thread_pool_interface.h>
#include <queue> #include <queue>
#include <vector> #include <vector>
#include "src/cpp/server/thread_pool_interface.h"
namespace grpc { namespace grpc {
class FixedSizeThreadPool GRPC_FINAL : public ThreadPoolInterface { class FixedSizeThreadPool GRPC_FINAL : public ThreadPoolInterface {
@ -64,4 +65,4 @@ class FixedSizeThreadPool GRPC_FINAL : public ThreadPoolInterface {
} // namespace grpc } // namespace grpc
#endif // GRPCXX_FIXED_SIZE_THREAD_POOL_H #endif // GRPC_INTERNAL_CPP_FIXED_SIZE_THREAD_POOL_H

@ -43,13 +43,59 @@
#include <grpc++/impl/service_type.h> #include <grpc++/impl/service_type.h>
#include <grpc++/server_context.h> #include <grpc++/server_context.h>
#include <grpc++/server_credentials.h> #include <grpc++/server_credentials.h>
#include <grpc++/thread_pool_interface.h>
#include <grpc++/time.h> #include <grpc++/time.h>
#include "src/core/profiling/timers.h" #include "src/core/profiling/timers.h"
#include "src/cpp/server/thread_pool_interface.h"
namespace grpc { namespace grpc {
class Server::UnimplementedAsyncRequestContext {
protected:
UnimplementedAsyncRequestContext() : generic_stream_(&server_context_) {}
GenericServerContext server_context_;
GenericServerAsyncReaderWriter generic_stream_;
};
class Server::UnimplementedAsyncRequest GRPC_FINAL
: public UnimplementedAsyncRequestContext,
public GenericAsyncRequest {
public:
UnimplementedAsyncRequest(Server* server, ServerCompletionQueue* cq)
: GenericAsyncRequest(server, &server_context_, &generic_stream_, cq, cq,
NULL, false),
server_(server),
cq_(cq) {}
bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE;
ServerContext* context() { return &server_context_; }
GenericServerAsyncReaderWriter* stream() { return &generic_stream_; }
private:
Server* const server_;
ServerCompletionQueue* const cq_;
};
typedef SneakyCallOpSet<CallOpSendInitialMetadata, CallOpServerSendStatus>
UnimplementedAsyncResponseOp;
class Server::UnimplementedAsyncResponse GRPC_FINAL
: public UnimplementedAsyncResponseOp {
public:
UnimplementedAsyncResponse(UnimplementedAsyncRequest* request);
~UnimplementedAsyncResponse() { delete request_; }
bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE {
bool r = UnimplementedAsyncResponseOp::FinalizeResult(tag, status);
delete this;
return r;
}
private:
UnimplementedAsyncRequest* const request_;
};
class Server::ShutdownRequest GRPC_FINAL : public CompletionQueueTag { class Server::ShutdownRequest GRPC_FINAL : public CompletionQueueTag {
public: public:
bool FinalizeResult(void** tag, bool* status) { bool FinalizeResult(void** tag, bool* status) {
@ -297,18 +343,23 @@ int Server::AddListeningPort(const grpc::string& addr,
return creds->AddPortToServer(addr, server_); return creds->AddPortToServer(addr, server_);
} }
bool Server::Start() { bool Server::Start(ServerCompletionQueue** cqs, size_t num_cqs) {
GPR_ASSERT(!started_); GPR_ASSERT(!started_);
started_ = true; started_ = true;
grpc_server_start(server_); grpc_server_start(server_);
if (!has_generic_service_) { if (!has_generic_service_) {
unknown_method_.reset(new RpcServiceMethod( if (!sync_methods_->empty()) {
"unknown", RpcMethod::BIDI_STREAMING, new UnknownMethodHandler)); unknown_method_.reset(new RpcServiceMethod(
// Use of emplace_back with just constructor arguments is not accepted here "unknown", RpcMethod::BIDI_STREAMING, new UnknownMethodHandler));
// by gcc-4.4 because it can't match the anonymous nullptr with a proper // Use of emplace_back with just constructor arguments is not accepted
// constructor implicitly. Construct the object and use push_back. // here by gcc-4.4 because it can't match the anonymous nullptr with a
sync_methods_->push_back(SyncRequest(unknown_method_.get(), nullptr)); // proper constructor implicitly. Construct the object and use push_back.
sync_methods_->push_back(SyncRequest(unknown_method_.get(), nullptr));
}
for (size_t i = 0; i < num_cqs; i++) {
new UnimplementedAsyncRequest(this, cqs[i]);
}
} }
// Start processing rpcs. // Start processing rpcs.
if (!sync_methods_->empty()) { if (!sync_methods_->empty()) {
@ -370,12 +421,14 @@ void Server::PerformOpsOnCall(CallOpSetInterface* ops, Call* call) {
Server::BaseAsyncRequest::BaseAsyncRequest( Server::BaseAsyncRequest::BaseAsyncRequest(
Server* server, ServerContext* context, Server* server, ServerContext* context,
ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq, void* tag) ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq, void* tag,
bool delete_on_finalize)
: server_(server), : server_(server),
context_(context), context_(context),
stream_(stream), stream_(stream),
call_cq_(call_cq), call_cq_(call_cq),
tag_(tag), tag_(tag),
delete_on_finalize_(delete_on_finalize),
call_(nullptr) { call_(nullptr) {
memset(&initial_metadata_array_, 0, sizeof(initial_metadata_array_)); memset(&initial_metadata_array_, 0, sizeof(initial_metadata_array_));
} }
@ -402,14 +455,16 @@ bool Server::BaseAsyncRequest::FinalizeResult(void** tag, bool* status) {
// just the pointers inside call are copied here // just the pointers inside call are copied here
stream_->BindCall(&call); stream_->BindCall(&call);
*tag = tag_; *tag = tag_;
delete this; if (delete_on_finalize_) {
delete this;
}
return true; return true;
} }
Server::RegisteredAsyncRequest::RegisteredAsyncRequest( Server::RegisteredAsyncRequest::RegisteredAsyncRequest(
Server* server, ServerContext* context, Server* server, ServerContext* context,
ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq, void* tag) ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq, void* tag)
: BaseAsyncRequest(server, context, stream, call_cq, tag) {} : BaseAsyncRequest(server, context, stream, call_cq, tag, true) {}
void Server::RegisteredAsyncRequest::IssueRequest( void Server::RegisteredAsyncRequest::IssueRequest(
void* registered_method, grpc_byte_buffer** payload, void* registered_method, grpc_byte_buffer** payload,
@ -423,8 +478,9 @@ void Server::RegisteredAsyncRequest::IssueRequest(
Server::GenericAsyncRequest::GenericAsyncRequest( Server::GenericAsyncRequest::GenericAsyncRequest(
Server* server, GenericServerContext* context, Server* server, GenericServerContext* context,
ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq, ServerAsyncStreamingInterface* stream, CompletionQueue* call_cq,
ServerCompletionQueue* notification_cq, void* tag) ServerCompletionQueue* notification_cq, void* tag, bool delete_on_finalize)
: BaseAsyncRequest(server, context, stream, call_cq, tag) { : BaseAsyncRequest(server, context, stream, call_cq, tag,
delete_on_finalize) {
grpc_call_details_init(&call_details_); grpc_call_details_init(&call_details_);
GPR_ASSERT(notification_cq); GPR_ASSERT(notification_cq);
GPR_ASSERT(call_cq); GPR_ASSERT(call_cq);
@ -445,6 +501,25 @@ bool Server::GenericAsyncRequest::FinalizeResult(void** tag, bool* status) {
return BaseAsyncRequest::FinalizeResult(tag, status); return BaseAsyncRequest::FinalizeResult(tag, status);
} }
bool Server::UnimplementedAsyncRequest::FinalizeResult(void** tag,
bool* status) {
if (GenericAsyncRequest::FinalizeResult(tag, status) && *status) {
new UnimplementedAsyncRequest(server_, cq_);
new UnimplementedAsyncResponse(this);
} else {
delete this;
}
return false;
}
Server::UnimplementedAsyncResponse::UnimplementedAsyncResponse(
UnimplementedAsyncRequest* request)
: request_(request) {
Status status(StatusCode::UNIMPLEMENTED, "");
UnknownMethodHandler::FillOps(request_->context(), this);
request_->stream()->call_.PerformOps(this);
}
void Server::ScheduleCallback() { void Server::ScheduleCallback() {
{ {
grpc::unique_lock<grpc::mutex> lock(mu_); grpc::unique_lock<grpc::mutex> lock(mu_);

@ -37,8 +37,8 @@
#include <grpc/support/log.h> #include <grpc/support/log.h>
#include <grpc++/impl/service_type.h> #include <grpc++/impl/service_type.h>
#include <grpc++/server.h> #include <grpc++/server.h>
#include <grpc++/thread_pool_interface.h> #include "src/cpp/server/thread_pool_interface.h"
#include <grpc++/fixed_size_thread_pool.h> #include "src/cpp/server/fixed_size_thread_pool.h"
namespace grpc { namespace grpc {
@ -89,10 +89,6 @@ void ServerBuilder::AddListeningPort(const grpc::string& addr,
ports_.push_back(port); ports_.push_back(port);
} }
void ServerBuilder::SetThreadPool(ThreadPoolInterface* thread_pool) {
thread_pool_ = thread_pool;
}
std::unique_ptr<Server> ServerBuilder::BuildAndStart() { std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
bool thread_pool_owned = false; bool thread_pool_owned = false;
if (!async_services_.empty() && !services_.empty()) { if (!async_services_.empty() && !services_.empty()) {
@ -103,12 +99,6 @@ std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
thread_pool_ = CreateDefaultThreadPool(); thread_pool_ = CreateDefaultThreadPool();
thread_pool_owned = true; thread_pool_owned = true;
} }
// Async services only, create a thread pool to handle requests to unknown
// services.
if (!thread_pool_ && !generic_service_ && !async_services_.empty()) {
thread_pool_ = new FixedSizeThreadPool(1);
thread_pool_owned = true;
}
std::unique_ptr<Server> server( std::unique_ptr<Server> server(
new Server(thread_pool_, thread_pool_owned, max_message_size_)); new Server(thread_pool_, thread_pool_owned, max_message_size_));
for (auto cq = cqs_.begin(); cq != cqs_.end(); ++cq) { for (auto cq = cqs_.begin(); cq != cqs_.end(); ++cq) {
@ -138,7 +128,7 @@ std::unique_ptr<Server> ServerBuilder::BuildAndStart() {
*port->selected_port = r; *port->selected_port = r;
} }
} }
if (!server->Start()) { if (!server->Start(&cqs_[0], cqs_.size())) {
return nullptr; return nullptr;
} }
return server; return server;

@ -31,8 +31,8 @@
* *
*/ */
#ifndef GRPCXX_THREAD_POOL_INTERFACE_H #ifndef GRPC_INTERNAL_CPP_THREAD_POOL_INTERFACE_H
#define GRPCXX_THREAD_POOL_INTERFACE_H #define GRPC_INTERNAL_CPP_THREAD_POOL_INTERFACE_H
#include <functional> #include <functional>
@ -51,4 +51,4 @@ ThreadPoolInterface* CreateDefaultThreadPool();
} // namespace grpc } // namespace grpc
#endif // GRPCXX_THREAD_POOL_INTERFACE_H #endif // GRPC_INTERNAL_CPP_THREAD_POOL_INTERFACE_H

@ -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" />

@ -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;
}
}
}
}

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

@ -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);
}
}

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

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

@ -120,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)
@ -171,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;
@ -460,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,

@ -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();
} }

@ -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);
}); });

@ -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;
}])); }]);
} }
} }

@ -146,6 +146,7 @@ typedef struct Server {
PyObject_HEAD PyObject_HEAD
grpc_server *c_serv; grpc_server *c_serv;
CompletionQueue *cq; CompletionQueue *cq;
int shutdown_called;
} Server; } Server;
Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs); Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs);
void pygrpc_Server_dealloc(Server *self); void pygrpc_Server_dealloc(Server *self);
@ -156,6 +157,7 @@ PyObject *pygrpc_Server_add_http2_port(
PyObject *pygrpc_Server_start(Server *self, PyObject *ignored); PyObject *pygrpc_Server_start(Server *self, PyObject *ignored);
PyObject *pygrpc_Server_shutdown( PyObject *pygrpc_Server_shutdown(
Server *self, PyObject *args, PyObject *kwargs); Server *self, PyObject *args, PyObject *kwargs);
PyObject *pygrpc_Server_cancel_all_calls(Server *self, PyObject *unused);
extern PyTypeObject pygrpc_Server_type; extern PyTypeObject pygrpc_Server_type;
/*=========*/ /*=========*/

@ -45,6 +45,8 @@ PyMethodDef pygrpc_Server_methods[] = {
METH_KEYWORDS, ""}, METH_KEYWORDS, ""},
{"start", (PyCFunction)pygrpc_Server_start, METH_NOARGS, ""}, {"start", (PyCFunction)pygrpc_Server_start, METH_NOARGS, ""},
{"shutdown", (PyCFunction)pygrpc_Server_shutdown, METH_KEYWORDS, ""}, {"shutdown", (PyCFunction)pygrpc_Server_shutdown, METH_KEYWORDS, ""},
{"cancel_all_calls", (PyCFunction)pygrpc_Server_cancel_all_calls,
METH_NOARGS, ""},
{NULL} {NULL}
}; };
const char pygrpc_Server_doc[] = "See grpc._adapter._types.Server."; const char pygrpc_Server_doc[] = "See grpc._adapter._types.Server.";
@ -109,6 +111,7 @@ Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
pygrpc_discard_channel_args(c_args); pygrpc_discard_channel_args(c_args);
self->cq = cq; self->cq = cq;
Py_INCREF(self->cq); Py_INCREF(self->cq);
self->shutdown_called = 0;
return self; return self;
} }
@ -163,6 +166,7 @@ PyObject *pygrpc_Server_add_http2_port(
PyObject *pygrpc_Server_start(Server *self, PyObject *ignored) { PyObject *pygrpc_Server_start(Server *self, PyObject *ignored) {
grpc_server_start(self->c_serv); grpc_server_start(self->c_serv);
self->shutdown_called = 0;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -176,5 +180,17 @@ PyObject *pygrpc_Server_shutdown(
} }
tag = pygrpc_produce_server_shutdown_tag(user_tag); tag = pygrpc_produce_server_shutdown_tag(user_tag);
grpc_server_shutdown_and_notify(self->c_serv, self->cq->c_cq, tag); grpc_server_shutdown_and_notify(self->c_serv, self->cq->c_cq, tag);
self->shutdown_called = 1;
Py_RETURN_NONE;
}
PyObject *pygrpc_Server_cancel_all_calls(Server *self, PyObject *unused) {
if (!self->shutdown_called) {
PyErr_SetString(
PyExc_RuntimeError,
"shutdown must have been called prior to calling cancel_all_calls!");
return NULL;
}
grpc_server_cancel_all_calls(self->c_serv);
Py_RETURN_NONE; Py_RETURN_NONE;
} }

@ -124,3 +124,6 @@ class Server(_types.Server):
def request_call(self, completion_queue, tag): def request_call(self, completion_queue, tag):
return self.server.request_call(completion_queue.completion_queue, tag) return self.server.request_call(completion_queue.completion_queue, tag)
def cancel_all_calls(self):
return self.server.cancel_all_calls()

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

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

Loading…
Cancel
Save