Merge github.com:grpc/grpc into no-worries-i-can-wait

pull/2403/head^2
Craig Tiller 10 years ago
commit a508c80eb1
  1. 2
      .gitignore
  2. 57
      doc/connection-backoff.md
  3. 34
      include/grpc++/impl/call.h
  4. 2
      include/grpc/support/port_platform.h
  5. 54
      src/compiler/objective_c_generator.cc
  6. 30
      src/core/iomgr/socket_windows.c
  7. 52
      src/core/iomgr/tcp_server_windows.c
  8. 9
      src/core/transport/chttp2/frame_data.c
  9. 6
      src/csharp/Grpc.Core.Tests/ClientServerTest.cs
  10. 17
      src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs
  11. 12
      src/csharp/Grpc.Core.Tests/PInvokeTest.cs
  12. 3
      src/csharp/Grpc.Core.Tests/ServerTest.cs
  13. 16
      src/csharp/Grpc.Core/Calls.cs
  14. 30
      src/csharp/Grpc.Core/Channel.cs
  15. 102
      src/csharp/Grpc.Core/GrpcEnvironment.cs
  16. 9
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  17. 9
      src/csharp/Grpc.Core/Internal/AsyncCallServer.cs
  18. 30
      src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
  19. 10
      src/csharp/Grpc.Core/Internal/CompletionRegistry.cs
  20. 35
      src/csharp/Grpc.Core/Internal/DebugStats.cs
  21. 8
      src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs
  22. 26
      src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
  23. 12
      src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs
  24. 17
      src/csharp/Grpc.Core/Server.cs
  25. 2
      src/csharp/Grpc.Examples.MathClient/MathClient.cs
  26. 2
      src/csharp/Grpc.Examples.MathServer/MathServer.cs
  27. 3
      src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
  28. 3
      src/csharp/Grpc.IntegrationTesting/InteropClient.cs
  29. 3
      src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
  30. 2
      src/csharp/Grpc.IntegrationTesting/InteropServer.cs
  31. 6
      src/node/src/server.js
  32. 10
      src/php/bin/determine_extension_dir.sh
  33. 2
      src/php/bin/interop_client.sh
  34. 6
      src/php/bin/run_gen_code_test.sh
  35. 2
      src/php/bin/run_tests.sh
  36. 30
      src/python/src/grpc/_links/__init__.py
  37. 88
      src/python/src/grpc/_links/_lonely_invocation_link_test.py
  38. 261
      src/python/src/grpc/_links/_proto_scenarios.py
  39. 226
      src/python/src/grpc/_links/_transmission_test.py
  40. 363
      src/python/src/grpc/_links/invocation.py
  41. 402
      src/python/src/grpc/_links/service.py
  42. 37
      src/python/src/grpc/framework/common/test_constants.py
  43. 87
      src/python/src/grpc/framework/common/test_control.py
  44. 116
      src/python/src/grpc/framework/common/test_coverage.py
  45. 175
      src/python/src/grpc/framework/foundation/relay.py
  46. 30
      src/python/src/grpc/framework/interfaces/__init__.py
  47. 30
      src/python/src/grpc/framework/interfaces/links/__init__.py
  48. 124
      src/python/src/grpc/framework/interfaces/links/links.py
  49. 332
      src/python/src/grpc/framework/interfaces/links/test_cases.py
  50. 66
      src/python/src/grpc/framework/interfaces/links/test_utilities.py
  51. 44
      src/python/src/grpc/framework/interfaces/links/utilities.py
  52. 4
      src/python/src/setup.py
  53. 10
      src/ruby/lib/grpc/generic/rpc_server.rb
  54. 38
      test/core/util/test_config.c
  55. 8
      test/cpp/end2end/end2end_test.cc
  56. 3
      tools/run_tests/jobset.py
  57. 12
      tools/run_tests/python_tests.json

2
.gitignore vendored

@ -31,3 +31,5 @@ coverage
# vim temp files
.*.swp
# Makefile's cache
cache.mk

@ -8,58 +8,39 @@ requests) and instead do some form of exponential backoff.
We have several parameters:
1. INITIAL_BACKOFF (how long to wait after the first failure before retrying)
2. MULTIPLIER (factor with which to multiply backoff after a failed retry)
3. MAX_BACKOFF (Upper bound on backoff)
4. MIN_CONNECTION_TIMEOUT
3. MAX_BACKOFF (upper bound on backoff)
4. MIN_CONNECT_TIMEOUT (minimum time we're willing to give a connection to
complete)
## Proposed Backoff Algorithm
Exponentially back off the start time of connection attempts up to a limit of
MAX_BACKOFF.
MAX_BACKOFF, with jitter.
```
ConnectWithBackoff()
current_backoff = INITIAL_BACKOFF
current_deadline = now() + INITIAL_BACKOFF
while (TryConnect(Max(current_deadline, MIN_CONNECT_TIMEOUT))
while (TryConnect(Max(current_deadline, now() + MIN_CONNECT_TIMEOUT))
!= SUCCESS)
SleepUntil(current_deadline)
current_backoff = Min(current_backoff * MULTIPLIER, MAX_BACKOFF)
current_deadline = now() + current_backoff
```
## Historical Algorithm in Stubby
Exponentially increase up to a limit of MAX_BACKOFF the intervals between
connection attempts. This is what stubby 2 uses, and is equivalent if
TryConnect() fails instantly.
current_deadline = now() + current_backoff +
UniformRandom(-JITTER * current_backoff, JITTER * current_backoff)
```
LegacyConnectWithBackoff()
current_backoff = INITIAL_BACKOFF
while (TryConnect(MIN_CONNECT_TIMEOUT) != SUCCESS)
SleepFor(current_backoff)
current_backoff = Min(current_backoff * MULTIPLIER, MAX_BACKOFF)
```
The grpc C implementation currently uses this approach with an initial backoff
of 1 second, multiplier of 2, and maximum backoff of 120 seconds. (This will
change)
Stubby, or at least rpc2, uses exactly this algorithm with an initial backoff
of 1 second, multiplier of 1.2, and a maximum backoff of 120 seconds.
With specific parameters of
MIN_CONNECT_TIMEOUT = 20 seconds
INITIAL_BACKOFF = 1 second
MULTIPLIER = 1.6
MAX_BACKOFF = 120 seconds
JITTER = 0.2
## Use Cases to Consider
Implementations with pressing concerns (such as minimizing the number of wakeups
on a mobile phone) may wish to use a different algorithm, and in particular
different jitter logic.
* Client tries to connect to a server which is down for multiple hours, eg for
maintenance
* Client tries to connect to a server which is overloaded
* User is bringing up both a client and a server at the same time
* In particular, we would like to avoid a large unnecessary delay if the
client connects to a server which is about to come up
* Client/server are misconfigured such that connection attempts always fail
* We want to make sure these don’t put too much load on the server by
default.
* Server is overloaded and wants to transiently make clients back off
* Application has out of band reason to believe a server is back
* We should consider an out of band mechanism for the client to hint that
we should short circuit the backoff.
Alternate implementations must ensure that connection backoffs started at the
same time disperse, and must not attempt connections substantially more often
than the above algorithm.

@ -172,17 +172,34 @@ class CallOpRecvMessage {
grpc_byte_buffer* recv_buf_;
};
namespace CallOpGenericRecvMessageHelper {
class DeserializeFunc {
public:
virtual Status Deserialize(grpc_byte_buffer* buf, int max_message_size) = 0;
};
template <class R>
class DeserializeFuncType GRPC_FINAL : public DeserializeFunc {
public:
DeserializeFuncType(R* message) : message_(message) {}
Status Deserialize(grpc_byte_buffer* buf,
int max_message_size) GRPC_OVERRIDE {
return SerializationTraits<R>::Deserialize(buf, message_, max_message_size);
}
private:
R* message_; // Not a managed pointer because management is external to this
};
} // namespace CallOpGenericRecvMessageHelper
class CallOpGenericRecvMessage {
public:
CallOpGenericRecvMessage() : got_message(false) {}
template <class R>
void RecvMessage(R* message) {
deserialize_ = [message](grpc_byte_buffer* buf,
int max_message_size) -> Status {
return SerializationTraits<R>::Deserialize(buf, message,
max_message_size);
};
deserialize_.reset(
new CallOpGenericRecvMessageHelper::DeserializeFuncType<R>(message));
}
bool got_message;
@ -201,7 +218,7 @@ class CallOpGenericRecvMessage {
if (recv_buf_) {
if (*status) {
got_message = true;
*status = deserialize_(recv_buf_, max_message_size).ok();
*status = deserialize_->Deserialize(recv_buf_, max_message_size).ok();
} else {
got_message = false;
grpc_byte_buffer_destroy(recv_buf_);
@ -210,12 +227,11 @@ class CallOpGenericRecvMessage {
got_message = false;
*status = false;
}
deserialize_ = DeserializeFunc();
deserialize_.reset();
}
private:
typedef std::function<Status(grpc_byte_buffer*, int)> DeserializeFunc;
DeserializeFunc deserialize_;
std::unique_ptr<CallOpGenericRecvMessageHelper::DeserializeFunc> deserialize_;
grpc_byte_buffer* recv_buf_;
};

@ -82,6 +82,7 @@
#define GPR_WIN32_ATOMIC 1
#define GPR_MSVC_TLS 1
#endif
#define GPR_WINDOWS_CRASH_HANDLER 1
#elif defined(_WIN32) || defined(WIN32)
#define GPR_ARCH_32 1
#define GPR_WIN32 1
@ -94,6 +95,7 @@
#define GPR_WIN32_ATOMIC 1
#define GPR_MSVC_TLS 1
#endif
#define GPR_WINDOWS_CRASH_HANDLER 1
#elif defined(ANDROID) || defined(__ANDROID__)
#define GPR_ANDROID 1
#define GPR_ARCH_32 1

@ -61,9 +61,8 @@ void PrintProtoRpcDeclarationAsPragma(Printer *printer,
" returns ($server_stream$$response_type$)\n\n");
}
void PrintMethodSignature(Printer *printer,
const MethodDescriptor *method,
const map<string, string>& vars) {
void PrintMethodSignature(Printer *printer, const MethodDescriptor *method,
const map<string, string> &vars) {
// TODO(jcanizales): Print method comments.
printer->Print(vars, "- ($return_type$)$method_name$With");
@ -75,16 +74,17 @@ void PrintMethodSignature(Printer *printer,
// TODO(jcanizales): Put this on a new line and align colons.
if (method->server_streaming()) {
printer->Print(vars, " eventHandler:(void(^)(BOOL done, "
printer->Print(vars,
" eventHandler:(void(^)(BOOL done, "
"$response_class$ *response, NSError *error))eventHandler");
} else {
printer->Print(vars, " handler:(void(^)($response_class$ *response, "
printer->Print(vars,
" handler:(void(^)($response_class$ *response, "
"NSError *error))handler");
}
}
void PrintSimpleSignature(Printer *printer,
const MethodDescriptor *method,
void PrintSimpleSignature(Printer *printer, const MethodDescriptor *method,
map<string, string> vars) {
vars["method_name"] =
grpc_generator::LowercaseFirstLetter(vars["method_name"]);
@ -92,8 +92,7 @@ void PrintSimpleSignature(Printer *printer,
PrintMethodSignature(printer, method, vars);
}
void PrintAdvancedSignature(Printer *printer,
const MethodDescriptor *method,
void PrintAdvancedSignature(Printer *printer, const MethodDescriptor *method,
map<string, string> vars) {
vars["method_name"] = "RPCTo" + vars["method_name"];
vars["return_type"] = "ProtoRPC *";
@ -101,15 +100,16 @@ void PrintAdvancedSignature(Printer *printer,
}
inline map<string, string> GetMethodVars(const MethodDescriptor *method) {
return {{ "method_name", method->name() },
{ "request_type", method->input_type()->name() },
{ "response_type", method->output_type()->name() },
{ "request_class", ClassName(method->input_type()) },
{ "response_class", ClassName(method->output_type()) }};
map<string, string> res;
res["method_name"] = method->name();
res["request_type"] = method->input_type()->name();
res["response_type"] = method->output_type()->name();
res["request_class"] = ClassName(method->input_type());
res["response_class"] = ClassName(method->output_type());
return res;
}
void PrintMethodDeclarations(Printer *printer,
const MethodDescriptor *method) {
void PrintMethodDeclarations(Printer *printer, const MethodDescriptor *method) {
map<string, string> vars = GetMethodVars(method);
PrintProtoRpcDeclarationAsPragma(printer, method, vars);
@ -120,8 +120,7 @@ void PrintMethodDeclarations(Printer *printer,
printer->Print(";\n\n\n");
}
void PrintSimpleImplementation(Printer *printer,
const MethodDescriptor *method,
void PrintSimpleImplementation(Printer *printer, const MethodDescriptor *method,
map<string, string> vars) {
printer->Print("{\n");
printer->Print(vars, " [[self RPCTo$method_name$With");
@ -199,11 +198,14 @@ string GetHeader(const ServiceDescriptor *service) {
}
printer.Print("@end\n\n");
printer.Print("// Basic service implementation, over gRPC, that only does"
printer.Print(
"// Basic service implementation, over gRPC, that only does"
" marshalling and parsing.\n");
printer.Print(vars, "@interface $service_class$ :"
printer.Print(vars,
"@interface $service_class$ :"
" ProtoService<$service_class$>\n");
printer.Print("- (instancetype)initWithHost:(NSString *)host"
printer.Print(
"- (instancetype)initWithHost:(NSString *)host"
" NS_DESIGNATED_INITIALIZER;\n");
printer.Print("@end\n");
}
@ -223,17 +225,19 @@ string GetSource(const ServiceDescriptor *service) {
printer.Print(vars,
"static NSString *const kPackageName = @\"$package$\";\n");
printer.Print(vars,
"static NSString *const kServiceName = @\"$service_name$\";\n\n");
printer.Print(
vars, "static NSString *const kServiceName = @\"$service_name$\";\n\n");
printer.Print(vars, "@implementation $service_class$\n\n");
printer.Print("// Designated initializer\n");
printer.Print("- (instancetype)initWithHost:(NSString *)host {\n");
printer.Print(" return (self = [super initWithHost:host"
printer.Print(
" return (self = [super initWithHost:host"
" packageName:kPackageName serviceName:kServiceName]);\n");
printer.Print("}\n\n");
printer.Print("// Override superclass initializer to disallow different"
printer.Print(
"// Override superclass initializer to disallow different"
" package and service names.\n");
printer.Print("- (instancetype)initWithHost:(NSString *)host\n");
printer.Print(" packageName:(NSString *)packageName\n");

@ -37,6 +37,7 @@
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include "src/core/iomgr/iocp_windows.h"
#include "src/core/iomgr/iomgr_internal.h"
@ -61,22 +62,27 @@ grpc_winsocket *grpc_winsocket_create(SOCKET socket, const char *name) {
operations to abort them. We need to do that this way because of the
various callsites of that function, which happens to be in various
mutex hold states, and that'd be unsafe to call them directly. */
int grpc_winsocket_shutdown(grpc_winsocket *socket) {
int grpc_winsocket_shutdown(grpc_winsocket *winsocket) {
int callbacks_set = 0;
gpr_mu_lock(&socket->state_mu);
if (socket->read_info.cb) {
SOCKET socket;
gpr_mu_lock(&winsocket->state_mu);
socket = winsocket->socket;
if (winsocket->read_info.cb) {
callbacks_set++;
grpc_iomgr_closure_init(&socket->shutdown_closure, socket->read_info.cb,
socket->read_info.opaque);
grpc_iomgr_add_delayed_callback(&socket->shutdown_closure, 0);
grpc_iomgr_closure_init(&winsocket->shutdown_closure,
winsocket->read_info.cb,
winsocket->read_info.opaque);
grpc_iomgr_add_delayed_callback(&winsocket->shutdown_closure, 0);
}
if (socket->write_info.cb) {
if (winsocket->write_info.cb) {
callbacks_set++;
grpc_iomgr_closure_init(&socket->shutdown_closure, socket->write_info.cb,
socket->write_info.opaque);
grpc_iomgr_add_delayed_callback(&socket->shutdown_closure, 0);
grpc_iomgr_closure_init(&winsocket->shutdown_closure,
winsocket->write_info.cb,
winsocket->write_info.opaque);
grpc_iomgr_add_delayed_callback(&winsocket->shutdown_closure, 0);
}
gpr_mu_unlock(&socket->state_mu);
gpr_mu_unlock(&winsocket->state_mu);
closesocket(socket);
return callbacks_set;
}
@ -87,14 +93,12 @@ int grpc_winsocket_shutdown(grpc_winsocket *socket) {
an "idle" socket which is neither trying to read or write, we'd start leaking
both memory and sockets. */
void grpc_winsocket_orphan(grpc_winsocket *winsocket) {
SOCKET socket = winsocket->socket;
grpc_iomgr_unregister_object(&winsocket->iomgr_object);
if (winsocket->read_info.outstanding || winsocket->write_info.outstanding) {
grpc_iocp_socket_orphan(winsocket);
} else {
grpc_winsocket_destroy(winsocket);
}
closesocket(socket);
}
void grpc_winsocket_destroy(grpc_winsocket *winsocket) {

@ -108,9 +108,10 @@ void grpc_tcp_server_destroy(grpc_tcp_server *s,
size_t i;
gpr_mu_lock(&s->mu);
/* First, shutdown all fd's. This will queue abortion calls for all
of the pending accepts. */
of the pending accepts due to the normal operation mechanism. */
for (i = 0; i < s->nports; i++) {
server_port *sp = &s->ports[i];
sp->shutting_down = 1;
grpc_winsocket_shutdown(sp->socket);
}
/* This happens asynchronously. Wait while that happens. */
@ -242,12 +243,24 @@ static void on_accept(void *arg, int from_iocp) {
SOCKET sock = sp->new_socket;
grpc_winsocket_callback_info *info = &sp->socket->read_info;
grpc_endpoint *ep = NULL;
DWORD transfered_bytes;
DWORD flags;
BOOL wsa_success;
/* The general mechanism for shutting down is to queue abortion calls. While
this is necessary in the read/write case, it's useless for the accept
case. Let's do nothing. */
if (!from_iocp) return;
/* The shutdown sequence is done in two parts. This is the second
part here, acknowledging the IOCP notification, and doing nothing
else, especially not queuing a new accept. */
/* The IOCP notified us of a completed operation. Let's grab the results,
and act accordingly. */
transfered_bytes = 0;
wsa_success = WSAGetOverlappedResult(sock, &info->overlapped,
&transfered_bytes, FALSE, &flags);
if (!wsa_success) {
if (sp->shutting_down) {
GPR_ASSERT(from_iocp);
/* During the shutdown case, we ARE expecting an error. So that's swell,
and we can wake up the shutdown thread. */
sp->shutting_down = 0;
sp->socket->read_info.outstanding = 0;
gpr_mu_lock(&sp->server->mu);
@ -256,40 +269,17 @@ static void on_accept(void *arg, int from_iocp) {
}
gpr_mu_unlock(&sp->server->mu);
return;
}
if (from_iocp) {
/* The IOCP notified us of a completed operation. Let's grab the results,
and act accordingly. */
DWORD transfered_bytes = 0;
DWORD flags;
BOOL wsa_success = WSAGetOverlappedResult(sock, &info->overlapped,
&transfered_bytes, FALSE, &flags);
if (!wsa_success) {
} else {
char *utf8_message = gpr_format_message(WSAGetLastError());
gpr_log(GPR_ERROR, "on_accept error: %s", utf8_message);
gpr_free(utf8_message);
closesocket(sock);
}
} else {
if (!sp->shutting_down) {
/* TODO(ctiller): add sockaddr address to label */
ep = grpc_tcp_create(grpc_winsocket_create(sock, "server"));
}
} else {
/* If we're not notified from the IOCP, it means we are asked to shutdown.
This will initiate that shutdown. Calling closesocket will trigger an
IOCP notification, that will call this function a second time, from
the IOCP thread. Of course, this only works if the socket was, in fact,
listening. If that's not the case, we'd wait indefinitely. That's a bit
of a degenerate case, but it can happen if you create a server, but
don't start it. So let's support that by recursing once. */
sp->shutting_down = 1;
sp->new_socket = INVALID_SOCKET;
if (sock != INVALID_SOCKET) {
closesocket(sock);
} else {
on_accept(sp, 1);
}
return;
}
/* The only time we should call our callback, is where we successfully

@ -89,12 +89,9 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
fh_0:
case GRPC_CHTTP2_DATA_FH_0:
p->frame_type = *cur;
if (++cur == end) {
p->state = GRPC_CHTTP2_DATA_FH_1;
return GRPC_CHTTP2_PARSE_OK;
}
switch (p->frame_type) {
case 0:
/* noop */
break;
case 1:
gpr_log(GPR_ERROR, "Compressed GRPC frames not yet supported");
@ -103,6 +100,10 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse(
gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type);
return GRPC_CHTTP2_STREAM_ERROR;
}
if (++cur == end) {
p->state = GRPC_CHTTP2_DATA_FH_1;
return GRPC_CHTTP2_PARSE_OK;
}
/* fallthrough */
case GRPC_CHTTP2_DATA_FH_1:
p->frame_size = ((gpr_uint32)*cur) << 24;

@ -73,12 +73,6 @@ namespace Grpc.Core.Tests
Server server;
Channel channel;
[TestFixtureSetUp]
public void InitClass()
{
GrpcEnvironment.Initialize();
}
[SetUp]
public void Init()
{

@ -43,16 +43,17 @@ namespace Grpc.Core.Tests
[Test]
public void InitializeAndShutdownGrpcEnvironment()
{
GrpcEnvironment.Initialize();
Assert.IsNotNull(GrpcEnvironment.ThreadPool.CompletionQueue);
var env = GrpcEnvironment.GetInstance();
Assert.IsNotNull(env.CompletionQueue);
GrpcEnvironment.Shutdown();
}
[Test]
public void SubsequentInvocations()
{
GrpcEnvironment.Initialize();
GrpcEnvironment.Initialize();
var env1 = GrpcEnvironment.GetInstance();
var env2 = GrpcEnvironment.GetInstance();
Assert.IsTrue(object.ReferenceEquals(env1, env2));
GrpcEnvironment.Shutdown();
GrpcEnvironment.Shutdown();
}
@ -60,15 +61,13 @@ namespace Grpc.Core.Tests
[Test]
public void InitializeAfterShutdown()
{
GrpcEnvironment.Initialize();
var tp1 = GrpcEnvironment.ThreadPool;
var env1 = GrpcEnvironment.GetInstance();
GrpcEnvironment.Shutdown();
GrpcEnvironment.Initialize();
var tp2 = GrpcEnvironment.ThreadPool;
var env2 = GrpcEnvironment.GetInstance();
GrpcEnvironment.Shutdown();
Assert.IsFalse(object.ReferenceEquals(tp1, tp2));
Assert.IsFalse(object.ReferenceEquals(env1, env2));
}
}
}

@ -53,18 +53,6 @@ namespace Grpc.Core.Tests
[DllImport("grpc_csharp_ext.dll")]
static extern IntPtr grpcsharp_test_nop(IntPtr ptr);
[TestFixtureSetUp]
public void Init()
{
GrpcEnvironment.Initialize();
}
[TestFixtureTearDown]
public void Cleanup()
{
GrpcEnvironment.Shutdown();
}
/// <summary>
/// (~1.26us .NET Windows)
/// </summary>

@ -44,13 +44,10 @@ namespace Grpc.Core.Tests
[Test]
public void StartAndShutdownServer()
{
GrpcEnvironment.Initialize();
Server server = new Server();
server.AddListeningPort("localhost", Server.PickUnusedPort);
server.Start();
server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
}
}

@ -58,7 +58,7 @@ namespace Grpc.Core
where TResponse : class
{
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer);
asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name);
asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name);
var asyncResult = asyncCall.UnaryCallAsync(req, call.Headers);
RegisterCancellationCallback(asyncCall, token);
return await asyncResult;
@ -69,7 +69,7 @@ namespace Grpc.Core
where TResponse : class
{
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer);
asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name);
asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name);
asyncCall.StartServerStreamingCall(req, call.Headers);
RegisterCancellationCallback(asyncCall, token);
var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall);
@ -81,7 +81,7 @@ namespace Grpc.Core
where TResponse : class
{
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer);
asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name);
asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name);
var resultTask = asyncCall.ClientStreamingCallAsync(call.Headers);
RegisterCancellationCallback(asyncCall, token);
var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall);
@ -93,7 +93,7 @@ namespace Grpc.Core
where TResponse : class
{
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer);
asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name);
asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name);
asyncCall.StartDuplexStreamingCall(call.Headers);
RegisterCancellationCallback(asyncCall, token);
var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall);
@ -108,13 +108,5 @@ namespace Grpc.Core
token.Register(() => asyncCall.Cancel());
}
}
/// <summary>
/// Gets shared completion queue used for async calls.
/// </summary>
private static CompletionQueueSafeHandle GetCompletionQueue()
{
return GrpcEnvironment.ThreadPool.CompletionQueue;
}
}
}

@ -42,8 +42,10 @@ namespace Grpc.Core
/// </summary>
public class Channel : IDisposable
{
readonly GrpcEnvironment environment;
readonly ChannelSafeHandle handle;
readonly string target;
bool disposed;
/// <summary>
/// Creates a channel that connects to a specific host.
@ -54,6 +56,7 @@ namespace Grpc.Core
/// <param name="options">Channel options.</param>
public Channel(string host, Credentials credentials = null, IEnumerable<ChannelOption> options = null)
{
this.environment = GrpcEnvironment.GetInstance();
using (ChannelArgsSafeHandle nativeChannelArgs = ChannelOptions.CreateChannelArgs(options))
{
if (credentials != null)
@ -105,10 +108,35 @@ namespace Grpc.Core
}
}
internal CompletionQueueSafeHandle CompletionQueue
{
get
{
return this.environment.CompletionQueue;
}
}
internal CompletionRegistry CompletionRegistry
{
get
{
return this.environment.CompletionRegistry;
}
}
internal GrpcEnvironment Environment
{
get
{
return this.environment;
}
}
protected virtual void Dispose(bool disposing)
{
if (handle != null && !handle.IsInvalid)
if (disposing && handle != null && !disposed)
{
disposed = true;
handle.Dispose();
}
}

@ -33,7 +33,9 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
namespace Grpc.Core
{
@ -51,20 +53,18 @@ namespace Grpc.Core
static extern void grpcsharp_shutdown();
static object staticLock = new object();
static volatile GrpcEnvironment instance;
static GrpcEnvironment instance;
readonly GrpcThreadPool threadPool;
readonly CompletionRegistry completionRegistry;
readonly DebugStats debugStats = new DebugStats();
bool isClosed;
/// <summary>
/// Makes sure GRPC environment is initialized. Subsequent invocations don't have any
/// effect unless you call Shutdown first.
/// Although normal use cases assume you will call this just once in your application's
/// lifetime (and call Shutdown once you're done), for the sake of easier testing it's
/// allowed to initialize the environment again after it has been successfully shutdown.
/// Returns an instance of initialized gRPC environment.
/// Subsequent invocations return the same instance unless Shutdown has been called first.
/// </summary>
public static void Initialize()
internal static GrpcEnvironment GetInstance()
{
lock (staticLock)
{
@ -72,12 +72,13 @@ namespace Grpc.Core
{
instance = new GrpcEnvironment();
}
return instance;
}
}
/// <summary>
/// Shuts down the GRPC environment if it was initialized before.
/// Repeated invocations have no effect.
/// Shuts down the gRPC environment if it was initialized before.
/// Blocks until the environment has been fully shutdown.
/// </summary>
public static void Shutdown()
{
@ -87,50 +88,55 @@ namespace Grpc.Core
{
instance.Close();
instance = null;
CheckDebugStats();
}
}
}
internal static GrpcThreadPool ThreadPool
/// <summary>
/// Creates gRPC environment.
/// </summary>
private GrpcEnvironment()
{
get
GrpcLog.RedirectNativeLogs(Console.Error);
grpcsharp_init();
completionRegistry = new CompletionRegistry(this);
threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE);
threadPool.Start();
// TODO: use proper logging here
Console.WriteLine("GRPC initialized.");
}
/// <summary>
/// Gets the completion registry used by this gRPC environment.
/// </summary>
internal CompletionRegistry CompletionRegistry
{
var inst = instance;
if (inst == null)
get
{
throw new InvalidOperationException("GRPC environment not initialized");
}
return inst.threadPool;
return this.completionRegistry;
}
}
internal static CompletionRegistry CompletionRegistry
/// <summary>
/// Gets the completion queue used by this gRPC environment.
/// </summary>
internal CompletionQueueSafeHandle CompletionQueue
{
get
{
var inst = instance;
if (inst == null)
{
throw new InvalidOperationException("GRPC environment not initialized");
}
return inst.completionRegistry;
return this.threadPool.CompletionQueue;
}
}
/// <summary>
/// Creates gRPC environment.
/// Gets the completion queue used by this gRPC environment.
/// </summary>
private GrpcEnvironment()
internal DebugStats DebugStats
{
GrpcLog.RedirectNativeLogs(Console.Error);
grpcsharp_init();
completionRegistry = new CompletionRegistry();
threadPool = new GrpcThreadPool(THREAD_POOL_SIZE);
threadPool.Start();
// TODO: use proper logging here
Console.WriteLine("GRPC initialized.");
get
{
return this.debugStats;
}
}
/// <summary>
@ -146,32 +152,28 @@ namespace Grpc.Core
grpcsharp_shutdown();
isClosed = true;
debugStats.CheckOK();
// TODO: use proper logging here
Console.WriteLine("GRPC shutdown.");
}
private static void CheckDebugStats()
/// <summary>
/// Shuts down this environment asynchronously.
/// </summary>
private Task CloseAsync()
{
var remainingClientCalls = DebugStats.ActiveClientCalls.Count;
if (remainingClientCalls != 0)
return Task.Run(() =>
{
DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls));
}
var remainingServerCalls = DebugStats.ActiveServerCalls.Count;
if (remainingServerCalls != 0)
try
{
DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls));
Close();
}
var pendingBatchCompletions = DebugStats.PendingBatchCompletions.Count;
if (pendingBatchCompletions != 0)
catch (Exception e)
{
DebugWarning(string.Format("Detected {0} pending batch completions.", pendingBatchCompletions));
}
Console.WriteLine("Error occured while shutting down GrpcEnvironment: " + e);
}
private static void DebugWarning(string message)
{
throw new Exception("Shutdown check: " + message);
});
}
}
}

@ -47,6 +47,8 @@ namespace Grpc.Core.Internal
/// </summary>
internal class AsyncCall<TRequest, TResponse> : AsyncCallBase<TRequest, TResponse>
{
Channel channel;
// Completion of a pending unary response if not null.
TaskCompletionSource<TResponse> unaryResponseTcs;
@ -61,8 +63,9 @@ namespace Grpc.Core.Internal
public void Initialize(Channel channel, CompletionQueueSafeHandle cq, string methodName)
{
var call = CallSafeHandle.Create(channel.Handle, cq, methodName, channel.Target, Timespec.InfFuture);
DebugStats.ActiveClientCalls.Increment();
this.channel = channel;
var call = CallSafeHandle.Create(channel.Handle, channel.CompletionRegistry, cq, methodName, channel.Target, Timespec.InfFuture);
channel.Environment.DebugStats.ActiveClientCalls.Increment();
InitializeInternal(call);
}
@ -277,7 +280,7 @@ namespace Grpc.Core.Internal
protected override void OnReleaseResources()
{
DebugStats.ActiveClientCalls.Decrement();
channel.Environment.DebugStats.ActiveClientCalls.Decrement();
}
/// <summary>

@ -48,14 +48,17 @@ namespace Grpc.Core.Internal
internal class AsyncCallServer<TRequest, TResponse> : AsyncCallBase<TResponse, TRequest>
{
readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>();
readonly GrpcEnvironment environment;
public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer) : base(serializer, deserializer)
public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment) : base(serializer, deserializer)
{
this.environment = Preconditions.CheckNotNull(environment);
}
public void Initialize(CallSafeHandle call)
{
DebugStats.ActiveServerCalls.Increment();
call.SetCompletionRegistry(environment.CompletionRegistry);
environment.DebugStats.ActiveServerCalls.Increment();
InitializeInternal(call);
}
@ -114,7 +117,7 @@ namespace Grpc.Core.Internal
protected override void OnReleaseResources()
{
DebugStats.ActiveServerCalls.Decrement();
environment.DebugStats.ActiveServerCalls.Decrement();
}
/// <summary>

@ -43,6 +43,7 @@ namespace Grpc.Core.Internal
internal class CallSafeHandle : SafeHandleZeroIsInvalid
{
const uint GRPC_WRITE_BUFFER_HINT = 1;
CompletionRegistry completionRegistry;
[DllImport("grpc_csharp_ext.dll")]
static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline);
@ -97,15 +98,22 @@ namespace Grpc.Core.Internal
{
}
public static CallSafeHandle Create(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline)
public static CallSafeHandle Create(ChannelSafeHandle channel, CompletionRegistry registry, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline)
{
return grpcsharp_channel_create_call(channel, cq, method, host, deadline);
var result = grpcsharp_channel_create_call(channel, cq, method, host, deadline);
result.SetCompletionRegistry(registry);
return result;
}
public void SetCompletionRegistry(CompletionRegistry completionRegistry)
{
this.completionRegistry = completionRegistry;
}
public void StartUnary(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray)
.CheckOk();
}
@ -119,56 +127,56 @@ namespace Grpc.Core.Internal
public void StartClientStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk();
}
public void StartServerStreaming(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray).CheckOk();
}
public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk();
}
public void StartSendMessage(byte[] payload, BatchCompletionDelegate callback)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length)).CheckOk();
}
public void StartSendCloseFromClient(BatchCompletionDelegate callback)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_send_close_from_client(this, ctx).CheckOk();
}
public void StartSendStatusFromServer(Status status, BatchCompletionDelegate callback)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail).CheckOk();
}
public void StartReceiveMessage(BatchCompletionDelegate callback)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_recv_message(this, ctx).CheckOk();
}
public void StartServerSide(BatchCompletionDelegate callback)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
completionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_call_start_serverside(this, ctx).CheckOk();
}

@ -45,11 +45,17 @@ namespace Grpc.Core.Internal
internal class CompletionRegistry
{
readonly GrpcEnvironment environment;
readonly ConcurrentDictionary<IntPtr, OpCompletionDelegate> dict = new ConcurrentDictionary<IntPtr, OpCompletionDelegate>();
public CompletionRegistry(GrpcEnvironment environment)
{
this.environment = environment;
}
public void Register(IntPtr key, OpCompletionDelegate callback)
{
DebugStats.PendingBatchCompletions.Increment();
environment.DebugStats.PendingBatchCompletions.Increment();
Preconditions.CheckState(dict.TryAdd(key, callback));
}
@ -63,7 +69,7 @@ namespace Grpc.Core.Internal
{
OpCompletionDelegate value;
Preconditions.CheckState(dict.TryRemove(key, out value));
DebugStats.PendingBatchCompletions.Decrement();
environment.DebugStats.PendingBatchCompletions.Decrement();
return value;
}

@ -36,12 +36,39 @@ using System.Threading;
namespace Grpc.Core.Internal
{
internal static class DebugStats
internal class DebugStats
{
public static readonly AtomicCounter ActiveClientCalls = new AtomicCounter();
public readonly AtomicCounter ActiveClientCalls = new AtomicCounter();
public static readonly AtomicCounter ActiveServerCalls = new AtomicCounter();
public readonly AtomicCounter ActiveServerCalls = new AtomicCounter();
public static readonly AtomicCounter PendingBatchCompletions = new AtomicCounter();
public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter();
/// <summary>
/// Checks the debug stats and take action for any inconsistency found.
/// </summary>
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;
if (pendingBatchCompletions != 0)
{
DebugWarning(string.Format("Detected {0} pending batch completions.", pendingBatchCompletions));
}
}
private void DebugWarning(string message)
{
throw new Exception("Shutdown check: " + message);
}
}
}

@ -45,14 +45,16 @@ namespace Grpc.Core.Internal
/// </summary>
internal class GrpcThreadPool
{
readonly GrpcEnvironment environment;
readonly object myLock = new object();
readonly List<Thread> threads = new List<Thread>();
readonly int poolSize;
CompletionQueueSafeHandle cq;
public GrpcThreadPool(int poolSize)
public GrpcThreadPool(GrpcEnvironment environment, int poolSize)
{
this.environment = environment;
this.poolSize = poolSize;
}
@ -80,7 +82,7 @@ namespace Grpc.Core.Internal
{
cq.Shutdown();
Console.WriteLine("Waiting for GPRC threads to finish.");
Console.WriteLine("Waiting for GRPC threads to finish.");
foreach (var thread in threads)
{
thread.Join();
@ -122,7 +124,7 @@ namespace Grpc.Core.Internal
IntPtr tag = ev.tag;
try
{
var callback = GrpcEnvironment.CompletionRegistry.Extract(tag);
var callback = environment.CompletionRegistry.Extract(tag);
callback(success);
}
catch (Exception e)

@ -42,7 +42,7 @@ namespace Grpc.Core.Internal
{
internal interface IServerCallHandler
{
Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq);
Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment);
}
internal class UnaryServerCallHandler<TRequest, TResponse> : IServerCallHandler
@ -58,11 +58,12 @@ namespace Grpc.Core.Internal
this.handler = handler;
}
public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment)
{
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer);
method.RequestMarshaller.Deserializer,
environment);
asyncCall.Initialize(call);
var finishedTask = asyncCall.ServerSideCallAsync();
@ -110,11 +111,12 @@ namespace Grpc.Core.Internal
this.handler = handler;
}
public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment)
{
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer);
method.RequestMarshaller.Deserializer,
environment);
asyncCall.Initialize(call);
var finishedTask = asyncCall.ServerSideCallAsync();
@ -163,11 +165,12 @@ namespace Grpc.Core.Internal
this.handler = handler;
}
public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment)
{
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer);
method.RequestMarshaller.Deserializer,
environment);
asyncCall.Initialize(call);
var finishedTask = asyncCall.ServerSideCallAsync();
@ -219,11 +222,12 @@ namespace Grpc.Core.Internal
this.handler = handler;
}
public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment)
{
var asyncCall = new AsyncCallServer<TRequest, TResponse>(
method.ResponseMarshaller.Serializer,
method.RequestMarshaller.Deserializer);
method.RequestMarshaller.Deserializer,
environment);
asyncCall.Initialize(call);
var finishedTask = asyncCall.ServerSideCallAsync();
@ -255,11 +259,11 @@ namespace Grpc.Core.Internal
internal class NoSuchMethodCallHandler : IServerCallHandler
{
public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq)
public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment)
{
// We don't care about the payload type here.
var asyncCall = new AsyncCallServer<byte[], byte[]>(
(payload) => payload, (payload) => payload);
(payload) => payload, (payload) => payload, environment);
asyncCall.Initialize(call);
var finishedTask = asyncCall.ServerSideCallAsync();

@ -92,18 +92,18 @@ namespace Grpc.Core.Internal
grpcsharp_server_start(this);
}
public void ShutdownAndNotify(CompletionQueueSafeHandle cq, BatchCompletionDelegate callback)
public void ShutdownAndNotify(BatchCompletionDelegate callback, GrpcEnvironment environment)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_server_shutdown_and_notify_callback(this, cq, ctx);
environment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_server_shutdown_and_notify_callback(this, environment.CompletionQueue, ctx);
}
public void RequestCall(CompletionQueueSafeHandle cq, BatchCompletionDelegate callback)
public void RequestCall(BatchCompletionDelegate callback, GrpcEnvironment environment)
{
var ctx = BatchContextSafeHandle.Create();
GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_server_request_call(this, cq, ctx).CheckOk();
environment.CompletionRegistry.RegisterBatchCompletion(ctx, callback);
grpcsharp_server_request_call(this, environment.CompletionQueue, ctx).CheckOk();
}
protected override bool ReleaseHandle()

@ -52,6 +52,7 @@ namespace Grpc.Core
/// </summary>
public const int PickUnusedPort = 0;
readonly GrpcEnvironment environment;
readonly ServerSafeHandle handle;
readonly object myLock = new object();
@ -67,9 +68,10 @@ namespace Grpc.Core
/// <param name="options">Channel options.</param>
public Server(IEnumerable<ChannelOption> options = null)
{
this.environment = GrpcEnvironment.GetInstance();
using (var channelArgs = ChannelOptions.CreateChannelArgs(options))
{
this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), channelArgs);
this.handle = ServerSafeHandle.NewServer(environment.CompletionQueue, channelArgs);
}
}
@ -144,7 +146,7 @@ namespace Grpc.Core
shutdownRequested = true;
}
handle.ShutdownAndNotify(GetCompletionQueue(), HandleServerShutdown);
handle.ShutdownAndNotify(HandleServerShutdown, environment);
await shutdownTcs.Task;
handle.Dispose();
}
@ -173,7 +175,7 @@ namespace Grpc.Core
shutdownRequested = true;
}
handle.ShutdownAndNotify(GetCompletionQueue(), HandleServerShutdown);
handle.ShutdownAndNotify(HandleServerShutdown, environment);
handle.CancelAllCalls();
await shutdownTcs.Task;
handle.Dispose();
@ -208,7 +210,7 @@ namespace Grpc.Core
{
if (!shutdownRequested)
{
handle.RequestCall(GetCompletionQueue(), HandleNewServerRpc);
handle.RequestCall(HandleNewServerRpc, environment);
}
}
}
@ -225,7 +227,7 @@ namespace Grpc.Core
{
callHandler = new NoSuchMethodCallHandler();
}
await callHandler.HandleCall(method, call, GetCompletionQueue());
await callHandler.HandleCall(method, call, environment);
}
catch (Exception e)
{
@ -259,10 +261,5 @@ namespace Grpc.Core
{
shutdownTcs.SetResult(null);
}
private static CompletionQueueSafeHandle GetCompletionQueue()
{
return GrpcEnvironment.ThreadPool.CompletionQueue;
}
}
}

@ -39,8 +39,6 @@ namespace math
{
public static void Main(string[] args)
{
GrpcEnvironment.Initialize();
using (Channel channel = new Channel("127.0.0.1", 23456))
{
Math.IMathClient stub = new Math.MathClient(channel);

@ -42,8 +42,6 @@ namespace math
{
string host = "0.0.0.0";
GrpcEnvironment.Initialize();
Server server = new Server();
server.AddServiceDefinition(Math.BindService(new MathServiceImpl()));
int port = server.AddListeningPort(host, 23456);

@ -54,8 +54,6 @@ namespace math.Tests
[TestFixtureSetUp]
public void Init()
{
GrpcEnvironment.Initialize();
server = new Server();
server.AddServiceDefinition(Math.BindService(new MathServiceImpl()));
int port = server.AddListeningPort(host, Server.PickUnusedPort);
@ -75,7 +73,6 @@ namespace math.Tests
public void Cleanup()
{
channel.Dispose();
server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
}

@ -102,8 +102,6 @@ namespace Grpc.IntegrationTesting
private void Run()
{
GrpcEnvironment.Initialize();
Credentials credentials = null;
if (options.useTls)
{
@ -135,7 +133,6 @@ namespace Grpc.IntegrationTesting
TestService.ITestServiceClient client = new TestService.TestServiceClient(channel, stubConfig);
RunTestCase(options.testCase, client);
}
GrpcEnvironment.Shutdown();
}

@ -55,8 +55,6 @@ namespace Grpc.IntegrationTesting
[TestFixtureSetUp]
public void Init()
{
GrpcEnvironment.Initialize();
server = new Server();
server.AddServiceDefinition(TestService.BindService(new TestServiceImpl()));
int port = server.AddListeningPort(host, Server.PickUnusedPort, TestCredentials.CreateTestServerCredentials());
@ -74,7 +72,6 @@ namespace Grpc.IntegrationTesting
public void Cleanup()
{
channel.Dispose();
server.ShutdownAsync().Wait();
GrpcEnvironment.Shutdown();
}

@ -88,8 +88,6 @@ namespace Grpc.IntegrationTesting
private void Run()
{
GrpcEnvironment.Initialize();
var server = new Server();
server.AddServiceDefinition(TestService.BindService(new TestServiceImpl()));

@ -55,7 +55,7 @@ var EventEmitter = require('events').EventEmitter;
*/
function handleError(call, error) {
var status = {
code: grpc.status.INTERNAL,
code: grpc.status.UNKNOWN,
details: 'Unknown Error',
metadata: {}
};
@ -142,12 +142,12 @@ function setUpWritable(stream, serialize) {
stream.on('finish', sendStatus);
/**
* Set the pending status to a given error status. If the error does not have
* code or details properties, the code will be set to grpc.status.INTERNAL
* code or details properties, the code will be set to grpc.status.UNKNOWN
* and the details will be set to 'Unknown Error'.
* @param {Error} err The error object
*/
function setStatus(err) {
var code = grpc.status.INTERNAL;
var code = grpc.status.UNKNOWN;
var details = 'Unknown Error';
var metadata = {};
if (err.hasOwnProperty('message')) {

@ -27,12 +27,12 @@
# 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.
set -e
default_extension_dir=$(php-config --extension-dir)
if command -v brew >/dev/null && [ -d $(brew --prefix)/opt/grpc-php ]; then
# homebrew and the grpc-php formula are installed
extension_dir="-d extension_dir="$(brew --prefix)/opt/grpc-php
if command -v brew > /dev/null && \
brew ls --versions | grep php5[56]-grpc > /dev/null; then
# the grpc php extension was installed by homebrew
:
elif [ ! -e $default_extension_dir/grpc.so ]; then
# the grpc extension is not found in the default PHP extension dir
# try the source modules directory
@ -45,5 +45,5 @@ elif [ ! -e $default_extension_dir/grpc.so ]; then
for f in $default_extension_dir/*.so; do
ln -s $f $module_dir/$(basename $f) &> /dev/null || true
done
extension_dir="-d extension_dir="$module_dir
extension_dir="-d extension_dir=${module_dir} -d extension=grpc.so"
fi

@ -31,5 +31,5 @@
set -e
cd $(dirname $0)
source ./determine_extension_dir.sh
php $extension_dir -d extension=grpc.so \
php $extension_dir \
../tests/interop/interop_client.php $@ 1>&2

@ -31,8 +31,8 @@
set -e
cd $(dirname $0)
source ./determine_extension_dir.sh
export GRPC_TEST_HOST=localhost:7071
php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \
export GRPC_TEST_HOST=localhost:50051
php $extension_dir $(which phpunit) -v --debug --strict \
../tests/generated_code/GeneratedCodeTest.php
php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \
php $extension_dir $(which phpunit) -v --debug --strict \
../tests/generated_code/GeneratedCodeWithCallbackTest.php

@ -33,5 +33,5 @@
set -e
cd $(dirname $0)
source ./determine_extension_dir.sh
php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \
php $extension_dir $(which phpunit) -v --debug --strict \
../tests/unit_tests

@ -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,88 @@
# 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.
"""A test of invocation-side code unconnected to an RPC server."""
import unittest
from grpc._adapter import _intermediary_low
from grpc._links import invocation
from grpc.framework.common import test_constants
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import test_cases
from grpc.framework.interfaces.links import test_utilities
_NULL_BEHAVIOR = lambda unused_argument: None
class LonelyInvocationLinkTest(unittest.TestCase):
def testUpAndDown(self):
channel = _intermediary_low.Channel('nonexistent:54321', None)
invocation_link = invocation.invocation_link(channel, 'nonexistent', {}, {})
invocation_link.start()
invocation_link.stop()
def _test_lonely_invocation_with_termination(self, termination):
test_operation_id = object()
test_group = 'test package.Test Service'
test_method = 'test method'
invocation_link_mate = test_utilities.RecordingLink()
channel = _intermediary_low.Channel('nonexistent:54321', None)
invocation_link = invocation.invocation_link(
channel, 'nonexistent', {(test_group, test_method): _NULL_BEHAVIOR},
{(test_group, test_method): _NULL_BEHAVIOR})
invocation_link.join_link(invocation_link_mate)
invocation_link.start()
ticket = links.Ticket(
test_operation_id, 0, test_group, test_method,
links.Ticket.Subscription.FULL, test_constants.SHORT_TIMEOUT, 1, None,
None, None, None, None, termination)
invocation_link.accept_ticket(ticket)
invocation_link_mate.block_until_tickets_satisfy(test_cases.terminated)
invocation_link.stop()
self.assertIsNot(
invocation_link_mate.tickets()[-1].termination,
links.Ticket.Termination.COMPLETION)
def testLonelyInvocationLinkWithCommencementTicket(self):
self._test_lonely_invocation_with_termination(None)
def testLonelyInvocationLinkWithEntireTicket(self):
self._test_lonely_invocation_with_termination(
links.Ticket.Termination.COMPLETION)
if __name__ == '__main__':
unittest.main()

@ -0,0 +1,261 @@
# 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.
"""Test scenarios using protocol buffers."""
import abc
import threading
from grpc._junkdrawer import math_pb2
class ProtoScenario(object):
"""An RPC test scenario using protocol buffers."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def group_and_method(self):
"""Access the test group and method.
Returns:
The test group and method as a pair.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_request(self, request):
"""Serialize a request protocol buffer.
Args:
request: A request protocol buffer.
Returns:
The bytestring serialization of the given request protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_request(self, request_bytestring):
"""Deserialize a request protocol buffer.
Args:
request_bytestring: The bytestring serialization of a request protocol
buffer.
Returns:
The request protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def serialize_response(self, response):
"""Serialize a response protocol buffer.
Args:
response: A response protocol buffer.
Returns:
The bytestring serialization of the given response protocol buffer.
"""
raise NotImplementedError()
@abc.abstractmethod
def deserialize_response(self, response_bytestring):
"""Deserialize a response protocol buffer.
Args:
response_bytestring: The bytestring serialization of a response protocol
buffer.
Returns:
The response protocol buffer deserialized from the given byte string.
"""
raise NotImplementedError()
@abc.abstractmethod
def requests(self):
"""Access the sequence of requests for this scenario.
Returns:
A sequence of request protocol buffers.
"""
raise NotImplementedError()
@abc.abstractmethod
def response_for_request(self, request):
"""Access the response for a particular request.
Args:
request: A request protocol buffer.
Returns:
The response protocol buffer appropriate for the given request.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_requests(self, experimental_requests):
"""Verify the requests transmitted through the system under test.
Args:
experimental_requests: The request protocol buffers transmitted through
the system under test.
Returns:
True if the requests satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
@abc.abstractmethod
def verify_responses(self, experimental_responses):
"""Verify the responses transmitted through the system under test.
Args:
experimental_responses: The response protocol buffers transmitted through
the system under test.
Returns:
True if the responses satisfy this test scenario; False otherwise.
"""
raise NotImplementedError()
class EmptyScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
def group_and_method(self):
return 'math.Math', 'DivMany'
def serialize_request(self, request):
raise ValueError('This should not be necessary to call!')
def deserialize_request(self, request_bytestring):
raise ValueError('This should not be necessary to call!')
def serialize_response(self, response):
raise ValueError('This should not be necessary to call!')
def deserialize_response(self, response_bytestring):
raise ValueError('This should not be necessary to call!')
def requests(self):
return ()
def response_for_request(self, request):
raise ValueError('This should not be necessary to call!')
def verify_requests(self, experimental_requests):
return not experimental_requests
def verify_responses(self, experimental_responses):
return not experimental_responses
class BidirectionallyUnaryScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_DIVIDEND = 59
_DIVISOR = 7
_QUOTIENT = 8
_REMAINDER = 3
_REQUEST = math_pb2.DivArgs(dividend=_DIVIDEND, divisor=_DIVISOR)
_RESPONSE = math_pb2.DivReply(quotient=_QUOTIENT, remainder=_REMAINDER)
def group_and_method(self):
return 'math.Math', 'Div'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return [self._REQUEST]
def response_for_request(self, request):
return self._RESPONSE
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == (self._REQUEST,)
def verify_responses(self, experimental_responses):
return tuple(experimental_responses) == (self._RESPONSE,)
class BidirectionallyStreamingScenario(ProtoScenario):
"""A scenario that transmits no protocol buffers in either direction."""
_STREAM_LENGTH = 200
_REQUESTS = tuple(
math_pb2.DivArgs(dividend=59 + index, divisor=7 + index)
for index in range(_STREAM_LENGTH))
def __init__(self):
self._lock = threading.Lock()
self._responses = []
def group_and_method(self):
return 'math.Math', 'DivMany'
def serialize_request(self, request):
return request.SerializeToString()
def deserialize_request(self, request_bytestring):
return math_pb2.DivArgs.FromString(request_bytestring)
def serialize_response(self, response):
return response.SerializeToString()
def deserialize_response(self, response_bytestring):
return math_pb2.DivReply.FromString(response_bytestring)
def requests(self):
return self._REQUESTS
def response_for_request(self, request):
quotient, remainder = divmod(request.dividend, request.divisor)
response = math_pb2.DivReply(quotient=quotient, remainder=remainder)
with self._lock:
self._responses.append(response)
return response
def verify_requests(self, experimental_requests):
return tuple(experimental_requests) == self._REQUESTS
def verify_responses(self, experimental_responses):
with self._lock:
return tuple(experimental_responses) == tuple(self._responses)

@ -0,0 +1,226 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Tests transmission of tickets across gRPC-on-the-wire."""
import unittest
from grpc._adapter import _intermediary_low
from grpc._links import _proto_scenarios
from grpc._links import invocation
from grpc._links import service
from grpc.framework.common import test_constants
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import test_cases
from grpc.framework.interfaces.links import test_utilities
_IDENTITY = lambda x: x
class TransmissionTest(test_cases.TransmissionTest, unittest.TestCase):
def create_transmitting_links(self):
service_link = service.service_link(
{self.group_and_method(): self.deserialize_request},
{self.group_and_method(): self.serialize_response})
port = service_link.add_port(0, None)
service_link.start()
channel = _intermediary_low.Channel('localhost:%d' % port, None)
invocation_link = invocation.invocation_link(
channel, 'localhost',
{self.group_and_method(): self.serialize_request},
{self.group_and_method(): self.deserialize_response})
invocation_link.start()
return invocation_link, service_link
def destroy_transmitting_links(self, invocation_side_link, service_side_link):
invocation_side_link.stop()
service_side_link.stop_gracefully()
def create_invocation_initial_metadata(self):
return (
('first invocation initial metadata key', 'just a string value'),
('second invocation initial metadata key', '0123456789'),
('third invocation initial metadata key-bin', '\x00\x57' * 100),
)
def create_invocation_terminal_metadata(self):
return None
def create_service_initial_metadata(self):
return (
('first service initial metadata key', 'just another string value'),
('second service initial metadata key', '9876543210'),
('third service initial metadata key-bin', '\x00\x59\x02' * 100),
)
def create_service_terminal_metadata(self):
return (
('first service terminal metadata key', 'yet another string value'),
('second service terminal metadata key', 'abcdefghij'),
('third service terminal metadata key-bin', '\x00\x37' * 100),
)
def create_invocation_completion(self):
return None, None
def create_service_completion(self):
return _intermediary_low.Code.OK, 'An exuberant test "details" message!'
def assertMetadataEqual(self, original_metadata, transmitted_metadata):
self.assertSequenceEqual(original_metadata, transmitted_metadata)
class RoundTripTest(unittest.TestCase):
def testZeroMessageRoundTrip(self):
test_operation_id = object()
test_group = 'test package.Test Group'
test_method = 'test method'
identity_transformation = {(test_group, test_method): _IDENTITY}
test_code = _intermediary_low.Code.OK
test_message = 'a test message'
service_link = service.service_link(
identity_transformation, identity_transformation)
service_mate = test_utilities.RecordingLink()
service_link.join_link(service_mate)
port = service_link.add_port(0, None)
service_link.start()
channel = _intermediary_low.Channel('localhost:%d' % port, None)
invocation_link = invocation.invocation_link(
channel, 'localhost', identity_transformation, identity_transformation)
invocation_mate = test_utilities.RecordingLink()
invocation_link.join_link(invocation_mate)
invocation_link.start()
invocation_ticket = links.Ticket(
test_operation_id, 0, test_group, test_method,
links.Ticket.Subscription.FULL, test_constants.LONG_TIMEOUT, None, None,
None, None, None, None, links.Ticket.Termination.COMPLETION)
invocation_link.accept_ticket(invocation_ticket)
service_mate.block_until_tickets_satisfy(test_cases.terminated)
service_ticket = links.Ticket(
service_mate.tickets()[-1].operation_id, 0, None, None, None, None,
None, None, None, None, test_code, test_message,
links.Ticket.Termination.COMPLETION)
service_link.accept_ticket(service_ticket)
invocation_mate.block_until_tickets_satisfy(test_cases.terminated)
invocation_link.stop()
service_link.stop_gracefully()
self.assertIs(
service_mate.tickets()[-1].termination,
links.Ticket.Termination.COMPLETION)
self.assertIs(
invocation_mate.tickets()[-1].termination,
links.Ticket.Termination.COMPLETION)
def _perform_scenario_test(self, scenario):
test_operation_id = object()
test_group, test_method = scenario.group_and_method()
test_code = _intermediary_low.Code.OK
test_message = 'a scenario test message'
service_link = service.service_link(
{(test_group, test_method): scenario.deserialize_request},
{(test_group, test_method): scenario.serialize_response})
service_mate = test_utilities.RecordingLink()
service_link.join_link(service_mate)
port = service_link.add_port(0, None)
service_link.start()
channel = _intermediary_low.Channel('localhost:%d' % port, None)
invocation_link = invocation.invocation_link(
channel, 'localhost',
{(test_group, test_method): scenario.serialize_request},
{(test_group, test_method): scenario.deserialize_response})
invocation_mate = test_utilities.RecordingLink()
invocation_link.join_link(invocation_mate)
invocation_link.start()
invocation_ticket = links.Ticket(
test_operation_id, 0, test_group, test_method,
links.Ticket.Subscription.FULL, test_constants.LONG_TIMEOUT, None, None,
None, None, None, None, None)
invocation_link.accept_ticket(invocation_ticket)
requests = scenario.requests()
for request_index, request in enumerate(requests):
request_ticket = links.Ticket(
test_operation_id, 1 + request_index, None, None, None, None, 1, None,
request, None, None, None, None)
invocation_link.accept_ticket(request_ticket)
service_mate.block_until_tickets_satisfy(
test_cases.at_least_n_payloads_received_predicate(1 + request_index))
response_ticket = links.Ticket(
service_mate.tickets()[0].operation_id, request_index, None, None,
None, None, 1, None, scenario.response_for_request(request), None,
None, None, None)
service_link.accept_ticket(response_ticket)
invocation_mate.block_until_tickets_satisfy(
test_cases.at_least_n_payloads_received_predicate(1 + request_index))
request_count = len(requests)
invocation_completion_ticket = links.Ticket(
test_operation_id, request_count + 1, None, None, None, None, None,
None, None, None, None, None, links.Ticket.Termination.COMPLETION)
invocation_link.accept_ticket(invocation_completion_ticket)
service_mate.block_until_tickets_satisfy(test_cases.terminated)
service_completion_ticket = links.Ticket(
service_mate.tickets()[0].operation_id, request_count, None, None, None,
None, None, None, None, None, test_code, test_message,
links.Ticket.Termination.COMPLETION)
service_link.accept_ticket(service_completion_ticket)
invocation_mate.block_until_tickets_satisfy(test_cases.terminated)
invocation_link.stop()
service_link.stop_gracefully()
observed_requests = tuple(
ticket.payload for ticket in service_mate.tickets()
if ticket.payload is not None)
observed_responses = tuple(
ticket.payload for ticket in invocation_mate.tickets()
if ticket.payload is not None)
self.assertTrue(scenario.verify_requests(observed_requests))
self.assertTrue(scenario.verify_responses(observed_responses))
def testEmptyScenario(self):
self._perform_scenario_test(_proto_scenarios.EmptyScenario())
def testBidirectionallyUnaryScenario(self):
self._perform_scenario_test(_proto_scenarios.BidirectionallyUnaryScenario())
def testBidirectionallyStreamingScenario(self):
self._perform_scenario_test(
_proto_scenarios.BidirectionallyStreamingScenario())
if __name__ == '__main__':
unittest.main(verbosity=2)

@ -0,0 +1,363 @@
# 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.
"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire."""
import abc
import enum
import logging
import threading
import time
from grpc._adapter import _intermediary_low
from grpc.framework.foundation import activated
from grpc.framework.foundation import logging_pool
from grpc.framework.foundation import relay
from grpc.framework.interfaces.links import links
@enum.unique
class _Read(enum.Enum):
AWAITING_METADATA = 'awaiting metadata'
READING = 'reading'
AWAITING_ALLOWANCE = 'awaiting allowance'
CLOSED = 'closed'
@enum.unique
class _HighWrite(enum.Enum):
OPEN = 'open'
CLOSED = 'closed'
@enum.unique
class _LowWrite(enum.Enum):
OPEN = 'OPEN'
ACTIVE = 'ACTIVE'
CLOSED = 'CLOSED'
class _RPCState(object):
def __init__(
self, call, request_serializer, response_deserializer, sequence_number,
read, allowance, high_write, low_write):
self.call = call
self.request_serializer = request_serializer
self.response_deserializer = response_deserializer
self.sequence_number = sequence_number
self.read = read
self.allowance = allowance
self.high_write = high_write
self.low_write = low_write
class _Kernel(object):
def __init__(
self, channel, host, request_serializers, response_deserializers,
ticket_relay):
self._lock = threading.Lock()
self._channel = channel
self._host = host
self._request_serializers = request_serializers
self._response_deserializers = response_deserializers
self._relay = ticket_relay
self._completion_queue = None
self._rpc_states = None
self._pool = None
def _on_write_event(self, operation_id, unused_event, rpc_state):
if rpc_state.high_write is _HighWrite.CLOSED:
rpc_state.call.complete(operation_id)
rpc_state.low_write = _LowWrite.CLOSED
else:
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, 1,
None, None, None, None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
rpc_state.low_write = _LowWrite.OPEN
def _on_read_event(self, operation_id, event, rpc_state):
if event.bytes is None:
rpc_state.read = _Read.CLOSED
else:
if 0 < rpc_state.allowance:
rpc_state.allowance -= 1
rpc_state.call.read(operation_id)
else:
rpc_state.read = _Read.AWAITING_ALLOWANCE
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, None,
None, rpc_state.response_deserializer(event.bytes), None, None, None,
None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _on_metadata_event(self, operation_id, event, rpc_state):
rpc_state.allowance -= 1
rpc_state.call.read(operation_id)
rpc_state.read = _Read.READING
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None,
links.Ticket.Subscription.FULL, None, None, event.metadata, None, None,
None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _on_finish_event(self, operation_id, event, rpc_state):
self._rpc_states.pop(operation_id, None)
if event.status.code is _intermediary_low.Code.OK:
termination = links.Ticket.Termination.COMPLETION
elif event.status.code is _intermediary_low.Code.CANCELLED:
termination = links.Ticket.Termination.CANCELLATION
elif event.status.code is _intermediary_low.Code.DEADLINE_EXCEEDED:
termination = links.Ticket.Termination.EXPIRATION
else:
termination = links.Ticket.Termination.TRANSMISSION_FAILURE
ticket = links.Ticket(
operation_id, rpc_state.sequence_number, None, None, None, None, None,
None, None, event.metadata, event.status.code, event.status.details,
termination)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _spin(self, completion_queue):
while True:
event = completion_queue.get(None)
if event.kind is _intermediary_low.Event.Kind.STOP:
return
operation_id = event.tag
with self._lock:
if self._completion_queue is None:
continue
rpc_state = self._rpc_states.get(operation_id)
if rpc_state is not None:
if event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED:
self._on_write_event(operation_id, event, rpc_state)
elif event.kind is _intermediary_low.Event.Kind.METADATA_ACCEPTED:
self._on_metadata_event(operation_id, event, rpc_state)
elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED:
self._on_read_event(operation_id, event, rpc_state)
elif event.kind is _intermediary_low.Event.Kind.FINISH:
self._on_finish_event(operation_id, event, rpc_state)
elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED:
pass
else:
logging.error('Illegal RPC event! %s', (event,))
def _invoke(
self, operation_id, group, method, initial_metadata, payload, termination,
timeout, allowance):
"""Invoke an RPC.
Args:
operation_id: Any object to be used as an operation ID for the RPC.
group: The group to which the RPC method belongs.
method: The RPC method name.
initial_metadata: The initial metadata object for the RPC.
payload: A payload object for the RPC or None if no payload was given at
invocation-time.
termination: A links.Ticket.Termination value or None indicated whether or
not more writes will follow from this side of the RPC.
timeout: A duration of time in seconds to allow for the RPC.
allowance: The number of payloads (beyond the free first one) that the
local ticket exchange mate has granted permission to be read.
"""
if termination is links.Ticket.Termination.COMPLETION:
high_write = _HighWrite.CLOSED
elif termination is None:
high_write = _HighWrite.OPEN
else:
return
request_serializer = self._request_serializers.get((group, method))
response_deserializer = self._response_deserializers.get((group, method))
if request_serializer is None or response_deserializer is None:
cancellation_ticket = links.Ticket(
operation_id, 0, None, None, None, None, None, None, None, None, None,
None, links.Ticket.Termination.CANCELLATION)
self._relay.add_value(cancellation_ticket)
return
call = _intermediary_low.Call(
self._channel, self._completion_queue, '/%s/%s' % (group, method),
self._host, time.time() + timeout)
if initial_metadata is not None:
for metadata_key, metadata_value in initial_metadata:
call.add_metadata(metadata_key, metadata_value)
call.invoke(self._completion_queue, operation_id, operation_id)
if payload is None:
if high_write is _HighWrite.CLOSED:
call.complete(operation_id)
low_write = _LowWrite.CLOSED
else:
low_write = _LowWrite.OPEN
else:
call.write(request_serializer(payload), operation_id)
low_write = _LowWrite.ACTIVE
self._rpc_states[operation_id] = _RPCState(
call, request_serializer, response_deserializer, 0,
_Read.AWAITING_METADATA, 1 if allowance is None else (1 + allowance),
high_write, low_write)
def _advance(self, operation_id, rpc_state, payload, termination, allowance):
if payload is not None:
rpc_state.call.write(rpc_state.request_serializer(payload), operation_id)
rpc_state.low_write = _LowWrite.ACTIVE
if allowance is not None:
if rpc_state.read is _Read.AWAITING_ALLOWANCE:
rpc_state.allowance += allowance - 1
rpc_state.call.read(operation_id)
rpc_state.read = _Read.READING
else:
rpc_state.allowance += allowance
if termination is links.Ticket.Termination.COMPLETION:
rpc_state.high_write = _HighWrite.CLOSED
if rpc_state.low_write is _LowWrite.OPEN:
rpc_state.call.complete(operation_id)
rpc_state.low_write = _LowWrite.CLOSED
elif termination is not None:
rpc_state.call.cancel()
def add_ticket(self, ticket):
with self._lock:
if self._completion_queue is None:
return
if ticket.sequence_number == 0:
self._invoke(
ticket.operation_id, ticket.group, ticket.method,
ticket.initial_metadata, ticket.payload, ticket.termination,
ticket.timeout, ticket.allowance)
else:
rpc_state = self._rpc_states.get(ticket.operation_id)
if rpc_state is not None:
self._advance(
ticket.operation_id, rpc_state, ticket.payload,
ticket.termination, ticket.allowance)
def start(self):
"""Starts this object.
This method must be called before attempting to exchange tickets with this
object.
"""
with self._lock:
self._completion_queue = _intermediary_low.CompletionQueue()
self._rpc_states = {}
self._pool = logging_pool.pool(1)
self._pool.submit(self._spin, self._completion_queue)
def stop(self):
"""Stops this object.
This method must be called for proper termination of this object, and no
attempts to exchange tickets with this object may be made after this method
has been called.
"""
with self._lock:
self._completion_queue.stop()
self._completion_queue = None
pool = self._pool
self._pool = None
self._rpc_states = None
pool.shutdown(wait=True)
class InvocationLink(links.Link, activated.Activated):
"""A links.Link for use on the invocation-side of a gRPC connection.
Implementations of this interface are only valid for use when activated.
"""
__metaclass__ = abc.ABCMeta
class _InvocationLink(InvocationLink):
def __init__(
self, channel, host, request_serializers, response_deserializers):
self._relay = relay.relay(None)
self._kernel = _Kernel(
channel, host, request_serializers, response_deserializers, self._relay)
def _start(self):
self._relay.start()
self._kernel.start()
return self
def _stop(self):
self._kernel.stop()
self._relay.stop()
def accept_ticket(self, ticket):
"""See links.Link.accept_ticket for specification."""
self._kernel.add_ticket(ticket)
def join_link(self, link):
"""See links.Link.join_link for specification."""
self._relay.set_behavior(link.accept_ticket)
def __enter__(self):
"""See activated.Activated.__enter__ for specification."""
return self._start()
def __exit__(self, exc_type, exc_val, exc_tb):
"""See activated.Activated.__exit__ for specification."""
self._stop()
return False
def start(self):
"""See activated.Activated.start for specification."""
return self._start()
def stop(self):
"""See activated.Activated.stop for specification."""
self._stop()
def invocation_link(channel, host, request_serializers, response_deserializers):
"""Creates an InvocationLink.
Args:
channel: A channel for use by the link.
host: The host to specify when invoking RPCs.
request_serializers: A dict from group-method pair to request object
serialization behavior.
response_deserializers: A dict from group-method pair to response object
deserialization behavior.
Returns:
An InvocationLink.
"""
return _InvocationLink(
channel, host, request_serializers, response_deserializers)

@ -0,0 +1,402 @@
# 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.
"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire."""
import abc
import enum
import logging
import threading
import time
from grpc._adapter import _intermediary_low
from grpc.framework.foundation import logging_pool
from grpc.framework.foundation import relay
from grpc.framework.interfaces.links import links
@enum.unique
class _Read(enum.Enum):
READING = 'reading'
AWAITING_ALLOWANCE = 'awaiting allowance'
CLOSED = 'closed'
@enum.unique
class _HighWrite(enum.Enum):
OPEN = 'open'
CLOSED = 'closed'
@enum.unique
class _LowWrite(enum.Enum):
"""The possible categories of low-level write state."""
OPEN = 'OPEN'
ACTIVE = 'ACTIVE'
CLOSED = 'CLOSED'
class _RPCState(object):
def __init__(
self, request_deserializer, response_serializer, sequence_number, read,
allowance, high_write, low_write, premetadataed, terminal_metadata, code,
message):
self.request_deserializer = request_deserializer
self.response_serializer = response_serializer
self.sequence_number = sequence_number
self.read = read
self.allowance = allowance
self.high_write = high_write
self.low_write = low_write
self.premetadataed = premetadataed
self.terminal_metadata = terminal_metadata
self.code = code
self.message = message
def _metadatafy(call, metadata):
for metadata_key, metadata_value in metadata:
call.add_metadata(metadata_key, metadata_value)
class _Kernel(object):
def __init__(self, request_deserializers, response_serializers, ticket_relay):
self._lock = threading.Lock()
self._request_deserializers = request_deserializers
self._response_serializers = response_serializers
self._relay = ticket_relay
self._completion_queue = None
self._server = None
self._rpc_states = {}
self._pool = None
def _on_service_acceptance_event(self, event, server):
server.service(None)
service_acceptance = event.service_acceptance
call = service_acceptance.call
call.accept(self._completion_queue, call)
try:
group, method = service_acceptance.method.split('/')[1:3]
except ValueError:
logging.info('Illegal path "%s"!', service_acceptance.method)
return
request_deserializer = self._request_deserializers.get((group, method))
response_serializer = self._response_serializers.get((group, method))
if request_deserializer is None or response_serializer is None:
# TODO(nathaniel): Terminate the RPC with code NOT_FOUND.
call.cancel()
return
call.read(call)
self._rpc_states[call] = _RPCState(
request_deserializer, response_serializer, 1, _Read.READING, 0,
_HighWrite.OPEN, _LowWrite.OPEN, False, None, None, None)
ticket = links.Ticket(
call, 0, group, method, links.Ticket.Subscription.FULL,
service_acceptance.deadline - time.time(), None, event.metadata, None,
None, None, None, None)
self._relay.add_value(ticket)
def _on_read_event(self, event):
call = event.tag
rpc_state = self._rpc_states.get(call, None)
if rpc_state is None:
return
if event.bytes is None:
rpc_state.read = _Read.CLOSED
payload = None
termination = links.Ticket.Termination.COMPLETION
else:
if 0 < rpc_state.allowance:
rpc_state.allowance -= 1
call.read(call)
else:
rpc_state.read = _Read.AWAITING_ALLOWANCE
payload = rpc_state.request_deserializer(event.bytes)
termination = None
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, None, None,
payload, None, None, None, termination)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _on_write_event(self, event):
call = event.tag
rpc_state = self._rpc_states.get(call, None)
if rpc_state is None:
return
if rpc_state.high_write is _HighWrite.CLOSED:
if rpc_state.terminal_metadata is not None:
_metadatafy(call, rpc_state.terminal_metadata)
call.status(
_intermediary_low.Status(rpc_state.code, rpc_state.message), call)
rpc_state.low_write = _LowWrite.CLOSED
else:
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, 1, None,
None, None, None, None, None)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
rpc_state.low_write = _LowWrite.OPEN
def _on_finish_event(self, event):
call = event.tag
rpc_state = self._rpc_states.pop(call, None)
if rpc_state is None:
return
code = event.status.code
if code is _intermediary_low.Code.OK:
return
if code is _intermediary_low.Code.CANCELLED:
termination = links.Ticket.Termination.CANCELLATION
elif code is _intermediary_low.Code.DEADLINE_EXCEEDED:
termination = links.Ticket.Termination.EXPIRATION
else:
termination = links.Ticket.Termination.TRANSMISSION_FAILURE
ticket = links.Ticket(
call, rpc_state.sequence_number, None, None, None, None, None, None,
None, None, None, None, termination)
rpc_state.sequence_number += 1
self._relay.add_value(ticket)
def _spin(self, completion_queue, server):
while True:
event = completion_queue.get(None)
if event.kind is _intermediary_low.Event.Kind.STOP:
return
with self._lock:
if self._server is None:
continue
elif event.kind is _intermediary_low.Event.Kind.SERVICE_ACCEPTED:
self._on_service_acceptance_event(event, server)
elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED:
self._on_read_event(event)
elif event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED:
self._on_write_event(event)
elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED:
pass
elif event.kind is _intermediary_low.Event.Kind.FINISH:
self._on_finish_event(event)
else:
logging.error('Illegal event! %s', (event,))
def add_ticket(self, ticket):
with self._lock:
if self._server is None:
return
call = ticket.operation_id
rpc_state = self._rpc_states.get(call)
if rpc_state is None:
return
if ticket.initial_metadata is not None:
_metadatafy(call, ticket.initial_metadata)
call.premetadata()
rpc_state.premetadataed = True
elif not rpc_state.premetadataed:
if (ticket.terminal_metadata is not None or
ticket.payload is not None or
ticket.termination is links.Ticket.Termination.COMPLETION or
ticket.code is not None or
ticket.message is not None):
call.premetadata()
rpc_state.premetadataed = True
if ticket.allowance is not None:
if rpc_state.read is _Read.AWAITING_ALLOWANCE:
rpc_state.allowance += ticket.allowance - 1
call.read(call)
rpc_state.read = _Read.READING
else:
rpc_state.allowance += ticket.allowance
if ticket.payload is not None:
call.write(rpc_state.response_serializer(ticket.payload), call)
rpc_state.low_write = _LowWrite.ACTIVE
if ticket.terminal_metadata is not None:
rpc_state.terminal_metadata = ticket.terminal_metadata
if ticket.code is not None:
rpc_state.code = ticket.code
if ticket.message is not None:
rpc_state.message = ticket.message
if ticket.termination is links.Ticket.Termination.COMPLETION:
rpc_state.high_write = _HighWrite.CLOSED
if rpc_state.low_write is _LowWrite.OPEN:
if rpc_state.terminal_metadata is not None:
_metadatafy(call, rpc_state.terminal_metadata)
status = _intermediary_low.Status(
_intermediary_low.Code.OK
if rpc_state.code is None else rpc_state.code,
'' if rpc_state.message is None else rpc_state.message)
call.status(status, call)
rpc_state.low_write = _LowWrite.CLOSED
elif ticket.termination is not None:
call.cancel()
self._rpc_states.pop(call, None)
def add_port(self, port, server_credentials):
with self._lock:
address = '[::]:%d' % port
if self._server is None:
self._completion_queue = _intermediary_low.CompletionQueue()
self._server = _intermediary_low.Server(self._completion_queue)
if server_credentials is None:
return self._server.add_http2_addr(address)
else:
return self._server.add_secure_http2_addr(address, server_credentials)
def start(self):
with self._lock:
if self._server is None:
self._completion_queue = _intermediary_low.CompletionQueue()
self._server = _intermediary_low.Server(self._completion_queue)
self._pool = logging_pool.pool(1)
self._pool.submit(self._spin, self._completion_queue, self._server)
self._server.start()
self._server.service(None)
def graceful_stop(self):
with self._lock:
self._server.stop()
self._server = None
self._completion_queue.stop()
self._completion_queue = None
pool = self._pool
self._pool = None
self._rpc_states = None
pool.shutdown(wait=True)
def immediate_stop(self):
# TODO(nathaniel): Implementation.
raise NotImplementedError(
'TODO(nathaniel): after merge of rewritten lower layers')
class ServiceLink(links.Link):
"""A links.Link for use on the service-side of a gRPC connection.
Implementations of this interface are only valid for use between calls to
their start method and one of their stop methods.
"""
@abc.abstractmethod
def add_port(self, port, server_credentials):
"""Adds a port on which to service RPCs after this link has been started.
Args:
port: The port on which to service RPCs, or zero to request that a port be
automatically selected and used.
server_credentials: A ServerCredentials object, or None for insecure
service.
Returns:
A port on which RPCs will be serviced after this link has been started.
"""
raise NotImplementedError()
@abc.abstractmethod
def start(self):
"""Starts this object.
This method must be called before attempting to use this Link in ticket
exchange.
"""
raise NotImplementedError()
@abc.abstractmethod
def stop_gracefully(self):
"""Stops this link.
New RPCs will be rejected as soon as this method is called, but ongoing RPCs
will be allowed to continue until they terminate. This method blocks until
all RPCs have terminated.
"""
raise NotImplementedError()
@abc.abstractmethod
def stop_immediately(self):
"""Stops this link.
All in-progress RPCs will be terminated immediately.
"""
raise NotImplementedError()
class _ServiceLink(ServiceLink):
def __init__(self, request_deserializers, response_serializers):
self._relay = relay.relay(None)
self._kernel = _Kernel(
request_deserializers, response_serializers, self._relay)
def accept_ticket(self, ticket):
self._kernel.add_ticket(ticket)
def join_link(self, link):
self._relay.set_behavior(link.accept_ticket)
def add_port(self, port, server_credentials):
return self._kernel.add_port(port, server_credentials)
def start(self):
self._relay.start()
return self._kernel.start()
def stop_gracefully(self):
self._kernel.graceful_stop()
self._relay.stop()
def stop_immediately(self):
self._kernel.immediate_stop()
self._relay.stop()
def service_link(request_deserializers, response_serializers):
"""Creates a ServiceLink.
Args:
request_deserializers: A dict from group-method pair to request object
deserialization behavior.
response_serializers: A dict from group-method pair to response ojbect
serialization behavior.
Returns:
A ServiceLink.
"""
return _ServiceLink(request_deserializers, response_serializers)

@ -0,0 +1,37 @@
# 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.
"""Constants shared among tests throughout RPC Framework."""
# Value for maximum duration in seconds of RPCs that may time out as part of a
# test.
SHORT_TIMEOUT = 4
# Absurdly large value for maximum duration in seconds for should-not-time-out
# RPCs made during tests.
LONG_TIMEOUT = 3000

@ -0,0 +1,87 @@
# 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.
"""Code for instructing systems under test to block or fail."""
import abc
import contextlib
import threading
class Control(object):
"""An object that accepts program control from a system under test.
Systems under test passed a Control should call its control() method
frequently during execution. The control() method may block, raise an
exception, or do nothing, all according to the enclosing test's desire for
the system under test to simulate hanging, failing, or functioning.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def control(self):
"""Potentially does anything."""
raise NotImplementedError()
class PauseFailControl(Control):
"""A Control that can be used to pause or fail code under control."""
def __init__(self):
self._condition = threading.Condition()
self._paused = False
self._fail = False
def control(self):
with self._condition:
if self._fail:
raise ValueError()
while self._paused:
self._condition.wait()
@contextlib.contextmanager
def pause(self):
"""Pauses code under control while controlling code is in context."""
with self._condition:
self._paused = True
yield
with self._condition:
self._paused = False
self._condition.notify_all()
@contextlib.contextmanager
def fail(self):
"""Fails code under control while controlling code is in context."""
with self._condition:
self._fail = True
yield
with self._condition:
self._fail = False

@ -0,0 +1,116 @@
# 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.
"""Governs coverage for tests of RPCs throughout RPC Framework."""
import abc
# This code is designed for use with the unittest module.
# pylint: disable=invalid-name
class Coverage(object):
"""Specification of test coverage."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def testSuccessfulUnaryRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testSuccessfulUnaryRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testSuccessfulStreamRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testSuccessfulStreamRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testSequentialInvocations(self):
raise NotImplementedError()
@abc.abstractmethod
def testParallelInvocations(self):
raise NotImplementedError()
@abc.abstractmethod
def testWaitingForSomeButNotAllParallelInvocations(self):
raise NotImplementedError()
@abc.abstractmethod
def testCancelledUnaryRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testCancelledUnaryRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testCancelledStreamRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testCancelledStreamRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testExpiredUnaryRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testExpiredUnaryRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testExpiredStreamRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testExpiredStreamRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testFailedUnaryRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testFailedUnaryRequestStreamResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testFailedStreamRequestUnaryResponse(self):
raise NotImplementedError()
@abc.abstractmethod
def testFailedStreamRequestStreamResponse(self):
raise NotImplementedError()

@ -0,0 +1,175 @@
# 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.
"""Implementations of in-order work deference."""
import abc
import enum
import threading
from grpc.framework.foundation import activated
from grpc.framework.foundation import logging_pool
_NULL_BEHAVIOR = lambda unused_value: None
class Relay(object):
"""Performs work submitted to it in another thread.
Performs work in the order in which work was submitted to it; otherwise there
would be no reason to use an implementation of this interface instead of a
thread pool.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def add_value(self, value):
"""Adds a value to be passed to the behavior registered with this Relay.
Args:
value: A value that will be passed to a call made in another thread to the
behavior registered with this Relay.
"""
raise NotImplementedError()
@abc.abstractmethod
def set_behavior(self, behavior):
"""Sets the behavior that this Relay should call when passed values.
Args:
behavior: The behavior that this Relay should call in another thread when
passed a value, or None to have passed values ignored.
"""
raise NotImplementedError()
class _PoolRelay(activated.Activated, Relay):
@enum.unique
class _State(enum.Enum):
INACTIVE = 'inactive'
IDLE = 'idle'
SPINNING = 'spinning'
def __init__(self, pool, behavior):
self._condition = threading.Condition()
self._pool = pool
self._own_pool = pool is None
self._state = _PoolRelay._State.INACTIVE
self._activated = False
self._spinning = False
self._values = []
self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
def _spin(self, behavior, value):
while True:
behavior(value)
with self._condition:
if self._values:
value = self._values.pop(0)
behavior = self._behavior
else:
self._state = _PoolRelay._State.IDLE
self._condition.notify_all()
break
def add_value(self, value):
with self._condition:
if self._state is _PoolRelay._State.INACTIVE:
raise ValueError('add_value not valid on inactive Relay!')
elif self._state is _PoolRelay._State.IDLE:
self._pool.submit(self._spin, self._behavior, value)
self._state = _PoolRelay._State.SPINNING
else:
self._values.append(value)
def set_behavior(self, behavior):
with self._condition:
self._behavior = _NULL_BEHAVIOR if behavior is None else behavior
def _start(self):
with self._condition:
self._state = _PoolRelay._State.IDLE
if self._own_pool:
self._pool = logging_pool.pool(1)
return self
def _stop(self):
with self._condition:
while self._state is _PoolRelay._State.SPINNING:
self._condition.wait()
if self._own_pool:
self._pool.shutdown(wait=True)
self._state = _PoolRelay._State.INACTIVE
def __enter__(self):
return self._start()
def __exit__(self, exc_type, exc_val, exc_tb):
self._stop()
return False
def start(self):
return self._start()
def stop(self):
self._stop()
def relay(behavior):
"""Creates a Relay.
Args:
behavior: The behavior to be called by the created Relay, or None to have
passed values dropped until a different behavior is given to the returned
Relay later.
Returns:
An object that is both an activated.Activated and a Relay. The object is
only valid for use as a Relay when activated.
"""
return _PoolRelay(None, behavior)
def pool_relay(pool, behavior):
"""Creates a Relay that uses a given thread pool.
This object will make use of at most one thread in the given pool.
Args:
pool: A futures.ThreadPoolExecutor for use by the created Relay.
behavior: The behavior to be called by the created Relay, or None to have
passed values dropped until a different behavior is given to the returned
Relay later.
Returns:
An object that is both an activated.Activated and a Relay. The object is
only valid for use as a Relay when activated.
"""
return _PoolRelay(pool, behavior)

@ -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,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,124 @@
# 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.
"""The low-level ticket-exchanging-links interface of RPC Framework."""
import abc
import collections
import enum
class Ticket(
collections.namedtuple(
'Ticket',
['operation_id', 'sequence_number', 'group', 'method', 'subscription',
'timeout', 'allowance', 'initial_metadata', 'payload',
'terminal_metadata', 'code', 'message', 'termination'])):
"""A sum type for all values sent from a front to a back.
Attributes:
operation_id: A unique-with-respect-to-equality hashable object identifying
a particular operation.
sequence_number: A zero-indexed integer sequence number identifying the
ticket's place in the stream of tickets sent in one direction for the
particular operation.
group: The group to which the method of the operation belongs. Must be
present in the first ticket from invocation side to service side. Ignored
for all other tickets exchanged during the operation.
method: The name of an operation. Must be present in the first ticket from
invocation side to service side. Ignored for all other tickets exchanged
during the operation.
subscription: A Subscription value describing the interest one side has in
receiving information from the other side. Must be present in the first
ticket from either side. Ignored for all other tickets exchanged during
the operation.
timeout: A nonzero length of time (measured from the beginning of the
operation) to allow for the entire operation. Must be present in the first
ticket from invocation side to service side. Optional for all other
tickets exchanged during the operation. Receipt of a value from the other
side of the operation indicates the value in use by that side. Setting a
value on a later ticket allows either side to request time extensions (or
even time reductions!) on in-progress operations.
allowance: A positive integer granting permission for a number of payloads
to be transmitted to the communicating side of the operation, or None if
no additional allowance is being granted with this ticket.
initial_metadata: An optional metadata value communicated from one side to
the other at the beginning of the operation. May be non-None in at most
one ticket from each side. Any non-None value must appear no later than
the first payload value.
payload: A customer payload object. May be None.
terminal_metadata: A metadata value comminicated from one side to the other
at the end of the operation. May be non-None in the same ticket as
the code and message, but must be None for all earlier tickets.
code: A value communicated at operation completion. May be None.
message: A value communicated at operation completion. May be None.
termination: A Termination value describing the end of the operation, or
None if the operation has not yet terminated. If set, no further tickets
may be sent in the same direction.
"""
@enum.unique
class Subscription(enum.Enum):
"""Identifies the level of subscription of a side of an operation."""
NONE = 'none'
TERMINATION = 'termination'
FULL = 'full'
@enum.unique
class Termination(enum.Enum):
"""Identifies the termination of an operation."""
COMPLETION = 'completion'
CANCELLATION = 'cancellation'
EXPIRATION = 'expiration'
LOCAL_SHUTDOWN = 'local shutdown'
RECEPTION_FAILURE = 'reception failure'
TRANSMISSION_FAILURE = 'transmission failure'
LOCAL_FAILURE = 'local failure'
REMOTE_FAILURE = 'remote failure'
class Link(object):
"""Accepts and emits tickets."""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def accept_ticket(self, ticket):
"""Accept a Ticket.
Args:
ticket: Any Ticket.
"""
raise NotImplementedError()
@abc.abstractmethod
def join_link(self, link):
"""Mates this object with a peer with which it will exchange tickets."""
raise NotImplementedError()

@ -0,0 +1,332 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Tests of the links interface of RPC Framework."""
# unittest is referenced from specification in this module.
import abc
import unittest # pylint: disable=unused-import
from grpc.framework.common import test_constants
from grpc.framework.interfaces.links import links
from grpc.framework.interfaces.links import test_utilities
def at_least_n_payloads_received_predicate(n):
def predicate(ticket_sequence):
payload_count = 0
for ticket in ticket_sequence:
if ticket.payload is not None:
payload_count += 1
if n <= payload_count:
return True
else:
return False
return predicate
def terminated(ticket_sequence):
return ticket_sequence and ticket_sequence[-1].termination is not None
_TRANSMISSION_GROUP = 'test.Group'
_TRANSMISSION_METHOD = 'TestMethod'
class TransmissionTest(object):
"""Tests ticket transmission between two connected links.
This class must be mixed into a unittest.TestCase that implements the abstract
methods it provides.
"""
__metaclass__ = abc.ABCMeta
# This is a unittest.TestCase mix-in.
# pylint: disable=invalid-name
@abc.abstractmethod
def create_transmitting_links(self):
"""Creates two connected links for use in this test.
Returns:
Two links.Links, the first of which will be used on the invocation side
of RPCs and the second of which will be used on the service side of
RPCs.
"""
raise NotImplementedError()
@abc.abstractmethod
def destroy_transmitting_links(self, invocation_side_link, service_side_link):
"""Destroys the two connected links created for this test.
Args:
invocation_side_link: The link used on the invocation side of RPCs in
this test.
service_side_link: The link used on the service side of RPCs in this
test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_initial_metadata(self):
"""Creates a value for use as invocation-side initial metadata.
Returns:
A metadata value appropriate for use as invocation-side initial metadata
or None if invocation-side initial metadata transmission is not
supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_terminal_metadata(self):
"""Creates a value for use as invocation-side terminal metadata.
Returns:
A metadata value appropriate for use as invocation-side terminal
metadata or None if invocation-side terminal metadata transmission is
not supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_initial_metadata(self):
"""Creates a value for use as service-side initial metadata.
Returns:
A metadata value appropriate for use as service-side initial metadata or
None if service-side initial metadata transmission is not supported by
the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_terminal_metadata(self):
"""Creates a value for use as service-side terminal metadata.
Returns:
A metadata value appropriate for use as service-side terminal metadata or
None if service-side terminal metadata transmission is not supported by
the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_invocation_completion(self):
"""Creates values for use as invocation-side code and message.
Returns:
An invocation-side code value and an invocation-side message value.
Either or both may be None if invocation-side code and/or
invocation-side message transmission is not supported by the links
under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def create_service_completion(self):
"""Creates values for use as service-side code and message.
Returns:
A service-side code value and a service-side message value. Either or
both may be None if service-side code and/or service-side message
transmission is not supported by the links under test.
"""
raise NotImplementedError()
@abc.abstractmethod
def assertMetadataEqual(self, original_metadata, transmitted_metadata):
"""Asserts that two metadata objects are equal.
Args:
original_metadata: A metadata object used in this test.
transmitted_metadata: A metadata object obtained after transmission
through the system under test.
Raises:
AssertionError: if the two metadata objects are not equal.
"""
raise NotImplementedError()
def group_and_method(self):
"""Returns the group and method used in this test case.
Returns:
A pair of the group and method used in this test case.
"""
return _TRANSMISSION_GROUP, _TRANSMISSION_METHOD
def serialize_request(self, request):
"""Serializes a request value used in this test case.
Args:
request: A request value created by this test case.
Returns:
A bytestring that is the serialization of the given request.
"""
return request
def deserialize_request(self, serialized_request):
"""Deserializes a request value used in this test case.
Args:
serialized_request: A bytestring that is the serialization of some request
used in this test case.
Returns:
The request value encoded by the given bytestring.
"""
return serialized_request
def serialize_response(self, response):
"""Serializes a response value used in this test case.
Args:
response: A response value created by this test case.
Returns:
A bytestring that is the serialization of the given response.
"""
return response
def deserialize_response(self, serialized_response):
"""Deserializes a response value used in this test case.
Args:
serialized_response: A bytestring that is the serialization of some
response used in this test case.
Returns:
The response value encoded by the given bytestring.
"""
return serialized_response
def _assert_is_valid_metadata_payload_sequence(
self, ticket_sequence, payloads, initial_metadata, terminal_metadata):
initial_metadata_seen = False
seen_payloads = []
terminal_metadata_seen = False
for ticket in ticket_sequence:
if ticket.initial_metadata is not None:
self.assertFalse(initial_metadata_seen)
self.assertFalse(seen_payloads)
self.assertFalse(terminal_metadata_seen)
self.assertMetadataEqual(initial_metadata, ticket.initial_metadata)
initial_metadata_seen = True
if ticket.payload is not None:
self.assertFalse(terminal_metadata_seen)
seen_payloads.append(ticket.payload)
if ticket.terminal_metadata is not None:
self.assertFalse(terminal_metadata_seen)
self.assertMetadataEqual(terminal_metadata, ticket.terminal_metadata)
terminal_metadata_seen = True
self.assertSequenceEqual(payloads, seen_payloads)
def _assert_is_valid_invocation_sequence(
self, ticket_sequence, group, method, payloads, initial_metadata,
terminal_metadata, termination):
self.assertLess(0, len(ticket_sequence))
self.assertEqual(group, ticket_sequence[0].group)
self.assertEqual(method, ticket_sequence[0].method)
self._assert_is_valid_metadata_payload_sequence(
ticket_sequence, payloads, initial_metadata, terminal_metadata)
self.assertIs(termination, ticket_sequence[-1].termination)
def _assert_is_valid_service_sequence(
self, ticket_sequence, payloads, initial_metadata, terminal_metadata,
code, message, termination):
self.assertLess(0, len(ticket_sequence))
self._assert_is_valid_metadata_payload_sequence(
ticket_sequence, payloads, initial_metadata, terminal_metadata)
self.assertEqual(code, ticket_sequence[-1].code)
self.assertEqual(message, ticket_sequence[-1].message)
self.assertIs(termination, ticket_sequence[-1].termination)
def setUp(self):
self._invocation_link, self._service_link = self.create_transmitting_links()
self._invocation_mate = test_utilities.RecordingLink()
self._service_mate = test_utilities.RecordingLink()
self._invocation_link.join_link(self._invocation_mate)
self._service_link.join_link(self._service_mate)
def tearDown(self):
self.destroy_transmitting_links(self._invocation_link, self._service_link)
def testSimplestRoundTrip(self):
"""Tests transmission of one ticket in each direction."""
invocation_operation_id = object()
invocation_payload = b'\x07' * 1023
timeout = test_constants.LONG_TIMEOUT
invocation_initial_metadata = self.create_invocation_initial_metadata()
invocation_terminal_metadata = self.create_invocation_terminal_metadata()
invocation_code, invocation_message = self.create_invocation_completion()
service_payload = b'\x08' * 1025
service_initial_metadata = self.create_service_initial_metadata()
service_terminal_metadata = self.create_service_terminal_metadata()
service_code, service_message = self.create_service_completion()
original_invocation_ticket = links.Ticket(
invocation_operation_id, 0, _TRANSMISSION_GROUP, _TRANSMISSION_METHOD,
links.Ticket.Subscription.FULL, timeout, 0, invocation_initial_metadata,
invocation_payload, invocation_terminal_metadata, invocation_code,
invocation_message, links.Ticket.Termination.COMPLETION)
self._invocation_link.accept_ticket(original_invocation_ticket)
# TODO(nathaniel): This shouldn't be necessary. Detecting the end of the
# invocation-side ticket sequence shouldn't require granting allowance for
# another payload.
self._service_mate.block_until_tickets_satisfy(
at_least_n_payloads_received_predicate(1))
service_operation_id = self._service_mate.tickets()[0].operation_id
self._service_link.accept_ticket(
links.Ticket(
service_operation_id, 0, None, None, links.Ticket.Subscription.FULL,
None, 1, None, None, None, None, None, None))
self._service_mate.block_until_tickets_satisfy(terminated)
self._assert_is_valid_invocation_sequence(
self._service_mate.tickets(), _TRANSMISSION_GROUP, _TRANSMISSION_METHOD,
(invocation_payload,), invocation_initial_metadata,
invocation_terminal_metadata, links.Ticket.Termination.COMPLETION)
original_service_ticket = links.Ticket(
service_operation_id, 1, None, None, links.Ticket.Subscription.FULL,
timeout, 0, service_initial_metadata, service_payload,
service_terminal_metadata, service_code, service_message,
links.Ticket.Termination.COMPLETION)
self._service_link.accept_ticket(original_service_ticket)
self._invocation_mate.block_until_tickets_satisfy(terminated)
self._assert_is_valid_service_sequence(
self._invocation_mate.tickets(), (service_payload,),
service_initial_metadata, service_terminal_metadata, service_code,
service_message, links.Ticket.Termination.COMPLETION)

@ -0,0 +1,66 @@
# 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 appropriate for use in tests."""
import threading
from grpc.framework.interfaces.links import links
class RecordingLink(links.Link):
"""A Link that records every ticket passed to it."""
def __init__(self):
self._condition = threading.Condition()
self._tickets = []
def accept_ticket(self, ticket):
with self._condition:
self._tickets.append(ticket)
self._condition.notify_all()
def join_link(self, link):
pass
def block_until_tickets_satisfy(self, predicate):
"""Blocks until the received tickets satisfy the given predicate.
Args:
predicate: A callable that takes a sequence of tickets and returns a
boolean value.
"""
with self._condition:
while not predicate(self._tickets):
self._condition.wait()
def tickets(self):
"""Returns a copy of the list of all tickets received by this Link."""
with self._condition:
return tuple(self._tickets)

@ -0,0 +1,44 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Utilities provided as part of the links interface."""
from grpc.framework.interfaces.links import links
class _NullLink(links.Link):
"""A do-nothing links.Link."""
def accept_ticket(self, ticket):
pass
def join_link(self, link):
pass
NULL_LINK = _NullLink()

@ -76,6 +76,7 @@ _PACKAGES = (
'grpc',
'grpc._adapter',
'grpc._junkdrawer',
'grpc._links',
'grpc.early_adopter',
'grpc.framework',
'grpc.framework.alpha',
@ -84,12 +85,15 @@ _PACKAGES = (
'grpc.framework.face',
'grpc.framework.face.testing',
'grpc.framework.foundation',
'grpc.framework.interfaces',
'grpc.framework.interfaces.links',
)
_PACKAGE_DIRECTORIES = {
'grpc': 'grpc',
'grpc._adapter': 'grpc/_adapter',
'grpc._junkdrawer': 'grpc/_junkdrawer',
'grpc._links': 'grpc/_links',
'grpc.early_adopter': 'grpc/early_adopter',
'grpc.framework': 'grpc/framework',
}

@ -398,14 +398,14 @@ module GRPC
nil
end
# Sends NOT_FOUND if the method can't be found
def found?(an_rpc)
# Sends UNIMPLEMENTED if the method is not implemented by this server
def implemented?(an_rpc)
mth = an_rpc.method.to_sym
return an_rpc if rpc_descs.key?(mth)
GRPC.logger.warn("NOT_FOUND: #{an_rpc}")
GRPC.logger.warn("UNIMPLEMENTED: #{an_rpc}")
noop = proc { |x| x }
c = ActiveCall.new(an_rpc.call, @cq, noop, noop, an_rpc.deadline)
c.send_status(StatusCodes::NOT_FOUND, '')
c.send_status(StatusCodes::UNIMPLEMENTED, '')
nil
end
@ -446,7 +446,7 @@ module GRPC
an_rpc.call.run_batch(@cq, handle_call_tag, INFINITE_FUTURE,
SEND_INITIAL_METADATA => connect_md)
return nil unless available?(an_rpc)
return nil unless found?(an_rpc)
return nil unless implemented?(an_rpc)
# Create the ActiveCall
GRPC.logger.info("deadline is #{an_rpc.deadline}; (now=#{Time.now})")

@ -48,7 +48,45 @@ static int seed(void) { return getpid(); }
static int seed(void) { return _getpid(); }
#endif
#if GPR_WINDOWS_CRASH_HANDLER
LONG crash_handler(struct _EXCEPTION_POINTERS* ex_info) {
gpr_log(GPR_DEBUG, "Exception handler called, dumping information");
while (ex_info->ExceptionRecord) {
DWORD code = ex_info->ExceptionRecord->ExceptionCode;
DWORD flgs = ex_info->ExceptionRecord->ExceptionFlags;
PVOID addr = ex_info->ExceptionRecord->ExceptionAddress;
gpr_log("code: %x - flags: %d - address: %p", code, flgs, addr);
ex_info->ExceptionRecord = ex_info->ExceptionRecord->ExceptionRecord;
}
if (IsDebuggerPresent()) {
__debugbreak();
} else {
_exit(1);
}
return EXCEPTION_EXECUTE_HANDLER;
}
void abort_handler(int sig) {
gpr_log(GPR_DEBUG, "Abort handler called.");
if (IsDebuggerPresent()) {
__debugbreak();
} else {
_exit(1);
}
}
static void install_crash_handler() {
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER) crash_handler);
_set_abort_behavior(0, _WRITE_ABORT_MSG);
_set_abort_behavior(0, _CALL_REPORTFAULT);
signal(SIGABRT, abort_handler);
}
#else
static void install_crash_handler() { }
#endif
void grpc_test_init(int argc, char **argv) {
install_crash_handler();
gpr_log(GPR_DEBUG, "test slowdown: machine=%f build=%f total=%f",
(double)GRPC_TEST_SLOWDOWN_MACHINE_FACTOR,
(double)GRPC_TEST_SLOWDOWN_BUILD_FACTOR,

@ -98,8 +98,9 @@ void CheckAuthContext(T* context) {
class TestServiceImpl : public ::grpc::cpp::test::util::TestService::Service {
public:
TestServiceImpl() : signal_client_(false), host_(nullptr) {}
explicit TestServiceImpl(const grpc::string& host) : signal_client_(false), host_(new grpc::string(host)) {}
TestServiceImpl() : signal_client_(false), host_() {}
explicit TestServiceImpl(const grpc::string& host)
: signal_client_(false), host_(new grpc::string(host)) {}
Status Echo(ServerContext* context, const EchoRequest* request,
EchoResponse* response) GRPC_OVERRIDE {
@ -226,7 +227,8 @@ class TestServiceImplDupPkg
class End2endTest : public ::testing::Test {
protected:
End2endTest() : kMaxMessageSize_(8192), special_service_("special"), thread_pool_(2) {}
End2endTest()
: kMaxMessageSize_(8192), special_service_("special"), thread_pool_(2) {}
void SetUp() GRPC_OVERRIDE {
int port = grpc_pick_unused_port_or_die();

@ -209,10 +209,11 @@ class Job(object):
elif self._state == _RUNNING and time.time() - self._start > 600:
self._tempfile.seek(0)
stdout = self._tempfile.read()
filtered_stdout = filter(lambda x: x in string.printable, stdout.decode(errors='ignore'))
message('TIMEOUT', self._spec.shortname, stdout, do_newline=True)
self.kill()
if self._xml_test is not None:
ET.SubElement(self._xml_test, 'system-out').text = stdout
ET.SubElement(self._xml_test, 'system-out').text = filtered_stdout
ET.SubElement(self._xml_test, 'error', message='Timeout')
return self._state

@ -47,6 +47,18 @@
"2.7"
]
},
{
"module": "grpc._links._lonely_invocation_link_test",
"pythonVersions": [
"2.7"
]
},
{
"module": "grpc._links._transmission_test",
"pythonVersions": [
"2.7"
]
},
{
"module": "grpc.early_adopter.implementations_test",
"pythonVersions": [

Loading…
Cancel
Save