diff options
Diffstat (limited to 'src')
488 files changed, 9618 insertions, 3153 deletions
diff --git a/src/compiler/cpp_generator.cc b/src/compiler/cpp_generator.cc index 75659947df..ea487bcd89 100644 --- a/src/compiler/cpp_generator.cc +++ b/src/compiler/cpp_generator.cc @@ -119,6 +119,7 @@ grpc::string GetHeaderIncludes(const grpc::protobuf::FileDescriptor *file, "#include <grpc++/async_unary_call.h>\n" "#include <grpc++/status.h>\n" "#include <grpc++/stream.h>\n" + "#include <grpc++/stub_options.h>\n" "\n" "namespace grpc {\n" "class CompletionQueue;\n" @@ -574,8 +575,8 @@ void PrintHeaderService(grpc::protobuf::io::Printer *printer, printer->Print("};\n"); printer->Print( "static std::unique_ptr<Stub> NewStub(const std::shared_ptr< " - "::grpc::ChannelInterface>& " - "channel);\n"); + "::grpc::ChannelInterface>& channel, " + "const ::grpc::StubOptions& options = ::grpc::StubOptions());\n"); printer->Print("\n"); @@ -966,7 +967,8 @@ void PrintSourceService(grpc::protobuf::io::Printer *printer, printer->Print( *vars, "std::unique_ptr< $ns$$Service$::Stub> $ns$$Service$::NewStub(" - "const std::shared_ptr< ::grpc::ChannelInterface>& channel) {\n" + "const std::shared_ptr< ::grpc::ChannelInterface>& channel, " + "const ::grpc::StubOptions& options) {\n" " std::unique_ptr< $ns$$Service$::Stub> stub(new " "$ns$$Service$::Stub(channel));\n" " return stub;\n" diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc index 586d6a7994..9432bdda96 100644 --- a/src/compiler/csharp_generator.cc +++ b/src/compiler/csharp_generator.cc @@ -246,6 +246,8 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) { out->Indent(); out->Print("$methodtype$,\n", "methodtype", GetCSharpMethodType(GetMethodType(method))); + out->Print("$servicenamefield$,\n", "servicenamefield", + GetServiceNameFieldName()); out->Print("\"$methodname$\",\n", "methodname", method->name()); out->Print("$requestmarshaller$,\n", "requestmarshaller", GetMarshallerFieldName(method->input_type())); @@ -273,6 +275,13 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) { "methodname", method->name(), "request", GetClassName(method->input_type()), "response", GetClassName(method->output_type())); + + // overload taking CallOptions as a param + out->Print( + "$response$ $methodname$($request$ request, CallOptions options);\n", + "methodname", method->name(), "request", + GetClassName(method->input_type()), "response", + GetClassName(method->output_type())); } std::string method_name = method->name(); @@ -284,6 +293,13 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) { "methodname", method_name, "request_maybe", GetMethodRequestParamMaybe(method), "returntype", GetMethodReturnTypeClient(method)); + + // overload taking CallOptions as a param + out->Print( + "$returntype$ $methodname$($request_maybe$CallOptions options);\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); } out->Outdent(); out->Print("}\n"); @@ -298,11 +314,13 @@ void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) { out->Indent(); for (int i = 0; i < service->method_count(); i++) { const MethodDescriptor *method = service->method(i); - out->Print("$returntype$ $methodname$($request$$response_stream_maybe$, ServerCallContext context);\n", - "methodname", method->name(), "returntype", - GetMethodReturnTypeServer(method), "request", - GetMethodRequestParamServer(method), "response_stream_maybe", - GetMethodResponseStreamMaybe(method)); + out->Print( + "$returntype$ $methodname$($request$$response_stream_maybe$, " + "ServerCallContext context);\n", + "methodname", method->name(), "returntype", + GetMethodReturnTypeServer(method), "request", + GetMethodRequestParamServer(method), "response_stream_maybe", + GetMethodResponseStreamMaybe(method)); } out->Outdent(); out->Print("}\n"); @@ -338,10 +356,23 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) { GetClassName(method->output_type())); out->Print("{\n"); out->Indent(); - out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers, deadline);\n", - "servicenamefield", GetServiceNameFieldName(), "methodfield", - GetMethodFieldName(method)); - out->Print("return Calls.BlockingUnaryCall(call, request, cancellationToken);\n"); + out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n", + "methodfield", GetMethodFieldName(method)); + out->Print("return Calls.BlockingUnaryCall(call, request);\n"); + out->Outdent(); + out->Print("}\n"); + + // overload taking CallOptions as a param + out->Print( + "public $response$ $methodname$($request$ request, CallOptions options)\n", + "methodname", method->name(), "request", + GetClassName(method->input_type()), "response", + GetClassName(method->output_type())); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($methodfield$, options);\n", + "methodfield", GetMethodFieldName(method)); + out->Print("return Calls.BlockingUnaryCall(call, request);\n"); out->Outdent(); out->Print("}\n"); } @@ -357,26 +388,55 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) { GetMethodReturnTypeClient(method)); out->Print("{\n"); out->Indent(); - out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers, deadline);\n", - "servicenamefield", GetServiceNameFieldName(), "methodfield", - GetMethodFieldName(method)); + out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n", + "methodfield", GetMethodFieldName(method)); + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + out->Print("return Calls.AsyncUnaryCall(call, request);\n"); + break; + case METHODTYPE_CLIENT_STREAMING: + out->Print("return Calls.AsyncClientStreamingCall(call);\n"); + break; + case METHODTYPE_SERVER_STREAMING: + out->Print( + "return Calls.AsyncServerStreamingCall(call, request);\n"); + break; + case METHODTYPE_BIDI_STREAMING: + out->Print("return Calls.AsyncDuplexStreamingCall(call);\n"); + break; + default: + GOOGLE_LOG(FATAL)<< "Can't get here."; + } + out->Outdent(); + out->Print("}\n"); + + // overload taking CallOptions as a param + out->Print( + "public $returntype$ $methodname$($request_maybe$CallOptions options)\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($methodfield$, options);\n", + "methodfield", GetMethodFieldName(method)); switch (GetMethodType(method)) { case METHODTYPE_NO_STREAMING: - out->Print("return Calls.AsyncUnaryCall(call, request, cancellationToken);\n"); + out->Print("return Calls.AsyncUnaryCall(call, request);\n"); break; case METHODTYPE_CLIENT_STREAMING: - out->Print("return Calls.AsyncClientStreamingCall(call, cancellationToken);\n"); + out->Print("return Calls.AsyncClientStreamingCall(call);\n"); break; case METHODTYPE_SERVER_STREAMING: out->Print( - "return Calls.AsyncServerStreamingCall(call, request, cancellationToken);\n"); + "return Calls.AsyncServerStreamingCall(call, request);\n"); break; case METHODTYPE_BIDI_STREAMING: - out->Print("return Calls.AsyncDuplexStreamingCall(call, cancellationToken);\n"); + out->Print("return Calls.AsyncDuplexStreamingCall(call);\n"); break; default: GOOGLE_LOG(FATAL)<< "Can't get here."; - } + } out->Outdent(); out->Print("}\n"); } diff --git a/src/compiler/generator_helpers.h b/src/compiler/generator_helpers.h index 7bdaff1c9b..68b807b057 100644 --- a/src/compiler/generator_helpers.h +++ b/src/compiler/generator_helpers.h @@ -126,7 +126,13 @@ inline grpc::string LowerUnderscoreToUpperCamel(grpc::string str) { } inline grpc::string FileNameInUpperCamel(const grpc::protobuf::FileDescriptor *file) { - return LowerUnderscoreToUpperCamel(StripProto(file->name())); + std::vector<grpc::string> tokens = tokenize(StripProto(file->name()), "/"); + grpc::string result = ""; + for (unsigned int i = 0; i < tokens.size() - 1; i++) { + result += tokens[i] + "/"; + } + result += LowerUnderscoreToUpperCamel(tokens.back()); + return result; } enum MethodType { diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc index 69b3805bb1..483c6573a8 100644 --- a/src/compiler/objective_c_generator.cc +++ b/src/compiler/objective_c_generator.cc @@ -154,9 +154,9 @@ void PrintAdvancedImplementation(Printer *printer, printer->Print(" responsesWriteable:[GRXWriteable "); if (method->server_streaming()) { - printer->Print("writeableWithStreamHandler:eventHandler]];\n"); + printer->Print("writeableWithEventHandler:eventHandler]];\n"); } else { - printer->Print("writeableWithSingleValueHandler:handler]];\n"); + printer->Print("writeableWithSingleHandler:handler]];\n"); } printer->Print("}\n"); diff --git a/src/compiler/python_generator.cc b/src/compiler/python_generator.cc index 724b69c703..2982a89fad 100644 --- a/src/compiler/python_generator.cc +++ b/src/compiler/python_generator.cc @@ -249,7 +249,7 @@ bool GetModuleAndMessagePath(const Descriptor* type, do { message_path.push_back(path_elem_type); path_elem_type = path_elem_type->containing_type(); - } while (path_elem_type != nullptr); + } while (path_elem_type); // implicit nullptr comparison; don't be explicit grpc::string file_name = type->file()->name(); static const int proto_suffix_length = strlen(".proto"); if (!(file_name.size() > static_cast<size_t>(proto_suffix_length) && diff --git a/src/core/census/grpc_context.c b/src/core/census/grpc_context.c index 0ed63469b6..11f1eb3d5d 100644 --- a/src/core/census/grpc_context.c +++ b/src/core/census/grpc_context.c @@ -32,14 +32,15 @@ */ #include <grpc/census.h> -#include "src/core/census/grpc_context.h" +#include <grpc/grpc.h> +#include "src/core/surface/call.h" static void grpc_census_context_destroy(void *context) { census_context_destroy((census_context *)context); } void grpc_census_call_set_context(grpc_call *call, census_context *context) { - if (!census_available()) { + if (census_enabled() == CENSUS_FEATURE_NONE) { return; } if (context == NULL) { diff --git a/src/core/census/initialize.c b/src/core/census/initialize.c index 8016520641..8d60f790eb 100644 --- a/src/core/census/initialize.c +++ b/src/core/census/initialize.c @@ -33,20 +33,25 @@ #include <grpc/census.h> -static int census_fns_enabled = CENSUS_NONE; +static int features_enabled = CENSUS_FEATURE_NONE; -int census_initialize(int functions) { - if (census_fns_enabled != CENSUS_NONE) { +int census_initialize(int features) { + if (features_enabled != CENSUS_FEATURE_NONE) { return 1; } - if (functions != CENSUS_NONE) { + if (features != CENSUS_FEATURE_NONE) { return 1; } else { - census_fns_enabled = functions; + features_enabled = features; return 0; } } -void census_shutdown() { census_fns_enabled = CENSUS_NONE; } +void census_shutdown(void) { features_enabled = CENSUS_FEATURE_NONE; } -int census_available() { return (census_fns_enabled != CENSUS_NONE); } +int census_supported(void) { + /* TODO(aveitch): improve this as we implement features... */ + return CENSUS_FEATURE_NONE; +} + +int census_enabled(void) { return features_enabled; } diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c index ec6ca42889..6c2e6b38a8 100644 --- a/src/core/channel/client_channel.c +++ b/src/core/channel/client_channel.c @@ -40,7 +40,6 @@ #include "src/core/channel/connected_channel.h" #include "src/core/surface/channel.h" #include "src/core/iomgr/iomgr.h" -#include "src/core/iomgr/pollset_set.h" #include "src/core/support/string.h" #include "src/core/transport/connectivity_state.h" #include <grpc/support/alloc.h> @@ -57,6 +56,8 @@ typedef struct { grpc_mdctx *mdctx; /** resolver for this channel */ grpc_resolver *resolver; + /** have we started resolving this channel */ + int started_resolving; /** master channel - the grpc_channel instance that ultimately owns this channel_data via its channel stack. We occasionally use this to bump the refcount on the master channel @@ -77,8 +78,22 @@ typedef struct { grpc_iomgr_closure on_config_changed; /** connectivity state being tracked */ grpc_connectivity_state_tracker state_tracker; + /** when an lb_policy arrives, should we try to exit idle */ + int exit_idle_when_lb_policy_arrives; + /** pollset_set of interested parties in a new connection */ + grpc_pollset_set pollset_set; } channel_data; +/** We create one watcher for each new lb_policy that is returned from a resolver, + to watch for state changes from the lb_policy. When a state change is seen, we + update the channel, and create a new watcher */ +typedef struct { + channel_data *chand; + grpc_iomgr_closure on_changed; + grpc_connectivity_state state; + grpc_lb_policy *lb_policy; +} lb_policy_connectivity_watcher; + typedef enum { CALL_CREATED, CALL_WAITING_FOR_SEND, @@ -385,6 +400,13 @@ static void perform_transport_stream_op(grpc_call_element *elem, } else if (chand->resolver != NULL) { calld->state = CALL_WAITING_FOR_CONFIG; add_to_lb_policy_wait_queue_locked_state_config(elem); + if (!chand->started_resolving && chand->resolver != NULL) { + GRPC_CHANNEL_INTERNAL_REF(chand->master, "resolver"); + chand->started_resolving = 1; + grpc_resolver_next(chand->resolver, + &chand->incoming_configuration, + &chand->on_config_changed); + } gpr_mu_unlock(&chand->mu_config); gpr_mu_unlock(&calld->mu_state); } else { @@ -408,16 +430,53 @@ static void cc_start_transport_stream_op(grpc_call_element *elem, perform_transport_stream_op(elem, op, 0); } +static void watch_lb_policy(channel_data *chand, grpc_lb_policy *lb_policy, grpc_connectivity_state current_state); + +static void on_lb_policy_state_changed(void *arg, int iomgr_success) { + lb_policy_connectivity_watcher *w = arg; + + gpr_mu_lock(&w->chand->mu_config); + /* check if the notification is for a stale policy */ + if (w->lb_policy == w->chand->lb_policy) { + grpc_connectivity_state_set(&w->chand->state_tracker, w->state, + "lb_changed"); + if (w->state != GRPC_CHANNEL_FATAL_FAILURE) { + watch_lb_policy(w->chand, w->lb_policy, w->state); + } + } + gpr_mu_unlock(&w->chand->mu_config); + + GRPC_CHANNEL_INTERNAL_UNREF(w->chand->master, "watch_lb_policy"); + gpr_free(w); +} + +static void watch_lb_policy(channel_data *chand, grpc_lb_policy *lb_policy, grpc_connectivity_state current_state) { + lb_policy_connectivity_watcher *w = gpr_malloc(sizeof(*w)); + GRPC_CHANNEL_INTERNAL_REF(chand->master, "watch_lb_policy"); + + w->chand = chand; + grpc_iomgr_closure_init(&w->on_changed, on_lb_policy_state_changed, w); + w->state = current_state; + w->lb_policy = lb_policy; + grpc_lb_policy_notify_on_state_change(lb_policy, &w->state, &w->on_changed); +} + static void cc_on_config_changed(void *arg, int iomgr_success) { channel_data *chand = arg; grpc_lb_policy *lb_policy = NULL; grpc_lb_policy *old_lb_policy; grpc_resolver *old_resolver; grpc_iomgr_closure *wakeup_closures = NULL; + grpc_connectivity_state state = GRPC_CHANNEL_TRANSIENT_FAILURE; + int exit_idle = 0; if (chand->incoming_configuration != NULL) { lb_policy = grpc_client_config_get_lb_policy(chand->incoming_configuration); - GRPC_LB_POLICY_REF(lb_policy, "channel"); + if (lb_policy != NULL) { + GRPC_LB_POLICY_REF(lb_policy, "channel"); + GRPC_LB_POLICY_REF(lb_policy, "config_change"); + state = grpc_lb_policy_check_connectivity(lb_policy); + } grpc_client_config_unref(chand->incoming_configuration); } @@ -431,13 +490,12 @@ static void cc_on_config_changed(void *arg, int iomgr_success) { wakeup_closures = chand->waiting_for_config_closures; chand->waiting_for_config_closures = NULL; } - gpr_mu_unlock(&chand->mu_config); - - if (old_lb_policy) { - GRPC_LB_POLICY_UNREF(old_lb_policy, "channel"); + if (lb_policy != NULL && chand->exit_idle_when_lb_policy_arrives) { + GRPC_LB_POLICY_REF(lb_policy, "exit_idle"); + exit_idle = 1; + chand->exit_idle_when_lb_policy_arrives = 0; } - gpr_mu_lock(&chand->mu_config); if (iomgr_success && chand->resolver) { grpc_resolver *resolver = chand->resolver; GRPC_RESOLVER_REF(resolver, "channel-next"); @@ -446,11 +504,16 @@ static void cc_on_config_changed(void *arg, int iomgr_success) { grpc_resolver_next(resolver, &chand->incoming_configuration, &chand->on_config_changed); GRPC_RESOLVER_UNREF(resolver, "channel-next"); + grpc_connectivity_state_set(&chand->state_tracker, state, + "new_lb+resolver"); + if (lb_policy != NULL) { + watch_lb_policy(chand, lb_policy, state); + } } else { old_resolver = chand->resolver; chand->resolver = NULL; grpc_connectivity_state_set(&chand->state_tracker, - GRPC_CHANNEL_FATAL_FAILURE); + GRPC_CHANNEL_FATAL_FAILURE, "resolver_gone"); gpr_mu_unlock(&chand->mu_config); if (old_resolver != NULL) { grpc_resolver_shutdown(old_resolver); @@ -458,12 +521,25 @@ static void cc_on_config_changed(void *arg, int iomgr_success) { } } + if (exit_idle) { + grpc_lb_policy_exit_idle(lb_policy); + GRPC_LB_POLICY_UNREF(lb_policy, "exit_idle"); + } + + if (old_lb_policy != NULL) { + grpc_lb_policy_shutdown(old_lb_policy); + GRPC_LB_POLICY_UNREF(old_lb_policy, "channel"); + } + while (wakeup_closures) { grpc_iomgr_closure *next = wakeup_closures->next; wakeup_closures->cb(wakeup_closures->cb_arg, 1); wakeup_closures = next; } + if (lb_policy != NULL) { + GRPC_LB_POLICY_UNREF(lb_policy, "config_change"); + } GRPC_CHANNEL_INTERNAL_UNREF(chand->master, "resolver"); } @@ -487,20 +563,22 @@ static void cc_start_transport_op(grpc_channel_element *elem, op->connectivity_state = NULL; } + if (!is_empty(op, sizeof(*op))) { + lb_policy = chand->lb_policy; + if (lb_policy) { + GRPC_LB_POLICY_REF(lb_policy, "broadcast"); + } + } + if (op->disconnect && chand->resolver != NULL) { grpc_connectivity_state_set(&chand->state_tracker, - GRPC_CHANNEL_FATAL_FAILURE); + GRPC_CHANNEL_FATAL_FAILURE, "disconnect"); destroy_resolver = chand->resolver; chand->resolver = NULL; if (chand->lb_policy != NULL) { grpc_lb_policy_shutdown(chand->lb_policy); - } - } - - if (!is_empty(op, sizeof(*op))) { - lb_policy = chand->lb_policy; - if (lb_policy) { - GRPC_LB_POLICY_REF(lb_policy, "broadcast"); + GRPC_LB_POLICY_UNREF(chand->lb_policy, "channel"); + chand->lb_policy = NULL; } } gpr_mu_unlock(&chand->mu_config); @@ -581,10 +659,11 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, gpr_mu_init(&chand->mu_config); chand->mdctx = metadata_context; chand->master = master; + grpc_pollset_set_init(&chand->pollset_set); grpc_iomgr_closure_init(&chand->on_config_changed, cc_on_config_changed, chand); - grpc_connectivity_state_init(&chand->state_tracker, GRPC_CHANNEL_IDLE); + grpc_connectivity_state_init(&chand->state_tracker, GRPC_CHANNEL_IDLE, "client_channel"); } /* Destructor for channel_data */ @@ -598,6 +677,8 @@ static void destroy_channel_elem(grpc_channel_element *elem) { if (chand->lb_policy != NULL) { GRPC_LB_POLICY_UNREF(chand->lb_policy, "channel"); } + grpc_connectivity_state_destroy(&chand->state_tracker); + grpc_pollset_set_destroy(&chand->pollset_set); gpr_mu_destroy(&chand->mu_config); } @@ -619,10 +700,66 @@ void grpc_client_channel_set_resolver(grpc_channel_stack *channel_stack, /* post construction initialization: set the transport setup pointer */ grpc_channel_element *elem = grpc_channel_stack_last_element(channel_stack); channel_data *chand = elem->channel_data; + gpr_mu_lock(&chand->mu_config); GPR_ASSERT(!chand->resolver); chand->resolver = resolver; - GRPC_CHANNEL_INTERNAL_REF(chand->master, "resolver"); GRPC_RESOLVER_REF(resolver, "channel"); - grpc_resolver_next(resolver, &chand->incoming_configuration, - &chand->on_config_changed); + if (chand->waiting_for_config_closures != NULL || + chand->exit_idle_when_lb_policy_arrives) { + chand->started_resolving = 1; + GRPC_CHANNEL_INTERNAL_REF(chand->master, "resolver"); + grpc_resolver_next(resolver, &chand->incoming_configuration, + &chand->on_config_changed); + } + gpr_mu_unlock(&chand->mu_config); +} + +grpc_connectivity_state grpc_client_channel_check_connectivity_state( + grpc_channel_element *elem, int try_to_connect) { + channel_data *chand = elem->channel_data; + grpc_connectivity_state out; + gpr_mu_lock(&chand->mu_config); + out = grpc_connectivity_state_check(&chand->state_tracker); + if (out == GRPC_CHANNEL_IDLE && try_to_connect) { + if (chand->lb_policy != NULL) { + grpc_lb_policy_exit_idle(chand->lb_policy); + } else { + chand->exit_idle_when_lb_policy_arrives = 1; + if (!chand->started_resolving && chand->resolver != NULL) { + GRPC_CHANNEL_INTERNAL_REF(chand->master, "resolver"); + chand->started_resolving = 1; + grpc_resolver_next(chand->resolver, &chand->incoming_configuration, + &chand->on_config_changed); + } + } + } + gpr_mu_unlock(&chand->mu_config); + return out; +} + +void grpc_client_channel_watch_connectivity_state( + grpc_channel_element *elem, grpc_connectivity_state *state, + grpc_iomgr_closure *on_complete) { + channel_data *chand = elem->channel_data; + gpr_mu_lock(&chand->mu_config); + grpc_connectivity_state_notify_on_state_change(&chand->state_tracker, state, + on_complete); + gpr_mu_unlock(&chand->mu_config); +} + +grpc_pollset_set *grpc_client_channel_get_connecting_pollset_set(grpc_channel_element *elem) { + channel_data *chand = elem->channel_data; + return &chand->pollset_set; +} + +void grpc_client_channel_add_interested_party(grpc_channel_element *elem, + grpc_pollset *pollset) { + channel_data *chand = elem->channel_data; + grpc_pollset_set_add_pollset(&chand->pollset_set, pollset); +} + +void grpc_client_channel_del_interested_party(grpc_channel_element *elem, + grpc_pollset *pollset) { + channel_data *chand = elem->channel_data; + grpc_pollset_set_del_pollset(&chand->pollset_set, pollset); } diff --git a/src/core/channel/client_channel.h b/src/core/channel/client_channel.h index fd2be46145..cd81294eb3 100644 --- a/src/core/channel/client_channel.h +++ b/src/core/channel/client_channel.h @@ -52,4 +52,18 @@ extern const grpc_channel_filter grpc_client_channel_filter; void grpc_client_channel_set_resolver(grpc_channel_stack *channel_stack, grpc_resolver *resolver); +grpc_connectivity_state grpc_client_channel_check_connectivity_state( + grpc_channel_element *elem, int try_to_connect); + +void grpc_client_channel_watch_connectivity_state( + grpc_channel_element *elem, grpc_connectivity_state *state, + grpc_iomgr_closure *on_complete); + +grpc_pollset_set *grpc_client_channel_get_connecting_pollset_set(grpc_channel_element *elem); + +void grpc_client_channel_add_interested_party(grpc_channel_element *channel, + grpc_pollset *pollset); +void grpc_client_channel_del_interested_party(grpc_channel_element *channel, + grpc_pollset *pollset); + #endif /* GRPC_INTERNAL_CORE_CHANNEL_CLIENT_CHANNEL_H */ diff --git a/src/core/channel/compress_filter.c b/src/core/channel/compress_filter.c index 4bf24e7db3..8963c13b0f 100644 --- a/src/core/channel/compress_filter.c +++ b/src/core/channel/compress_filter.c @@ -204,7 +204,7 @@ static void process_send_ops(grpc_call_element *elem, } grpc_metadata_batch_add_tail( &(sop->data.metadata), &calld->compression_algorithm_storage, - grpc_mdelem_ref(channeld->mdelem_compression_algorithms + GRPC_MDELEM_REF(channeld->mdelem_compression_algorithms [calld->compression_algorithm])); calld->written_initial_metadata = 1; /* GPR_TRUE */ } @@ -284,19 +284,19 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, grpc_channel_args_get_compression_algorithm(args); channeld->mdstr_request_compression_algorithm_key = - grpc_mdstr_from_string(mdctx, GRPC_COMPRESS_REQUEST_ALGORITHM_KEY); + grpc_mdstr_from_string(mdctx, GRPC_COMPRESS_REQUEST_ALGORITHM_KEY, 0); channeld->mdstr_outgoing_compression_algorithm_key = - grpc_mdstr_from_string(mdctx, "grpc-encoding"); + grpc_mdstr_from_string(mdctx, "grpc-encoding", 0); for (algo_idx = 0; algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; ++algo_idx) { - char *algorith_name; - GPR_ASSERT(grpc_compression_algorithm_name(algo_idx, &algorith_name) != 0); + char *algorithm_name; + GPR_ASSERT(grpc_compression_algorithm_name(algo_idx, &algorithm_name) != 0); channeld->mdelem_compression_algorithms[algo_idx] = grpc_mdelem_from_metadata_strings( mdctx, - grpc_mdstr_ref(channeld->mdstr_outgoing_compression_algorithm_key), - grpc_mdstr_from_string(mdctx, algorith_name)); + GRPC_MDSTR_REF(channeld->mdstr_outgoing_compression_algorithm_key), + grpc_mdstr_from_string(mdctx, algorithm_name, 0)); } GPR_ASSERT(!is_last); @@ -307,11 +307,11 @@ static void destroy_channel_elem(grpc_channel_element *elem) { channel_data *channeld = elem->channel_data; grpc_compression_algorithm algo_idx; - grpc_mdstr_unref(channeld->mdstr_request_compression_algorithm_key); - grpc_mdstr_unref(channeld->mdstr_outgoing_compression_algorithm_key); + GRPC_MDSTR_UNREF(channeld->mdstr_request_compression_algorithm_key); + GRPC_MDSTR_UNREF(channeld->mdstr_outgoing_compression_algorithm_key); for (algo_idx = 0; algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; ++algo_idx) { - grpc_mdelem_unref(channeld->mdelem_compression_algorithms[algo_idx]); + GRPC_MDELEM_UNREF(channeld->mdelem_compression_algorithms[algo_idx]); } } diff --git a/src/core/channel/http_client_filter.c b/src/core/channel/http_client_filter.c index 6ae8488070..48c623d359 100644 --- a/src/core/channel/http_client_filter.c +++ b/src/core/channel/http_client_filter.c @@ -40,10 +40,12 @@ typedef struct call_data { grpc_linked_mdelem method; grpc_linked_mdelem scheme; + grpc_linked_mdelem authority; grpc_linked_mdelem te_trailers; grpc_linked_mdelem content_type; grpc_linked_mdelem user_agent; int sent_initial_metadata; + int sent_authority; int got_initial_metadata; grpc_stream_op_buffer *recv_ops; @@ -62,6 +64,7 @@ typedef struct channel_data { grpc_mdelem *scheme; grpc_mdelem *content_type; grpc_mdelem *status; + grpc_mdelem *default_authority; /** complete user agent mdelem */ grpc_mdelem *user_agent; } channel_data; @@ -100,6 +103,7 @@ static void hc_on_recv(void *user_data, int success) { static grpc_mdelem *client_strip_filter(void *user_data, grpc_mdelem *md) { grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; channel_data *channeld = elem->channel_data; /* eat the things we'd like to set ourselves */ if (md->key == channeld->method->key) return NULL; @@ -107,6 +111,10 @@ static grpc_mdelem *client_strip_filter(void *user_data, grpc_mdelem *md) { if (md->key == channeld->te_trailers->key) return NULL; if (md->key == channeld->content_type->key) return NULL; if (md->key == channeld->user_agent->key) return NULL; + if (channeld->default_authority && + channeld->default_authority->key == md->key) { + calld->sent_authority = 1; + } return md; } @@ -130,6 +138,11 @@ static void hc_mutate_op(grpc_call_element *elem, GRPC_MDELEM_REF(channeld->method)); grpc_metadata_batch_add_head(&op->data.metadata, &calld->scheme, GRPC_MDELEM_REF(channeld->scheme)); + if (channeld->default_authority && !calld->sent_authority) { + grpc_metadata_batch_add_head( + &op->data.metadata, &calld->authority, + GRPC_MDELEM_REF(channeld->default_authority)); + } grpc_metadata_batch_add_tail(&op->data.metadata, &calld->te_trailers, GRPC_MDELEM_REF(channeld->te_trailers)); grpc_metadata_batch_add_tail(&op->data.metadata, &calld->content_type, @@ -162,6 +175,7 @@ static void init_call_elem(grpc_call_element *elem, call_data *calld = elem->call_data; calld->sent_initial_metadata = 0; calld->got_initial_metadata = 0; + calld->sent_authority = 0; calld->on_done_recv = NULL; grpc_iomgr_closure_init(&calld->hc_on_recv, hc_on_recv, elem); if (initial_op) hc_mutate_op(elem, initial_op); @@ -233,7 +247,7 @@ static grpc_mdstr *user_agent_from_args(grpc_mdctx *mdctx, tmp = gpr_strvec_flatten(&v, NULL); gpr_strvec_destroy(&v); - result = grpc_mdstr_from_string(mdctx, tmp); + result = grpc_mdstr_from_string(mdctx, tmp, 0); gpr_free(tmp); return result; @@ -241,8 +255,10 @@ static grpc_mdstr *user_agent_from_args(grpc_mdctx *mdctx, /* Constructor for channel_data */ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, - const grpc_channel_args *args, grpc_mdctx *mdctx, - int is_first, int is_last) { + const grpc_channel_args *channel_args, + grpc_mdctx *mdctx, int is_first, int is_last) { + size_t i; + /* grab pointers to our data from the channel element */ channel_data *channeld = elem->channel_data; @@ -251,17 +267,32 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, path */ GPR_ASSERT(!is_last); + channeld->default_authority = NULL; + if (channel_args) { + for (i = 0; i < channel_args->num_args; i++) { + if (0 == strcmp(channel_args->args[i].key, GRPC_ARG_DEFAULT_AUTHORITY)) { + if (channel_args->args[i].type != GRPC_ARG_STRING) { + gpr_log(GPR_ERROR, "%s: must be an string", + GRPC_ARG_DEFAULT_AUTHORITY); + } else { + channeld->default_authority = grpc_mdelem_from_strings( + mdctx, ":authority", channel_args->args[i].value.string); + } + } + } + } + /* initialize members */ channeld->te_trailers = grpc_mdelem_from_strings(mdctx, "te", "trailers"); channeld->method = grpc_mdelem_from_strings(mdctx, ":method", "POST"); - channeld->scheme = - grpc_mdelem_from_strings(mdctx, ":scheme", scheme_from_args(args)); + channeld->scheme = grpc_mdelem_from_strings(mdctx, ":scheme", + scheme_from_args(channel_args)); channeld->content_type = grpc_mdelem_from_strings(mdctx, "content-type", "application/grpc"); channeld->status = grpc_mdelem_from_strings(mdctx, ":status", "200"); channeld->user_agent = grpc_mdelem_from_metadata_strings( - mdctx, grpc_mdstr_from_string(mdctx, "user-agent"), - user_agent_from_args(mdctx, args)); + mdctx, grpc_mdstr_from_string(mdctx, "user-agent", 0), + user_agent_from_args(mdctx, channel_args)); } /* Destructor for channel data */ @@ -275,6 +306,9 @@ static void destroy_channel_elem(grpc_channel_element *elem) { GRPC_MDELEM_UNREF(channeld->content_type); GRPC_MDELEM_UNREF(channeld->status); GRPC_MDELEM_UNREF(channeld->user_agent); + if (channeld->default_authority) { + GRPC_MDELEM_UNREF(channeld->default_authority); + } } const grpc_channel_filter grpc_http_client_filter = { diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index 7c798d2fb4..0955ae319a 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -44,6 +44,7 @@ typedef struct call_data { gpr_uint8 sent_status; gpr_uint8 seen_scheme; gpr_uint8 seen_te_trailers; + gpr_uint8 seen_authority; grpc_linked_mdelem status; grpc_stream_op_buffer *recv_ops; @@ -125,6 +126,9 @@ static grpc_mdelem *server_filter(void *user_data, grpc_mdelem *md) { } calld->seen_path = 1; return md; + } else if (md->key == channeld->authority_key) { + calld->seen_authority = 1; + return md; } else if (md->key == channeld->host_key) { /* translate host to :authority since :authority may be omitted */ @@ -132,6 +136,7 @@ static grpc_mdelem *server_filter(void *user_data, grpc_mdelem *md) { channeld->mdctx, GRPC_MDSTR_REF(channeld->authority_key), GRPC_MDSTR_REF(md->value)); GRPC_MDELEM_UNREF(md); + calld->seen_authority = 1; return authority; } else { return md; @@ -154,12 +159,15 @@ static void hs_on_recv(void *user_data, int success) { (:method, :scheme, content-type, with :path and :authority covered at the channel level right now) */ if (calld->seen_post && calld->seen_scheme && calld->seen_te_trailers && - calld->seen_path) { + calld->seen_path && calld->seen_authority) { /* do nothing */ } else { if (!calld->seen_path) { gpr_log(GPR_ERROR, "Missing :path header"); } + if (!calld->seen_authority) { + gpr_log(GPR_ERROR, "Missing :authority header"); + } if (!calld->seen_post) { gpr_log(GPR_ERROR, "Missing :method header"); } @@ -250,9 +258,9 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, channeld->http_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "http"); channeld->https_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "https"); channeld->grpc_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "grpc"); - channeld->path_key = grpc_mdstr_from_string(mdctx, ":path"); - channeld->authority_key = grpc_mdstr_from_string(mdctx, ":authority"); - channeld->host_key = grpc_mdstr_from_string(mdctx, "host"); + channeld->path_key = grpc_mdstr_from_string(mdctx, ":path", 0); + channeld->authority_key = grpc_mdstr_from_string(mdctx, ":authority", 0); + channeld->host_key = grpc_mdstr_from_string(mdctx, "host", 0); channeld->content_type = grpc_mdelem_from_strings(mdctx, "content-type", "application/grpc"); diff --git a/src/core/client_config/lb_policies/pick_first.c b/src/core/client_config/lb_policies/pick_first.c index 73da624aff..5ae2e0ea52 100644 --- a/src/core/client_config/lb_policies/pick_first.c +++ b/src/core/client_config/lb_policies/pick_first.c @@ -62,6 +62,8 @@ typedef struct { grpc_subchannel *selected; /** have we started picking? */ int started_picking; + /** are we shut down? */ + int shutdown; /** which subchannel are we watching? */ size_t checking_subchannel; /** what is the connectivity of that channel? */ @@ -73,12 +75,30 @@ typedef struct { grpc_connectivity_state_tracker state_tracker; } pick_first_lb_policy; +static void del_interested_parties_locked(pick_first_lb_policy *p) { + pending_pick *pp; + for (pp = p->pending_picks; pp; pp = pp->next) { + grpc_subchannel_del_interested_party(p->subchannels[p->checking_subchannel], + pp->pollset); + } +} + +static void add_interested_parties_locked(pick_first_lb_policy *p) { + pending_pick *pp; + for (pp = p->pending_picks; pp; pp = pp->next) { + grpc_subchannel_add_interested_party(p->subchannels[p->checking_subchannel], + pp->pollset); + } +} + void pf_destroy(grpc_lb_policy *pol) { pick_first_lb_policy *p = (pick_first_lb_policy *)pol; size_t i; + del_interested_parties_locked(p); for (i = 0; i < p->num_subchannels; i++) { GRPC_SUBCHANNEL_UNREF(p->subchannels[i], "pick_first"); } + grpc_connectivity_state_destroy(&p->state_tracker); gpr_free(p->subchannels); gpr_mu_destroy(&p->mu); gpr_free(p); @@ -88,12 +108,35 @@ void pf_shutdown(grpc_lb_policy *pol) { pick_first_lb_policy *p = (pick_first_lb_policy *)pol; pending_pick *pp; gpr_mu_lock(&p->mu); + del_interested_parties_locked(p); + p->shutdown = 1; while ((pp = p->pending_picks)) { p->pending_picks = pp->next; *pp->target = NULL; grpc_iomgr_add_delayed_callback(pp->on_complete, 0); gpr_free(pp); } + grpc_connectivity_state_set(&p->state_tracker, GRPC_CHANNEL_FATAL_FAILURE, + "shutdown"); + gpr_mu_unlock(&p->mu); +} + +static void start_picking(pick_first_lb_policy *p) { + p->started_picking = 1; + p->checking_subchannel = 0; + p->checking_connectivity = GRPC_CHANNEL_IDLE; + GRPC_LB_POLICY_REF(&p->base, "pick_first_connectivity"); + grpc_subchannel_notify_on_state_change(p->subchannels[p->checking_subchannel], + &p->checking_connectivity, + &p->connectivity_changed); +} + +void pf_exit_idle(grpc_lb_policy *pol) { + pick_first_lb_policy *p = (pick_first_lb_policy *)pol; + gpr_mu_lock(&p->mu); + if (!p->started_picking) { + start_picking(p); + } gpr_mu_unlock(&p->mu); } @@ -109,13 +152,7 @@ void pf_pick(grpc_lb_policy *pol, grpc_pollset *pollset, on_complete->cb(on_complete->cb_arg, 1); } else { if (!p->started_picking) { - p->started_picking = 1; - p->checking_subchannel = 0; - p->checking_connectivity = GRPC_CHANNEL_IDLE; - GRPC_LB_POLICY_REF(pol, "pick_first_connectivity"); - grpc_subchannel_notify_on_state_change( - p->subchannels[p->checking_subchannel], &p->checking_connectivity, - &p->connectivity_changed); + start_picking(p); } grpc_subchannel_add_interested_party(p->subchannels[p->checking_subchannel], pollset); @@ -129,77 +166,97 @@ void pf_pick(grpc_lb_policy *pol, grpc_pollset *pollset, } } -static void del_interested_parties_locked(pick_first_lb_policy *p) { - pending_pick *pp; - for (pp = p->pending_picks; pp; pp = pp->next) { - grpc_subchannel_del_interested_party(p->subchannels[p->checking_subchannel], - pp->pollset); - } -} - -static void add_interested_parties_locked(pick_first_lb_policy *p) { - pending_pick *pp; - for (pp = p->pending_picks; pp; pp = pp->next) { - grpc_subchannel_add_interested_party(p->subchannels[p->checking_subchannel], - pp->pollset); - } -} - static void pf_connectivity_changed(void *arg, int iomgr_success) { pick_first_lb_policy *p = arg; pending_pick *pp; int unref = 0; gpr_mu_lock(&p->mu); -loop: - switch (p->checking_connectivity) { - case GRPC_CHANNEL_READY: - p->selected = p->subchannels[p->checking_subchannel]; - while ((pp = p->pending_picks)) { - p->pending_picks = pp->next; - *pp->target = p->selected; - grpc_subchannel_del_interested_party(p->selected, pp->pollset); - grpc_iomgr_add_delayed_callback(pp->on_complete, 1); - gpr_free(pp); - } - unref = 1; - break; - case GRPC_CHANNEL_TRANSIENT_FAILURE: - del_interested_parties_locked(p); - p->checking_subchannel = - (p->checking_subchannel + 1) % p->num_subchannels; - p->checking_connectivity = grpc_subchannel_check_connectivity( - p->subchannels[p->checking_subchannel]); - add_interested_parties_locked(p); - goto loop; - case GRPC_CHANNEL_CONNECTING: - case GRPC_CHANNEL_IDLE: + + if (p->shutdown) { + unref = 1; + } else if (p->selected != NULL) { + grpc_connectivity_state_set(&p->state_tracker, p->checking_connectivity, + "selected_changed"); + if (p->checking_connectivity != GRPC_CHANNEL_FATAL_FAILURE) { grpc_subchannel_notify_on_state_change( - p->subchannels[p->checking_subchannel], &p->checking_connectivity, - &p->connectivity_changed); - break; - case GRPC_CHANNEL_FATAL_FAILURE: - del_interested_parties_locked(p); - GPR_SWAP(grpc_subchannel *, p->subchannels[p->checking_subchannel], - p->subchannels[p->num_subchannels - 1]); - p->num_subchannels--; - GRPC_SUBCHANNEL_UNREF(p->subchannels[p->num_subchannels], "pick_first"); - if (p->num_subchannels == 0) { + p->selected, &p->checking_connectivity, &p->connectivity_changed); + } else { + unref = 1; + } + } else { + loop: + switch (p->checking_connectivity) { + case GRPC_CHANNEL_READY: + grpc_connectivity_state_set(&p->state_tracker, GRPC_CHANNEL_READY, + "connecting_ready"); + p->selected = p->subchannels[p->checking_subchannel]; while ((pp = p->pending_picks)) { p->pending_picks = pp->next; - *pp->target = NULL; + *pp->target = p->selected; + grpc_subchannel_del_interested_party(p->selected, pp->pollset); grpc_iomgr_add_delayed_callback(pp->on_complete, 1); gpr_free(pp); } - unref = 1; - } else { - p->checking_subchannel %= p->num_subchannels; + grpc_subchannel_notify_on_state_change( + p->selected, &p->checking_connectivity, &p->connectivity_changed); + break; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + grpc_connectivity_state_set(&p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "connecting_transient_failure"); + del_interested_parties_locked(p); + p->checking_subchannel = + (p->checking_subchannel + 1) % p->num_subchannels; p->checking_connectivity = grpc_subchannel_check_connectivity( p->subchannels[p->checking_subchannel]); add_interested_parties_locked(p); - goto loop; - } + if (p->checking_connectivity == GRPC_CHANNEL_TRANSIENT_FAILURE) { + grpc_subchannel_notify_on_state_change( + p->subchannels[p->checking_subchannel], &p->checking_connectivity, + &p->connectivity_changed); + } else { + goto loop; + } + break; + case GRPC_CHANNEL_CONNECTING: + case GRPC_CHANNEL_IDLE: + grpc_connectivity_state_set(&p->state_tracker, p->checking_connectivity, + "connecting_changed"); + grpc_subchannel_notify_on_state_change( + p->subchannels[p->checking_subchannel], &p->checking_connectivity, + &p->connectivity_changed); + break; + case GRPC_CHANNEL_FATAL_FAILURE: + del_interested_parties_locked(p); + GPR_SWAP(grpc_subchannel *, p->subchannels[p->checking_subchannel], + p->subchannels[p->num_subchannels - 1]); + p->num_subchannels--; + GRPC_SUBCHANNEL_UNREF(p->subchannels[p->num_subchannels], "pick_first"); + if (p->num_subchannels == 0) { + grpc_connectivity_state_set(&p->state_tracker, + GRPC_CHANNEL_FATAL_FAILURE, + "no_more_channels"); + while ((pp = p->pending_picks)) { + p->pending_picks = pp->next; + *pp->target = NULL; + grpc_iomgr_add_delayed_callback(pp->on_complete, 1); + gpr_free(pp); + } + unref = 1; + } else { + grpc_connectivity_state_set(&p->state_tracker, + GRPC_CHANNEL_TRANSIENT_FAILURE, + "subchannel_failed"); + p->checking_subchannel %= p->num_subchannels; + p->checking_connectivity = grpc_subchannel_check_connectivity( + p->subchannels[p->checking_subchannel]); + add_interested_parties_locked(p); + goto loop; + } + } } + gpr_mu_unlock(&p->mu); if (unref) { @@ -249,8 +306,13 @@ static void pf_notify_on_state_change(grpc_lb_policy *pol, } static const grpc_lb_policy_vtable pick_first_lb_policy_vtable = { - pf_destroy, pf_shutdown, pf_pick, - pf_broadcast, pf_check_connectivity, pf_notify_on_state_change}; + pf_destroy, + pf_shutdown, + pf_pick, + pf_exit_idle, + pf_broadcast, + pf_check_connectivity, + pf_notify_on_state_change}; grpc_lb_policy *grpc_create_pick_first_lb_policy(grpc_subchannel **subchannels, size_t num_subchannels) { @@ -260,6 +322,8 @@ grpc_lb_policy *grpc_create_pick_first_lb_policy(grpc_subchannel **subchannels, grpc_lb_policy_init(&p->base, &pick_first_lb_policy_vtable); p->subchannels = gpr_malloc(sizeof(grpc_subchannel *) * num_subchannels); p->num_subchannels = num_subchannels; + grpc_connectivity_state_init(&p->state_tracker, GRPC_CHANNEL_IDLE, + "pick_first"); memcpy(p->subchannels, subchannels, sizeof(grpc_subchannel *) * num_subchannels); grpc_iomgr_closure_init(&p->connectivity_changed, pf_connectivity_changed, p); diff --git a/src/core/client_config/lb_policy.c b/src/core/client_config/lb_policy.c index 6d1c788742..90ec44432f 100644 --- a/src/core/client_config/lb_policy.c +++ b/src/core/client_config/lb_policy.c @@ -77,3 +77,18 @@ void grpc_lb_policy_pick(grpc_lb_policy *policy, grpc_pollset *pollset, void grpc_lb_policy_broadcast(grpc_lb_policy *policy, grpc_transport_op *op) { policy->vtable->broadcast(policy, op); } + +void grpc_lb_policy_exit_idle(grpc_lb_policy *policy) { + policy->vtable->exit_idle(policy); +} + +void grpc_lb_policy_notify_on_state_change(grpc_lb_policy *policy, + grpc_connectivity_state *state, + grpc_iomgr_closure *closure) { + policy->vtable->notify_on_state_change(policy, state, closure); +} + +grpc_connectivity_state grpc_lb_policy_check_connectivity( + grpc_lb_policy *policy) { + return policy->vtable->check_connectivity(policy); +} diff --git a/src/core/client_config/lb_policy.h b/src/core/client_config/lb_policy.h index a468f761cc..3f7ca8f28d 100644 --- a/src/core/client_config/lb_policy.h +++ b/src/core/client_config/lb_policy.h @@ -59,6 +59,9 @@ struct grpc_lb_policy_vtable { grpc_metadata_batch *initial_metadata, grpc_subchannel **target, grpc_iomgr_closure *on_complete); + /** try to enter a READY connectivity state */ + void (*exit_idle)(grpc_lb_policy *policy); + /** broadcast a transport op to all subchannels */ void (*broadcast)(grpc_lb_policy *policy, grpc_transport_op *op); @@ -106,4 +109,13 @@ void grpc_lb_policy_pick(grpc_lb_policy *policy, grpc_pollset *pollset, void grpc_lb_policy_broadcast(grpc_lb_policy *policy, grpc_transport_op *op); +void grpc_lb_policy_exit_idle(grpc_lb_policy *policy); + +void grpc_lb_policy_notify_on_state_change(grpc_lb_policy *policy, + grpc_connectivity_state *state, + grpc_iomgr_closure *closure); + +grpc_connectivity_state grpc_lb_policy_check_connectivity( + grpc_lb_policy *policy); + #endif /* GRPC_INTERNAL_CORE_CONFIG_LB_POLICY_H */ diff --git a/src/core/client_config/resolvers/dns_resolver.c b/src/core/client_config/resolvers/dns_resolver.c index ac401bc4d3..827b1a2be5 100644 --- a/src/core/client_config/resolvers/dns_resolver.c +++ b/src/core/client_config/resolvers/dns_resolver.c @@ -36,9 +36,11 @@ #include <string.h> #include <grpc/support/alloc.h> +#include <grpc/support/host_port.h> #include <grpc/support/string_util.h> #include "src/core/client_config/lb_policies/pick_first.h" +#include "src/core/client_config/subchannel_factory_decorators/add_channel_arg.h" #include "src/core/iomgr/resolve_address.h" #include "src/core/support/string.h" @@ -201,6 +203,9 @@ static grpc_resolver *dns_create( grpc_subchannel_factory *subchannel_factory) { dns_resolver *r; const char *path = uri->path; + grpc_arg default_host_arg; + char *host; + char *port; if (0 != strcmp(uri->authority, "")) { gpr_log(GPR_ERROR, "authority based uri's not supported"); @@ -209,6 +214,16 @@ static grpc_resolver *dns_create( if (path[0] == '/') ++path; + gpr_split_host_port(path, &host, &port); + + default_host_arg.type = GRPC_ARG_STRING; + default_host_arg.key = GRPC_ARG_DEFAULT_AUTHORITY; + default_host_arg.value.string = host; + subchannel_factory = grpc_subchannel_factory_add_channel_arg(subchannel_factory, &default_host_arg); + + gpr_free(host); + gpr_free(port); + r = gpr_malloc(sizeof(dns_resolver)); memset(r, 0, sizeof(*r)); gpr_ref_init(&r->refs, 1); @@ -218,7 +233,6 @@ static grpc_resolver *dns_create( r->default_port = gpr_strdup(default_port); r->subchannel_factory = subchannel_factory; r->lb_policy_factory = lb_policy_factory; - grpc_subchannel_factory_ref(subchannel_factory); return &r->base; } diff --git a/src/core/client_config/subchannel.c b/src/core/client_config/subchannel.c index 358b907e59..ca52c75beb 100644 --- a/src/core/client_config/subchannel.c +++ b/src/core/client_config/subchannel.c @@ -38,9 +38,17 @@ #include <grpc/support/alloc.h> #include "src/core/channel/channel_args.h" +#include "src/core/channel/client_channel.h" #include "src/core/channel/connected_channel.h" #include "src/core/iomgr/alarm.h" #include "src/core/transport/connectivity_state.h" +#include "src/core/surface/channel.h" + +#define GRPC_SUBCHANNEL_MIN_CONNECT_TIMEOUT_SECONDS 20 +#define GRPC_SUBCHANNEL_INITIAL_CONNECT_BACKOFF_SECONDS 1 +#define GRPC_SUBCHANNEL_RECONNECT_BACKOFF_MULTIPLIER 1.6 +#define GRPC_SUBCHANNEL_RECONNECT_MAX_BACKOFF_SECONDS 120 +#define GRPC_SUBCHANNEL_RECONNECT_JITTER 0.2 typedef struct { /* all fields protected by subchannel->mu */ @@ -94,8 +102,10 @@ struct grpc_subchannel { grpc_iomgr_closure connected; /** pollset_set tracking who's interested in a connection - being setup */ - grpc_pollset_set pollset_set; + being setup - owned by the master channel (in particular the + client_channel + filter there-in) */ + grpc_pollset_set *pollset_set; /** mutex protecting remaining elements */ gpr_mu mu; @@ -121,6 +131,8 @@ struct grpc_subchannel { int have_alarm; /** our alarm */ grpc_alarm alarm; + /** current random value */ + gpr_uint32 random; }; struct grpc_subchannel_call { @@ -132,7 +144,8 @@ struct grpc_subchannel_call { #define CHANNEL_STACK_FROM_CONNECTION(con) ((grpc_channel_stack *)((con) + 1)) static grpc_subchannel_call *create_call(connection *con); -static void connectivity_state_changed_locked(grpc_subchannel *c); +static void connectivity_state_changed_locked(grpc_subchannel *c, + const char *reason); static grpc_connectivity_state compute_connectivity_locked(grpc_subchannel *c); static gpr_timespec compute_connect_deadline(grpc_subchannel *c); static void subchannel_connected(void *subchannel, int iomgr_success); @@ -244,7 +257,6 @@ static void subchannel_destroy(grpc_subchannel *c) { grpc_channel_args_destroy(c->args); gpr_free(c->addr); grpc_mdctx_unref(c->mdctx); - grpc_pollset_set_destroy(&c->pollset_set); grpc_connectivity_state_destroy(&c->state_tracker); grpc_connector_unref(c->connector); gpr_free(c); @@ -252,17 +264,23 @@ static void subchannel_destroy(grpc_subchannel *c) { void grpc_subchannel_add_interested_party(grpc_subchannel *c, grpc_pollset *pollset) { - grpc_pollset_set_add_pollset(&c->pollset_set, pollset); + grpc_pollset_set_add_pollset(c->pollset_set, pollset); } void grpc_subchannel_del_interested_party(grpc_subchannel *c, grpc_pollset *pollset) { - grpc_pollset_set_del_pollset(&c->pollset_set, pollset); + grpc_pollset_set_del_pollset(c->pollset_set, pollset); +} + +static gpr_uint32 random_seed() { + return (gpr_uint32)(gpr_time_to_millis(gpr_now(GPR_CLOCK_MONOTONIC))); } grpc_subchannel *grpc_subchannel_create(grpc_connector *connector, grpc_subchannel_args *args) { grpc_subchannel *c = gpr_malloc(sizeof(*c)); + grpc_channel_element *parent_elem = grpc_channel_stack_last_element( + grpc_channel_get_channel_stack(args->master)); memset(c, 0, sizeof(*c)); c->refs = 1; c->connector = connector; @@ -277,10 +295,12 @@ grpc_subchannel *grpc_subchannel_create(grpc_connector *connector, c->args = grpc_channel_args_copy(args->args); c->mdctx = args->mdctx; c->master = args->master; + c->pollset_set = grpc_client_channel_get_connecting_pollset_set(parent_elem); + c->random = random_seed(); grpc_mdctx_ref(c->mdctx); - grpc_pollset_set_init(&c->pollset_set); grpc_iomgr_closure_init(&c->connected, subchannel_connected, c); - grpc_connectivity_state_init(&c->state_tracker, GRPC_CHANNEL_IDLE); + grpc_connectivity_state_init(&c->state_tracker, GRPC_CHANNEL_IDLE, + "subchannel"); gpr_mu_init(&c->mu); return c; } @@ -288,7 +308,7 @@ grpc_subchannel *grpc_subchannel_create(grpc_connector *connector, static void continue_connect(grpc_subchannel *c) { grpc_connect_in_args args; - args.interested_parties = &c->pollset_set; + args.interested_parties = c->pollset_set; args.addr = c->addr; args.addr_len = c->addr_len; args.deadline = compute_connect_deadline(c); @@ -300,15 +320,16 @@ static void continue_connect(grpc_subchannel *c) { } static void start_connect(grpc_subchannel *c) { - gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); - c->next_attempt = now; - c->backoff_delta = gpr_time_from_seconds(1, GPR_TIMESPAN); - + c->backoff_delta = gpr_time_from_seconds( + GRPC_SUBCHANNEL_INITIAL_CONNECT_BACKOFF_SECONDS, GPR_TIMESPAN); + c->next_attempt = + gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), c->backoff_delta); continue_connect(c); } static void continue_creating_call(void *arg, int iomgr_success) { waiting_for_connect *w4c = arg; + grpc_subchannel_del_interested_party(w4c->subchannel, w4c->pollset); grpc_subchannel_create_call(w4c->subchannel, w4c->pollset, w4c->target, w4c->notify); GRPC_SUBCHANNEL_UNREF(w4c->subchannel, "waiting_for_connect"); @@ -341,9 +362,10 @@ void grpc_subchannel_create_call(grpc_subchannel *c, grpc_pollset *pollset, grpc_subchannel_add_interested_party(c, pollset); if (!c->connecting) { c->connecting = 1; - connectivity_state_changed_locked(c); + connectivity_state_changed_locked(c, "create_call"); /* released by connection */ SUBCHANNEL_REF_LOCKED(c, "connecting"); + GRPC_CHANNEL_INTERNAL_REF(c->master, "connecting"); gpr_mu_unlock(&c->mu); start_connect(c); @@ -372,7 +394,8 @@ void grpc_subchannel_notify_on_state_change(grpc_subchannel *c, c->connecting = 1; /* released by connection */ SUBCHANNEL_REF_LOCKED(c, "connecting"); - connectivity_state_changed_locked(c); + GRPC_CHANNEL_INTERNAL_REF(c->master, "connecting"); + connectivity_state_changed_locked(c, "state_change"); } gpr_mu_unlock(&c->mu); if (do_connect) { @@ -388,7 +411,7 @@ void grpc_subchannel_process_transport_op(grpc_subchannel *c, gpr_mu_lock(&c->mu); if (op->disconnect) { c->disconnected = 1; - connectivity_state_changed_locked(c); + connectivity_state_changed_locked(c, "disconnect"); if (c->have_alarm) { cancel_alarm = 1; } @@ -456,13 +479,15 @@ static void on_state_changed(void *p, int iomgr_success) { destroy_connection = sw->subchannel->active; } sw->subchannel->active = NULL; - grpc_connectivity_state_set(&c->state_tracker, - GRPC_CHANNEL_TRANSIENT_FAILURE); + grpc_connectivity_state_set( + &c->state_tracker, c->disconnected ? GRPC_CHANNEL_FATAL_FAILURE + : GRPC_CHANNEL_TRANSIENT_FAILURE, + "connection_failed"); break; } done: - connectivity_state_changed_locked(c); + connectivity_state_changed_locked(c, "transport_state_changed"); destroy = SUBCHANNEL_UNREF_LOCKED(c, "state_watcher"); gpr_free(sw); gpr_mu_unlock(mu); @@ -519,6 +544,8 @@ static void publish_transport(grpc_subchannel *c) { gpr_free(sw); gpr_free(filters); grpc_channel_stack_destroy(stk); + GRPC_CHANNEL_INTERNAL_UNREF(c->master, "connecting"); + GRPC_SUBCHANNEL_UNREF(c, "connecting"); return; } @@ -536,14 +563,16 @@ static void publish_transport(grpc_subchannel *c) { memset(&op, 0, sizeof(op)); op.connectivity_state = &sw->connectivity_state; op.on_connectivity_state_change = &sw->closure; + op.bind_pollset_set = c->pollset_set; SUBCHANNEL_REF_LOCKED(c, "state_watcher"); + GRPC_CHANNEL_INTERNAL_UNREF(c->master, "connecting"); GPR_ASSERT(!SUBCHANNEL_UNREF_LOCKED(c, "connecting")); elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CONNECTION(c->active), 0); elem->filter->start_transport_op(elem, &op); /* signal completion */ - connectivity_state_changed_locked(c); + connectivity_state_changed_locked(c, "connected"); while ((w4c = c->waiting)) { c->waiting = w4c->next; grpc_iomgr_add_callback(&w4c->continuation); @@ -558,6 +587,35 @@ static void publish_transport(grpc_subchannel *c) { } } +/* Generate a random number between 0 and 1. */ +static double generate_uniform_random_number(grpc_subchannel *c) { + c->random = (1103515245 * c->random + 12345) % ((gpr_uint32)1 << 31); + return c->random / (double)((gpr_uint32)1 << 31); +} + +/* Update backoff_delta and next_attempt in subchannel */ +static void update_reconnect_parameters(grpc_subchannel *c) { + gpr_int32 backoff_delta_millis, jitter; + gpr_int32 max_backoff_millis = + GRPC_SUBCHANNEL_RECONNECT_MAX_BACKOFF_SECONDS * 1000; + double jitter_range; + backoff_delta_millis = + (gpr_int32)(gpr_time_to_millis(c->backoff_delta) * + GRPC_SUBCHANNEL_RECONNECT_BACKOFF_MULTIPLIER); + if (backoff_delta_millis > max_backoff_millis) { + backoff_delta_millis = max_backoff_millis; + } + c->backoff_delta = gpr_time_from_millis(backoff_delta_millis, GPR_TIMESPAN); + c->next_attempt = + gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), c->backoff_delta); + + jitter_range = GRPC_SUBCHANNEL_RECONNECT_JITTER * backoff_delta_millis; + jitter = + (gpr_int32)((2 * generate_uniform_random_number(c) - 1) * jitter_range); + c->next_attempt = + gpr_time_add(c->next_attempt, gpr_time_from_millis(jitter, GPR_TIMESPAN)); +} + static void on_alarm(void *arg, int iomgr_success) { grpc_subchannel *c = arg; gpr_mu_lock(&c->mu); @@ -565,11 +623,13 @@ static void on_alarm(void *arg, int iomgr_success) { if (c->disconnected) { iomgr_success = 0; } - connectivity_state_changed_locked(c); + connectivity_state_changed_locked(c, "alarm"); gpr_mu_unlock(&c->mu); if (iomgr_success) { + update_reconnect_parameters(c); continue_connect(c); } else { + GRPC_CHANNEL_INTERNAL_UNREF(c->master, "connecting"); GRPC_SUBCHANNEL_UNREF(c, "connecting"); } } @@ -579,19 +639,25 @@ static void subchannel_connected(void *arg, int iomgr_success) { if (c->connecting_result.transport != NULL) { publish_transport(c); } else { + gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); gpr_mu_lock(&c->mu); - connectivity_state_changed_locked(c); GPR_ASSERT(!c->have_alarm); c->have_alarm = 1; - c->next_attempt = gpr_time_add(c->next_attempt, c->backoff_delta); - c->backoff_delta = gpr_time_add(c->backoff_delta, c->backoff_delta); - grpc_alarm_init(&c->alarm, c->next_attempt, on_alarm, c, gpr_now(GPR_CLOCK_MONOTONIC)); + connectivity_state_changed_locked(c, "connect_failed"); + grpc_alarm_init(&c->alarm, c->next_attempt, on_alarm, c, now); gpr_mu_unlock(&c->mu); } } static gpr_timespec compute_connect_deadline(grpc_subchannel *c) { - return gpr_time_add(c->next_attempt, c->backoff_delta); + gpr_timespec current_deadline = + gpr_time_add(c->next_attempt, c->backoff_delta); + gpr_timespec min_deadline = gpr_time_add( + gpr_now(GPR_CLOCK_MONOTONIC), + gpr_time_from_seconds(GRPC_SUBCHANNEL_MIN_CONNECT_TIMEOUT_SECONDS, + GPR_TIMESPAN)); + return gpr_time_cmp(current_deadline, min_deadline) > 0 ? current_deadline + : min_deadline; } static grpc_connectivity_state compute_connectivity_locked(grpc_subchannel *c) { @@ -610,9 +676,10 @@ static grpc_connectivity_state compute_connectivity_locked(grpc_subchannel *c) { return GRPC_CHANNEL_IDLE; } -static void connectivity_state_changed_locked(grpc_subchannel *c) { +static void connectivity_state_changed_locked(grpc_subchannel *c, + const char *reason) { grpc_connectivity_state current = compute_connectivity_locked(c); - grpc_connectivity_state_set(&c->state_tracker, current); + grpc_connectivity_state_set(&c->state_tracker, current, reason); } /* diff --git a/src/core/client_config/subchannel_factory_decorators/add_channel_arg.c b/src/core/client_config/subchannel_factory_decorators/add_channel_arg.c new file mode 100644 index 0000000000..7dc6d99ebe --- /dev/null +++ b/src/core/client_config/subchannel_factory_decorators/add_channel_arg.c @@ -0,0 +1,43 @@ +/* + * + * 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. + * + */ + +#include "src/core/client_config/subchannel_factory_decorators/add_channel_arg.h" +#include "src/core/client_config/subchannel_factory_decorators/merge_channel_args.h" + +grpc_subchannel_factory *grpc_subchannel_factory_add_channel_arg( + grpc_subchannel_factory *input, const grpc_arg *arg) { + grpc_channel_args args; + args.num_args = 1; + args.args = (grpc_arg *)arg; + return grpc_subchannel_factory_merge_channel_args(input, &args); +} diff --git a/src/core/client_config/subchannel_factory_decorators/add_channel_arg.h b/src/core/client_config/subchannel_factory_decorators/add_channel_arg.h new file mode 100644 index 0000000000..1937623374 --- /dev/null +++ b/src/core/client_config/subchannel_factory_decorators/add_channel_arg.h @@ -0,0 +1,45 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_ADD_CHANNEL_ARG_H +#define GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_ADD_CHANNEL_ARG_H + +#include "src/core/client_config/subchannel_factory.h" + +/** Takes a subchannel factory, returns a new one that mutates incoming + channel_args by adding a new argument; ownership of input, arg is retained + by the caller. */ +grpc_subchannel_factory *grpc_subchannel_factory_add_channel_arg( + grpc_subchannel_factory *input, const grpc_arg *arg); + +#endif /* GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_ADD_CHANNEL_ARG_H */ diff --git a/src/core/client_config/subchannel_factory_decorators/merge_channel_args.c b/src/core/client_config/subchannel_factory_decorators/merge_channel_args.c new file mode 100644 index 0000000000..7e028857ac --- /dev/null +++ b/src/core/client_config/subchannel_factory_decorators/merge_channel_args.c @@ -0,0 +1,84 @@ +/* + * + * 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. + * + */ + +#include "src/core/client_config/subchannel_factory_decorators/merge_channel_args.h" +#include <grpc/support/alloc.h> +#include "src/core/channel/channel_args.h" + +typedef struct { + grpc_subchannel_factory base; + gpr_refcount refs; + grpc_subchannel_factory *wrapped; + grpc_channel_args *merge_args; +} merge_args_factory; + +static void merge_args_factory_ref(grpc_subchannel_factory *scf) { + merge_args_factory *f = (merge_args_factory *)scf; + gpr_ref(&f->refs); +} + +static void merge_args_factory_unref(grpc_subchannel_factory *scf) { + merge_args_factory *f = (merge_args_factory *)scf; + if (gpr_unref(&f->refs)) { + grpc_subchannel_factory_unref(f->wrapped); + grpc_channel_args_destroy(f->merge_args); + gpr_free(f); + } +} + +static grpc_subchannel *merge_args_factory_create_subchannel( + grpc_subchannel_factory *scf, grpc_subchannel_args *args) { + merge_args_factory *f = (merge_args_factory *)scf; + grpc_channel_args *final_args = + grpc_channel_args_merge(args->args, f->merge_args); + grpc_subchannel *s; + args->args = final_args; + s = grpc_subchannel_factory_create_subchannel(f->wrapped, args); + grpc_channel_args_destroy(final_args); + return s; +} + +static const grpc_subchannel_factory_vtable merge_args_factory_vtable = { + merge_args_factory_ref, merge_args_factory_unref, + merge_args_factory_create_subchannel}; + +grpc_subchannel_factory *grpc_subchannel_factory_merge_channel_args( + grpc_subchannel_factory *input, const grpc_channel_args *args) { + merge_args_factory *f = gpr_malloc(sizeof(*f)); + f->base.vtable = &merge_args_factory_vtable; + gpr_ref_init(&f->refs, 1); + grpc_subchannel_factory_ref(input); + f->wrapped = input; + f->merge_args = grpc_channel_args_copy(args); + return &f->base; +} diff --git a/src/core/client_config/subchannel_factory_decorators/merge_channel_args.h b/src/core/client_config/subchannel_factory_decorators/merge_channel_args.h new file mode 100644 index 0000000000..73a03b752f --- /dev/null +++ b/src/core/client_config/subchannel_factory_decorators/merge_channel_args.h @@ -0,0 +1,45 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_MERGE_CHANNEL_ARGS_H +#define GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_MERGE_CHANNEL_ARGS_H + +#include "src/core/client_config/subchannel_factory.h" + +/** Takes a subchannel factory, returns a new one that mutates incoming + channel_args by adding a new argument; ownership of input, args is retained + by the caller. */ +grpc_subchannel_factory *grpc_subchannel_factory_merge_channel_args( + grpc_subchannel_factory *input, const grpc_channel_args *args); + +#endif /* GRPC_INTERNAL_CORE_CLIENT_CONFIG_SUBCHANNEL_FACTORY_DECORATORS_MERGE_CHANNEL_ARGS_H */ diff --git a/src/core/httpcli/httpcli.c b/src/core/httpcli/httpcli.c index 65997d5f44..9012070e8e 100644 --- a/src/core/httpcli/httpcli.c +++ b/src/core/httpcli/httpcli.c @@ -40,9 +40,7 @@ #include "src/core/iomgr/resolve_address.h" #include "src/core/iomgr/tcp_client.h" #include "src/core/httpcli/format_request.h" -#include "src/core/httpcli/httpcli_security_connector.h" #include "src/core/httpcli/parser.h" -#include "src/core/security/secure_transport_setup.h" #include "src/core/support/string.h" #include <grpc/support/alloc.h> #include <grpc/support/log.h> @@ -57,7 +55,7 @@ typedef struct { char *host; gpr_timespec deadline; int have_read_byte; - int use_ssl; + const grpc_httpcli_handshaker *handshaker; grpc_httpcli_response_cb on_response; void *user_data; grpc_httpcli_context *context; @@ -68,6 +66,16 @@ typedef struct { static grpc_httpcli_get_override g_get_override = NULL; static grpc_httpcli_post_override g_post_override = NULL; +static void plaintext_handshake(void *arg, grpc_endpoint *endpoint, + const char *host, + void (*on_done)(void *arg, + grpc_endpoint *endpoint)) { + on_done(arg, endpoint); +} + +const grpc_httpcli_handshaker grpc_httpcli_plaintext = {"http", + plaintext_handshake}; + void grpc_httpcli_context_init(grpc_httpcli_context *context) { grpc_pollset_set_init(&context->pollset_set); } @@ -163,18 +171,16 @@ static void start_write(internal_request *req) { } } -static void on_secure_transport_setup_done(void *rp, - grpc_security_status status, - grpc_endpoint *wrapped_endpoint, - grpc_endpoint *secure_endpoint) { - internal_request *req = rp; - if (status != GRPC_SECURITY_OK) { - gpr_log(GPR_ERROR, "Secure transport setup failed with error %d.", status); - finish(req, 0); - } else { - req->ep = secure_endpoint; - start_write(req); +static void on_handshake_done(void *arg, grpc_endpoint *ep) { + internal_request *req = arg; + + if (!ep) { + next_address(req); + return; } + + req->ep = ep; + start_write(req); } static void on_connected(void *arg, grpc_endpoint *tcp) { @@ -184,25 +190,7 @@ static void on_connected(void *arg, grpc_endpoint *tcp) { next_address(req); return; } - req->ep = tcp; - if (req->use_ssl) { - grpc_channel_security_connector *sc = NULL; - const unsigned char *pem_root_certs = NULL; - size_t pem_root_certs_size = grpc_get_default_ssl_roots(&pem_root_certs); - if (pem_root_certs == NULL || pem_root_certs_size == 0) { - gpr_log(GPR_ERROR, "Could not get default pem root certs."); - finish(req, 0); - return; - } - GPR_ASSERT(grpc_httpcli_ssl_channel_security_connector_create( - pem_root_certs, pem_root_certs_size, req->host, &sc) == - GRPC_SECURITY_OK); - grpc_setup_secure_transport(&sc->base, tcp, on_secure_transport_setup_done, - req); - GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "httpcli"); - } else { - start_write(req); - } + req->handshaker->handshake(req, tcp, req->host, on_handshake_done); } static void next_address(internal_request *req) { @@ -245,18 +233,17 @@ void grpc_httpcli_get(grpc_httpcli_context *context, grpc_pollset *pollset, req->on_response = on_response; req->user_data = user_data; req->deadline = deadline; - req->use_ssl = request->use_ssl; + req->handshaker = + request->handshaker ? request->handshaker : &grpc_httpcli_plaintext; req->context = context; req->pollset = pollset; gpr_asprintf(&name, "HTTP:GET:%s:%s", request->host, request->path); grpc_iomgr_register_object(&req->iomgr_obj, name); gpr_free(name); - if (req->use_ssl) { - req->host = gpr_strdup(request->host); - } + req->host = gpr_strdup(request->host); grpc_pollset_set_add_pollset(&req->context->pollset_set, req->pollset); - grpc_resolve_address(request->host, req->use_ssl ? "https" : "http", + grpc_resolve_address(request->host, req->handshaker->default_port, on_resolved, req); } @@ -279,18 +266,17 @@ void grpc_httpcli_post(grpc_httpcli_context *context, grpc_pollset *pollset, req->on_response = on_response; req->user_data = user_data; req->deadline = deadline; - req->use_ssl = request->use_ssl; + req->handshaker = + request->handshaker ? request->handshaker : &grpc_httpcli_plaintext; req->context = context; req->pollset = pollset; gpr_asprintf(&name, "HTTP:GET:%s:%s", request->host, request->path); grpc_iomgr_register_object(&req->iomgr_obj, name); gpr_free(name); - if (req->use_ssl) { - req->host = gpr_strdup(request->host); - } + req->host = gpr_strdup(request->host); grpc_pollset_set_add_pollset(&req->context->pollset_set, req->pollset); - grpc_resolve_address(request->host, req->use_ssl ? "https" : "http", + grpc_resolve_address(request->host, req->handshaker->default_port, on_resolved, req); } diff --git a/src/core/httpcli/httpcli.h b/src/core/httpcli/httpcli.h index ab98178f8a..c45966714c 100644 --- a/src/core/httpcli/httpcli.h +++ b/src/core/httpcli/httpcli.h @@ -38,6 +38,7 @@ #include <grpc/support/time.h> +#include "src/core/iomgr/endpoint.h" #include "src/core/iomgr/pollset_set.h" /* User agent this library reports */ @@ -58,6 +59,15 @@ typedef struct grpc_httpcli_context { grpc_pollset_set pollset_set; } grpc_httpcli_context; +typedef struct { + const char *default_port; + void (*handshake)(void *arg, grpc_endpoint *endpoint, const char *host, + void (*on_done)(void *arg, grpc_endpoint *endpoint)); +} grpc_httpcli_handshaker; + +extern const grpc_httpcli_handshaker grpc_httpcli_plaintext; +extern const grpc_httpcli_handshaker grpc_httpcli_ssl; + /* A request */ typedef struct grpc_httpcli_request { /* The host name to connect to */ @@ -69,8 +79,8 @@ typedef struct grpc_httpcli_request { Host, Connection, User-Agent */ size_t hdr_count; grpc_httpcli_header *hdrs; - /* whether to use ssl for the request */ - int use_ssl; + /* handshaker to use ssl for the request */ + const grpc_httpcli_handshaker *handshaker; } grpc_httpcli_request; /* A response */ diff --git a/src/core/httpcli/httpcli_security_connector.c b/src/core/httpcli/httpcli_security_connector.c index ce0d3d5a70..7887f9d530 100644 --- a/src/core/httpcli/httpcli_security_connector.c +++ b/src/core/httpcli/httpcli_security_connector.c @@ -31,7 +31,7 @@ * */ -#include "src/core/httpcli/httpcli_security_connector.h" +#include "src/core/httpcli/httpcli.h" #include <string.h> @@ -96,7 +96,7 @@ static grpc_security_status httpcli_ssl_check_peer(grpc_security_connector *sc, static grpc_security_connector_vtable httpcli_ssl_vtable = { httpcli_ssl_destroy, httpcli_ssl_create_handshaker, httpcli_ssl_check_peer}; -grpc_security_status grpc_httpcli_ssl_channel_security_connector_create( +static grpc_security_status httpcli_ssl_channel_security_connector_create( const unsigned char *pem_root_certs, size_t pem_root_certs_size, const char *secure_peer_name, grpc_channel_security_connector **sc) { tsi_result result = TSI_OK; @@ -130,3 +130,48 @@ grpc_security_status grpc_httpcli_ssl_channel_security_connector_create( *sc = &c->base; return GRPC_SECURITY_OK; } + +/* handshaker */ + +typedef struct { + void (*func)(void *arg, grpc_endpoint *endpoint); + void *arg; +} on_done_closure; + +static void on_secure_transport_setup_done(void *rp, + grpc_security_status status, + grpc_endpoint *wrapped_endpoint, + grpc_endpoint *secure_endpoint) { + on_done_closure *c = rp; + if (status != GRPC_SECURITY_OK) { + gpr_log(GPR_ERROR, "Secure transport setup failed with error %d.", status); + c->func(c->arg, NULL); + } else { + c->func(c->arg, secure_endpoint); + } + gpr_free(c); +} + +static void ssl_handshake(void *arg, grpc_endpoint *tcp, const char *host, + void (*on_done)(void *arg, grpc_endpoint *endpoint)) { + grpc_channel_security_connector *sc = NULL; + const unsigned char *pem_root_certs = NULL; + on_done_closure *c = gpr_malloc(sizeof(*c)); + size_t pem_root_certs_size = grpc_get_default_ssl_roots(&pem_root_certs); + if (pem_root_certs == NULL || pem_root_certs_size == 0) { + gpr_log(GPR_ERROR, "Could not get default pem root certs."); + on_done(arg, NULL); + gpr_free(c); + return; + } + c->func = on_done; + c->arg = arg; + GPR_ASSERT(httpcli_ssl_channel_security_connector_create( + pem_root_certs, pem_root_certs_size, host, &sc) == + GRPC_SECURITY_OK); + grpc_setup_secure_transport(&sc->base, tcp, on_secure_transport_setup_done, + c); + GRPC_SECURITY_CONNECTOR_UNREF(&sc->base, "httpcli"); +} + +const grpc_httpcli_handshaker grpc_httpcli_ssl = {"https", ssl_handshake}; diff --git a/src/core/iomgr/alarm.c b/src/core/iomgr/alarm.c index 931f746f75..68d33b9cf6 100644 --- a/src/core/iomgr/alarm.c +++ b/src/core/iomgr/alarm.c @@ -361,7 +361,9 @@ static int run_some_expired_alarms(gpr_mu *drop_mu, gpr_timespec now, int grpc_alarm_check(gpr_mu *drop_mu, gpr_timespec now, gpr_timespec *next) { GPR_ASSERT(now.clock_type == g_clock_type); - return run_some_expired_alarms(drop_mu, now, next, 1); + return run_some_expired_alarms( + drop_mu, now, next, + gpr_time_cmp(now, gpr_inf_future(now.clock_type)) != 0); } gpr_timespec grpc_alarm_list_next_timeout(void) { diff --git a/src/core/iomgr/endpoint.c b/src/core/iomgr/endpoint.c index f2b44aad03..744fe7656c 100644 --- a/src/core/iomgr/endpoint.c +++ b/src/core/iomgr/endpoint.c @@ -50,6 +50,10 @@ void grpc_endpoint_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset) { ep->vtable->add_to_pollset(ep, pollset); } +void grpc_endpoint_add_to_pollset_set(grpc_endpoint *ep, grpc_pollset_set *pollset_set) { + ep->vtable->add_to_pollset_set(ep, pollset_set); +} + void grpc_endpoint_shutdown(grpc_endpoint *ep) { ep->vtable->shutdown(ep); } void grpc_endpoint_destroy(grpc_endpoint *ep) { ep->vtable->destroy(ep); } diff --git a/src/core/iomgr/endpoint.h b/src/core/iomgr/endpoint.h index ee0becf3d6..a2216925f9 100644 --- a/src/core/iomgr/endpoint.h +++ b/src/core/iomgr/endpoint.h @@ -35,6 +35,7 @@ #define GRPC_INTERNAL_CORE_IOMGR_ENDPOINT_H #include "src/core/iomgr/pollset.h" +#include "src/core/iomgr/pollset_set.h" #include <grpc/support/slice.h> #include <grpc/support/time.h> @@ -70,6 +71,7 @@ struct grpc_endpoint_vtable { size_t nslices, grpc_endpoint_write_cb cb, void *user_data); void (*add_to_pollset)(grpc_endpoint *ep, grpc_pollset *pollset); + void (*add_to_pollset_set)(grpc_endpoint *ep, grpc_pollset_set *pollset); void (*shutdown)(grpc_endpoint *ep); void (*destroy)(grpc_endpoint *ep); char *(*get_peer)(grpc_endpoint *ep); @@ -101,6 +103,7 @@ void grpc_endpoint_destroy(grpc_endpoint *ep); /* Add an endpoint to a pollset, so that when the pollset is polled, events from this endpoint are considered */ void grpc_endpoint_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset); +void grpc_endpoint_add_to_pollset_set(grpc_endpoint *ep, grpc_pollset_set *pollset_set); struct grpc_endpoint { const grpc_endpoint_vtable *vtable; diff --git a/src/core/iomgr/endpoint_pair_windows.c b/src/core/iomgr/endpoint_pair_windows.c index 7d81470a78..e8295df8b3 100644 --- a/src/core/iomgr/endpoint_pair_windows.c +++ b/src/core/iomgr/endpoint_pair_windows.c @@ -81,8 +81,10 @@ grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char *name, size_t read SOCKET sv[2]; grpc_endpoint_pair p; create_sockets(sv); - p.client = grpc_tcp_create(grpc_winsocket_create(sv[1], "endpoint:client"), "endpoint:server"); - p.server = grpc_tcp_create(grpc_winsocket_create(sv[0], "endpoint:server"), "endpoint:client"); + p.client = grpc_tcp_create(grpc_winsocket_create(sv[1], "endpoint:client"), + "endpoint:server"); + p.server = grpc_tcp_create(grpc_winsocket_create(sv[0], "endpoint:server"), + "endpoint:client"); return p; } diff --git a/src/core/iomgr/fd_posix.c b/src/core/iomgr/fd_posix.c index 6ad377ce1c..2d08a77a70 100644 --- a/src/core/iomgr/fd_posix.c +++ b/src/core/iomgr/fd_posix.c @@ -102,6 +102,7 @@ static grpc_fd *alloc_fd(int fd) { r->freelist_next = NULL; r->read_watcher = r->write_watcher = NULL; r->on_done_closure = NULL; + r->closed = 0; return r; } @@ -167,13 +168,19 @@ int grpc_fd_is_orphaned(grpc_fd *fd) { return (gpr_atm_acq_load(&fd->refst) & 1) == 0; } +static void pollset_kick_locked(grpc_pollset *pollset) { + gpr_mu_lock(GRPC_POLLSET_MU(pollset)); + grpc_pollset_kick(pollset, NULL); + gpr_mu_unlock(GRPC_POLLSET_MU(pollset)); +} + static void maybe_wake_one_watcher_locked(grpc_fd *fd) { if (fd->inactive_watcher_root.next != &fd->inactive_watcher_root) { - grpc_pollset_force_kick(fd->inactive_watcher_root.next->pollset); + pollset_kick_locked(fd->inactive_watcher_root.next->pollset); } else if (fd->read_watcher) { - grpc_pollset_force_kick(fd->read_watcher->pollset); + pollset_kick_locked(fd->read_watcher->pollset); } else if (fd->write_watcher) { - grpc_pollset_force_kick(fd->write_watcher->pollset); + pollset_kick_locked(fd->write_watcher->pollset); } } @@ -187,13 +194,13 @@ static void wake_all_watchers_locked(grpc_fd *fd) { grpc_fd_watcher *watcher; for (watcher = fd->inactive_watcher_root.next; watcher != &fd->inactive_watcher_root; watcher = watcher->next) { - grpc_pollset_force_kick(watcher->pollset); + pollset_kick_locked(watcher->pollset); } if (fd->read_watcher) { - grpc_pollset_force_kick(fd->read_watcher->pollset); + pollset_kick_locked(fd->read_watcher->pollset); } if (fd->write_watcher && fd->write_watcher != fd->read_watcher) { - grpc_pollset_force_kick(fd->write_watcher->pollset); + pollset_kick_locked(fd->write_watcher->pollset); } } @@ -209,6 +216,8 @@ void grpc_fd_orphan(grpc_fd *fd, grpc_iomgr_closure *on_done, REF_BY(fd, 1, reason); /* remove active status, but keep referenced */ gpr_mu_lock(&fd->watcher_mu); if (!has_watchers(fd)) { + GPR_ASSERT(!fd->closed); + fd->closed = 1; close(fd->fd); if (fd->on_done_closure) { grpc_iomgr_add_callback(fd->on_done_closure); @@ -373,13 +382,15 @@ gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, return 0; } /* if there is nobody polling for read, but we need to, then start doing so */ - if (read_mask && !fd->read_watcher && gpr_atm_acq_load(&fd->readst) > READY) { + if (read_mask && !fd->read_watcher && + (gpr_uintptr)gpr_atm_acq_load(&fd->readst) > READY) { fd->read_watcher = watcher; mask |= read_mask; } /* if there is nobody polling for write, but we need to, then start doing so */ - if (write_mask && !fd->write_watcher && gpr_atm_acq_load(&fd->writest) > READY) { + if (write_mask && !fd->write_watcher && + (gpr_uintptr)gpr_atm_acq_load(&fd->writest) > READY) { fd->write_watcher = watcher; mask |= write_mask; } @@ -426,7 +437,8 @@ void grpc_fd_end_poll(grpc_fd_watcher *watcher, int got_read, int got_write) { if (kick) { maybe_wake_one_watcher_locked(fd); } - if (grpc_fd_is_orphaned(fd) && !has_watchers(fd)) { + if (grpc_fd_is_orphaned(fd) && !has_watchers(fd) && !fd->closed) { + fd->closed = 1; close(fd->fd); if (fd->on_done_closure != NULL) { grpc_iomgr_add_callback(fd->on_done_closure); diff --git a/src/core/iomgr/fd_posix.h b/src/core/iomgr/fd_posix.h index 94d0019fa4..835e9b339a 100644 --- a/src/core/iomgr/fd_posix.h +++ b/src/core/iomgr/fd_posix.h @@ -60,6 +60,7 @@ struct grpc_fd { gpr_mu set_state_mu; gpr_atm shutdown; + int closed; /* The watcher list. @@ -108,7 +109,8 @@ grpc_fd *grpc_fd_create(int fd, const char *name); on_done is called when the underlying file descriptor is definitely close()d. If on_done is NULL, no callback will be made. Requires: *fd initialized; no outstanding notify_on_read or - notify_on_write. */ + notify_on_write. + MUST NOT be called with a pollset lock taken */ void grpc_fd_orphan(grpc_fd *fd, grpc_iomgr_closure *on_done, const char *reason); @@ -121,11 +123,13 @@ void grpc_fd_orphan(grpc_fd *fd, grpc_iomgr_closure *on_done, i.e. a combination of read_mask and write_mask determined by the fd's current interest in said events. Polling strategies that do not need to alter their behavior depending on the - fd's current interest (such as epoll) do not need to call this function. */ + fd's current interest (such as epoll) do not need to call this function. + MUST NOT be called with a pollset lock taken */ gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, gpr_uint32 read_mask, gpr_uint32 write_mask, grpc_fd_watcher *rec); -/* Complete polling previously started with grpc_fd_begin_poll */ +/* Complete polling previously started with grpc_fd_begin_poll + MUST NOT be called with a pollset lock taken */ void grpc_fd_end_poll(grpc_fd_watcher *rec, int got_read, int got_write); /* Return 1 if this fd is orphaned, 0 otherwise */ diff --git a/src/core/iomgr/iomgr.c b/src/core/iomgr/iomgr.c index aa4bc6e20d..fdc9adf4af 100644 --- a/src/core/iomgr/iomgr.c +++ b/src/core/iomgr/iomgr.c @@ -147,7 +147,6 @@ void grpc_iomgr_shutdown(void) { continue; } if (grpc_alarm_check(&g_mu, gpr_inf_future(GPR_CLOCK_MONOTONIC), NULL)) { - gpr_log(GPR_DEBUG, "got late alarm"); continue; } if (g_root_object.next != &g_root_object) { diff --git a/src/core/iomgr/pollset.h b/src/core/iomgr/pollset.h index c40188b3c9..c474e4dbf1 100644 --- a/src/core/iomgr/pollset.h +++ b/src/core/iomgr/pollset.h @@ -37,6 +37,8 @@ #include <grpc/support/port_platform.h> #include <grpc/support/time.h> +#define GRPC_POLLSET_KICK_BROADCAST ((grpc_pollset_worker *)1) + /* A grpc_pollset is a set of file descriptors that a higher level item is interested in. For example: - a server will typically keep a pollset containing all connected channels, @@ -63,13 +65,24 @@ void grpc_pollset_destroy(grpc_pollset *pollset); descriptors. Requires GRPC_POLLSET_MU(pollset) locked. May unlock GRPC_POLLSET_MU(pollset) during its execution. - + + worker is a (platform-specific) handle that can be used to wake up + from grpc_pollset_work before any events are received and before the timeout + has expired. It is both initialized and destroyed by grpc_pollset_work. + Initialization of worker is guaranteed to occur BEFORE the + GRPC_POLLSET_MU(pollset) is released for the first time by + grpc_pollset_work, and it is guaranteed that GRPC_POLLSET_MU(pollset) will + not be released by grpc_pollset_work AFTER worker has been destroyed. + Returns true if some work has been done, and false if the deadline - got attained. */ -int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline); + expired. */ +int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, + gpr_timespec deadline); /* Break one polling thread out of polling work for this pollset. - Requires GRPC_POLLSET_MU(pollset) locked. */ -void grpc_pollset_kick(grpc_pollset *pollset); + If specific_worker is GRPC_POLLSET_KICK_BROADCAST, kick ALL the workers. + Otherwise, if specific_worker is non-NULL, then kick that worker. */ +void grpc_pollset_kick(grpc_pollset *pollset, + grpc_pollset_worker *specific_worker); #endif /* GRPC_INTERNAL_CORE_IOMGR_POLLSET_H */ diff --git a/src/core/iomgr/pollset_kick_posix.c b/src/core/iomgr/pollset_kick_posix.c deleted file mode 100644 index 51021784f2..0000000000 --- a/src/core/iomgr/pollset_kick_posix.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * - * 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. - * - */ - -#include <grpc/support/port_platform.h> - -#ifdef GPR_POSIX_SOCKET -#include "src/core/iomgr/pollset_kick_posix.h" - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -#include "src/core/iomgr/socket_utils_posix.h" -#include "src/core/iomgr/wakeup_fd_posix.h" -#include <grpc/support/alloc.h> -#include <grpc/support/log.h> - -/* This implementation is based on a freelist of wakeup fds, with extra logic to - * handle kicks while there is no attached fd. */ - -/* TODO(klempner): Autosize this, and consider providing a way to disable the - * cap entirely on systems with large fd limits */ -#define GRPC_MAX_CACHED_WFDS 50 - -static grpc_kick_fd_info *fd_freelist = NULL; -static int fd_freelist_count = 0; -static gpr_mu fd_freelist_mu; - -static grpc_kick_fd_info *allocate_wfd(void) { - grpc_kick_fd_info *info = NULL; - gpr_mu_lock(&fd_freelist_mu); - if (fd_freelist != NULL) { - info = fd_freelist; - fd_freelist = fd_freelist->next; - --fd_freelist_count; - } - gpr_mu_unlock(&fd_freelist_mu); - if (info == NULL) { - info = gpr_malloc(sizeof(*info)); - grpc_wakeup_fd_create(&info->wakeup_fd); - info->next = NULL; - } - return info; -} - -static void destroy_wfd(grpc_kick_fd_info *wfd) { - grpc_wakeup_fd_destroy(&wfd->wakeup_fd); - gpr_free(wfd); -} - -static void free_wfd(grpc_kick_fd_info *fd_info) { - gpr_mu_lock(&fd_freelist_mu); - if (fd_freelist_count < GRPC_MAX_CACHED_WFDS) { - fd_info->next = fd_freelist; - fd_freelist = fd_info; - fd_freelist_count++; - fd_info = NULL; - } - gpr_mu_unlock(&fd_freelist_mu); - - if (fd_info) { - destroy_wfd(fd_info); - } -} - -void grpc_pollset_kick_init(grpc_pollset_kick_state *kick_state) { - gpr_mu_init(&kick_state->mu); - kick_state->kicked = 0; - kick_state->fd_list.next = kick_state->fd_list.prev = &kick_state->fd_list; -} - -void grpc_pollset_kick_destroy(grpc_pollset_kick_state *kick_state) { - gpr_mu_destroy(&kick_state->mu); - GPR_ASSERT(kick_state->fd_list.next == &kick_state->fd_list); -} - -grpc_kick_fd_info *grpc_pollset_kick_pre_poll( - grpc_pollset_kick_state *kick_state) { - grpc_kick_fd_info *fd_info; - gpr_mu_lock(&kick_state->mu); - if (kick_state->kicked) { - kick_state->kicked = 0; - gpr_mu_unlock(&kick_state->mu); - return NULL; - } - fd_info = allocate_wfd(); - fd_info->next = &kick_state->fd_list; - fd_info->prev = fd_info->next->prev; - fd_info->next->prev = fd_info->prev->next = fd_info; - gpr_mu_unlock(&kick_state->mu); - return fd_info; -} - -void grpc_pollset_kick_consume(grpc_pollset_kick_state *kick_state, - grpc_kick_fd_info *fd_info) { - grpc_wakeup_fd_consume_wakeup(&fd_info->wakeup_fd); -} - -void grpc_pollset_kick_post_poll(grpc_pollset_kick_state *kick_state, - grpc_kick_fd_info *fd_info) { - gpr_mu_lock(&kick_state->mu); - fd_info->next->prev = fd_info->prev; - fd_info->prev->next = fd_info->next; - free_wfd(fd_info); - gpr_mu_unlock(&kick_state->mu); -} - -void grpc_pollset_kick_kick(grpc_pollset_kick_state *kick_state) { - gpr_mu_lock(&kick_state->mu); - if (kick_state->fd_list.next != &kick_state->fd_list) { - grpc_wakeup_fd_wakeup(&kick_state->fd_list.next->wakeup_fd); - } else { - kick_state->kicked = 1; - } - gpr_mu_unlock(&kick_state->mu); -} - -void grpc_pollset_kick_global_init_fallback_fd(void) { - gpr_mu_init(&fd_freelist_mu); - grpc_wakeup_fd_global_init_force_fallback(); -} - -void grpc_pollset_kick_global_init(void) { - gpr_mu_init(&fd_freelist_mu); - grpc_wakeup_fd_global_init(); -} - -void grpc_pollset_kick_global_destroy(void) { - while (fd_freelist != NULL) { - grpc_kick_fd_info *current = fd_freelist; - fd_freelist = fd_freelist->next; - destroy_wfd(current); - } - grpc_wakeup_fd_global_destroy(); - gpr_mu_destroy(&fd_freelist_mu); -} - -#endif /* GPR_POSIX_SOCKET */ diff --git a/src/core/iomgr/pollset_kick_posix.h b/src/core/iomgr/pollset_kick_posix.h deleted file mode 100644 index 77e32a8d51..0000000000 --- a/src/core/iomgr/pollset_kick_posix.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * - * 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. - * - */ - -#ifndef GRPC_INTERNAL_CORE_IOMGR_POLLSET_KICK_POSIX_H -#define GRPC_INTERNAL_CORE_IOMGR_POLLSET_KICK_POSIX_H - -#include "src/core/iomgr/wakeup_fd_posix.h" -#include <grpc/support/sync.h> - -/* pollset kicking allows breaking a thread out of polling work for - a given pollset. - writing a byte to a pipe is used as a posix-ly portable base - mechanism, and eventfds are utilized on Linux for better performance. */ - -typedef struct grpc_kick_fd_info { - grpc_wakeup_fd_info wakeup_fd; - /* used for polling list and free list */ - struct grpc_kick_fd_info *next; - /* only used when polling */ - struct grpc_kick_fd_info *prev; -} grpc_kick_fd_info; - -typedef struct grpc_pollset_kick_state { - gpr_mu mu; - int kicked; - struct grpc_kick_fd_info fd_list; -} grpc_pollset_kick_state; - -#define GRPC_POLLSET_KICK_GET_FD(kick_fd_info) \ - GRPC_WAKEUP_FD_GET_READ_FD(&(kick_fd_info)->wakeup_fd) - -/* This is an abstraction around the typical pipe mechanism for waking up a - thread sitting in a poll() style call. */ - -void grpc_pollset_kick_global_init(void); -void grpc_pollset_kick_global_destroy(void); - -void grpc_pollset_kick_init(grpc_pollset_kick_state *kick_state); -void grpc_pollset_kick_destroy(grpc_pollset_kick_state *kick_state); - -/* Guarantees a pure posix implementation rather than a specialized one, if - * applicable. Intended for testing. */ -void grpc_pollset_kick_global_init_fallback_fd(void); - -/* Must be called before entering poll(). If return value is NULL, this consumed - an existing kick. Otherwise the return value is an FD to add to the poll set. - */ -grpc_kick_fd_info *grpc_pollset_kick_pre_poll( - grpc_pollset_kick_state *kick_state); - -/* Consume an existing kick. Must be called after poll returns that the fd was - readable, and before calling kick_post_poll. */ -void grpc_pollset_kick_consume(grpc_pollset_kick_state *kick_state, - grpc_kick_fd_info *fd_info); - -/* Must be called after pre_poll, and after consume if applicable */ -void grpc_pollset_kick_post_poll(grpc_pollset_kick_state *kick_state, - grpc_kick_fd_info *fd_info); - -/* Actually kick */ -void grpc_pollset_kick_kick(grpc_pollset_kick_state *kick_state); - -#endif /* GRPC_INTERNAL_CORE_IOMGR_POLLSET_KICK_POSIX_H */ diff --git a/src/core/iomgr/pollset_multipoller_with_epoll.c b/src/core/iomgr/pollset_multipoller_with_epoll.c index d697b59e4c..1320c64579 100644 --- a/src/core/iomgr/pollset_multipoller_with_epoll.c +++ b/src/core/iomgr/pollset_multipoller_with_epoll.c @@ -36,6 +36,7 @@ #ifdef GPR_LINUX_MULTIPOLL_WITH_EPOLL #include <errno.h> +#include <poll.h> #include <string.h> #include <sys/epoll.h> #include <unistd.h> @@ -44,23 +45,28 @@ #include <grpc/support/alloc.h> #include <grpc/support/log.h> +typedef struct wakeup_fd_hdl { + grpc_wakeup_fd wakeup_fd; + struct wakeup_fd_hdl *next; +} wakeup_fd_hdl; + +typedef struct { + grpc_pollset *pollset; + grpc_fd *fd; + grpc_iomgr_closure closure; +} delayed_add; + typedef struct { int epoll_fd; - grpc_wakeup_fd_info wakeup_fd; + wakeup_fd_hdl *free_wakeup_fds; } pollset_hdr; -static void multipoll_with_epoll_pollset_add_fd(grpc_pollset *pollset, - grpc_fd *fd, - int and_unlock_pollset) { +static void finally_add_fd(grpc_pollset *pollset, grpc_fd *fd) { pollset_hdr *h = pollset->data.ptr; struct epoll_event ev; int err; grpc_fd_watcher watcher; - if (and_unlock_pollset) { - gpr_mu_unlock(&pollset->mu); - } - /* We pretend to be polling whilst adding an fd to keep the fd from being closed during the add. This may result in a spurious wakeup being assigned to this pollset whilst adding, but that should be benign. */ @@ -80,6 +86,52 @@ static void multipoll_with_epoll_pollset_add_fd(grpc_pollset *pollset, grpc_fd_end_poll(&watcher, 0, 0); } +static void perform_delayed_add(void *arg, int iomgr_status) { + delayed_add *da = arg; + int do_shutdown_cb = 0; + + if (!grpc_fd_is_orphaned(da->fd)) { + finally_add_fd(da->pollset, da->fd); + } + + gpr_mu_lock(&da->pollset->mu); + da->pollset->in_flight_cbs--; + if (da->pollset->shutting_down) { + /* We don't care about this pollset anymore. */ + if (da->pollset->in_flight_cbs == 0 && !da->pollset->called_shutdown) { + GPR_ASSERT(!grpc_pollset_has_workers(da->pollset)); + da->pollset->called_shutdown = 1; + do_shutdown_cb = 1; + } + } + gpr_mu_unlock(&da->pollset->mu); + + GRPC_FD_UNREF(da->fd, "delayed_add"); + + if (do_shutdown_cb) { + da->pollset->shutdown_done_cb(da->pollset->shutdown_done_arg); + } + + gpr_free(da); +} + +static void multipoll_with_epoll_pollset_add_fd(grpc_pollset *pollset, + grpc_fd *fd, + int and_unlock_pollset) { + if (and_unlock_pollset) { + gpr_mu_unlock(&pollset->mu); + finally_add_fd(pollset, fd); + } else { + delayed_add *da = gpr_malloc(sizeof(*da)); + da->pollset = pollset; + da->fd = fd; + GRPC_FD_REF(fd, "delayed_add"); + grpc_iomgr_closure_init(&da->closure, perform_delayed_add, da); + pollset->in_flight_cbs++; + grpc_iomgr_add_callback(&da->closure); + } +} + static void multipoll_with_epoll_pollset_del_fd(grpc_pollset *pollset, grpc_fd *fd, int and_unlock_pollset) { @@ -103,12 +155,14 @@ static void multipoll_with_epoll_pollset_del_fd(grpc_pollset *pollset, #define GRPC_EPOLL_MAX_EVENTS 1000 static void multipoll_with_epoll_pollset_maybe_work( - grpc_pollset *pollset, gpr_timespec deadline, gpr_timespec now, - int allow_synchronous_callback) { + grpc_pollset *pollset, grpc_pollset_worker *worker, gpr_timespec deadline, + gpr_timespec now, int allow_synchronous_callback) { struct epoll_event ep_ev[GRPC_EPOLL_MAX_EVENTS]; int ep_rv; + int poll_rv; pollset_hdr *h = pollset->data.ptr; int timeout_ms; + struct pollfd pfds[2]; /* If you want to ignore epoll's ability to sanely handle parallel pollers, * for a more apples-to-apples performance comparison with poll, add a @@ -116,43 +170,58 @@ static void multipoll_with_epoll_pollset_maybe_work( * here. */ - pollset->counter += 1; gpr_mu_unlock(&pollset->mu); timeout_ms = grpc_poll_deadline_to_millis_timeout(deadline, now); - do { - ep_rv = epoll_wait(h->epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, timeout_ms); - if (ep_rv < 0) { - if (errno != EINTR) { - gpr_log(GPR_ERROR, "epoll_wait() failed: %s", strerror(errno)); - } - } else { - int i; - for (i = 0; i < ep_rv; ++i) { - if (ep_ev[i].data.ptr == 0) { - grpc_wakeup_fd_consume_wakeup(&h->wakeup_fd); - } else { - grpc_fd *fd = ep_ev[i].data.ptr; - /* TODO(klempner): We might want to consider making err and pri - * separate events */ - int cancel = ep_ev[i].events & (EPOLLERR | EPOLLHUP); - int read = ep_ev[i].events & (EPOLLIN | EPOLLPRI); - int write = ep_ev[i].events & EPOLLOUT; - if (read || cancel) { - grpc_fd_become_readable(fd, allow_synchronous_callback); + pfds[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd); + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = h->epoll_fd; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + + poll_rv = poll(pfds, 2, timeout_ms); + + if (poll_rv < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + } + } else if (poll_rv == 0) { + /* do nothing */ + } else { + if (pfds[0].revents) { + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd); + } + if (pfds[1].revents) { + do { + ep_rv = epoll_wait(h->epoll_fd, ep_ev, GRPC_EPOLL_MAX_EVENTS, 0); + if (ep_rv < 0) { + if (errno != EINTR) { + gpr_log(GPR_ERROR, "epoll_wait() failed: %s", strerror(errno)); } - if (write || cancel) { - grpc_fd_become_writable(fd, allow_synchronous_callback); + } else { + int i; + for (i = 0; i < ep_rv; ++i) { + grpc_fd *fd = ep_ev[i].data.ptr; + /* TODO(klempner): We might want to consider making err and pri + * separate events */ + int cancel = ep_ev[i].events & (EPOLLERR | EPOLLHUP); + int read = ep_ev[i].events & (EPOLLIN | EPOLLPRI); + int write = ep_ev[i].events & EPOLLOUT; + if (read || cancel) { + grpc_fd_become_readable(fd, allow_synchronous_callback); + } + if (write || cancel) { + grpc_fd_become_writable(fd, allow_synchronous_callback); + } } } - } + } while (ep_rv == GRPC_EPOLL_MAX_EVENTS); } - timeout_ms = 0; - } while (ep_rv == GRPC_EPOLL_MAX_EVENTS); + } gpr_mu_lock(&pollset->mu); - pollset->counter -= 1; } static void multipoll_with_epoll_pollset_finish_shutdown( @@ -160,21 +229,14 @@ static void multipoll_with_epoll_pollset_finish_shutdown( static void multipoll_with_epoll_pollset_destroy(grpc_pollset *pollset) { pollset_hdr *h = pollset->data.ptr; - grpc_wakeup_fd_destroy(&h->wakeup_fd); close(h->epoll_fd); gpr_free(h); } -static void epoll_kick(grpc_pollset *pollset) { - pollset_hdr *h = pollset->data.ptr; - grpc_wakeup_fd_wakeup(&h->wakeup_fd); -} - static const grpc_pollset_vtable multipoll_with_epoll_pollset = { multipoll_with_epoll_pollset_add_fd, multipoll_with_epoll_pollset_del_fd, multipoll_with_epoll_pollset_maybe_work, - epoll_kick, multipoll_with_epoll_pollset_finish_shutdown, multipoll_with_epoll_pollset_destroy}; @@ -182,8 +244,6 @@ static void epoll_become_multipoller(grpc_pollset *pollset, grpc_fd **fds, size_t nfds) { size_t i; pollset_hdr *h = gpr_malloc(sizeof(pollset_hdr)); - struct epoll_event ev; - int err; pollset->vtable = &multipoll_with_epoll_pollset; pollset->data.ptr = h; @@ -196,16 +256,6 @@ static void epoll_become_multipoller(grpc_pollset *pollset, grpc_fd **fds, for (i = 0; i < nfds; i++) { multipoll_with_epoll_pollset_add_fd(pollset, fds[i], 0); } - - grpc_wakeup_fd_create(&h->wakeup_fd); - ev.events = EPOLLIN; - ev.data.ptr = 0; - err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD, - GRPC_WAKEUP_FD_GET_READ_FD(&h->wakeup_fd), &ev); - if (err < 0) { - gpr_log(GPR_ERROR, "Wakeup fd epoll_ctl failed: %s", strerror(errno)); - abort(); - } } grpc_platform_become_multipoller_type grpc_platform_become_multipoller = diff --git a/src/core/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/iomgr/pollset_multipoller_with_poll_posix.c index 0084e83953..b5b2d7534d 100644 --- a/src/core/iomgr/pollset_multipoller_with_poll_posix.c +++ b/src/core/iomgr/pollset_multipoller_with_poll_posix.c @@ -53,12 +53,6 @@ typedef struct { size_t fd_count; size_t fd_capacity; grpc_fd **fds; - /* fds being polled by the current poller: parallel arrays of pollfd, and - a grpc_fd_watcher */ - size_t pfd_count; - size_t pfd_capacity; - grpc_fd_watcher *watchers; - struct pollfd *pfds; /* fds that have been removed from the pollset explicitly */ size_t del_count; size_t del_capacity; @@ -102,80 +96,60 @@ static void multipoll_with_poll_pollset_del_fd(grpc_pollset *pollset, } } -static void end_polling(grpc_pollset *pollset) { - size_t i; - pollset_hdr *h; - h = pollset->data.ptr; - for (i = 1; i < h->pfd_count; i++) { - grpc_fd_end_poll(&h->watchers[i], h->pfds[i].revents & POLLIN, - h->pfds[i].revents & POLLOUT); - } -} - static void multipoll_with_poll_pollset_maybe_work( - grpc_pollset *pollset, gpr_timespec deadline, gpr_timespec now, - int allow_synchronous_callback) { + grpc_pollset *pollset, grpc_pollset_worker *worker, gpr_timespec deadline, + gpr_timespec now, int allow_synchronous_callback) { int timeout; int r; - size_t i, np, nf, nd; + size_t i, j, pfd_count, fd_count; pollset_hdr *h; - grpc_kick_fd_info *kfd; + /* TODO(ctiller): inline some elements to avoid an allocation */ + grpc_fd_watcher *watchers; + struct pollfd *pfds; h = pollset->data.ptr; timeout = grpc_poll_deadline_to_millis_timeout(deadline, now); - if (h->pfd_capacity < h->fd_count + 1) { - h->pfd_capacity = GPR_MAX(h->pfd_capacity * 3 / 2, h->fd_count + 1); - gpr_free(h->pfds); - gpr_free(h->watchers); - h->pfds = gpr_malloc(sizeof(struct pollfd) * h->pfd_capacity); - h->watchers = gpr_malloc(sizeof(grpc_fd_watcher) * h->pfd_capacity); - } - nf = 0; - np = 1; - kfd = grpc_pollset_kick_pre_poll(&pollset->kick_state); - if (kfd == NULL) { - /* Already kicked */ - return; - } - h->pfds[0].fd = GRPC_POLLSET_KICK_GET_FD(kfd); - h->pfds[0].events = POLLIN; - h->pfds[0].revents = POLLOUT; + /* TODO(ctiller): perform just one malloc here if we exceed the inline case */ + pfds = gpr_malloc(sizeof(*pfds) * (h->fd_count + 1)); + watchers = gpr_malloc(sizeof(*watchers) * (h->fd_count + 1)); + fd_count = 0; + pfd_count = 1; + pfds[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd); + pfds[0].events = POLLIN; + pfds[0].revents = POLLOUT; for (i = 0; i < h->fd_count; i++) { int remove = grpc_fd_is_orphaned(h->fds[i]); - for (nd = 0; nd < h->del_count; nd++) { - if (h->fds[i] == h->dels[nd]) remove = 1; + for (j = 0; !remove && j < h->del_count; j++) { + if (h->fds[i] == h->dels[j]) remove = 1; } if (remove) { GRPC_FD_UNREF(h->fds[i], "multipoller"); } else { - h->fds[nf++] = h->fds[i]; - h->watchers[np].fd = h->fds[i]; - h->pfds[np].fd = h->fds[i]->fd; - h->pfds[np].revents = 0; - np++; + h->fds[fd_count++] = h->fds[i]; + watchers[pfd_count].fd = h->fds[i]; + pfds[pfd_count].fd = h->fds[i]->fd; + pfds[pfd_count].revents = 0; + pfd_count++; } } - h->pfd_count = np; - h->fd_count = nf; - for (nd = 0; nd < h->del_count; nd++) { - GRPC_FD_UNREF(h->dels[nd], "multipoller_del"); + for (j = 0; j < h->del_count; j++) { + GRPC_FD_UNREF(h->dels[j], "multipoller_del"); } h->del_count = 0; - if (h->pfd_count == 0) { - end_polling(pollset); - return; - } - pollset->counter++; + h->fd_count = fd_count; gpr_mu_unlock(&pollset->mu); - for (i = 1; i < np; i++) { - h->pfds[i].events = grpc_fd_begin_poll(h->watchers[i].fd, pollset, POLLIN, - POLLOUT, &h->watchers[i]); + for (i = 1; i < pfd_count; i++) { + pfds[i].events = grpc_fd_begin_poll(watchers[i].fd, pollset, POLLIN, + POLLOUT, &watchers[i]); } - r = poll(h->pfds, h->pfd_count, timeout); + r = poll(pfds, pfd_count, timeout); - end_polling(pollset); + for (i = 1; i < pfd_count; i++) { + grpc_fd_end_poll(&watchers[i], pfds[i].revents & POLLIN, + pfds[i].revents & POLLOUT); + } if (r < 0) { if (errno != EINTR) { @@ -184,35 +158,31 @@ static void multipoll_with_poll_pollset_maybe_work( } else if (r == 0) { /* do nothing */ } else { - if (h->pfds[0].revents & POLLIN) { - grpc_pollset_kick_consume(&pollset->kick_state, kfd); + if (pfds[0].revents & POLLIN) { + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd); } - for (i = 1; i < np; i++) { - if (h->watchers[i].fd == NULL) { + for (i = 1; i < pfd_count; i++) { + if (watchers[i].fd == NULL) { continue; } - if (h->pfds[i].revents & (POLLIN | POLLHUP | POLLERR)) { - grpc_fd_become_readable(h->watchers[i].fd, allow_synchronous_callback); + if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR)) { + grpc_fd_become_readable(watchers[i].fd, allow_synchronous_callback); } - if (h->pfds[i].revents & (POLLOUT | POLLHUP | POLLERR)) { - grpc_fd_become_writable(h->watchers[i].fd, allow_synchronous_callback); + if (pfds[i].revents & (POLLOUT | POLLHUP | POLLERR)) { + grpc_fd_become_writable(watchers[i].fd, allow_synchronous_callback); } } } - grpc_pollset_kick_post_poll(&pollset->kick_state, kfd); - gpr_mu_lock(&pollset->mu); - pollset->counter--; -} + gpr_free(pfds); + gpr_free(watchers); -static void multipoll_with_poll_pollset_kick(grpc_pollset *p) { - grpc_pollset_force_kick(p); + gpr_mu_lock(&pollset->mu); } static void multipoll_with_poll_pollset_finish_shutdown(grpc_pollset *pollset) { size_t i; pollset_hdr *h = pollset->data.ptr; - GPR_ASSERT(pollset->counter == 0); for (i = 0; i < h->fd_count; i++) { GRPC_FD_UNREF(h->fds[i], "multipoller"); } @@ -226,8 +196,6 @@ static void multipoll_with_poll_pollset_finish_shutdown(grpc_pollset *pollset) { static void multipoll_with_poll_pollset_destroy(grpc_pollset *pollset) { pollset_hdr *h = pollset->data.ptr; multipoll_with_poll_pollset_finish_shutdown(pollset); - gpr_free(h->pfds); - gpr_free(h->watchers); gpr_free(h->fds); gpr_free(h->dels); gpr_free(h); @@ -237,7 +205,6 @@ static const grpc_pollset_vtable multipoll_with_poll_pollset = { multipoll_with_poll_pollset_add_fd, multipoll_with_poll_pollset_del_fd, multipoll_with_poll_pollset_maybe_work, - multipoll_with_poll_pollset_kick, multipoll_with_poll_pollset_finish_shutdown, multipoll_with_poll_pollset_destroy}; @@ -250,10 +217,6 @@ void grpc_poll_become_multipoller(grpc_pollset *pollset, grpc_fd **fds, h->fd_count = nfds; h->fd_capacity = nfds; h->fds = gpr_malloc(nfds * sizeof(grpc_fd *)); - h->pfd_count = 0; - h->pfd_capacity = 0; - h->pfds = NULL; - h->watchers = NULL; h->del_count = 0; h->del_capacity = 0; h->dels = NULL; diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c index c8646af615..d3a9193af1 100644 --- a/src/core/iomgr/pollset_posix.c +++ b/src/core/iomgr/pollset_posix.c @@ -55,22 +55,60 @@ #include <grpc/support/useful.h> GPR_TLS_DECL(g_current_thread_poller); +GPR_TLS_DECL(g_current_thread_worker); -void grpc_pollset_kick(grpc_pollset *p) { - if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p && p->counter) { - p->vtable->kick(p); - } +static void remove_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev->next = worker->next; + worker->next->prev = worker->prev; +} + +int grpc_pollset_has_workers(grpc_pollset *p) { + return p->root_worker.next != &p->root_worker; } -void grpc_pollset_force_kick(grpc_pollset *p) { - if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p) { - grpc_pollset_kick_kick(&p->kick_state); +static grpc_pollset_worker *pop_front_worker(grpc_pollset *p) { + if (grpc_pollset_has_workers(p)) { + grpc_pollset_worker *w = p->root_worker.next; + remove_worker(p, w); + return w; + } else { + return NULL; } } -static void kick_using_pollset_kick(grpc_pollset *p) { - if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p) { - grpc_pollset_kick_kick(&p->kick_state); +static void push_back_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->next = &p->root_worker; + worker->prev = worker->next->prev; + worker->prev->next = worker->next->prev = worker; +} + +static void push_front_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev = &p->root_worker; + worker->next = worker->prev->next; + worker->prev->next = worker->next->prev = worker; +} + +void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { + if (specific_worker != NULL) { + if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { + for (specific_worker = p->root_worker.next; + specific_worker != &p->root_worker; + specific_worker = specific_worker->next) { + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + } + p->kicked_without_pollers = 1; + } else if (gpr_tls_get(&g_current_thread_worker) != + (gpr_intptr)specific_worker) { + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + } + } else if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p) { + specific_worker = pop_front_worker(p); + if (specific_worker != NULL) { + push_back_worker(p, specific_worker); + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + } else { + p->kicked_without_pollers = 1; + } } } @@ -78,16 +116,12 @@ static void kick_using_pollset_kick(grpc_pollset *p) { void grpc_pollset_global_init(void) { gpr_tls_init(&g_current_thread_poller); - - /* Initialize kick fd state */ - grpc_pollset_kick_global_init(); + grpc_wakeup_fd_global_init(); } void grpc_pollset_global_shutdown(void) { - /* destroy the kick pipes */ - grpc_pollset_kick_global_destroy(); - gpr_tls_destroy(&g_current_thread_poller); + grpc_wakeup_fd_global_destroy(); } /* main interface */ @@ -96,7 +130,7 @@ static void become_basic_pollset(grpc_pollset *pollset, grpc_fd *fd_or_null); void grpc_pollset_init(grpc_pollset *pollset) { gpr_mu_init(&pollset->mu); - grpc_pollset_kick_init(&pollset->kick_state); + pollset->root_worker.next = pollset->root_worker.prev = &pollset->root_worker; pollset->in_flight_cbs = 0; pollset->shutting_down = 0; pollset->called_shutdown = 0; @@ -134,27 +168,44 @@ static void finish_shutdown(grpc_pollset *pollset) { pollset->shutdown_done_cb(pollset->shutdown_done_arg); } -int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline) { +int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, + gpr_timespec deadline) { /* pollset->mu already held */ gpr_timespec now = gpr_now(GPR_CLOCK_MONOTONIC); + int added_worker = 0; if (gpr_time_cmp(now, deadline) > 0) { return 0; } + /* this must happen before we (potentially) drop pollset->mu */ + worker->next = worker->prev = NULL; + /* TODO(ctiller): pool these */ + grpc_wakeup_fd_init(&worker->wakeup_fd); if (grpc_maybe_call_delayed_callbacks(&pollset->mu, 1)) { - return 1; + goto done; } if (grpc_alarm_check(&pollset->mu, now, &deadline)) { - return 1; + goto done; } if (pollset->shutting_down) { - return 1; + goto done; + } + if (!pollset->kicked_without_pollers) { + push_front_worker(pollset, worker); + added_worker = 1; + gpr_tls_set(&g_current_thread_poller, (gpr_intptr)pollset); + pollset->vtable->maybe_work(pollset, worker, deadline, now, 1); + gpr_tls_set(&g_current_thread_poller, 0); + } else { + pollset->kicked_without_pollers = 0; + } +done: + grpc_wakeup_fd_destroy(&worker->wakeup_fd); + if (added_worker) { + remove_worker(pollset, worker); } - gpr_tls_set(&g_current_thread_poller, (gpr_intptr)pollset); - pollset->vtable->maybe_work(pollset, deadline, now, 1); - gpr_tls_set(&g_current_thread_poller, 0); if (pollset->shutting_down) { - if (pollset->counter > 0) { - grpc_pollset_kick(pollset); + if (grpc_pollset_has_workers(pollset)) { + grpc_pollset_kick(pollset, NULL); } else if (!pollset->called_shutdown && pollset->in_flight_cbs == 0) { pollset->called_shutdown = 1; gpr_mu_unlock(&pollset->mu); @@ -177,15 +228,13 @@ void grpc_pollset_shutdown(grpc_pollset *pollset, GPR_ASSERT(!pollset->shutting_down); pollset->shutting_down = 1; if (!pollset->called_shutdown && pollset->in_flight_cbs == 0 && - pollset->counter == 0) { + !grpc_pollset_has_workers(pollset)) { pollset->called_shutdown = 1; call_shutdown = 1; } pollset->shutdown_done_cb = shutdown_done; pollset->shutdown_done_arg = shutdown_done_arg; - if (pollset->counter > 0) { - grpc_pollset_kick(pollset); - } + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); gpr_mu_unlock(&pollset->mu); if (call_shutdown) { @@ -196,8 +245,8 @@ void grpc_pollset_shutdown(grpc_pollset *pollset, void grpc_pollset_destroy(grpc_pollset *pollset) { GPR_ASSERT(pollset->shutting_down); GPR_ASSERT(pollset->in_flight_cbs == 0); + GPR_ASSERT(!grpc_pollset_has_workers(pollset)); pollset->vtable->destroy(pollset); - grpc_pollset_kick_destroy(&pollset->kick_state); gpr_mu_destroy(&pollset->mu); } @@ -248,8 +297,8 @@ static void basic_do_promote(void *args, int success) { gpr_mu_lock(&pollset->mu); /* First we need to ensure that nobody is polling concurrently */ - if (pollset->counter != 0) { - grpc_pollset_kick(pollset); + if (grpc_pollset_has_workers(pollset)) { + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); grpc_iomgr_add_callback(&up_args->promotion_closure); gpr_mu_unlock(&pollset->mu); return; @@ -264,7 +313,8 @@ static void basic_do_promote(void *args, int success) { pollset->in_flight_cbs--; if (pollset->shutting_down) { /* We don't care about this pollset anymore. */ - if (pollset->in_flight_cbs == 0 && pollset->counter == 0 && !pollset->called_shutdown) { + if (pollset->in_flight_cbs == 0 && !pollset->called_shutdown) { + GPR_ASSERT(!grpc_pollset_has_workers(pollset)); pollset->called_shutdown = 1; do_shutdown_cb = 1; } @@ -307,7 +357,7 @@ static void basic_pollset_add_fd(grpc_pollset *pollset, grpc_fd *fd, GPR_ASSERT(fd); if (fd == pollset->data.ptr) goto exit; - if (!pollset->counter) { + if (!grpc_pollset_has_workers(pollset)) { /* Fast path -- no in flight cbs */ /* TODO(klempner): Comment this out and fix any test failures or establish * they are due to timing issues */ @@ -343,7 +393,7 @@ static void basic_pollset_add_fd(grpc_pollset *pollset, grpc_fd *fd, up_args->promotion_closure.cb_arg = up_args; grpc_iomgr_add_callback(&up_args->promotion_closure); - grpc_pollset_kick(pollset); + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); exit: if (and_unlock_pollset) { @@ -365,12 +415,12 @@ static void basic_pollset_del_fd(grpc_pollset *pollset, grpc_fd *fd, } static void basic_pollset_maybe_work(grpc_pollset *pollset, + grpc_pollset_worker *worker, gpr_timespec deadline, gpr_timespec now, int allow_synchronous_callback) { struct pollfd pfd[2]; grpc_fd *fd; grpc_fd_watcher fd_watcher; - grpc_kick_fd_info *kfd; int timeout; int r; int nfds; @@ -387,16 +437,10 @@ static void basic_pollset_maybe_work(grpc_pollset *pollset, fd = pollset->data.ptr = NULL; } timeout = grpc_poll_deadline_to_millis_timeout(deadline, now); - kfd = grpc_pollset_kick_pre_poll(&pollset->kick_state); - if (kfd == NULL) { - /* Already kicked */ - return; - } - pfd[0].fd = GRPC_POLLSET_KICK_GET_FD(kfd); + pfd[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&worker->wakeup_fd); pfd[0].events = POLLIN; pfd[0].revents = 0; nfds = 1; - pollset->counter++; if (fd) { pfd[1].fd = fd->fd; pfd[1].revents = 0; @@ -428,7 +472,7 @@ static void basic_pollset_maybe_work(grpc_pollset *pollset, /* do nothing */ } else { if (pfd[0].revents & POLLIN) { - grpc_pollset_kick_consume(&pollset->kick_state, kfd); + grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd); } if (nfds > 1) { if (pfd[1].revents & (POLLIN | POLLHUP | POLLERR)) { @@ -440,14 +484,10 @@ static void basic_pollset_maybe_work(grpc_pollset *pollset, } } - grpc_pollset_kick_post_poll(&pollset->kick_state, kfd); - gpr_mu_lock(&pollset->mu); - pollset->counter--; } static void basic_pollset_destroy(grpc_pollset *pollset) { - GPR_ASSERT(pollset->counter == 0); if (pollset->data.ptr != NULL) { GRPC_FD_UNREF(pollset->data.ptr, "basicpoll"); pollset->data.ptr = NULL; @@ -455,14 +495,13 @@ static void basic_pollset_destroy(grpc_pollset *pollset) { } static const grpc_pollset_vtable basic_pollset = { - basic_pollset_add_fd, basic_pollset_del_fd, basic_pollset_maybe_work, - kick_using_pollset_kick, basic_pollset_destroy, basic_pollset_destroy}; + basic_pollset_add_fd, basic_pollset_del_fd, basic_pollset_maybe_work, + basic_pollset_destroy, basic_pollset_destroy}; static void become_basic_pollset(grpc_pollset *pollset, grpc_fd *fd_or_null) { pollset->vtable = &basic_pollset; - pollset->counter = 0; pollset->data.ptr = fd_or_null; - if (fd_or_null) { + if (fd_or_null != NULL) { GRPC_FD_REF(fd_or_null, "basicpoll"); } } diff --git a/src/core/iomgr/pollset_posix.h b/src/core/iomgr/pollset_posix.h index 37de1276d1..1c1b736193 100644 --- a/src/core/iomgr/pollset_posix.h +++ b/src/core/iomgr/pollset_posix.h @@ -35,8 +35,7 @@ #define GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H #include <grpc/support/sync.h> - -#include "src/core/iomgr/pollset_kick_posix.h" +#include "src/core/iomgr/wakeup_fd_posix.h" typedef struct grpc_pollset_vtable grpc_pollset_vtable; @@ -45,6 +44,12 @@ typedef struct grpc_pollset_vtable grpc_pollset_vtable; use the struct tag */ struct grpc_fd; +typedef struct grpc_pollset_worker { + grpc_wakeup_fd wakeup_fd; + struct grpc_pollset_worker *next; + struct grpc_pollset_worker *prev; +} grpc_pollset_worker; + typedef struct grpc_pollset { /* pollsets under posix can mutate representation as fds are added and removed. @@ -52,11 +57,11 @@ typedef struct grpc_pollset { few fds, and an epoll() based implementation for many fds */ const grpc_pollset_vtable *vtable; gpr_mu mu; - grpc_pollset_kick_state kick_state; - int counter; + grpc_pollset_worker root_worker; int in_flight_cbs; int shutting_down; int called_shutdown; + int kicked_without_pollers; void (*shutdown_done_cb)(void *arg); void *shutdown_done_arg; union { @@ -70,9 +75,9 @@ struct grpc_pollset_vtable { int and_unlock_pollset); void (*del_fd)(grpc_pollset *pollset, struct grpc_fd *fd, int and_unlock_pollset); - void (*maybe_work)(grpc_pollset *pollset, gpr_timespec deadline, - gpr_timespec now, int allow_synchronous_callback); - void (*kick)(grpc_pollset *pollset); + void (*maybe_work)(grpc_pollset *pollset, grpc_pollset_worker *worker, + gpr_timespec deadline, gpr_timespec now, + int allow_synchronous_callback); void (*finish_shutdown)(grpc_pollset *pollset); void (*destroy)(grpc_pollset *pollset); }; @@ -85,22 +90,16 @@ void grpc_pollset_add_fd(grpc_pollset *pollset, struct grpc_fd *fd); poll after an fd is orphaned) */ void grpc_pollset_del_fd(grpc_pollset *pollset, struct grpc_fd *fd); -/* Force any current pollers to break polling: it's the callers responsibility - to ensure that the pollset indeed needs to be kicked - no verification that - the pollset is actually performing polling work is done. At worst this will - result in spurious wakeups if performed at the wrong moment. - Does not touch pollset->mu. */ -void grpc_pollset_force_kick(grpc_pollset *pollset); /* Returns the fd to listen on for kicks */ int grpc_kick_read_fd(grpc_pollset *p); /* Call after polling has been kicked to leave the kicked state */ void grpc_kick_drain(grpc_pollset *p); /* Convert a timespec to milliseconds: - - very small or negative poll times are clamped to zero to do a + - very small or negative poll times are clamped to zero to do a non-blocking poll (which becomes spin polling) - other small values are rounded up to one millisecond - - longer than a millisecond polls are rounded up to the next nearest + - longer than a millisecond polls are rounded up to the next nearest millisecond to avoid spinning - infinite timeouts are converted to -1 */ int grpc_poll_deadline_to_millis_timeout(gpr_timespec deadline, gpr_timespec now); @@ -114,4 +113,8 @@ extern grpc_platform_become_multipoller_type grpc_platform_become_multipoller; void grpc_poll_become_multipoller(grpc_pollset *pollset, struct grpc_fd **fds, size_t fd_count); +/* Return 1 if the pollset has active threads in grpc_pollset_work (pollset must + * be locked) */ +int grpc_pollset_has_workers(grpc_pollset *pollset); + #endif /* GRPC_INTERNAL_CORE_IOMGR_POLLSET_POSIX_H */ diff --git a/src/core/iomgr/pollset_set_posix.c b/src/core/iomgr/pollset_set_posix.c index 5ff7df1dcd..2076ac70ef 100644 --- a/src/core/iomgr/pollset_set_posix.c +++ b/src/core/iomgr/pollset_set_posix.c @@ -60,7 +60,7 @@ void grpc_pollset_set_destroy(grpc_pollset_set *pollset_set) { void grpc_pollset_set_add_pollset(grpc_pollset_set *pollset_set, grpc_pollset *pollset) { - size_t i; + size_t i, j; gpr_mu_lock(&pollset_set->mu); if (pollset_set->pollset_count == pollset_set->pollset_capacity) { pollset_set->pollset_capacity = @@ -70,9 +70,15 @@ void grpc_pollset_set_add_pollset(grpc_pollset_set *pollset_set, sizeof(*pollset_set->pollsets)); } pollset_set->pollsets[pollset_set->pollset_count++] = pollset; - for (i = 0; i < pollset_set->fd_count; i++) { - grpc_pollset_add_fd(pollset, pollset_set->fds[i]); + for (i = 0, j = 0; i < pollset_set->fd_count; i++) { + if (grpc_fd_is_orphaned(pollset_set->fds[i])) { + GRPC_FD_UNREF(pollset_set->fds[i], "pollset"); + } else { + grpc_pollset_add_fd(pollset, pollset_set->fds[i]); + pollset_set->fds[j++] = pollset_set->fds[i]; + } } + pollset_set->fd_count = j; gpr_mu_unlock(&pollset_set->mu); } diff --git a/src/core/iomgr/pollset_windows.c b/src/core/iomgr/pollset_windows.c index a9c4739c7c..22dc5891c3 100644 --- a/src/core/iomgr/pollset_windows.c +++ b/src/core/iomgr/pollset_windows.c @@ -42,6 +42,38 @@ #include "src/core/iomgr/pollset.h" #include "src/core/iomgr/pollset_windows.h" +static void remove_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev->next = worker->next; + worker->next->prev = worker->prev; +} + +static int has_workers(grpc_pollset *p) { + return p->root_worker.next != &p->root_worker; +} + +static grpc_pollset_worker *pop_front_worker(grpc_pollset *p) { + if (has_workers(p)) { + grpc_pollset_worker *w = p->root_worker.next; + remove_worker(p, w); + return w; + } + else { + return NULL; + } +} + +static void push_back_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->next = &p->root_worker; + worker->prev = worker->next->prev; + worker->prev->next = worker->next->prev = worker; +} + +static void push_front_worker(grpc_pollset *p, grpc_pollset_worker *worker) { + worker->prev = &p->root_worker; + worker->next = worker->prev->next; + worker->prev->next = worker->next->prev = worker; +} + /* There isn't really any such thing as a pollset under Windows, due to the nature of the IO completion ports. We're still going to provide a minimal set of features for the sake of the rest of grpc. But grpc_pollset_work @@ -50,7 +82,8 @@ void grpc_pollset_init(grpc_pollset *pollset) { memset(pollset, 0, sizeof(*pollset)); gpr_mu_init(&pollset->mu); - gpr_cv_init(&pollset->cv); + pollset->root_worker.next = pollset->root_worker.prev = &pollset->root_worker; + pollset->kicked_without_pollers = 0; } void grpc_pollset_shutdown(grpc_pollset *pollset, @@ -58,34 +91,66 @@ void grpc_pollset_shutdown(grpc_pollset *pollset, void *shutdown_done_arg) { gpr_mu_lock(&pollset->mu); pollset->shutting_down = 1; - gpr_cv_broadcast(&pollset->cv); + grpc_pollset_kick(pollset, GRPC_POLLSET_KICK_BROADCAST); gpr_mu_unlock(&pollset->mu); shutdown_done(shutdown_done_arg); } void grpc_pollset_destroy(grpc_pollset *pollset) { gpr_mu_destroy(&pollset->mu); - gpr_cv_destroy(&pollset->cv); } -int grpc_pollset_work(grpc_pollset *pollset, gpr_timespec deadline) { +int grpc_pollset_work(grpc_pollset *pollset, grpc_pollset_worker *worker, gpr_timespec deadline) { gpr_timespec now; + int added_worker = 0; now = gpr_now(GPR_CLOCK_MONOTONIC); if (gpr_time_cmp(now, deadline) > 0) { return 0 /* GPR_FALSE */; } + worker->next = worker->prev = NULL; + gpr_cv_init(&worker->cv); if (grpc_maybe_call_delayed_callbacks(&pollset->mu, 1 /* GPR_TRUE */)) { - return 1 /* GPR_TRUE */; + goto done; } if (grpc_alarm_check(&pollset->mu, now, &deadline)) { - return 1 /* GPR_TRUE */; + goto done; } - if (!pollset->shutting_down) { - gpr_cv_wait(&pollset->cv, &pollset->mu, deadline); + if (!pollset->kicked_without_pollers && !pollset->shutting_down) { + push_front_worker(pollset, worker); + added_worker = 1; + gpr_cv_wait(&worker->cv, &pollset->mu, deadline); + } else { + pollset->kicked_without_pollers = 0; + } +done: + gpr_cv_destroy(&worker->cv); + if (added_worker) { + remove_worker(pollset, worker); } return 1 /* GPR_TRUE */; } -void grpc_pollset_kick(grpc_pollset *p) { gpr_cv_signal(&p->cv); } +void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { + if (specific_worker != NULL) { + if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { + for (specific_worker = p->root_worker.next; + specific_worker != &p->root_worker; + specific_worker = specific_worker->next) { + gpr_cv_signal(&specific_worker->cv); + } + p->kicked_without_pollers = 1; + } else { + gpr_cv_signal(&specific_worker->cv); + } + } else { + specific_worker = pop_front_worker(p); + if (specific_worker != NULL) { + push_back_worker(p, specific_worker); + gpr_cv_signal(&specific_worker->cv); + } else { + p->kicked_without_pollers = 1; + } + } +} #endif /* GPR_WINSOCK_SOCKET */ diff --git a/src/core/iomgr/pollset_windows.h b/src/core/iomgr/pollset_windows.h index c9b8d3f374..4efa5a1717 100644 --- a/src/core/iomgr/pollset_windows.h +++ b/src/core/iomgr/pollset_windows.h @@ -40,12 +40,20 @@ /* There isn't really any such thing as a pollset under Windows, due to the nature of the IO completion ports. A Windows "pollset" is merely a mutex - and a condition variable, used to synchronize with the IOCP. */ + used to synchronize with the IOCP, and workers are condition variables + used to block threads until work is ready. */ + +typedef struct grpc_pollset_worker { + gpr_cv cv; + struct grpc_pollset_worker *next; + struct grpc_pollset_worker *prev; +} grpc_pollset_worker; typedef struct grpc_pollset { gpr_mu mu; - gpr_cv cv; int shutting_down; + int kicked_without_pollers; + grpc_pollset_worker root_worker; } grpc_pollset; #define GRPC_POLLSET_MU(pollset) (&(pollset)->mu) diff --git a/src/core/iomgr/sockaddr_utils.c b/src/core/iomgr/sockaddr_utils.c index 71ac12e87b..65ec1f94ac 100644 --- a/src/core/iomgr/sockaddr_utils.c +++ b/src/core/iomgr/sockaddr_utils.c @@ -170,6 +170,11 @@ int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr, char *grpc_sockaddr_to_uri(const struct sockaddr *addr) { char *temp; char *result; + struct sockaddr_in addr_normalized; + + if (grpc_sockaddr_is_v4mapped(addr, &addr_normalized)) { + addr = (const struct sockaddr *)&addr_normalized; + } switch (addr->sa_family) { case AF_INET: diff --git a/src/core/iomgr/tcp_client_posix.c b/src/core/iomgr/tcp_client_posix.c index 392eda999e..9572ce5980 100644 --- a/src/core/iomgr/tcp_client_posix.c +++ b/src/core/iomgr/tcp_client_posix.c @@ -89,11 +89,11 @@ error: return 0; } -static void on_alarm(void *acp, int success) { +static void tc_on_alarm(void *acp, int success) { int done; async_connect *ac = acp; gpr_mu_lock(&ac->mu); - if (ac->fd != NULL && success) { + if (ac->fd != NULL) { grpc_fd_shutdown(ac->fd); } done = (--ac->refs == 0); @@ -110,11 +110,17 @@ static void on_writable(void *acp, int success) { int so_error = 0; socklen_t so_error_size; int err; - int fd = ac->fd->fd; int done; grpc_endpoint *ep = NULL; void (*cb)(void *arg, grpc_endpoint *tcp) = ac->cb; void *cb_arg = ac->cb_arg; + grpc_fd *fd; + + gpr_mu_lock(&ac->mu); + GPR_ASSERT(ac->fd); + fd = ac->fd; + ac->fd = NULL; + gpr_mu_unlock(&ac->mu); grpc_alarm_cancel(&ac->alarm); @@ -122,7 +128,7 @@ static void on_writable(void *acp, int success) { if (success) { do { so_error_size = sizeof(so_error); - err = getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_size); + err = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_size); } while (err < 0 && errno == EINTR); if (err < 0) { gpr_log(GPR_ERROR, "getsockopt(ERROR): %s", strerror(errno)); @@ -145,7 +151,7 @@ static void on_writable(void *acp, int success) { don't do that! */ gpr_log(GPR_ERROR, "kernel out of buffers"); gpr_mu_unlock(&ac->mu); - grpc_fd_notify_on_write(ac->fd, &ac->write_closure); + grpc_fd_notify_on_write(fd, &ac->write_closure); return; } else { switch (so_error) { @@ -159,9 +165,9 @@ static void on_writable(void *acp, int success) { goto finish; } } else { - grpc_pollset_set_del_fd(ac->interested_parties, ac->fd); - ep = grpc_tcp_create(ac->fd, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, - ac->addr_str); + grpc_pollset_set_del_fd(ac->interested_parties, fd); + ep = grpc_tcp_create(fd, GRPC_TCP_DEFAULT_READ_SLICE_SIZE, ac->addr_str); + fd = NULL; goto finish; } } else { @@ -172,11 +178,10 @@ static void on_writable(void *acp, int success) { abort(); finish: - if (ep == NULL) { - grpc_pollset_set_del_fd(ac->interested_parties, ac->fd); - grpc_fd_orphan(ac->fd, NULL, "tcp_client_orphan"); - } else { - ac->fd = NULL; + if (fd != NULL) { + grpc_pollset_set_del_fd(ac->interested_parties, fd); + grpc_fd_orphan(fd, NULL, "tcp_client_orphan"); + fd = NULL; } done = (--ac->refs == 0); gpr_mu_unlock(&ac->mu); @@ -260,7 +265,7 @@ void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *ep), gpr_mu_lock(&ac->mu); grpc_alarm_init(&ac->alarm, gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), - on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC)); + tc_on_alarm, ac, gpr_now(GPR_CLOCK_MONOTONIC)); grpc_fd_notify_on_write(ac->fd, &ac->write_closure); gpr_mu_unlock(&ac->mu); diff --git a/src/core/iomgr/tcp_posix.c b/src/core/iomgr/tcp_posix.c index 63a8a2720e..24fee0596f 100644 --- a/src/core/iomgr/tcp_posix.c +++ b/src/core/iomgr/tcp_posix.c @@ -572,14 +572,21 @@ static void grpc_tcp_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset) { grpc_pollset_add_fd(pollset, tcp->em_fd); } +static void grpc_tcp_add_to_pollset_set(grpc_endpoint *ep, grpc_pollset_set *pollset_set) { + grpc_tcp *tcp = (grpc_tcp *)ep; + grpc_pollset_set_add_fd(pollset_set, tcp->em_fd); +} + static char *grpc_tcp_get_peer(grpc_endpoint *ep) { grpc_tcp *tcp = (grpc_tcp *)ep; return gpr_strdup(tcp->peer_string); } static const grpc_endpoint_vtable vtable = { - grpc_tcp_notify_on_read, grpc_tcp_write, grpc_tcp_add_to_pollset, - grpc_tcp_shutdown, grpc_tcp_destroy, grpc_tcp_get_peer}; + grpc_tcp_notify_on_read, grpc_tcp_write, + grpc_tcp_add_to_pollset, grpc_tcp_add_to_pollset_set, + grpc_tcp_shutdown, grpc_tcp_destroy, + grpc_tcp_get_peer}; grpc_endpoint *grpc_tcp_create(grpc_fd *em_fd, size_t slice_size, const char *peer_string) { diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c index 8538600112..6399aaadb9 100644 --- a/src/core/iomgr/tcp_server_posix.c +++ b/src/core/iomgr/tcp_server_posix.c @@ -142,6 +142,7 @@ grpc_tcp_server *grpc_tcp_server_create(void) { static void finish_shutdown(grpc_tcp_server *s) { s->shutdown_complete(s->shutdown_complete_arg); + s->shutdown_complete = NULL; gpr_mu_destroy(&s->mu); @@ -157,6 +158,7 @@ static void destroyed_port(void *server, int success) { gpr_mu_unlock(&s->mu); finish_shutdown(s); } else { + GPR_ASSERT(s->destroyed_ports < s->nports); gpr_mu_unlock(&s->mu); } } diff --git a/src/core/iomgr/tcp_server_windows.c b/src/core/iomgr/tcp_server_windows.c index cc680507ff..0adbe9507c 100644 --- a/src/core/iomgr/tcp_server_windows.c +++ b/src/core/iomgr/tcp_server_windows.c @@ -79,6 +79,8 @@ struct grpc_tcp_server { /* active port count: how many ports are actually still listening */ int active_ports; + /* number of iomgr callbacks that have been explicitly scheduled during shutdown */ + int iomgr_callbacks_pending; /* all listening ports */ server_port *ports; @@ -93,6 +95,7 @@ grpc_tcp_server *grpc_tcp_server_create(void) { gpr_mu_init(&s->mu); gpr_cv_init(&s->cv); s->active_ports = 0; + s->iomgr_callbacks_pending = 0; s->cb = NULL; s->cb_arg = NULL; s->ports = gpr_malloc(sizeof(server_port) * INIT_PORT_CAP); @@ -112,10 +115,10 @@ void grpc_tcp_server_destroy(grpc_tcp_server *s, for (i = 0; i < s->nports; i++) { server_port *sp = &s->ports[i]; sp->shutting_down = 1; - grpc_winsocket_shutdown(sp->socket); + s->iomgr_callbacks_pending += grpc_winsocket_shutdown(sp->socket); } /* This happens asynchronously. Wait while that happens. */ - while (s->active_ports) { + while (s->active_ports || s->iomgr_callbacks_pending) { gpr_cv_wait(&s->cv, &s->mu, gpr_inf_future(GPR_CLOCK_REALTIME)); } gpr_mu_unlock(&s->mu); @@ -183,6 +186,17 @@ error: return -1; } +static void decrement_active_ports_and_notify(server_port *sp) { + sp->shutting_down = 0; + sp->socket->read_info.outstanding = 0; + gpr_mu_lock(&sp->server->mu); + GPR_ASSERT(sp->server->active_ports > 0); + if (0 == --sp->server->active_ports) { + gpr_cv_broadcast(&sp->server->cv); + } + gpr_mu_unlock(&sp->server->mu); +} + /* start_accept will reference that for the IOCP notification request. */ static void on_accept(void *arg, int from_iocp); @@ -231,6 +245,15 @@ static void start_accept(server_port *port) { return; failure: + if (port->shutting_down) { + /* We are abandoning the listener port, take that into account to prevent + occasional hangs on shutdown. The hang happens when sp->shutting_down + change is not seen by on_accept and we proceed to trying new accept, + but we fail there because the listening port has been closed in the + meantime. */ + decrement_active_ports_and_notify(port); + return; + } utf8_message = gpr_format_message(WSAGetLastError()); gpr_log(GPR_ERROR, message, utf8_message); gpr_free(utf8_message); @@ -254,8 +277,16 @@ static void on_accept(void *arg, int from_iocp) { /* 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; + case. We only need to adjust the pending callback count */ + if (!from_iocp) { + gpr_mu_lock(&sp->server->mu); + GPR_ASSERT(sp->server->iomgr_callbacks_pending > 0); + if (0 == --sp->server->iomgr_callbacks_pending) { + gpr_cv_broadcast(&sp->server->cv); + } + gpr_mu_unlock(&sp->server->mu); + return; + } /* The IOCP notified us of a completed operation. Let's grab the results, and act accordingly. */ @@ -264,15 +295,9 @@ static void on_accept(void *arg, int from_iocp) { &transfered_bytes, FALSE, &flags); if (!wsa_success) { if (sp->shutting_down) { - /* During the shutdown case, we ARE expecting an error. So that's swell, + /* During the shutdown case, we ARE expecting an error. So that's well, and we can wake up the shutdown thread. */ - sp->shutting_down = 0; - sp->socket->read_info.outstanding = 0; - gpr_mu_lock(&sp->server->mu); - if (0 == --sp->server->active_ports) { - gpr_cv_broadcast(&sp->server->cv); - } - gpr_mu_unlock(&sp->server->mu); + decrement_active_ports_and_notify(sp); return; } else { char *utf8_message = gpr_format_message(WSAGetLastError()); diff --git a/src/core/iomgr/tcp_windows.c b/src/core/iomgr/tcp_windows.c index d68e6aee79..89aa741470 100644 --- a/src/core/iomgr/tcp_windows.c +++ b/src/core/iomgr/tcp_windows.c @@ -368,8 +368,17 @@ static grpc_endpoint_write_status win_write(grpc_endpoint *ep, return GRPC_ENDPOINT_WRITE_PENDING; } -static void win_add_to_pollset(grpc_endpoint *ep, grpc_pollset *pollset) { - grpc_tcp *tcp = (grpc_tcp *) ep; +static void win_add_to_pollset(grpc_endpoint *ep, grpc_pollset *ps) { + grpc_tcp *tcp; + (void) ps; + tcp = (grpc_tcp *) ep; + grpc_iocp_add_socket(tcp->socket); +} + +static void win_add_to_pollset_set(grpc_endpoint *ep, grpc_pollset_set *pss) { + grpc_tcp *tcp; + (void) pss; + tcp = (grpc_tcp *) ep; grpc_iocp_add_socket(tcp->socket); } @@ -401,9 +410,10 @@ static char *win_get_peer(grpc_endpoint *ep) { return gpr_strdup(tcp->peer_string); } -static grpc_endpoint_vtable vtable = { - win_notify_on_read, win_write, win_add_to_pollset, win_shutdown, win_destroy, win_get_peer -}; +static grpc_endpoint_vtable vtable = {win_notify_on_read, win_write, + win_add_to_pollset, win_add_to_pollset_set, + win_shutdown, win_destroy, + win_get_peer}; grpc_endpoint *grpc_tcp_create(grpc_winsocket *socket, char *peer_string) { grpc_tcp *tcp = (grpc_tcp *) gpr_malloc(sizeof(grpc_tcp)); diff --git a/src/core/iomgr/wakeup_fd_eventfd.c b/src/core/iomgr/wakeup_fd_eventfd.c index 99c32bb9db..52912235f8 100644 --- a/src/core/iomgr/wakeup_fd_eventfd.c +++ b/src/core/iomgr/wakeup_fd_eventfd.c @@ -42,7 +42,7 @@ #include "src/core/iomgr/wakeup_fd_posix.h" #include <grpc/support/log.h> -static void eventfd_create(grpc_wakeup_fd_info *fd_info) { +static void eventfd_create(grpc_wakeup_fd *fd_info) { int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); /* TODO(klempner): Handle failure more gracefully */ GPR_ASSERT(efd >= 0); @@ -50,7 +50,7 @@ static void eventfd_create(grpc_wakeup_fd_info *fd_info) { fd_info->write_fd = -1; } -static void eventfd_consume(grpc_wakeup_fd_info *fd_info) { +static void eventfd_consume(grpc_wakeup_fd *fd_info) { eventfd_t value; int err; do { @@ -58,14 +58,14 @@ static void eventfd_consume(grpc_wakeup_fd_info *fd_info) { } while (err < 0 && errno == EINTR); } -static void eventfd_wakeup(grpc_wakeup_fd_info *fd_info) { +static void eventfd_wakeup(grpc_wakeup_fd *fd_info) { int err; do { err = eventfd_write(fd_info->read_fd, 1); } while (err < 0 && errno == EINTR); } -static void eventfd_destroy(grpc_wakeup_fd_info *fd_info) { +static void eventfd_destroy(grpc_wakeup_fd *fd_info) { close(fd_info->read_fd); } diff --git a/src/core/iomgr/wakeup_fd_pipe.c b/src/core/iomgr/wakeup_fd_pipe.c index f895478990..9fc4ee2388 100644 --- a/src/core/iomgr/wakeup_fd_pipe.c +++ b/src/core/iomgr/wakeup_fd_pipe.c @@ -44,7 +44,7 @@ #include "src/core/iomgr/socket_utils_posix.h" #include <grpc/support/log.h> -static void pipe_create(grpc_wakeup_fd_info *fd_info) { +static void pipe_init(grpc_wakeup_fd *fd_info) { int pipefd[2]; /* TODO(klempner): Make this nonfatal */ GPR_ASSERT(0 == pipe(pipefd)); @@ -54,7 +54,7 @@ static void pipe_create(grpc_wakeup_fd_info *fd_info) { fd_info->write_fd = pipefd[1]; } -static void pipe_consume(grpc_wakeup_fd_info *fd_info) { +static void pipe_consume(grpc_wakeup_fd *fd_info) { char buf[128]; int r; @@ -74,13 +74,13 @@ static void pipe_consume(grpc_wakeup_fd_info *fd_info) { } } -static void pipe_wakeup(grpc_wakeup_fd_info *fd_info) { +static void pipe_wakeup(grpc_wakeup_fd *fd_info) { char c = 0; while (write(fd_info->write_fd, &c, 1) != 1 && errno == EINTR) ; } -static void pipe_destroy(grpc_wakeup_fd_info *fd_info) { +static void pipe_destroy(grpc_wakeup_fd *fd_info) { close(fd_info->read_fd); close(fd_info->write_fd); } @@ -91,7 +91,7 @@ static int pipe_check_availability(void) { } const grpc_wakeup_fd_vtable grpc_pipe_wakeup_fd_vtable = { - pipe_create, pipe_consume, pipe_wakeup, pipe_destroy, pipe_check_availability -}; + pipe_init, pipe_consume, pipe_wakeup, pipe_destroy, + pipe_check_availability}; #endif /* GPR_POSIX_WAKUP_FD */ diff --git a/src/core/iomgr/wakeup_fd_posix.c b/src/core/iomgr/wakeup_fd_posix.c index d3cc3ec570..e48f5223fa 100644 --- a/src/core/iomgr/wakeup_fd_posix.c +++ b/src/core/iomgr/wakeup_fd_posix.c @@ -57,19 +57,19 @@ void grpc_wakeup_fd_global_destroy(void) { wakeup_fd_vtable = NULL; } -void grpc_wakeup_fd_create(grpc_wakeup_fd_info *fd_info) { - wakeup_fd_vtable->create(fd_info); +void grpc_wakeup_fd_init(grpc_wakeup_fd *fd_info) { + wakeup_fd_vtable->init(fd_info); } -void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd_info *fd_info) { +void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd *fd_info) { wakeup_fd_vtable->consume(fd_info); } -void grpc_wakeup_fd_wakeup(grpc_wakeup_fd_info *fd_info) { +void grpc_wakeup_fd_wakeup(grpc_wakeup_fd *fd_info) { wakeup_fd_vtable->wakeup(fd_info); } -void grpc_wakeup_fd_destroy(grpc_wakeup_fd_info *fd_info) { +void grpc_wakeup_fd_destroy(grpc_wakeup_fd *fd_info) { wakeup_fd_vtable->destroy(fd_info); } diff --git a/src/core/iomgr/wakeup_fd_posix.h b/src/core/iomgr/wakeup_fd_posix.h index 1b0ff70c7f..a4da4df51f 100644 --- a/src/core/iomgr/wakeup_fd_posix.h +++ b/src/core/iomgr/wakeup_fd_posix.h @@ -69,28 +69,28 @@ void grpc_wakeup_fd_global_destroy(void); * purposes only.*/ void grpc_wakeup_fd_global_init_force_fallback(void); -typedef struct grpc_wakeup_fd_info grpc_wakeup_fd_info; +typedef struct grpc_wakeup_fd grpc_wakeup_fd; typedef struct grpc_wakeup_fd_vtable { - void (*create)(grpc_wakeup_fd_info *fd_info); - void (*consume)(grpc_wakeup_fd_info *fd_info); - void (*wakeup)(grpc_wakeup_fd_info *fd_info); - void (*destroy)(grpc_wakeup_fd_info *fd_info); + void (*init)(grpc_wakeup_fd *fd_info); + void (*consume)(grpc_wakeup_fd *fd_info); + void (*wakeup)(grpc_wakeup_fd *fd_info); + void (*destroy)(grpc_wakeup_fd *fd_info); /* Must be called before calling any other functions */ int (*check_availability)(void); } grpc_wakeup_fd_vtable; -struct grpc_wakeup_fd_info { +struct grpc_wakeup_fd { int read_fd; int write_fd; }; #define GRPC_WAKEUP_FD_GET_READ_FD(fd_info) ((fd_info)->read_fd) -void grpc_wakeup_fd_create(grpc_wakeup_fd_info *fd_info); -void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd_info *fd_info); -void grpc_wakeup_fd_wakeup(grpc_wakeup_fd_info *fd_info); -void grpc_wakeup_fd_destroy(grpc_wakeup_fd_info *fd_info); +void grpc_wakeup_fd_init(grpc_wakeup_fd *fd_info); +void grpc_wakeup_fd_consume_wakeup(grpc_wakeup_fd *fd_info); +void grpc_wakeup_fd_wakeup(grpc_wakeup_fd *fd_info); +void grpc_wakeup_fd_destroy(grpc_wakeup_fd *fd_info); /* Defined in some specialized implementation's .c file, or by * wakeup_fd_nospecial.c if no such implementation exists. */ diff --git a/src/core/security/client_auth_filter.c b/src/core/security/client_auth_filter.c index 9a69f53a5a..410852da52 100644 --- a/src/core/security/client_auth_filter.c +++ b/src/core/security/client_auth_filter.c @@ -75,12 +75,11 @@ typedef struct { grpc_mdstr *status_key; } channel_data; -static void bubble_up_error(grpc_call_element *elem, const char *error_msg) { +static void bubble_up_error(grpc_call_element *elem, grpc_status_code status, + const char *error_msg) { call_data *calld = elem->call_data; - channel_data *chand = elem->channel_data; - grpc_transport_stream_op_add_cancellation( - &calld->op, GRPC_STATUS_UNAUTHENTICATED, - grpc_mdstr_from_string(chand->md_ctx, error_msg)); + gpr_log(GPR_ERROR, "Client side authentication failure: %s", error_msg); + grpc_transport_stream_op_add_cancellation(&calld->op, status); grpc_call_next_op(elem, &calld->op); } @@ -95,7 +94,8 @@ static void on_credentials_metadata(void *user_data, grpc_metadata_batch *mdb; size_t i; if (status != GRPC_CREDENTIALS_OK) { - bubble_up_error(elem, "Credentials failed to get metadata."); + bubble_up_error(elem, GRPC_STATUS_UNAUTHENTICATED, + "Credentials failed to get metadata."); return; } GPR_ASSERT(num_md <= MAX_CREDENTIALS_METADATA_COUNT); @@ -155,7 +155,7 @@ static void send_security_metadata(grpc_call_element *elem, if (channel_creds_has_md && call_creds_has_md) { calld->creds = grpc_composite_credentials_create(channel_creds, ctx->creds); if (calld->creds == NULL) { - bubble_up_error(elem, + bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT, "Incompatible credentials set on channel and call."); return; } @@ -183,7 +183,7 @@ static void on_host_checked(void *user_data, grpc_security_status status) { char *error_msg; gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.", grpc_mdstr_as_c_string(calld->host)); - bubble_up_error(elem, error_msg); + bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg); gpr_free(error_msg); } } @@ -253,7 +253,7 @@ static void auth_start_transport_op(grpc_call_element *elem, gpr_asprintf(&error_msg, "Invalid host %s set in :authority metadata.", call_host); - bubble_up_error(elem, error_msg); + bubble_up_error(elem, GRPC_STATUS_INVALID_ARGUMENT, error_msg); gpr_free(error_msg); } return; /* early exit */ @@ -316,10 +316,10 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, (grpc_channel_security_connector *)GRPC_SECURITY_CONNECTOR_REF( sc, "client_auth_filter"); chand->md_ctx = metadata_context; - chand->authority_string = grpc_mdstr_from_string(chand->md_ctx, ":authority"); - chand->path_string = grpc_mdstr_from_string(chand->md_ctx, ":path"); - chand->error_msg_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-message"); - chand->status_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-status"); + chand->authority_string = grpc_mdstr_from_string(chand->md_ctx, ":authority", 0); + chand->path_string = grpc_mdstr_from_string(chand->md_ctx, ":path", 0); + chand->error_msg_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-message", 0); + chand->status_key = grpc_mdstr_from_string(chand->md_ctx, "grpc-status", 0); } /* Destructor for channel data */ diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c index 38612cf308..6421ce673d 100644 --- a/src/core/security/credentials.c +++ b/src/core/security/credentials.c @@ -149,6 +149,12 @@ grpc_security_status grpc_server_credentials_create_security_connector( return creds->vtable->create_security_connector(creds, sc); } +void grpc_server_credentials_set_auth_metadata_processor( + grpc_server_credentials *creds, grpc_auth_metadata_processor processor) { + if (creds == NULL) return; + creds->processor = processor; +} + /* -- Ssl credentials. -- */ static void ssl_destroy(grpc_credentials *creds) { @@ -259,8 +265,10 @@ static void ssl_build_config(const char *pem_root_certs, static void ssl_build_server_config( const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs, - size_t num_key_cert_pairs, grpc_ssl_server_config *config) { + size_t num_key_cert_pairs, int force_client_auth, + grpc_ssl_server_config *config) { size_t i; + config->force_client_auth = force_client_auth; if (pem_root_certs != NULL) { ssl_copy_key_material(pem_root_certs, &config->pem_root_certs, &config->pem_root_certs_size); @@ -302,14 +310,14 @@ grpc_credentials *grpc_ssl_credentials_create( grpc_server_credentials *grpc_ssl_server_credentials_create( const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pairs, - size_t num_key_cert_pairs) { + size_t num_key_cert_pairs, int force_client_auth) { grpc_ssl_server_credentials *c = gpr_malloc(sizeof(grpc_ssl_server_credentials)); memset(c, 0, sizeof(grpc_ssl_server_credentials)); c->base.type = GRPC_CREDENTIALS_TYPE_SSL; c->base.vtable = &ssl_server_vtable; ssl_build_server_config(pem_root_certs, pem_key_cert_pairs, - num_key_cert_pairs, &c->config); + num_key_cert_pairs, force_client_auth, &c->config); return &c->base; } @@ -677,7 +685,7 @@ static void service_account_fetch_oauth2( request.path = GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH; request.hdr_count = 1; request.hdrs = &header; - request.use_ssl = 1; + request.handshaker = &grpc_httpcli_ssl; grpc_httpcli_post(httpcli_context, pollset, &request, body, strlen(body), deadline, response_cb, metadata_req); gpr_free(body); @@ -736,7 +744,7 @@ static void refresh_token_fetch_oauth2( request.path = GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH; request.hdr_count = 1; request.hdrs = &header; - request.use_ssl = 1; + request.handshaker = &grpc_httpcli_ssl; grpc_httpcli_post(httpcli_context, pollset, &request, body, strlen(body), deadline, response_cb, metadata_req); gpr_free(body); @@ -763,19 +771,19 @@ grpc_credentials *grpc_refresh_token_credentials_create( grpc_auth_refresh_token_create_from_string(json_refresh_token)); } -/* -- Fake Oauth2 credentials. -- */ +/* -- Metadata-only credentials. -- */ -static void fake_oauth2_destroy(grpc_credentials *creds) { - grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds; - grpc_credentials_md_store_unref(c->access_token_md); +static void md_only_test_destroy(grpc_credentials *creds) { + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)creds; + grpc_credentials_md_store_unref(c->md_store); gpr_free(c); } -static int fake_oauth2_has_request_metadata(const grpc_credentials *creds) { +static int md_only_test_has_request_metadata(const grpc_credentials *creds) { return 1; } -static int fake_oauth2_has_request_metadata_only( +static int md_only_test_has_request_metadata_only( const grpc_credentials *creds) { return 1; } @@ -783,19 +791,19 @@ static int fake_oauth2_has_request_metadata_only( void on_simulated_token_fetch_done(void *user_data, int success) { grpc_credentials_metadata_request *r = (grpc_credentials_metadata_request *)user_data; - grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)r->creds; + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)r->creds; GPR_ASSERT(success); - r->cb(r->user_data, c->access_token_md->entries, - c->access_token_md->num_entries, GRPC_CREDENTIALS_OK); + r->cb(r->user_data, c->md_store->entries, + c->md_store->num_entries, GRPC_CREDENTIALS_OK); grpc_credentials_metadata_request_destroy(r); } -static void fake_oauth2_get_request_metadata(grpc_credentials *creds, +static void md_only_test_get_request_metadata(grpc_credentials *creds, grpc_pollset *pollset, const char *service_url, grpc_credentials_metadata_cb cb, void *user_data) { - grpc_fake_oauth2_credentials *c = (grpc_fake_oauth2_credentials *)creds; + grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)creds; if (c->is_async) { grpc_credentials_metadata_request *cb_arg = @@ -804,26 +812,26 @@ static void fake_oauth2_get_request_metadata(grpc_credentials *creds, on_simulated_token_fetch_done, cb_arg); grpc_iomgr_add_callback(cb_arg->on_simulated_token_fetch_done_closure); } else { - cb(user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK); + cb(user_data, c->md_store->entries, 1, GRPC_CREDENTIALS_OK); } } -static grpc_credentials_vtable fake_oauth2_vtable = { - fake_oauth2_destroy, fake_oauth2_has_request_metadata, - fake_oauth2_has_request_metadata_only, fake_oauth2_get_request_metadata, +static grpc_credentials_vtable md_only_test_vtable = { + md_only_test_destroy, md_only_test_has_request_metadata, + md_only_test_has_request_metadata_only, md_only_test_get_request_metadata, NULL}; -grpc_credentials *grpc_fake_oauth2_credentials_create( - const char *token_md_value, int is_async) { - grpc_fake_oauth2_credentials *c = - gpr_malloc(sizeof(grpc_fake_oauth2_credentials)); - memset(c, 0, sizeof(grpc_fake_oauth2_credentials)); +grpc_credentials *grpc_md_only_test_credentials_create(const char *md_key, + const char *md_value, + int is_async) { + grpc_md_only_test_credentials *c = + gpr_malloc(sizeof(grpc_md_only_test_credentials)); + memset(c, 0, sizeof(grpc_md_only_test_credentials)); c->base.type = GRPC_CREDENTIALS_TYPE_OAUTH2; - c->base.vtable = &fake_oauth2_vtable; + c->base.vtable = &md_only_test_vtable; gpr_ref_init(&c->base.refcount, 1); - c->access_token_md = grpc_credentials_md_store_create(1); - grpc_credentials_md_store_add_cstrings( - c->access_token_md, GRPC_AUTHORIZATION_METADATA_KEY, token_md_value); + c->md_store = grpc_credentials_md_store_create(1); + grpc_credentials_md_store_add_cstrings(c->md_store, md_key, md_value); c->is_async = is_async; return &c->base; } diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h index 8d40da47c1..04736525dc 100644 --- a/src/core/security/credentials.h +++ b/src/core/security/credentials.h @@ -190,9 +190,10 @@ grpc_oauth2_token_fetcher_credentials_parse_server_response( grpc_credentials_md_store **token_md, gpr_timespec *token_lifetime); void grpc_flush_cached_google_default_credentials(void); -/* Simulates an oauth2 token fetch with the specified value for testing. */ -grpc_credentials *grpc_fake_oauth2_credentials_create( - const char *token_md_value, int is_async); +/* Metadata-only credentials with the specified key and value where + asynchronicity can be simulated for testing. */ +grpc_credentials *grpc_md_only_test_credentials_create( + const char *md_key, const char *md_value, int is_async); /* Private constructor for jwt credentials from an already parsed json key. Takes ownership of the key. */ @@ -216,6 +217,7 @@ typedef struct { struct grpc_server_credentials { const grpc_server_credentials_vtable *vtable; const char *type; + grpc_auth_metadata_processor processor; }; grpc_security_status grpc_server_credentials_create_security_connector( @@ -297,13 +299,13 @@ typedef struct { grpc_credentials_md_store *access_token_md; } grpc_access_token_credentials; -/* -- Fake Oauth2 credentials. -- */ +/* -- Metadata-only Test credentials. -- */ typedef struct { grpc_credentials base; - grpc_credentials_md_store *access_token_md; + grpc_credentials_md_store *md_store; int is_async; -} grpc_fake_oauth2_credentials; +} grpc_md_only_test_credentials; /* -- IAM credentials. -- */ diff --git a/src/core/security/google_default_credentials.c b/src/core/security/google_default_credentials.c index de1929fe76..d1f228665f 100644 --- a/src/core/security/google_default_credentials.c +++ b/src/core/security/google_default_credentials.c @@ -80,10 +80,12 @@ static void on_compute_engine_detection_http_response( } gpr_mu_lock(GRPC_POLLSET_MU(&detector->pollset)); detector->is_done = 1; - grpc_pollset_kick(&detector->pollset); + grpc_pollset_kick(&detector->pollset, NULL); gpr_mu_unlock(GRPC_POLLSET_MU(&detector->pollset)); } +static void destroy_pollset(void *p) { grpc_pollset_destroy(p); } + static int is_stack_running_on_compute_engine(void) { compute_engine_detector detector; grpc_httpcli_request request; @@ -112,12 +114,14 @@ static int is_stack_running_on_compute_engine(void) { called once for the lifetime of the process by the default credentials. */ gpr_mu_lock(GRPC_POLLSET_MU(&detector.pollset)); while (!detector.is_done) { - grpc_pollset_work(&detector.pollset, gpr_inf_future(GPR_CLOCK_REALTIME)); + grpc_pollset_worker worker; + grpc_pollset_work(&detector.pollset, &worker, + gpr_inf_future(GPR_CLOCK_MONOTONIC)); } gpr_mu_unlock(GRPC_POLLSET_MU(&detector.pollset)); grpc_httpcli_context_destroy(&context); - grpc_pollset_destroy(&detector.pollset); + grpc_pollset_shutdown(&detector.pollset, destroy_pollset, &detector.pollset); return detector.success; } diff --git a/src/core/security/jwt_verifier.c b/src/core/security/jwt_verifier.c index 1276693da7..38ad134a6a 100644 --- a/src/core/security/jwt_verifier.c +++ b/src/core/security/jwt_verifier.c @@ -628,7 +628,7 @@ static void on_openid_config_retrieved(void *user_data, goto error; } jwks_uri += 8; - req.use_ssl = 1; + req.handshaker = &grpc_httpcli_ssl; req.host = gpr_strdup(jwks_uri); req.path = strchr(jwks_uri, '/'); if (req.path == NULL) { @@ -685,7 +685,7 @@ static void retrieve_key_and_verify(verifier_cb_ctx *ctx) { const char *iss; grpc_httpcli_request req; memset(&req, 0, sizeof(grpc_httpcli_request)); - req.use_ssl = 1; + req.handshaker = &grpc_httpcli_ssl; GPR_ASSERT(ctx != NULL && ctx->header != NULL && ctx->claims != NULL); iss = ctx->claims->iss; diff --git a/src/core/security/secure_endpoint.c b/src/core/security/secure_endpoint.c index e189380ec5..95fbf71f3d 100644 --- a/src/core/security/secure_endpoint.c +++ b/src/core/security/secure_endpoint.c @@ -331,14 +331,22 @@ static void endpoint_add_to_pollset(grpc_endpoint *secure_ep, grpc_endpoint_add_to_pollset(ep->wrapped_ep, pollset); } +static void endpoint_add_to_pollset_set(grpc_endpoint *secure_ep, + grpc_pollset_set *pollset_set) { + secure_endpoint *ep = (secure_endpoint *)secure_ep; + grpc_endpoint_add_to_pollset_set(ep->wrapped_ep, pollset_set); +} + static char *endpoint_get_peer(grpc_endpoint *secure_ep) { secure_endpoint *ep = (secure_endpoint *)secure_ep; return grpc_endpoint_get_peer(ep->wrapped_ep); } static const grpc_endpoint_vtable vtable = { - endpoint_notify_on_read, endpoint_write, endpoint_add_to_pollset, - endpoint_shutdown, endpoint_unref, endpoint_get_peer}; + endpoint_notify_on_read, endpoint_write, + endpoint_add_to_pollset, endpoint_add_to_pollset_set, + endpoint_shutdown, endpoint_unref, + endpoint_get_peer}; grpc_endpoint *grpc_secure_endpoint_create( struct tsi_frame_protector *protector, grpc_endpoint *transport, diff --git a/src/core/security/security_connector.c b/src/core/security/security_connector.c index f6e423eb27..a354536dcd 100644 --- a/src/core/security/security_connector.c +++ b/src/core/security/security_connector.c @@ -263,9 +263,9 @@ static grpc_security_status fake_check_peer(grpc_security_connector *sc, goto end; } GRPC_AUTH_CONTEXT_UNREF(sc->auth_context, "connector"); - sc->auth_context = grpc_auth_context_create(NULL, 1); - sc->auth_context->properties[0] = grpc_auth_property_init_from_cstring( - GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, + sc->auth_context = grpc_auth_context_create(NULL); + grpc_auth_context_add_cstring_property( + sc->auth_context, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, GRPC_FAKE_TRANSPORT_SECURITY_TYPE); end: @@ -409,31 +409,35 @@ static int ssl_host_matches_name(const tsi_peer *peer, const char *peer_name) { grpc_auth_context *tsi_ssl_peer_to_auth_context(const tsi_peer *peer) { size_t i; grpc_auth_context *ctx = NULL; + const char *peer_identity_property_name = NULL; /* The caller has checked the certificate type property. */ GPR_ASSERT(peer->property_count >= 1); - ctx = grpc_auth_context_create(NULL, peer->property_count); - ctx->properties[0] = grpc_auth_property_init_from_cstring( - GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, + ctx = grpc_auth_context_create(NULL); + grpc_auth_context_add_cstring_property( + ctx, GRPC_TRANSPORT_SECURITY_TYPE_PROPERTY_NAME, GRPC_SSL_TRANSPORT_SECURITY_TYPE); - ctx->property_count = 1; for (i = 0; i < peer->property_count; i++) { const tsi_peer_property *prop = &peer->properties[i]; if (prop->name == NULL) continue; if (strcmp(prop->name, TSI_X509_SUBJECT_COMMON_NAME_PEER_PROPERTY) == 0) { /* If there is no subject alt name, have the CN as the identity. */ - if (ctx->peer_identity_property_name == NULL) { - ctx->peer_identity_property_name = GRPC_X509_CN_PROPERTY_NAME; + if (peer_identity_property_name == NULL) { + peer_identity_property_name = GRPC_X509_CN_PROPERTY_NAME; } - ctx->properties[ctx->property_count++] = grpc_auth_property_init( - GRPC_X509_CN_PROPERTY_NAME, prop->value.data, prop->value.length); + grpc_auth_context_add_property(ctx, GRPC_X509_CN_PROPERTY_NAME, + prop->value.data, prop->value.length); } else if (strcmp(prop->name, TSI_X509_SUBJECT_ALTERNATIVE_NAME_PEER_PROPERTY) == 0) { - ctx->peer_identity_property_name = GRPC_X509_SAN_PROPERTY_NAME; - ctx->properties[ctx->property_count++] = grpc_auth_property_init( - GRPC_X509_SAN_PROPERTY_NAME, prop->value.data, prop->value.length); + peer_identity_property_name = GRPC_X509_SAN_PROPERTY_NAME; + grpc_auth_context_add_property(ctx, GRPC_X509_SAN_PROPERTY_NAME, + prop->value.data, prop->value.length); } } + if (peer_identity_property_name != NULL) { + GPR_ASSERT(grpc_auth_context_set_peer_identity_property_name( + ctx, peer_identity_property_name) == 1); + } return ctx; } @@ -653,9 +657,10 @@ grpc_security_status grpc_ssl_server_security_connector_create( config->pem_private_keys_sizes, (const unsigned char **)config->pem_cert_chains, config->pem_cert_chains_sizes, config->num_key_cert_pairs, - config->pem_root_certs, config->pem_root_certs_size, ssl_cipher_suites(), - alpn_protocol_strings, alpn_protocol_string_lengths, - (uint16_t)num_alpn_protocols, &c->handshaker_factory); + config->pem_root_certs, config->pem_root_certs_size, + config->force_client_auth, ssl_cipher_suites(), alpn_protocol_strings, + alpn_protocol_string_lengths, (uint16_t)num_alpn_protocols, + &c->handshaker_factory); if (result != TSI_OK) { gpr_log(GPR_ERROR, "Handshaker factory creation failed with %s.", tsi_result_to_string(result)); diff --git a/src/core/security/security_connector.h b/src/core/security/security_connector.h index a4c723f026..2c9aa1c5a4 100644 --- a/src/core/security/security_connector.h +++ b/src/core/security/security_connector.h @@ -201,6 +201,7 @@ typedef struct { size_t num_key_cert_pairs; unsigned char *pem_root_certs; size_t pem_root_certs_size; + int force_client_auth; } grpc_ssl_server_config; /* Creates an SSL server_security_connector. diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c index 8ce7876bd8..1ef0fc9255 100644 --- a/src/core/security/security_context.c +++ b/src/core/security/security_context.c @@ -42,6 +42,19 @@ #include <grpc/support/log.h> #include <grpc/support/string_util.h> +/* --- grpc_process_auth_metadata_func --- */ + +static grpc_auth_metadata_processor server_processor = {NULL, NULL}; + +grpc_auth_metadata_processor grpc_server_get_auth_metadata_processor(void) { + return server_processor; +} + +void grpc_server_register_auth_metadata_processor( + grpc_auth_metadata_processor processor) { + server_processor = processor; +} + /* --- grpc_call --- */ grpc_call_error grpc_call_set_credentials(grpc_call *call, @@ -120,15 +133,15 @@ void grpc_server_security_context_destroy(void *ctx) { static grpc_auth_property_iterator empty_iterator = {NULL, 0, NULL}; -grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained, - size_t property_count) { +grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained) { grpc_auth_context *ctx = gpr_malloc(sizeof(grpc_auth_context)); memset(ctx, 0, sizeof(grpc_auth_context)); - ctx->properties = gpr_malloc(property_count * sizeof(grpc_auth_property)); - memset(ctx->properties, 0, property_count * sizeof(grpc_auth_property)); - ctx->property_count = property_count; gpr_ref_init(&ctx->refcount, 1); - if (chained != NULL) ctx->chained = GRPC_AUTH_CONTEXT_REF(chained, "chained"); + if (chained != NULL) { + ctx->chained = GRPC_AUTH_CONTEXT_REF(chained, "chained"); + ctx->peer_identity_property_name = + ctx->chained->peer_identity_property_name; + } return ctx; } @@ -162,11 +175,11 @@ void grpc_auth_context_unref(grpc_auth_context *ctx) { if (gpr_unref(&ctx->refcount)) { size_t i; GRPC_AUTH_CONTEXT_UNREF(ctx->chained, "chained"); - if (ctx->properties != NULL) { - for (i = 0; i < ctx->property_count; i++) { - grpc_auth_property_reset(&ctx->properties[i]); + if (ctx->properties.array != NULL) { + for (i = 0; i < ctx->properties.count; i++) { + grpc_auth_property_reset(&ctx->properties.array[i]); } - gpr_free(ctx->properties); + gpr_free(ctx->properties.array); } gpr_free(ctx); } @@ -177,6 +190,20 @@ const char *grpc_auth_context_peer_identity_property_name( return ctx->peer_identity_property_name; } +int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx, + const char *name) { + grpc_auth_property_iterator it = + grpc_auth_context_find_properties_by_name(ctx, name); + const grpc_auth_property *prop = grpc_auth_property_iterator_next(&it); + if (prop == NULL) { + gpr_log(GPR_ERROR, "Property name %s not found in auth context.", + name != NULL ? name : "NULL"); + return 0; + } + ctx->peer_identity_property_name = prop->name; + return 1; +} + int grpc_auth_context_peer_is_authenticated( const grpc_auth_context *ctx) { return ctx->peer_identity_property_name == NULL ? 0 : 1; @@ -193,16 +220,16 @@ grpc_auth_property_iterator grpc_auth_context_property_iterator( const grpc_auth_property *grpc_auth_property_iterator_next( grpc_auth_property_iterator *it) { if (it == NULL || it->ctx == NULL) return NULL; - while (it->index == it->ctx->property_count) { + while (it->index == it->ctx->properties.count) { if (it->ctx->chained == NULL) return NULL; it->ctx = it->ctx->chained; it->index = 0; } if (it->name == NULL) { - return &it->ctx->properties[it->index++]; + return &it->ctx->properties.array[it->index++]; } else { - while (it->index < it->ctx->property_count) { - const grpc_auth_property *prop = &it->ctx->properties[it->index++]; + while (it->index < it->ctx->properties.count) { + const grpc_auth_property *prop = &it->ctx->properties.array[it->index++]; GPR_ASSERT(prop->name != NULL); if (strcmp(it->name, prop->name) == 0) { return prop; @@ -229,24 +256,37 @@ grpc_auth_property_iterator grpc_auth_context_peer_identity( ctx, ctx->peer_identity_property_name); } -grpc_auth_property grpc_auth_property_init_from_cstring(const char *name, - const char *value) { - grpc_auth_property prop; - prop.name = gpr_strdup(name); - prop.value = gpr_strdup(value); - prop.value_length = strlen(value); - return prop; +static void ensure_auth_context_capacity(grpc_auth_context *ctx) { + if (ctx->properties.count == ctx->properties.capacity) { + ctx->properties.capacity = + GPR_MAX(ctx->properties.capacity + 8, ctx->properties.capacity * 2); + ctx->properties.array = + gpr_realloc(ctx->properties.array, + ctx->properties.capacity * sizeof(grpc_auth_property)); + } } -grpc_auth_property grpc_auth_property_init(const char *name, const char *value, - size_t value_length) { - grpc_auth_property prop; - prop.name = gpr_strdup(name); - prop.value = gpr_malloc(value_length + 1); - memcpy(prop.value, value, value_length); - prop.value[value_length] = '\0'; - prop.value_length = value_length; - return prop; +void grpc_auth_context_add_property(grpc_auth_context *ctx, const char *name, + const char *value, size_t value_length) { + grpc_auth_property *prop; + ensure_auth_context_capacity(ctx); + prop = &ctx->properties.array[ctx->properties.count++]; + prop->name = gpr_strdup(name); + prop->value = gpr_malloc(value_length + 1); + memcpy(prop->value, value, value_length); + prop->value[value_length] = '\0'; + prop->value_length = value_length; +} + +void grpc_auth_context_add_cstring_property(grpc_auth_context *ctx, + const char *name, + const char *value) { + grpc_auth_property *prop; + ensure_auth_context_capacity(ctx); + prop = &ctx->properties.array[ctx->properties.count++]; + prop->name = gpr_strdup(name); + prop->value = gpr_strdup(value); + prop->value_length = strlen(value); } void grpc_auth_property_reset(grpc_auth_property *property) { @@ -255,3 +295,35 @@ void grpc_auth_property_reset(grpc_auth_property *property) { memset(property, 0, sizeof(grpc_auth_property)); } +grpc_arg grpc_auth_metadata_processor_to_arg(grpc_auth_metadata_processor *p) { + grpc_arg arg; + memset(&arg, 0, sizeof(grpc_arg)); + arg.type = GRPC_ARG_POINTER; + arg.key = GRPC_AUTH_METADATA_PROCESSOR_ARG; + arg.value.pointer.p = p; + return arg; +} + +grpc_auth_metadata_processor *grpc_auth_metadata_processor_from_arg( + const grpc_arg *arg) { + if (strcmp(arg->key, GRPC_AUTH_METADATA_PROCESSOR_ARG) != 0) return NULL; + if (arg->type != GRPC_ARG_POINTER) { + gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type, + GRPC_AUTH_METADATA_PROCESSOR_ARG); + return NULL; + } + return arg->value.pointer.p; +} + +grpc_auth_metadata_processor *grpc_find_auth_metadata_processor_in_args( + const grpc_channel_args *args) { + size_t i; + if (args == NULL) return NULL; + for (i = 0; i < args->num_args; i++) { + grpc_auth_metadata_processor *p = + grpc_auth_metadata_processor_from_arg(&args->args[i]); + if (p != NULL) return p; + } + return NULL; +} + diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h index 76a45910bb..7fcd438cf6 100644 --- a/src/core/security/security_context.h +++ b/src/core/security/security_context.h @@ -34,29 +34,31 @@ #ifndef GRPC_INTERNAL_CORE_SECURITY_SECURITY_CONTEXT_H #define GRPC_INTERNAL_CORE_SECURITY_SECURITY_CONTEXT_H +#include "src/core/iomgr/pollset.h" #include "src/core/security/credentials.h" -#ifdef __cplusplus -extern "C" { -#endif - /* --- grpc_auth_context --- High level authentication context object. Can optionally be chained. */ /* Property names are always NULL terminated. */ +typedef struct { + grpc_auth_property *array; + size_t count; + size_t capacity; +} grpc_auth_property_array; + struct grpc_auth_context { struct grpc_auth_context *chained; - grpc_auth_property *properties; - size_t property_count; + grpc_auth_property_array properties; gpr_refcount refcount; const char *peer_identity_property_name; + grpc_pollset *pollset; }; -/* Constructor. */ -grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained, - size_t property_count); +/* Creation. */ +grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained); /* Refcounting. */ #ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG @@ -76,12 +78,6 @@ grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *policy); void grpc_auth_context_unref(grpc_auth_context *policy); #endif -grpc_auth_property grpc_auth_property_init_from_cstring(const char *name, - const char *value); - -grpc_auth_property grpc_auth_property_init(const char *name, const char *value, - size_t value_length); - void grpc_auth_property_reset(grpc_auth_property *property); /* --- grpc_client_security_context --- @@ -107,9 +103,14 @@ typedef struct { grpc_server_security_context *grpc_server_security_context_create(void); void grpc_server_security_context_destroy(void *ctx); -#ifdef __cplusplus -} -#endif +/* --- Auth metadata processing. --- */ +#define GRPC_AUTH_METADATA_PROCESSOR_ARG "grpc.auth_metadata_processor" + +grpc_arg grpc_auth_metadata_processor_to_arg(grpc_auth_metadata_processor *p); +grpc_auth_metadata_processor *grpc_auth_metadata_processor_from_arg( + const grpc_arg *arg); +grpc_auth_metadata_processor *grpc_find_auth_metadata_processor_in_args( + const grpc_channel_args *args); #endif /* GRPC_INTERNAL_CORE_SECURITY_SECURITY_CONTEXT_H */ diff --git a/src/core/security/server_auth_filter.c b/src/core/security/server_auth_filter.c index 69789c2f0d..2fc689caec 100644 --- a/src/core/security/server_auth_filter.c +++ b/src/core/security/server_auth_filter.c @@ -31,20 +31,140 @@ * */ +#include <string.h> + #include "src/core/security/auth_filters.h" #include "src/core/security/security_connector.h" #include "src/core/security/security_context.h" +#include <grpc/support/alloc.h> #include <grpc/support/log.h> typedef struct call_data { - int unused; /* C89 requires at least one struct element */ + gpr_uint8 got_client_metadata; + grpc_stream_op_buffer *recv_ops; + /* Closure to call when finished with the auth_on_recv hook. */ + grpc_iomgr_closure *on_done_recv; + /* Receive closures are chained: we inject this closure as the on_done_recv + up-call on transport_op, and remember to call our on_done_recv member after + handling it. */ + grpc_iomgr_closure auth_on_recv; + grpc_transport_stream_op transport_op; + const grpc_metadata *consumed_md; + size_t num_consumed_md; + grpc_stream_op *md_op; + grpc_auth_context *auth_context; } call_data; typedef struct channel_data { grpc_security_connector *security_connector; + grpc_auth_metadata_processor processor; + grpc_mdctx *mdctx; } channel_data; +static grpc_metadata_array metadata_batch_to_md_array( + const grpc_metadata_batch *batch) { + grpc_linked_mdelem *l; + grpc_metadata_array result; + grpc_metadata_array_init(&result); + for (l = batch->list.head; l != NULL; l = l->next) { + grpc_metadata *usr_md = NULL; + grpc_mdelem *md = l->md; + grpc_mdstr *key = md->key; + grpc_mdstr *value = md->value; + if (result.count == result.capacity) { + result.capacity = GPR_MAX(result.capacity + 8, result.capacity * 2); + result.metadata = + gpr_realloc(result.metadata, result.capacity * sizeof(grpc_metadata)); + } + usr_md = &result.metadata[result.count++]; + usr_md->key = grpc_mdstr_as_c_string(key); + usr_md->value = grpc_mdstr_as_c_string(value); + usr_md->value_length = GPR_SLICE_LENGTH(value->slice); + } + return result; +} + +static grpc_mdelem *remove_consumed_md(void *user_data, grpc_mdelem *md) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + size_t i; + for (i = 0; i < calld->num_consumed_md; i++) { + /* Maybe we could do a pointer comparison but we do not have any guarantee + that the metadata processor used the same pointers for consumed_md in the + callback. */ + if (memcmp(GPR_SLICE_START_PTR(md->key->slice), calld->consumed_md[i].key, + GPR_SLICE_LENGTH(md->key->slice)) == 0 && + memcmp(GPR_SLICE_START_PTR(md->value->slice), + calld->consumed_md[i].value, + GPR_SLICE_LENGTH(md->value->slice)) == 0) { + return NULL; /* Delete. */ + } + } + return md; +} + +static void on_md_processing_done(void *user_data, + const grpc_metadata *consumed_md, + size_t num_consumed_md, int success) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + + if (success) { + calld->consumed_md = consumed_md; + calld->num_consumed_md = num_consumed_md; + grpc_metadata_batch_filter(&calld->md_op->data.metadata, remove_consumed_md, + elem); + calld->on_done_recv->cb(calld->on_done_recv->cb_arg, success); + } else { + gpr_slice message = gpr_slice_from_copied_string( + "Authentication metadata processing failed."); + grpc_sopb_reset(calld->recv_ops); + grpc_transport_stream_op_add_close(&calld->transport_op, + GRPC_STATUS_UNAUTHENTICATED, &message); + grpc_call_next_op(elem, &calld->transport_op); + } +} + +static void auth_on_recv(void *user_data, int success) { + grpc_call_element *elem = user_data; + call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + if (success) { + size_t i; + size_t nops = calld->recv_ops->nops; + grpc_stream_op *ops = calld->recv_ops->ops; + for (i = 0; i < nops; i++) { + grpc_metadata_array md_array; + grpc_stream_op *op = &ops[i]; + if (op->type != GRPC_OP_METADATA || calld->got_client_metadata) continue; + calld->got_client_metadata = 1; + if (chand->processor.process == NULL) continue; + calld->md_op = op; + md_array = metadata_batch_to_md_array(&op->data.metadata); + chand->processor.process(chand->processor.state, calld->auth_context, + md_array.metadata, md_array.count, + on_md_processing_done, elem); + grpc_metadata_array_destroy(&md_array); + return; + } + } + calld->on_done_recv->cb(calld->on_done_recv->cb_arg, success); +} + +static void set_recv_ops_md_callbacks(grpc_call_element *elem, + grpc_transport_stream_op *op) { + call_data *calld = elem->call_data; + + if (op->recv_ops && !calld->got_client_metadata) { + /* substitute our callback for the higher callback */ + calld->recv_ops = op->recv_ops; + calld->on_done_recv = op->on_done_recv; + op->on_done_recv = &calld->auth_on_recv; + calld->transport_op = *op; + } +} + /* Called either: - in response to an API call (or similar) from above, to send something - a network event (or similar) from below, to receive something @@ -52,9 +172,7 @@ typedef struct channel_data { that is being sent or received. */ static void auth_start_transport_op(grpc_call_element *elem, grpc_transport_stream_op *op) { - /* TODO(jboeuf): Get the metadata and get a new context from it. */ - - /* pass control down the stack */ + set_recv_ops_md_callbacks(elem, op); grpc_call_next_op(elem, op); } @@ -68,7 +186,8 @@ static void init_call_elem(grpc_call_element *elem, grpc_server_security_context *server_ctx = NULL; /* initialize members */ - calld->unused = 0; + memset(calld, 0, sizeof(*calld)); + grpc_iomgr_closure_init(&calld->auth_on_recv, auth_on_recv, elem); GPR_ASSERT(initial_op && initial_op->context != NULL && initial_op->context[GRPC_CONTEXT_SECURITY].value == NULL); @@ -80,21 +199,29 @@ static void init_call_elem(grpc_call_element *elem, initial_op->context[GRPC_CONTEXT_SECURITY].value); } server_ctx = grpc_server_security_context_create(); - server_ctx->auth_context = GRPC_AUTH_CONTEXT_REF( - chand->security_connector->auth_context, "server_security_context"); + server_ctx->auth_context = + grpc_auth_context_create(chand->security_connector->auth_context); + server_ctx->auth_context->pollset = initial_op->bind_pollset; initial_op->context[GRPC_CONTEXT_SECURITY].value = server_ctx; initial_op->context[GRPC_CONTEXT_SECURITY].destroy = grpc_server_security_context_destroy; + calld->auth_context = server_ctx->auth_context; + + /* Set the metadata callbacks. */ + set_recv_ops_md_callbacks(elem, initial_op); } /* Destructor for call_data */ -static void destroy_call_elem(grpc_call_element *elem) {} +static void destroy_call_elem(grpc_call_element *elem) { +} /* Constructor for channel_data */ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, const grpc_channel_args *args, grpc_mdctx *mdctx, int is_first, int is_last) { grpc_security_connector *sc = grpc_find_security_connector_in_args(args); + grpc_auth_metadata_processor *processor = + grpc_find_auth_metadata_processor_in_args(args); /* grab pointers to our data from the channel element */ channel_data *chand = elem->channel_data; @@ -104,11 +231,14 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, GPR_ASSERT(!is_first); GPR_ASSERT(!is_last); GPR_ASSERT(sc != NULL); + GPR_ASSERT(processor != NULL); /* initialize members */ GPR_ASSERT(!sc->is_client_side); chand->security_connector = GRPC_SECURITY_CONNECTOR_REF(sc, "server_auth_filter"); + chand->mdctx = mdctx; + chand->processor = *processor; } /* Destructor for channel data */ diff --git a/src/core/security/server_secure_chttp2.c b/src/core/security/server_secure_chttp2.c index 3717b8989f..8d9d036d80 100644 --- a/src/core/security/server_secure_chttp2.c +++ b/src/core/security/server_secure_chttp2.c @@ -43,6 +43,7 @@ #include "src/core/security/auth_filters.h" #include "src/core/security/credentials.h" #include "src/core/security/security_connector.h" +#include "src/core/security/security_context.h" #include "src/core/security/secure_transport_setup.h" #include "src/core/surface/server.h" #include "src/core/transport/chttp2_transport.h" @@ -60,6 +61,7 @@ typedef struct grpc_server_secure_state { grpc_server *server; grpc_tcp_server *tcp; grpc_security_connector *sc; + grpc_auth_metadata_processor processor; tcp_endpoint_list *handshaking_tcp_endpoints; int is_shutdown; gpr_mu mu; @@ -86,9 +88,13 @@ static void setup_transport(void *statep, grpc_transport *transport, static grpc_channel_filter const *extra_filters[] = { &grpc_server_auth_filter, &grpc_http_server_filter}; grpc_server_secure_state *state = statep; - grpc_arg connector_arg = grpc_security_connector_to_arg(state->sc); - grpc_channel_args *args_copy = grpc_channel_args_copy_and_add( - grpc_server_get_channel_args(state->server), &connector_arg, 1); + grpc_channel_args *args_copy; + grpc_arg args_to_add[2]; + args_to_add[0] = grpc_security_connector_to_arg(state->sc); + args_to_add[1] = grpc_auth_metadata_processor_to_arg(&state->processor); + args_copy = grpc_channel_args_copy_and_add( + grpc_server_get_channel_args(state->server), args_to_add, + GPR_ARRAY_SIZE(args_to_add)); grpc_server_setup_transport(state->server, transport, extra_filters, GPR_ARRAY_SIZE(extra_filters), mdctx, args_copy); grpc_channel_args_destroy(args_copy); @@ -252,9 +258,11 @@ int grpc_server_add_secure_http2_port(grpc_server *server, const char *addr, grpc_resolved_addresses_destroy(resolved); state = gpr_malloc(sizeof(*state)); + memset(state, 0, sizeof(*state)); state->server = server; state->tcp = tcp; state->sc = sc; + state->processor = creds->processor; state->handshaking_tcp_endpoints = NULL; state->is_shutdown = 0; gpr_mu_init(&state->mu); diff --git a/src/core/support/stack_lockfree.c b/src/core/support/stack_lockfree.c index 2844330379..bc741f8c70 100644 --- a/src/core/support/stack_lockfree.c +++ b/src/core/support/stack_lockfree.c @@ -72,6 +72,11 @@ typedef union lockfree_node { struct gpr_stack_lockfree { lockfree_node *entries; lockfree_node head; /* An atomic entry describing curr head */ + +#ifndef NDEBUG + /* Bitmap of pushed entries to check for double-push or pop */ + gpr_atm pushed[(INVALID_ENTRY_INDEX+1)/(8*sizeof(gpr_atm))]; +#endif }; gpr_stack_lockfree *gpr_stack_lockfree_create(int entries) { @@ -86,6 +91,11 @@ gpr_stack_lockfree *gpr_stack_lockfree_create(int entries) { /* Clear out all entries */ memset(stack->entries, 0, entries * sizeof(stack->entries[0])); memset(&stack->head, 0, sizeof(stack->head)); +#ifndef NDEBUG + memset(&stack->pushed, 0, sizeof(stack->pushed)); +#endif + + GPR_ASSERT(sizeof(stack->entries->atm) == sizeof(stack->entries->contents)); /* Point the head at reserved dummy entry */ stack->head.contents.index = INVALID_ENTRY_INDEX; @@ -100,17 +110,36 @@ void gpr_stack_lockfree_destroy(gpr_stack_lockfree *stack) { int gpr_stack_lockfree_push(gpr_stack_lockfree *stack, int entry) { lockfree_node head; lockfree_node newhead; + lockfree_node curent; + lockfree_node newent; /* First fill in the entry's index and aba ctr for new head */ newhead.contents.index = (gpr_uint16)entry; /* Also post-increment the aba_ctr */ - newhead.contents.aba_ctr = stack->entries[entry].contents.aba_ctr++; + curent.atm = gpr_atm_no_barrier_load(&stack->entries[entry].atm); + newhead.contents.aba_ctr = ++curent.contents.aba_ctr; + gpr_atm_no_barrier_store(&stack->entries[entry].atm, curent.atm); + +#ifndef NDEBUG + /* Check for double push */ + { + int pushed_index = entry / (8*sizeof(gpr_atm)); + int pushed_bit = entry % (8*sizeof(gpr_atm)); + gpr_atm old_val; + + old_val = gpr_atm_no_barrier_fetch_add(&stack->pushed[pushed_index], + (gpr_atm)(1UL << pushed_bit)); + GPR_ASSERT((old_val & (1UL<<pushed_bit)) == 0); + } +#endif do { /* Atomically get the existing head value for use */ head.atm = gpr_atm_no_barrier_load(&(stack->head.atm)); /* Point to it */ - stack->entries[entry].contents.index = head.contents.index; + newent.atm = gpr_atm_no_barrier_load(&stack->entries[entry].atm); + newent.contents.index = head.contents.index; + gpr_atm_no_barrier_store(&stack->entries[entry].atm, newent.atm); } while (!gpr_atm_rel_cas(&(stack->head.atm), head.atm, newhead.atm)); /* Use rel_cas above to make sure that entry index is set properly */ return head.contents.index == INVALID_ENTRY_INDEX; @@ -119,6 +148,7 @@ int gpr_stack_lockfree_push(gpr_stack_lockfree *stack, int entry) { int gpr_stack_lockfree_pop(gpr_stack_lockfree *stack) { lockfree_node head; lockfree_node newhead; + do { head.atm = gpr_atm_acq_load(&(stack->head.atm)); if (head.contents.index == INVALID_ENTRY_INDEX) { @@ -128,5 +158,18 @@ int gpr_stack_lockfree_pop(gpr_stack_lockfree *stack) { gpr_atm_no_barrier_load(&(stack->entries[head.contents.index].atm)); } while (!gpr_atm_no_barrier_cas(&(stack->head.atm), head.atm, newhead.atm)); +#ifndef NDEBUG + /* Check for valid pop */ + { + int pushed_index = head.contents.index / (8*sizeof(gpr_atm)); + int pushed_bit = head.contents.index % (8*sizeof(gpr_atm)); + gpr_atm old_val; + + old_val = gpr_atm_no_barrier_fetch_add(&stack->pushed[pushed_index], + -(gpr_atm)(1UL << pushed_bit)); + GPR_ASSERT((old_val & (1UL<<pushed_bit)) != 0); + } +#endif + return head.contents.index; } diff --git a/src/core/surface/call.c b/src/core/surface/call.c index a1da822113..5839d3ac2e 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -40,7 +40,6 @@ #include <grpc/support/log.h> #include <grpc/support/string_util.h> -#include "src/core/census/grpc_context.h" #include "src/core/channel/channel_stack.h" #include "src/core/iomgr/alarm.h" #include "src/core/profiling/timers.h" @@ -144,6 +143,8 @@ typedef enum { struct grpc_call { grpc_completion_queue *cq; grpc_channel *channel; + grpc_call *parent; + grpc_call *first_child; grpc_mdctx *metadata_context; /* TODO(ctiller): share with cq if possible? */ gpr_mu mu; @@ -177,6 +178,8 @@ struct grpc_call { gpr_uint8 cancel_alarm; /** bitmask of allocated completion events in completions */ gpr_uint8 allocated_completions; + /** flag indicating that cancellation is inherited */ + gpr_uint8 cancellation_is_inherited; /* flags with bits corresponding to write states allowing us to determine what was sent */ @@ -268,6 +271,11 @@ struct grpc_call { /** completion events - for completion queue use */ grpc_cq_completion completions[MAX_CONCURRENT_COMPLETIONS]; + + /** siblings: children of the same parent form a list, and this list is protected under + parent->mu */ + grpc_call *sibling_next; + grpc_call *sibling_prev; }; #define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call) + 1)) @@ -291,7 +299,9 @@ static void finished_loose_op(void *call, int success); static void lock(grpc_call *call); static void unlock(grpc_call *call); -grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + gpr_uint32 propagation_mask, + grpc_completion_queue *cq, const void *server_transport_data, grpc_mdelem **add_initial_metadata, size_t add_initial_metadata_count, @@ -307,9 +317,10 @@ grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, gpr_mu_init(&call->completion_mu); call->channel = channel; call->cq = cq; - if (cq) { + if (cq != NULL) { GRPC_CQ_INTERNAL_REF(cq, "bind"); } + call->parent = parent_call; call->is_client = server_transport_data == NULL; for (i = 0; i < GRPC_IOREQ_OP_COUNT; i++) { call->request_set[i] = REQSET_EMPTY; @@ -348,7 +359,48 @@ grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, } grpc_call_stack_init(channel_stack, server_transport_data, initial_op_ptr, CALL_STACK_FROM_CALL(call)); - if (gpr_time_cmp(send_deadline, gpr_inf_future(send_deadline.clock_type)) != 0) { + if (parent_call != NULL) { + GRPC_CALL_INTERNAL_REF(parent_call, "child"); + GPR_ASSERT(call->is_client); + GPR_ASSERT(!parent_call->is_client); + + gpr_mu_lock(&parent_call->mu); + + if (propagation_mask & GRPC_PROPAGATE_DEADLINE) { + send_deadline = gpr_time_min( + gpr_convert_clock_type(send_deadline, + parent_call->send_deadline.clock_type), + parent_call->send_deadline); + } + /* for now GRPC_PROPAGATE_TRACING_CONTEXT *MUST* be passed with + * GRPC_PROPAGATE_STATS_CONTEXT */ + /* TODO(ctiller): This should change to use the appropriate census start_op + * call. */ + if (propagation_mask & GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT) { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + grpc_call_context_set(call, GRPC_CONTEXT_TRACING, + parent_call->context[GRPC_CONTEXT_TRACING].value, + NULL); + } else { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + } + if (propagation_mask & GRPC_PROPAGATE_CANCELLATION) { + call->cancellation_is_inherited = 1; + } + + if (parent_call->first_child == NULL) { + parent_call->first_child = call; + call->sibling_next = call->sibling_prev = call; + } else { + call->sibling_next = parent_call->first_child; + call->sibling_prev = parent_call->first_child->sibling_prev; + call->sibling_next->sibling_prev = call->sibling_prev->sibling_next = call; + } + + gpr_mu_unlock(&parent_call->mu); + } + if (gpr_time_cmp(send_deadline, gpr_inf_future(send_deadline.clock_type)) != + 0) { set_deadline_alarm(call, send_deadline); } return call; @@ -870,6 +922,8 @@ static int add_slice_to_message(grpc_call *call, gpr_slice slice) { static void call_on_done_recv(void *pc, int success) { grpc_call *call = pc; + grpc_call *child_call; + grpc_call *next_child_call; size_t i; GRPC_TIMER_BEGIN(GRPC_PTAG_CALL_ON_DONE_RECV, 0); lock(call); @@ -903,6 +957,19 @@ static void call_on_done_recv(void *pc, int success) { GPR_ASSERT(call->read_state <= READ_STATE_STREAM_CLOSED); call->read_state = READ_STATE_STREAM_CLOSED; call->cancel_alarm |= call->have_alarm; + /* propagate cancellation to any interested children */ + child_call = call->first_child; + if (child_call != NULL) { + do { + next_child_call = child_call->sibling_next; + if (child_call->cancellation_is_inherited) { + GRPC_CALL_INTERNAL_REF(child_call, "propagate_cancel"); + grpc_call_cancel(child_call); + GRPC_CALL_INTERNAL_UNREF(child_call, "propagate_cancel", 0); + } + child_call = next_child_call; + } while (child_call != call->first_child); + } GRPC_CALL_INTERNAL_UNREF(call, "closed", 0); } finish_read_ops(call); @@ -932,7 +999,7 @@ static int prepare_application_metadata(grpc_call *call, size_t count, GPR_ASSERT(sizeof(grpc_linked_mdelem) == sizeof(md->internal_data)); l->md = grpc_mdelem_from_string_and_buffer(call->metadata_context, md->key, (const gpr_uint8 *)md->value, - md->value_length); + md->value_length, 1); if (!grpc_mdstr_is_legal_header(l->md->key)) { gpr_log(GPR_ERROR, "attempt to send invalid metadata key"); return 0; @@ -1176,6 +1243,22 @@ grpc_call_error grpc_call_start_ioreq_and_call_back( void grpc_call_destroy(grpc_call *c) { int cancel; + grpc_call *parent = c->parent; + + if (parent) { + gpr_mu_lock(&parent->mu); + if (c == parent->first_child) { + parent->first_child = c->sibling_next; + if (c == parent->first_child) { + parent->first_child = NULL; + } + c->sibling_prev->sibling_next = c->sibling_next; + c->sibling_next->sibling_prev = c->sibling_prev; + } + gpr_mu_unlock(&parent->mu); + GRPC_CALL_INTERNAL_UNREF(parent, "child", 1); + } + lock(c); GPR_ASSERT(!c->destroy_called); c->destroy_called = 1; @@ -1203,7 +1286,7 @@ grpc_call_error grpc_call_cancel_with_status(grpc_call *c, static grpc_call_error cancel_with_status(grpc_call *c, grpc_status_code status, const char *description) { grpc_mdstr *details = - description ? grpc_mdstr_from_string(c->metadata_context, description) + description ? grpc_mdstr_from_string(c->metadata_context, description, 0) : NULL; GPR_ASSERT(status != GRPC_STATUS_OK); @@ -1283,7 +1366,8 @@ static void set_deadline_alarm(grpc_call *call, gpr_timespec deadline) { } GRPC_CALL_INTERNAL_REF(call, "alarm"); call->have_alarm = 1; - grpc_alarm_init(&call->alarm, gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), call_alarm, call, + call->send_deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); + grpc_alarm_init(&call->alarm, call->send_deadline, call_alarm, call, gpr_now(GPR_CLOCK_MONOTONIC)); } @@ -1319,15 +1403,17 @@ static gpr_uint32 decode_compression(grpc_mdelem *md) { grpc_compression_algorithm algorithm; void *user_data = grpc_mdelem_get_user_data(md, destroy_compression); if (user_data) { - algorithm = ((grpc_compression_level)(gpr_intptr)user_data) - COMPRESS_OFFSET; + algorithm = + ((grpc_compression_level)(gpr_intptr)user_data) - COMPRESS_OFFSET; } else { const char *md_c_str = grpc_mdstr_as_c_string(md->value); if (!grpc_compression_algorithm_parse(md_c_str, &algorithm)) { gpr_log(GPR_ERROR, "Invalid compression algorithm: '%s'", md_c_str); assert(0); } - grpc_mdelem_set_user_data(md, destroy_compression, - (void *)(gpr_intptr)(algorithm + COMPRESS_OFFSET)); + grpc_mdelem_set_user_data( + md, destroy_compression, + (void *)(gpr_intptr)(algorithm + COMPRESS_OFFSET)); } return algorithm; } @@ -1374,7 +1460,8 @@ static void recv_metadata(grpc_call *call, grpc_metadata_batch *md) { } } if (gpr_time_cmp(md->deadline, gpr_inf_future(md->deadline.clock_type)) != - 0) { + 0 && + !call->is_client) { set_deadline_alarm(call, md->deadline); } if (!is_trailing) { @@ -1452,6 +1539,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, /* Flag validation: currently allow no flags */ if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_INITIAL_METADATA; req->data.send_metadata.count = op->data.send_initial_metadata.count; req->data.send_metadata.metadata = @@ -1462,7 +1550,11 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, if (!are_write_flags_valid(op->flags)) { return GRPC_CALL_ERROR_INVALID_FLAGS; } + if (op->data.send_message == NULL) { + return GRPC_CALL_ERROR_INVALID_MESSAGE; + } req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_MESSAGE; req->data.send_message = op->data.send_message; req->flags = op->flags; @@ -1474,6 +1566,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, return GRPC_CALL_ERROR_NOT_ON_SERVER; } req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_CLOSE; req->flags = op->flags; break; @@ -1484,6 +1577,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, return GRPC_CALL_ERROR_NOT_ON_CLIENT; } req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_TRAILING_METADATA; req->flags = op->flags; req->data.send_metadata.count = @@ -1491,15 +1585,17 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, req->data.send_metadata.metadata = op->data.send_status_from_server.trailing_metadata; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_STATUS; req->data.send_status.code = op->data.send_status_from_server.status; req->data.send_status.details = op->data.send_status_from_server.status_details != NULL ? grpc_mdstr_from_string( call->metadata_context, - op->data.send_status_from_server.status_details) + op->data.send_status_from_server.status_details, 0) : NULL; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_SEND_CLOSE; break; case GRPC_OP_RECV_INITIAL_METADATA: @@ -1509,14 +1605,17 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, return GRPC_CALL_ERROR_NOT_ON_SERVER; } req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_INITIAL_METADATA; req->data.recv_metadata = op->data.recv_initial_metadata; + req->data.recv_metadata->count = 0; req->flags = op->flags; break; case GRPC_OP_RECV_MESSAGE: /* Flag validation: currently allow no flags */ if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_MESSAGE; req->data.recv_message = op->data.recv_message; req->flags = op->flags; @@ -1528,21 +1627,26 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, return GRPC_CALL_ERROR_NOT_ON_SERVER; } req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_STATUS; req->flags = op->flags; req->data.recv_status.set_value = set_status_value_directly; req->data.recv_status.user_data = op->data.recv_status_on_client.status; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_STATUS_DETAILS; req->data.recv_status_details.details = op->data.recv_status_on_client.status_details; req->data.recv_status_details.details_capacity = op->data.recv_status_on_client.status_details_capacity; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_TRAILING_METADATA; req->data.recv_metadata = op->data.recv_status_on_client.trailing_metadata; + req->data.recv_metadata->count = 0; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_CLOSE; finish_func = finish_batch_with_close; break; @@ -1550,12 +1654,14 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, /* Flag validation: currently allow no flags */ if (op->flags != 0) return GRPC_CALL_ERROR_INVALID_FLAGS; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_STATUS; req->flags = op->flags; req->data.recv_status.set_value = set_cancelled_value; req->data.recv_status.user_data = op->data.recv_close_on_server.cancelled; req = &reqs[out++]; + if (out > GRPC_IOREQ_OP_COUNT) return GRPC_CALL_ERROR_BATCH_TOO_BIG; req->op = GRPC_IOREQ_RECV_CLOSE; finish_func = finish_batch_with_close; break; diff --git a/src/core/surface/call.h b/src/core/surface/call.h index 265638d519..75bdbce980 100644 --- a/src/core/surface/call.h +++ b/src/core/surface/call.h @@ -85,7 +85,9 @@ typedef struct { typedef void (*grpc_ioreq_completion_func)(grpc_call *call, int success, void *user_data); -grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + gpr_uint32 propagation_mask, + grpc_completion_queue *cq, const void *server_transport_data, grpc_mdelem **add_initial_metadata, size_t add_initial_metadata_count, diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c index 4052c65cc6..8692aa3903 100644 --- a/src/core/surface/channel.c +++ b/src/core/surface/channel.c @@ -101,19 +101,19 @@ grpc_channel *grpc_channel_create_from_filters( /* decremented by grpc_channel_destroy */ gpr_ref_init(&channel->refs, 1); channel->metadata_context = mdctx; - channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status"); + channel->grpc_status_string = grpc_mdstr_from_string(mdctx, "grpc-status", 0); channel->grpc_compression_algorithm_string = - grpc_mdstr_from_string(mdctx, "grpc-encoding"); - channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message"); + grpc_mdstr_from_string(mdctx, "grpc-encoding", 0); + channel->grpc_message_string = grpc_mdstr_from_string(mdctx, "grpc-message", 0); for (i = 0; i < NUM_CACHED_STATUS_ELEMS; i++) { char buf[GPR_LTOA_MIN_BUFSIZE]; gpr_ltoa(i, buf); channel->grpc_status_elem[i] = grpc_mdelem_from_metadata_strings( mdctx, GRPC_MDSTR_REF(channel->grpc_status_string), - grpc_mdstr_from_string(mdctx, buf)); + grpc_mdstr_from_string(mdctx, buf, 0)); } - channel->path_string = grpc_mdstr_from_string(mdctx, ":path"); - channel->authority_string = grpc_mdstr_from_string(mdctx, ":authority"); + channel->path_string = grpc_mdstr_from_string(mdctx, ":path", 0); + channel->authority_string = grpc_mdstr_from_string(mdctx, ":authority", 0); gpr_mu_init(&channel->registered_call_mu); channel->registered_calls = NULL; @@ -146,31 +146,38 @@ char *grpc_channel_get_target(grpc_channel *channel) { } static grpc_call *grpc_channel_create_call_internal( - grpc_channel *channel, grpc_completion_queue *cq, grpc_mdelem *path_mdelem, + grpc_channel *channel, grpc_call *parent_call, gpr_uint32 propagation_mask, + grpc_completion_queue *cq, grpc_mdelem *path_mdelem, grpc_mdelem *authority_mdelem, gpr_timespec deadline) { grpc_mdelem *send_metadata[2]; + int num_metadata = 0; GPR_ASSERT(channel->is_client); - send_metadata[0] = path_mdelem; - send_metadata[1] = authority_mdelem; + send_metadata[num_metadata++] = path_mdelem; + if (authority_mdelem != NULL) { + send_metadata[num_metadata++] = authority_mdelem; + } - return grpc_call_create(channel, cq, NULL, send_metadata, - GPR_ARRAY_SIZE(send_metadata), deadline); + return grpc_call_create(channel, parent_call, propagation_mask, cq, NULL, + send_metadata, num_metadata, deadline); } grpc_call *grpc_channel_create_call(grpc_channel *channel, + grpc_call *parent_call, + gpr_uint32 propagation_mask, grpc_completion_queue *cq, const char *method, const char *host, gpr_timespec deadline) { return grpc_channel_create_call_internal( - channel, cq, + channel, parent_call, propagation_mask, cq, grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->path_string), - grpc_mdstr_from_string(channel->metadata_context, method)), + grpc_mdstr_from_string(channel->metadata_context, method, 0)), + host ? grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->authority_string), - grpc_mdstr_from_string(channel->metadata_context, host)), + grpc_mdstr_from_string(channel->metadata_context, host, 0)) : NULL, deadline); } @@ -179,10 +186,10 @@ void *grpc_channel_register_call(grpc_channel *channel, const char *method, registered_call *rc = gpr_malloc(sizeof(registered_call)); rc->path = grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->path_string), - grpc_mdstr_from_string(channel->metadata_context, method)); - rc->authority = grpc_mdelem_from_metadata_strings( + grpc_mdstr_from_string(channel->metadata_context, method, 0)); + rc->authority = host ? grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->authority_string), - grpc_mdstr_from_string(channel->metadata_context, host)); + grpc_mdstr_from_string(channel->metadata_context, host, 0)) : NULL; gpr_mu_lock(&channel->registered_call_mu); rc->next = channel->registered_calls; channel->registered_calls = rc; @@ -191,12 +198,14 @@ void *grpc_channel_register_call(grpc_channel *channel, const char *method, } grpc_call *grpc_channel_create_registered_call( - grpc_channel *channel, grpc_completion_queue *completion_queue, - void *registered_call_handle, gpr_timespec deadline) { + grpc_channel *channel, grpc_call *parent_call, gpr_uint32 propagation_mask, + grpc_completion_queue *completion_queue, void *registered_call_handle, + gpr_timespec deadline) { registered_call *rc = registered_call_handle; return grpc_channel_create_call_internal( - channel, completion_queue, GRPC_MDELEM_REF(rc->path), - GRPC_MDELEM_REF(rc->authority), deadline); + channel, parent_call, propagation_mask, completion_queue, + GRPC_MDELEM_REF(rc->path), + rc->authority ? GRPC_MDELEM_REF(rc->authority) : NULL, deadline); } #ifdef GRPC_CHANNEL_REF_COUNT_DEBUG @@ -225,7 +234,9 @@ static void destroy_channel(void *p, int ok) { registered_call *rc = channel->registered_calls; channel->registered_calls = rc->next; GRPC_MDELEM_UNREF(rc->path); - GRPC_MDELEM_UNREF(rc->authority); + if (rc->authority) { + GRPC_MDELEM_UNREF(rc->authority); + } gpr_free(rc); } grpc_mdctx_unref(channel->metadata_context); @@ -284,7 +295,7 @@ grpc_mdelem *grpc_channel_get_reffed_status_elem(grpc_channel *channel, int i) { gpr_ltoa(i, tmp); return grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->grpc_status_string), - grpc_mdstr_from_string(channel->metadata_context, tmp)); + grpc_mdstr_from_string(channel->metadata_context, tmp, 0)); } } diff --git a/src/core/surface/channel_connectivity.c b/src/core/surface/channel_connectivity.c new file mode 100644 index 0000000000..1223706457 --- /dev/null +++ b/src/core/surface/channel_connectivity.c @@ -0,0 +1,185 @@ +/* + * + * 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. + * + */ + +#include "src/core/surface/channel.h" + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/channel/client_channel.h" +#include "src/core/iomgr/alarm.h" +#include "src/core/surface/completion_queue.h" + +grpc_connectivity_state grpc_channel_check_connectivity_state( + grpc_channel *channel, int try_to_connect) { + /* forward through to the underlying client channel */ + grpc_channel_element *client_channel_elem = + grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel)); + if (client_channel_elem->filter != &grpc_client_channel_filter) { + gpr_log(GPR_ERROR, + "grpc_channel_check_connectivity_state called on something that is " + "not a client channel, but '%s'", + client_channel_elem->filter->name); + return GRPC_CHANNEL_FATAL_FAILURE; + } + return grpc_client_channel_check_connectivity_state(client_channel_elem, + try_to_connect); +} + +typedef enum { + WAITING, + CALLING_BACK, + CALLING_BACK_AND_FINISHED, + CALLED_BACK +} callback_phase; + +typedef struct { + gpr_mu mu; + callback_phase phase; + int success; + grpc_iomgr_closure on_complete; + grpc_alarm alarm; + grpc_connectivity_state state; + grpc_completion_queue *cq; + grpc_cq_completion completion_storage; + grpc_channel *channel; + void *tag; +} state_watcher; + +static void delete_state_watcher(state_watcher *w) { + grpc_channel_element *client_channel_elem = + grpc_channel_stack_last_element(grpc_channel_get_channel_stack(w->channel)); + grpc_client_channel_del_interested_party(client_channel_elem, grpc_cq_pollset(w->cq)); + GRPC_CHANNEL_INTERNAL_UNREF(w->channel, "watch_connectivity"); + gpr_mu_destroy(&w->mu); + gpr_free(w); +} + +static void finished_completion(void *pw, grpc_cq_completion *ignored) { + int delete = 0; + state_watcher *w = pw; + gpr_mu_lock(&w->mu); + switch (w->phase) { + case WAITING: + case CALLED_BACK: + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + break; + case CALLING_BACK: + w->phase = CALLED_BACK; + break; + case CALLING_BACK_AND_FINISHED: + delete = 1; + break; + } + gpr_mu_unlock(&w->mu); + + if (delete) { + delete_state_watcher(w); + } +} + +static void partly_done(state_watcher *w, int due_to_completion) { + int delete = 0; + + if (due_to_completion) { + gpr_mu_lock(&w->mu); + w->success = 1; + gpr_mu_unlock(&w->mu); + grpc_alarm_cancel(&w->alarm); + } + + gpr_mu_lock(&w->mu); + switch (w->phase) { + case WAITING: + w->phase = CALLING_BACK; + grpc_cq_end_op(w->cq, w->tag, w->success, finished_completion, w, + &w->completion_storage); + break; + case CALLING_BACK: + w->phase = CALLING_BACK_AND_FINISHED; + break; + case CALLING_BACK_AND_FINISHED: + gpr_log(GPR_ERROR, "should never reach here"); + abort(); + break; + case CALLED_BACK: + delete = 1; + break; + } + gpr_mu_unlock(&w->mu); + + if (delete) { + delete_state_watcher(w); + } +} + +static void watch_complete(void *pw, int success) { partly_done(pw, 1); } + +static void timeout_complete(void *pw, int success) { partly_done(pw, 0); } + +void grpc_channel_watch_connectivity_state( + grpc_channel *channel, grpc_connectivity_state last_observed_state, + gpr_timespec deadline, grpc_completion_queue *cq, void *tag) { + grpc_channel_element *client_channel_elem = + grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel)); + state_watcher *w = gpr_malloc(sizeof(*w)); + + grpc_cq_begin_op(cq); + + gpr_mu_init(&w->mu); + grpc_iomgr_closure_init(&w->on_complete, watch_complete, w); + w->phase = WAITING; + w->state = last_observed_state; + w->success = 0; + w->cq = cq; + w->tag = tag; + w->channel = channel; + + grpc_alarm_init( + &w->alarm, gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), + timeout_complete, w, gpr_now(GPR_CLOCK_MONOTONIC)); + + if (client_channel_elem->filter != &grpc_client_channel_filter) { + gpr_log(GPR_ERROR, + "grpc_channel_watch_connectivity_state called on something that is " + "not a client channel, but '%s'", + client_channel_elem->filter->name); + grpc_iomgr_add_delayed_callback(&w->on_complete, 1); + } else { + GRPC_CHANNEL_INTERNAL_REF(channel, "watch_connectivity"); + grpc_client_channel_add_interested_party(client_channel_elem, grpc_cq_pollset(cq)); + grpc_client_channel_watch_connectivity_state(client_channel_elem, &w->state, + &w->on_complete); + } +} diff --git a/src/core/surface/channel_create.c b/src/core/surface/channel_create.c index 778f7108fd..707d615688 100644 --- a/src/core/surface/channel_create.c +++ b/src/core/surface/channel_create.c @@ -109,6 +109,7 @@ typedef struct { gpr_refcount refs; grpc_mdctx *mdctx; grpc_channel_args *merge_args; + grpc_channel *master; } subchannel_factory; static void subchannel_factory_ref(grpc_subchannel_factory *scf) { @@ -119,6 +120,7 @@ static void subchannel_factory_ref(grpc_subchannel_factory *scf) { static void subchannel_factory_unref(grpc_subchannel_factory *scf) { subchannel_factory *f = (subchannel_factory *)scf; if (gpr_unref(&f->refs)) { + GRPC_CHANNEL_INTERNAL_UNREF(f->master, "subchannel_factory"); grpc_channel_args_destroy(f->merge_args); grpc_mdctx_unref(f->mdctx); gpr_free(f); @@ -137,6 +139,7 @@ static grpc_subchannel *subchannel_factory_create_subchannel( gpr_ref_init(&c->refs, 1); args->mdctx = f->mdctx; args->args = final_args; + args->master = f->master; s = grpc_subchannel_create(&c->base, args); grpc_connector_unref(&c->base); grpc_channel_args_destroy(final_args); @@ -151,8 +154,8 @@ static const grpc_subchannel_factory_vtable subchannel_factory_vtable = { Asynchronously: - resolve target - connect to it (trying alternatives as presented) - perform handshakes */ -grpc_channel *grpc_channel_create(const char *target, - const grpc_channel_args *args) { +grpc_channel *grpc_insecure_channel_create(const char *target, + const grpc_channel_args *args) { grpc_channel *channel = NULL; #define MAX_FILTERS 3 const grpc_channel_filter *filters[MAX_FILTERS]; @@ -168,19 +171,22 @@ grpc_channel *grpc_channel_create(const char *target, filters[n++] = &grpc_client_channel_filter; GPR_ASSERT(n <= MAX_FILTERS); + channel = + grpc_channel_create_from_filters(target, filters, n, args, mdctx, 1); + f = gpr_malloc(sizeof(*f)); f->base.vtable = &subchannel_factory_vtable; gpr_ref_init(&f->refs, 1); grpc_mdctx_ref(mdctx); f->mdctx = mdctx; f->merge_args = grpc_channel_args_copy(args); + f->master = channel; + GRPC_CHANNEL_INTERNAL_REF(f->master, "subchannel_factory"); resolver = grpc_resolver_create(target, &f->base); if (!resolver) { return NULL; } - channel = - grpc_channel_create_from_filters(target, filters, n, args, mdctx, 1); grpc_client_channel_set_resolver(grpc_channel_get_channel_stack(channel), resolver); GRPC_RESOLVER_UNREF(resolver, "create"); diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c index 3f60b0b0ba..36d69cfe5f 100644 --- a/src/core/surface/completion_queue.c +++ b/src/core/surface/completion_queue.c @@ -45,6 +45,11 @@ #include <grpc/support/atm.h> #include <grpc/support/log.h> +typedef struct { + grpc_pollset_worker *worker; + void *tag; +} plucker; + /* Completion queue structure */ struct grpc_completion_queue { /** completed events */ @@ -60,6 +65,8 @@ struct grpc_completion_queue { int shutdown; int shutdown_called; int is_server_cq; + int num_pluckers; + plucker pluckers[GRPC_MAX_COMPLETION_QUEUE_PLUCKERS]; }; grpc_completion_queue *grpc_completion_queue_create(void) { @@ -107,6 +114,11 @@ void grpc_cq_internal_unref(grpc_completion_queue *cc) { } void grpc_cq_begin_op(grpc_completion_queue *cc) { +#ifndef NDEBUG + gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset)); + GPR_ASSERT(!cc->shutdown_called); + gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); +#endif gpr_ref(&cc->pending_events); } @@ -117,6 +129,8 @@ void grpc_cq_end_op(grpc_completion_queue *cc, void *tag, int success, void (*done)(void *done_arg, grpc_cq_completion *storage), void *done_arg, grpc_cq_completion *storage) { int shutdown; + int i; + grpc_pollset_worker *pluck_worker; storage->tag = tag; storage->done = done; @@ -130,7 +144,14 @@ void grpc_cq_end_op(grpc_completion_queue *cc, void *tag, int success, cc->completed_tail->next = ((gpr_uintptr)storage) | (1u & (gpr_uintptr)cc->completed_tail->next); cc->completed_tail = storage; - grpc_pollset_kick(&cc->pollset); + pluck_worker = NULL; + for (i = 0; i < cc->num_pluckers; i++) { + if (cc->pluckers[i].tag == tag) { + pluck_worker = cc->pluckers[i].worker; + break; + } + } + grpc_pollset_kick(&cc->pollset, pluck_worker); gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); } else { cc->completed_tail->next = @@ -147,6 +168,7 @@ void grpc_cq_end_op(grpc_completion_queue *cc, void *tag, int success, grpc_event grpc_completion_queue_next(grpc_completion_queue *cc, gpr_timespec deadline) { grpc_event ret; + grpc_pollset_worker worker; deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); @@ -172,7 +194,7 @@ grpc_event grpc_completion_queue_next(grpc_completion_queue *cc, ret.type = GRPC_QUEUE_SHUTDOWN; break; } - if (!grpc_pollset_work(&cc->pollset, deadline)) { + if (!grpc_pollset_work(&cc->pollset, &worker, deadline)) { gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); memset(&ret, 0, sizeof(ret)); ret.type = GRPC_QUEUE_TIMEOUT; @@ -184,11 +206,37 @@ grpc_event grpc_completion_queue_next(grpc_completion_queue *cc, return ret; } +static int add_plucker(grpc_completion_queue *cc, void *tag, + grpc_pollset_worker *worker) { + if (cc->num_pluckers == GRPC_MAX_COMPLETION_QUEUE_PLUCKERS) { + return 0; + } + cc->pluckers[cc->num_pluckers].tag = tag; + cc->pluckers[cc->num_pluckers].worker = worker; + cc->num_pluckers++; + return 1; +} + +static void del_plucker(grpc_completion_queue *cc, void *tag, + grpc_pollset_worker *worker) { + int i; + for (i = 0; i < cc->num_pluckers; i++) { + if (cc->pluckers[i].tag == tag && cc->pluckers[i].worker == worker) { + cc->num_pluckers--; + GPR_SWAP(plucker, cc->pluckers[i], cc->pluckers[cc->num_pluckers]); + return; + } + } + gpr_log(GPR_ERROR, "should never reach here"); + abort(); +} + grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag, gpr_timespec deadline) { grpc_event ret; grpc_cq_completion *c; grpc_cq_completion *prev; + grpc_pollset_worker worker; deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); @@ -219,12 +267,24 @@ grpc_event grpc_completion_queue_pluck(grpc_completion_queue *cc, void *tag, ret.type = GRPC_QUEUE_SHUTDOWN; break; } - if (!grpc_pollset_work(&cc->pollset, deadline)) { + if (!add_plucker(cc, tag, &worker)) { + gpr_log(GPR_DEBUG, + "Too many outstanding grpc_completion_queue_pluck calls: maximum is %d", + GRPC_MAX_COMPLETION_QUEUE_PLUCKERS); gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); memset(&ret, 0, sizeof(ret)); + /* TODO(ctiller): should we use a different result here */ ret.type = GRPC_QUEUE_TIMEOUT; break; } + if (!grpc_pollset_work(&cc->pollset, &worker, deadline)) { + del_plucker(cc, tag, &worker); + gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); + memset(&ret, 0, sizeof(ret)); + ret.type = GRPC_QUEUE_TIMEOUT; + break; + } + del_plucker(cc, tag, &worker); } done: GRPC_SURFACE_TRACE_RETURNED_EVENT(cc, &ret); @@ -261,15 +321,6 @@ grpc_pollset *grpc_cq_pollset(grpc_completion_queue *cc) { return &cc->pollset; } -void grpc_cq_hack_spin_pollset(grpc_completion_queue *cc) { - gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset)); - grpc_pollset_kick(&cc->pollset); - grpc_pollset_work(&cc->pollset, - gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), - gpr_time_from_millis(100, GPR_TIMESPAN))); - gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); -} - void grpc_cq_mark_server_cq(grpc_completion_queue *cc) { cc->is_server_cq = 1; } int grpc_cq_is_server_cq(grpc_completion_queue *cc) { return cc->is_server_cq; } diff --git a/src/core/surface/completion_queue.h b/src/core/surface/completion_queue.h index f944f48d8e..8de024aaea 100644 --- a/src/core/surface/completion_queue.h +++ b/src/core/surface/completion_queue.h @@ -77,8 +77,6 @@ void grpc_cq_end_op(grpc_completion_queue *cc, void *tag, int success, grpc_pollset *grpc_cq_pollset(grpc_completion_queue *cc); -void grpc_cq_hack_spin_pollset(grpc_completion_queue *cc); - void grpc_cq_mark_server_cq(grpc_completion_queue *cc); int grpc_cq_is_server_cq(grpc_completion_queue *cc); diff --git a/src/core/surface/init.c b/src/core/surface/init.c index 5cba479317..442bc72f21 100644 --- a/src/core/surface/init.c +++ b/src/core/surface/init.c @@ -47,6 +47,7 @@ #include "src/core/surface/init.h" #include "src/core/surface/surface_trace.h" #include "src/core/transport/chttp2_transport.h" +#include "src/core/transport/connectivity_state.h" static gpr_once g_basic_init = GPR_ONCE_INIT; static gpr_mu g_init_mu; @@ -75,11 +76,15 @@ void grpc_init(void) { grpc_register_tracer("http", &grpc_http_trace); grpc_register_tracer("flowctl", &grpc_flowctl_trace); grpc_register_tracer("batch", &grpc_trace_batch); + grpc_register_tracer("connectivity_state", &grpc_connectivity_state_trace); grpc_security_pre_init(); grpc_iomgr_init(); grpc_tracer_init("GRPC_TRACE"); - if (census_initialize(CENSUS_NONE)) { - gpr_log(GPR_ERROR, "Could not initialize census."); + /* Only initialize census if noone else has. */ + if (census_enabled() == CENSUS_FEATURE_NONE) { + if (census_initialize(census_supported())) { /* enable all features. */ + gpr_log(GPR_ERROR, "Could not initialize census."); + } } grpc_timers_global_init(); } diff --git a/src/core/surface/secure_channel_create.c b/src/core/surface/secure_channel_create.c index a280311ba0..c3150250b8 100644 --- a/src/core/surface/secure_channel_create.c +++ b/src/core/surface/secure_channel_create.c @@ -88,8 +88,8 @@ static void on_secure_transport_setup_done(void *arg, c->args.channel_args, secure_endpoint, c->args.metadata_context, 1); grpc_chttp2_transport_start_reading(c->result->transport, NULL, 0); c->result->filters = gpr_malloc(sizeof(grpc_channel_filter *) * 2); - c->result->filters[0] = &grpc_client_auth_filter; - c->result->filters[1] = &grpc_http_client_filter; + c->result->filters[0] = &grpc_http_client_filter; + c->result->filters[1] = &grpc_client_auth_filter; c->result->num_filters = 2; } notify = c->notify; @@ -134,6 +134,7 @@ typedef struct { grpc_mdctx *mdctx; grpc_channel_args *merge_args; grpc_channel_security_connector *security_connector; + grpc_channel *master; } subchannel_factory; static void subchannel_factory_ref(grpc_subchannel_factory *scf) { @@ -146,6 +147,7 @@ static void subchannel_factory_unref(grpc_subchannel_factory *scf) { if (gpr_unref(&f->refs)) { GRPC_SECURITY_CONNECTOR_UNREF(&f->security_connector->base, "subchannel_factory"); + GRPC_CHANNEL_INTERNAL_UNREF(f->master, "subchannel_factory"); grpc_channel_args_destroy(f->merge_args); grpc_mdctx_unref(f->mdctx); gpr_free(f); @@ -165,6 +167,7 @@ static grpc_subchannel *subchannel_factory_create_subchannel( gpr_ref_init(&c->refs, 1); args->mdctx = f->mdctx; args->args = final_args; + args->master = f->master; s = grpc_subchannel_create(&c->base, args); grpc_connector_unref(&c->base); grpc_channel_args_destroy(final_args); @@ -218,6 +221,9 @@ grpc_channel *grpc_secure_channel_create(grpc_credentials *creds, filters[n++] = &grpc_client_channel_filter; GPR_ASSERT(n <= MAX_FILTERS); + channel = + grpc_channel_create_from_filters(target, filters, n, args_copy, mdctx, 1); + f = gpr_malloc(sizeof(*f)); f->base.vtable = &subchannel_factory_vtable; gpr_ref_init(&f->refs, 1); @@ -226,13 +232,13 @@ grpc_channel *grpc_secure_channel_create(grpc_credentials *creds, GRPC_SECURITY_CONNECTOR_REF(&connector->base, "subchannel_factory"); f->security_connector = connector; f->merge_args = grpc_channel_args_copy(args_copy); + f->master = channel; + GRPC_CHANNEL_INTERNAL_REF(channel, "subchannel_factory"); resolver = grpc_resolver_create(target, &f->base); if (!resolver) { return NULL; } - channel = - grpc_channel_create_from_filters(target, filters, n, args_copy, mdctx, 1); grpc_client_channel_set_resolver(grpc_channel_get_channel_stack(channel), resolver); GRPC_RESOLVER_UNREF(resolver, "create"); diff --git a/src/core/surface/server.c b/src/core/surface/server.c index 7b3e412db0..cd1dc589e1 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -327,6 +327,14 @@ static void request_matcher_zombify_all_pending_calls( } } +static void request_matcher_kill_requests(grpc_server *server, + request_matcher *rm) { + int request_id; + while ((request_id = gpr_stack_lockfree_pop(rm->requests)) != -1) { + fail_call(server, &server->requested_calls[request_id]); + } +} + /* * server proper */ @@ -492,12 +500,25 @@ static int num_channels(grpc_server *server) { return n; } +static void kill_pending_work_locked(grpc_server *server) { + registered_method *rm; + request_matcher_kill_requests(server, &server->unregistered_request_matcher); + request_matcher_zombify_all_pending_calls( + &server->unregistered_request_matcher); + for (rm = server->registered_methods; rm; rm = rm->next) { + request_matcher_kill_requests(server, &rm->request_matcher); + request_matcher_zombify_all_pending_calls(&rm->request_matcher); + } +} + static void maybe_finish_shutdown(grpc_server *server) { size_t i; if (!gpr_atm_acq_load(&server->shutdown_flag) || server->shutdown_published) { return; } + kill_pending_work_locked(server); + if (server->root_channel_data.next != &server->root_channel_data || server->listeners_destroyed < num_listeners(server)) { if (gpr_time_cmp(gpr_time_sub(gpr_now(GPR_CLOCK_REALTIME), @@ -554,8 +575,10 @@ static void server_on_recv(void *ptr, int success) { gpr_time_cmp(op_deadline, gpr_inf_future(op_deadline.clock_type))) { calld->deadline = op->data.metadata.deadline; } - calld->got_initial_metadata = 1; - start_new_rpc(elem); + if (calld->host && calld->path) { + calld->got_initial_metadata = 1; + start_new_rpc(elem); + } break; } } @@ -621,8 +644,8 @@ static void accept_stream(void *cd, grpc_transport *transport, const void *transport_server_data) { channel_data *chand = cd; /* create a call */ - grpc_call_create(chand->channel, NULL, transport_server_data, NULL, 0, - gpr_inf_future(GPR_CLOCK_REALTIME)); + grpc_call_create(chand->channel, NULL, 0, NULL, transport_server_data, NULL, + 0, gpr_inf_future(GPR_CLOCK_MONOTONIC)); } static void channel_connectivity_changed(void *cd, int iomgr_status_ignored) { @@ -688,8 +711,8 @@ static void init_channel_elem(grpc_channel_element *elem, grpc_channel *master, GPR_ASSERT(!is_last); chand->server = NULL; chand->channel = NULL; - chand->path_key = grpc_mdstr_from_string(metadata_context, ":path"); - chand->authority_key = grpc_mdstr_from_string(metadata_context, ":authority"); + chand->path_key = grpc_mdstr_from_string(metadata_context, ":path", 0); + chand->authority_key = grpc_mdstr_from_string(metadata_context, ":authority", 0); chand->next = chand->prev = chand; chand->registered_methods = NULL; chand->connectivity_state = GRPC_CHANNEL_IDLE; @@ -911,8 +934,8 @@ void grpc_server_setup_transport(grpc_server *s, grpc_transport *transport, chand->registered_methods = gpr_malloc(alloc); memset(chand->registered_methods, 0, alloc); for (rm = s->registered_methods; rm; rm = rm->next) { - host = rm->host ? grpc_mdstr_from_string(mdctx, rm->host) : NULL; - method = grpc_mdstr_from_string(mdctx, rm->method); + host = rm->host ? grpc_mdstr_from_string(mdctx, rm->host, 0) : NULL; + method = grpc_mdstr_from_string(mdctx, rm->method, 0); hash = GRPC_MDSTR_KV_HASH(host ? host->hash : 0, method->hash); for (probes = 0; chand->registered_methods[(hash + probes) % slots] .server_registered_method != NULL; @@ -945,52 +968,15 @@ void grpc_server_setup_transport(grpc_server *s, grpc_transport *transport, op.set_accept_stream_user_data = chand; op.on_connectivity_state_change = &chand->channel_connectivity_changed; op.connectivity_state = &chand->connectivity_state; + op.disconnect = gpr_atm_acq_load(&s->shutdown_flag); grpc_transport_perform_op(transport, &op); } -typedef struct { - requested_call **requests; - size_t count; - size_t capacity; -} request_killer; - -static void request_killer_init(request_killer *rk) { - memset(rk, 0, sizeof(*rk)); -} - -static void request_killer_add(request_killer *rk, requested_call *rc) { - if (rk->capacity == rk->count) { - rk->capacity = GPR_MAX(8, rk->capacity * 2); - rk->requests = - gpr_realloc(rk->requests, rk->capacity * sizeof(*rk->requests)); - } - rk->requests[rk->count++] = rc; -} - -static void request_killer_add_request_matcher(request_killer *rk, - grpc_server *server, - request_matcher *rm) { - int request_id; - while ((request_id = gpr_stack_lockfree_pop(rm->requests)) != -1) { - request_killer_add(rk, &server->requested_calls[request_id]); - } -} - -static void request_killer_run(request_killer *rk, grpc_server *server) { - size_t i; - for (i = 0; i < rk->count; i++) { - fail_call(server, rk->requests[i]); - } - gpr_free(rk->requests); -} - void grpc_server_shutdown_and_notify(grpc_server *server, grpc_completion_queue *cq, void *tag) { listener *l; - registered_method *rm; shutdown_tag *sdt; channel_broadcaster broadcaster; - request_killer reqkill; GRPC_SERVER_LOG_SHUTDOWN(GPR_INFO, server, cq, tag); @@ -1011,27 +997,16 @@ void grpc_server_shutdown_and_notify(grpc_server *server, server->last_shutdown_message_time = gpr_now(GPR_CLOCK_REALTIME); channel_broadcaster_init(server, &broadcaster); - request_killer_init(&reqkill); /* collect all unregistered then registered calls */ gpr_mu_lock(&server->mu_call); - request_killer_add_request_matcher(&reqkill, server, - &server->unregistered_request_matcher); - request_matcher_zombify_all_pending_calls( - &server->unregistered_request_matcher); - for (rm = server->registered_methods; rm; rm = rm->next) { - request_killer_add_request_matcher(&reqkill, server, &rm->request_matcher); - request_matcher_zombify_all_pending_calls(&rm->request_matcher); - } + kill_pending_work_locked(server); gpr_mu_unlock(&server->mu_call); gpr_atm_rel_store(&server->shutdown_flag, 1); maybe_finish_shutdown(server); gpr_mu_unlock(&server->mu_global); - /* terminate all the requested calls */ - request_killer_run(&reqkill, server); - /* Shutdown listeners */ for (l = server->listeners; l; l = l->next) { l->destroy(server, l->arg); @@ -1271,6 +1246,8 @@ static void done_request_event(void *req, grpc_cq_completion *c) { } else { gpr_free(req); } + + server_unref(server); } static void fail_call(grpc_server *server, requested_call *rc) { @@ -1283,6 +1260,7 @@ static void fail_call(grpc_server *server, requested_call *rc) { rc->data.registered.initial_metadata->count = 0; break; } + server_ref(server); grpc_cq_end_op(rc->cq_for_notification, rc->tag, 0, done_request_event, rc, &rc->completion); } @@ -1293,6 +1271,8 @@ static void publish_registered_or_batch(grpc_call *call, int success, grpc_call_stack_element(grpc_call_get_call_stack(call), 0); requested_call *rc = prc; call_data *calld = elem->call_data; + channel_data *chand = elem->channel_data; + server_ref(chand->server); grpc_cq_end_op(calld->cq_new, rc->tag, success, done_request_event, rc, &rc->completion); GRPC_CALL_INTERNAL_UNREF(call, "server", 0); diff --git a/src/core/surface/server_chttp2.c b/src/core/surface/server_chttp2.c index 78c53466b3..4ab845bc00 100644 --- a/src/core/surface/server_chttp2.c +++ b/src/core/surface/server_chttp2.c @@ -80,7 +80,7 @@ static void destroy(grpc_server *server, void *tcpp) { grpc_tcp_server_destroy(tcp, grpc_server_listener_destroy_done, server); } -int grpc_server_add_http2_port(grpc_server *server, const char *addr) { +int grpc_server_add_insecure_http2_port(grpc_server *server, const char *addr) { grpc_resolved_addresses *resolved = NULL; grpc_tcp_server *tcp = NULL; size_t i; diff --git a/src/core/surface/version.c b/src/core/surface/version.c index 4f5d648371..d7aaba3868 100644 --- a/src/core/surface/version.c +++ b/src/core/surface/version.c @@ -37,5 +37,5 @@ #include <grpc/grpc.h> const char *grpc_version_string(void) { - return "0.10.0.0"; + return "0.10.1.0"; } diff --git a/src/core/transport/chttp2/alpn.c b/src/core/transport/chttp2/alpn.c index 3ccd5796ba..69da4e6718 100644 --- a/src/core/transport/chttp2/alpn.c +++ b/src/core/transport/chttp2/alpn.c @@ -36,8 +36,7 @@ #include <grpc/support/useful.h> /* in order of preference */ -static const char *const supported_versions[] = {"h2", "h2-17", "h2-16", - "h2-15", "h2-14"}; +static const char *const supported_versions[] = {"h2"}; int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size) { size_t i; diff --git a/src/core/transport/chttp2/internal.h b/src/core/transport/chttp2/internal.h index f0eeb6de50..42cf0ecd5b 100644 --- a/src/core/transport/chttp2/internal.h +++ b/src/core/transport/chttp2/internal.h @@ -119,6 +119,10 @@ typedef enum { GRPC_WRITE_STATE_SENT_CLOSE } grpc_chttp2_write_state; +/* flags that can be or'd into stream_global::writing_now */ +#define GRPC_CHTTP2_WRITING_DATA 1 +#define GRPC_CHTTP2_WRITING_WINDOW 2 + typedef enum { GRPC_DONT_SEND_CLOSED = 0, GRPC_SEND_CLOSED, @@ -382,8 +386,10 @@ typedef struct { gpr_uint8 published_cancelled; /** is this stream in the stream map? (boolean) */ gpr_uint8 in_stream_map; - /** is this stream actively being written? */ + /** bitmask of GRPC_CHTTP2_WRITING_xxx above */ gpr_uint8 writing_now; + /** has anything been written to this stream? */ + gpr_uint8 written_anything; /** stream state already published to the upper layer */ grpc_stream_state published_state; diff --git a/src/core/transport/chttp2/stream_encoder.c b/src/core/transport/chttp2/stream_encoder.c index 65b31a5afd..0f04169741 100644 --- a/src/core/transport/chttp2/stream_encoder.c +++ b/src/core/transport/chttp2/stream_encoder.c @@ -441,7 +441,7 @@ static void deadline_enc(grpc_chttp2_hpack_compressor *c, gpr_timespec deadline, gpr_time_sub(deadline, gpr_now(deadline.clock_type)), timeout_str); mdelem = grpc_mdelem_from_metadata_strings( c->mdctx, GRPC_MDSTR_REF(c->timeout_key_str), - grpc_mdstr_from_string(c->mdctx, timeout_str)); + grpc_mdstr_from_string(c->mdctx, timeout_str, 0)); mdelem = hpack_enc(c, mdelem, st); if (mdelem) GRPC_MDELEM_UNREF(mdelem); } @@ -456,7 +456,7 @@ void grpc_chttp2_hpack_compressor_init(grpc_chttp2_hpack_compressor *c, grpc_mdctx *ctx) { memset(c, 0, sizeof(*c)); c->mdctx = ctx; - c->timeout_key_str = grpc_mdstr_from_string(ctx, "grpc-timeout"); + c->timeout_key_str = grpc_mdstr_from_string(ctx, "grpc-timeout", 0); } void grpc_chttp2_hpack_compressor_destroy(grpc_chttp2_hpack_compressor *c) { diff --git a/src/core/transport/chttp2/stream_lists.c b/src/core/transport/chttp2/stream_lists.c index 9e68c1e146..9c3ad7a777 100644 --- a/src/core/transport/chttp2/stream_lists.c +++ b/src/core/transport/chttp2/stream_lists.c @@ -164,9 +164,6 @@ void grpc_chttp2_list_add_first_writable_stream( grpc_chttp2_transport_global *transport_global, grpc_chttp2_stream_global *stream_global) { GPR_ASSERT(stream_global->id != 0); - gpr_log(GPR_DEBUG, "add:%d:%d:%d:%d", stream_global->id, - stream_global->write_state, stream_global->in_stream_map, - stream_global->read_closed); stream_list_add_head(TRANSPORT_FROM_GLOBAL(transport_global), STREAM_FROM_GLOBAL(stream_global), GRPC_CHTTP2_LIST_WRITABLE); diff --git a/src/core/transport/chttp2/writing.c b/src/core/transport/chttp2/writing.c index d39b0c42f7..b55e81fdca 100644 --- a/src/core/transport/chttp2/writing.c +++ b/src/core/transport/chttp2/writing.c @@ -77,7 +77,6 @@ int grpc_chttp2_unlocking_check_writes( stream_writing->id = stream_global->id; stream_writing->send_closed = GRPC_DONT_SEND_CLOSED; - GPR_ASSERT(!stream_global->writing_now); if (stream_global->outgoing_sopb) { window_delta = @@ -123,11 +122,13 @@ int grpc_chttp2_unlocking_check_writes( stream_global->unannounced_incoming_window = 0; grpc_chttp2_list_add_incoming_window_updated(transport_global, stream_global); - stream_global->writing_now = 1; - grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing); - } else if (stream_writing->sopb.nops > 0 || - stream_writing->send_closed != GRPC_DONT_SEND_CLOSED) { - stream_global->writing_now = 1; + stream_global->writing_now |= GRPC_CHTTP2_WRITING_WINDOW; + } + if (stream_writing->sopb.nops > 0 || + stream_writing->send_closed != GRPC_DONT_SEND_CLOSED) { + stream_global->writing_now |= GRPC_CHTTP2_WRITING_DATA; + } + if (stream_global->writing_now != 0) { grpc_chttp2_list_add_writing_stream(transport_writing, stream_writing); } } @@ -183,6 +184,7 @@ static void finalize_outbuf(grpc_chttp2_transport_writing *transport_writing) { stream_writing->send_closed != GRPC_DONT_SEND_CLOSED, stream_writing->id, &transport_writing->hpack_compressor, &transport_writing->outbuf); + stream_writing->sopb.nops = 0; } if (stream_writing->announce_window > 0) { gpr_slice_buffer_add( @@ -191,7 +193,6 @@ static void finalize_outbuf(grpc_chttp2_transport_writing *transport_writing) { stream_writing->id, stream_writing->announce_window)); stream_writing->announce_window = 0; } - stream_writing->sopb.nops = 0; if (stream_writing->send_closed == GRPC_SEND_CLOSED_WITH_RST_STREAM) { gpr_slice_buffer_add(&transport_writing->outbuf, grpc_chttp2_rst_stream_create(stream_writing->id, @@ -215,20 +216,23 @@ void grpc_chttp2_cleanup_writing( while (grpc_chttp2_list_pop_written_stream( transport_global, transport_writing, &stream_global, &stream_writing)) { - GPR_ASSERT(stream_global->writing_now); - stream_global->writing_now = 0; - if (stream_global->outgoing_sopb != NULL && - stream_global->outgoing_sopb->nops == 0) { - stream_global->outgoing_sopb = NULL; - grpc_chttp2_schedule_closure(transport_global, - stream_global->send_done_closure, 1); - } + GPR_ASSERT(stream_global->writing_now != 0); if (stream_writing->send_closed != GRPC_DONT_SEND_CLOSED) { stream_global->write_state = GRPC_WRITE_STATE_SENT_CLOSE; if (!transport_global->is_client) { stream_global->read_closed = 1; } } + if (stream_global->writing_now & GRPC_CHTTP2_WRITING_DATA) { + if (stream_global->outgoing_sopb != NULL && + stream_global->outgoing_sopb->nops == 0) { + GPR_ASSERT(stream_global->write_state != GRPC_WRITE_STATE_QUEUED_CLOSE); + stream_global->outgoing_sopb = NULL; + grpc_chttp2_schedule_closure(transport_global, + stream_global->send_done_closure, 1); + } + } + stream_global->writing_now = 0; grpc_chttp2_list_add_read_write_state_changed(transport_global, stream_global); } diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c index 5f49b2ddd6..a9f91b64d5 100644 --- a/src/core/transport/chttp2_transport.c +++ b/src/core/transport/chttp2_transport.c @@ -107,9 +107,16 @@ static void cancel_from_api(grpc_chttp2_transport_global *transport_global, grpc_chttp2_stream_global *stream_global, grpc_status_code status); +static void close_from_api(grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status, + gpr_slice *optional_message); + /** Add endpoint from this transport to pollset */ static void add_to_pollset_locked(grpc_chttp2_transport *t, grpc_pollset *pollset); +static void add_to_pollset_set_locked(grpc_chttp2_transport *t, + grpc_pollset_set *pollset_set); /** Start new streams that have been created if we can */ static void maybe_start_some_streams( @@ -117,7 +124,7 @@ static void maybe_start_some_streams( static void connectivity_state_set( grpc_chttp2_transport_global *transport_global, - grpc_connectivity_state state); + grpc_connectivity_state state, const char *reason); /* * CONSTRUCTION/DESTRUCTION/REFCOUNTING @@ -230,12 +237,12 @@ static void init_transport(grpc_chttp2_transport *t, t->global.pings.next = t->global.pings.prev = &t->global.pings; t->parsing.is_client = is_client; t->parsing.str_grpc_timeout = - grpc_mdstr_from_string(t->metadata_context, "grpc-timeout"); + grpc_mdstr_from_string(t->metadata_context, "grpc-timeout", 0); t->parsing.deframe_state = is_client ? GRPC_DTS_FH_0 : GRPC_DTS_CLIENT_PREFIX_0; t->writing.is_client = is_client; grpc_connectivity_state_init(&t->channel_callback.state_tracker, - GRPC_CHANNEL_READY); + GRPC_CHANNEL_READY, "transport"); gpr_slice_buffer_init(&t->global.qbuf); @@ -329,7 +336,8 @@ static void destroy_transport(grpc_transport *gt) { static void close_transport_locked(grpc_chttp2_transport *t) { if (!t->closed) { t->closed = 1; - connectivity_state_set(&t->global, GRPC_CHANNEL_FATAL_FAILURE); + connectivity_state_set(&t->global, GRPC_CHANNEL_FATAL_FAILURE, + "close_transport"); if (t->ep) { grpc_endpoint_shutdown(t->ep); } @@ -536,7 +544,8 @@ void grpc_chttp2_add_incoming_goaway( gpr_free(msg); gpr_slice_unref(goaway_text); transport_global->seen_goaway = 1; - connectivity_state_set(transport_global, GRPC_CHANNEL_FATAL_FAILURE); + connectivity_state_set(transport_global, GRPC_CHANNEL_FATAL_FAILURE, + "got_goaway"); } static void maybe_start_some_streams( @@ -561,7 +570,8 @@ static void maybe_start_some_streams( transport_global->next_stream_id += 2; if (transport_global->next_stream_id >= MAX_CLIENT_STREAM_ID) { - connectivity_state_set(transport_global, GRPC_CHANNEL_TRANSIENT_FAILURE); + connectivity_state_set(transport_global, GRPC_CHANNEL_TRANSIENT_FAILURE, + "no_more_stream_ids"); } stream_global->outgoing_window = @@ -597,10 +607,16 @@ static void perform_stream_op_locked( cancel_from_api(transport_global, stream_global, op->cancel_with_status); } + if (op->close_with_status != GRPC_STATUS_OK) { + close_from_api(transport_global, stream_global, op->close_with_status, + op->optional_close_message); + } + if (op->send_ops) { GPR_ASSERT(stream_global->outgoing_sopb == NULL); stream_global->send_done_closure = op->on_done_send; if (!stream_global->cancelled) { + stream_global->written_anything = 1; stream_global->outgoing_sopb = op->send_ops; if (op->is_last_send && stream_global->write_state == GRPC_WRITE_STATE_OPEN) { @@ -689,6 +705,7 @@ static void send_ping_locked(grpc_chttp2_transport *t, static void perform_transport_op(grpc_transport *gt, grpc_transport_op *op) { grpc_chttp2_transport *t = (grpc_chttp2_transport *)gt; + int close_transport = 0; lock(t); @@ -708,9 +725,7 @@ static void perform_transport_op(grpc_transport *gt, grpc_transport_op *op) { t->global.last_incoming_stream_id, grpc_chttp2_grpc_status_to_http2_error(op->goaway_status), gpr_slice_ref(*op->goaway_message), &t->global.qbuf); - if (!grpc_chttp2_has_streams(t)) { - close_transport_locked(t); - } + close_transport = !grpc_chttp2_has_streams(t); } if (op->set_accept_stream != NULL) { @@ -723,6 +738,10 @@ static void perform_transport_op(grpc_transport *gt, grpc_transport_op *op) { add_to_pollset_locked(t, op->bind_pollset); } + if (op->bind_pollset_set) { + add_to_pollset_set_locked(t, op->bind_pollset_set); + } + if (op->send_ping) { send_ping_locked(t, op->send_ping); } @@ -732,6 +751,12 @@ static void perform_transport_op(grpc_transport *gt, grpc_transport_op *op) { } unlock(t); + + if (close_transport) { + lock(t); + close_transport_locked(t); + unlock(t); + } } /* @@ -809,6 +834,12 @@ static void unlock_check_read_write_state(grpc_chttp2_transport *t) { stream_global); } else { stream_global->write_state = GRPC_WRITE_STATE_SENT_CLOSE; + if (stream_global->outgoing_sopb != NULL) { + grpc_sopb_reset(stream_global->outgoing_sopb); + stream_global->outgoing_sopb = NULL; + grpc_chttp2_schedule_closure(transport_global, + stream_global->send_done_closure, 1); + } stream_global->read_closed = 1; if (!stream_global->published_cancelled) { char buffer[GPR_LTOA_MIN_BUFSIZE]; @@ -835,7 +866,7 @@ static void unlock_check_read_write_state(grpc_chttp2_transport *t) { if (!stream_global->publish_sopb) { continue; } - if (stream_global->writing_now) { + if (stream_global->writing_now != 0) { continue; } /* FIXME(ctiller): we include in_stream_map in our computation of @@ -880,6 +911,108 @@ static void cancel_from_api(grpc_chttp2_transport_global *transport_global, stream_global); } +static void close_from_api(grpc_chttp2_transport_global *transport_global, + grpc_chttp2_stream_global *stream_global, + grpc_status_code status, + gpr_slice *optional_message) { + gpr_slice hdr; + gpr_slice status_hdr; + gpr_slice message_pfx; + gpr_uint8 *p; + gpr_uint32 len = 0; + + GPR_ASSERT(status >= 0 && (int)status < 100); + + stream_global->cancelled = 1; + stream_global->cancelled_status = status; + GPR_ASSERT(stream_global->id != 0); + GPR_ASSERT(!stream_global->written_anything); + + /* Hand roll a header block. + This is unnecessarily ugly - at some point we should find a more elegant + solution. + It's complicated by the fact that our send machinery would be dead by the + time we got around to sending this, so instead we ignore HPACK compression + and just write the uncompressed bytes onto the wire. */ + status_hdr = gpr_slice_malloc(15 + (status >= 10)); + p = GPR_SLICE_START_PTR(status_hdr); + *p++ = 0x40; /* literal header */ + *p++ = 11; /* len(grpc-status) */ + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'p'; + *p++ = 'c'; + *p++ = '-'; + *p++ = 's'; + *p++ = 't'; + *p++ = 'a'; + *p++ = 't'; + *p++ = 'u'; + *p++ = 's'; + if (status < 10) { + *p++ = 1; + *p++ = '0' + status; + } else { + *p++ = 2; + *p++ = '0' + (status / 10); + *p++ = '0' + (status % 10); + } + GPR_ASSERT(p == GPR_SLICE_END_PTR(status_hdr)); + len += GPR_SLICE_LENGTH(status_hdr); + + if (optional_message) { + GPR_ASSERT(GPR_SLICE_LENGTH(*optional_message) < 127); + message_pfx = gpr_slice_malloc(15); + p = GPR_SLICE_START_PTR(message_pfx); + *p++ = 0x40; + *p++ = 12; /* len(grpc-message) */ + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'p'; + *p++ = 'c'; + *p++ = '-'; + *p++ = 'm'; + *p++ = 'e'; + *p++ = 's'; + *p++ = 's'; + *p++ = 'a'; + *p++ = 'g'; + *p++ = 'e'; + *p++ = GPR_SLICE_LENGTH(*optional_message); + GPR_ASSERT(p == GPR_SLICE_END_PTR(message_pfx)); + len += GPR_SLICE_LENGTH(message_pfx); + len += GPR_SLICE_LENGTH(*optional_message); + } + + hdr = gpr_slice_malloc(9); + p = GPR_SLICE_START_PTR(hdr); + *p++ = len >> 16; + *p++ = len >> 8; + *p++ = len; + *p++ = GRPC_CHTTP2_FRAME_HEADER; + *p++ = GRPC_CHTTP2_DATA_FLAG_END_STREAM | GRPC_CHTTP2_DATA_FLAG_END_HEADERS; + *p++ = stream_global->id >> 24; + *p++ = stream_global->id >> 16; + *p++ = stream_global->id >> 8; + *p++ = stream_global->id; + GPR_ASSERT(p == GPR_SLICE_END_PTR(hdr)); + + gpr_slice_buffer_add(&transport_global->qbuf, hdr); + gpr_slice_buffer_add(&transport_global->qbuf, status_hdr); + if (optional_message) { + gpr_slice_buffer_add(&transport_global->qbuf, message_pfx); + gpr_slice_buffer_add(&transport_global->qbuf, + gpr_slice_ref(*optional_message)); + } + + gpr_slice_buffer_add( + &transport_global->qbuf, + grpc_chttp2_rst_stream_create(stream_global->id, GRPC_CHTTP2_NO_ERROR)); + + grpc_chttp2_list_add_read_write_state_changed(transport_global, + stream_global); +} + static void cancel_stream_cb(grpc_chttp2_transport_global *transport_global, void *user_data, grpc_chttp2_stream_global *stream_global) { @@ -1008,12 +1141,12 @@ static void schedule_closure_for_connectivity(void *a, static void connectivity_state_set( grpc_chttp2_transport_global *transport_global, - grpc_connectivity_state state) { + grpc_connectivity_state state, const char *reason) { GRPC_CHTTP2_IF_TRACING( gpr_log(GPR_DEBUG, "set connectivity_state=%d", state)); grpc_connectivity_state_set_with_scheduler( &TRANSPORT_FROM_GLOBAL(transport_global)->channel_callback.state_tracker, - state, schedule_closure_for_connectivity, transport_global); + state, schedule_closure_for_connectivity, transport_global, reason); } void grpc_chttp2_schedule_closure( @@ -1041,6 +1174,13 @@ static void add_to_pollset_locked(grpc_chttp2_transport *t, } } +static void add_to_pollset_set_locked(grpc_chttp2_transport *t, + grpc_pollset_set *pollset_set) { + if (t->ep) { + grpc_endpoint_add_to_pollset_set(t->ep, pollset_set); + } +} + /* * TRACING */ diff --git a/src/core/transport/connectivity_state.c b/src/core/transport/connectivity_state.c index 1091ceae44..61d26f06f0 100644 --- a/src/core/transport/connectivity_state.c +++ b/src/core/transport/connectivity_state.c @@ -34,11 +34,33 @@ #include "src/core/transport/connectivity_state.h" #include <grpc/support/alloc.h> #include <grpc/support/log.h> +#include <grpc/support/string_util.h> + +int grpc_connectivity_state_trace = 0; + +const char *grpc_connectivity_state_name(grpc_connectivity_state state) { + switch (state) { + case GRPC_CHANNEL_IDLE: + return "IDLE"; + case GRPC_CHANNEL_CONNECTING: + return "CONNECTING"; + case GRPC_CHANNEL_READY: + return "READY"; + case GRPC_CHANNEL_TRANSIENT_FAILURE: + return "TRANSIENT_FAILURE"; + case GRPC_CHANNEL_FATAL_FAILURE: + return "FATAL_FAILURE"; + } + abort(); + return "UNKNOWN"; +} void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, - grpc_connectivity_state init_state) { + grpc_connectivity_state init_state, + const char *name) { tracker->current_state = init_state; tracker->watchers = NULL; + tracker->name = gpr_strdup(name); } void grpc_connectivity_state_destroy(grpc_connectivity_state_tracker *tracker) { @@ -54,6 +76,7 @@ void grpc_connectivity_state_destroy(grpc_connectivity_state_tracker *tracker) { } gpr_free(w); } + gpr_free(tracker->name); } grpc_connectivity_state grpc_connectivity_state_check( @@ -64,6 +87,11 @@ grpc_connectivity_state grpc_connectivity_state_check( int grpc_connectivity_state_notify_on_state_change( grpc_connectivity_state_tracker *tracker, grpc_connectivity_state *current, grpc_iomgr_closure *notify) { + if (grpc_connectivity_state_trace) { + gpr_log(GPR_DEBUG, "CONWATCH: %s: from %s [cur=%s]", tracker->name, + grpc_connectivity_state_name(*current), + grpc_connectivity_state_name(tracker->current_state)); + } if (tracker->current_state != *current) { *current = tracker->current_state; grpc_iomgr_add_callback(notify); @@ -79,12 +107,19 @@ int grpc_connectivity_state_notify_on_state_change( void grpc_connectivity_state_set_with_scheduler( grpc_connectivity_state_tracker *tracker, grpc_connectivity_state state, - void (*scheduler)(void *arg, grpc_iomgr_closure *closure), void *arg) { + void (*scheduler)(void *arg, grpc_iomgr_closure *closure), void *arg, + const char *reason) { grpc_connectivity_state_watcher *new = NULL; grpc_connectivity_state_watcher *w; + if (grpc_connectivity_state_trace) { + gpr_log(GPR_DEBUG, "SET: %s: %s --> %s [%s]", tracker->name, + grpc_connectivity_state_name(tracker->current_state), + grpc_connectivity_state_name(state), reason); + } if (tracker->current_state == state) { return; } + GPR_ASSERT(tracker->current_state != GRPC_CHANNEL_FATAL_FAILURE); tracker->current_state = state; while ((w = tracker->watchers)) { tracker->watchers = w->next; @@ -106,7 +141,8 @@ static void default_scheduler(void *ignored, grpc_iomgr_closure *closure) { } void grpc_connectivity_state_set(grpc_connectivity_state_tracker *tracker, - grpc_connectivity_state state) { + grpc_connectivity_state state, + const char *reason) { grpc_connectivity_state_set_with_scheduler(tracker, state, default_scheduler, - NULL); + NULL, reason); } diff --git a/src/core/transport/connectivity_state.h b/src/core/transport/connectivity_state.h index bbdcbcb069..a3b0b80c98 100644 --- a/src/core/transport/connectivity_state.h +++ b/src/core/transport/connectivity_state.h @@ -51,17 +51,24 @@ typedef struct { grpc_connectivity_state current_state; /** all our watchers */ grpc_connectivity_state_watcher *watchers; + /** a name to help debugging */ + char *name; } grpc_connectivity_state_tracker; +extern int grpc_connectivity_state_trace; + void grpc_connectivity_state_init(grpc_connectivity_state_tracker *tracker, - grpc_connectivity_state init_state); + grpc_connectivity_state init_state, + const char *name); void grpc_connectivity_state_destroy(grpc_connectivity_state_tracker *tracker); void grpc_connectivity_state_set(grpc_connectivity_state_tracker *tracker, - grpc_connectivity_state state); + grpc_connectivity_state state, + const char *reason); void grpc_connectivity_state_set_with_scheduler( grpc_connectivity_state_tracker *tracker, grpc_connectivity_state state, - void (*scheduler)(void *arg, grpc_iomgr_closure *closure), void *arg); + void (*scheduler)(void *arg, grpc_iomgr_closure *closure), void *arg, + const char *reason); grpc_connectivity_state grpc_connectivity_state_check( grpc_connectivity_state_tracker *tracker); diff --git a/src/core/transport/metadata.c b/src/core/transport/metadata.c index e95b7a21f9..44d32b6cb2 100644 --- a/src/core/transport/metadata.c +++ b/src/core/transport/metadata.c @@ -135,7 +135,9 @@ static void unlock(grpc_mdctx *ctx) { if (ctx->refs == 0) { /* uncomment if you're having trouble diagnosing an mdelem leak to make things clearer (slows down destruction a lot, however) */ +#ifdef GRPC_METADATA_REFCOUNT_DEBUG gc_mdtab(ctx); +#endif if (ctx->mdtab_count && ctx->mdtab_count == ctx->mdtab_free) { discard_metadata(ctx); } @@ -309,7 +311,37 @@ static void slice_unref(void *p) { unlock(ctx); } -grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str) { +grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str, int canonicalize_key) { + if (canonicalize_key) { + size_t len; + size_t i; + int canonical = 1; + + for (i = 0; str[i]; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + canonical = 0; + /* Keep going in loop just to get string length */ + } + } + len = i; + + if (canonical) { + return grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)str, len); + } else { + char *copy = gpr_malloc(len); + grpc_mdstr *ret; + for (i = 0; i < len; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + copy[i] = str[i] - 'A' + 'a'; + } else { + copy[i] = str[i]; + } + } + ret = grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)copy, len); + gpr_free(copy); + return ret; + } + } return grpc_mdstr_from_buffer(ctx, (const gpr_uint8 *)str, strlen(str)); } @@ -491,8 +523,8 @@ grpc_mdelem *grpc_mdelem_from_metadata_strings(grpc_mdctx *ctx, grpc_mdelem *grpc_mdelem_from_strings(grpc_mdctx *ctx, const char *key, const char *value) { return grpc_mdelem_from_metadata_strings(ctx, - grpc_mdstr_from_string(ctx, key), - grpc_mdstr_from_string(ctx, value)); + grpc_mdstr_from_string(ctx, key, 0), + grpc_mdstr_from_string(ctx, value, 0)); } grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key, @@ -504,9 +536,10 @@ grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key, grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx, const char *key, const gpr_uint8 *value, - size_t value_length) { + size_t value_length, + int canonicalize_key) { return grpc_mdelem_from_metadata_strings( - ctx, grpc_mdstr_from_string(ctx, key), + ctx, grpc_mdstr_from_string(ctx, key, canonicalize_key), grpc_mdstr_from_buffer(ctx, value, value_length)); } diff --git a/src/core/transport/metadata.h b/src/core/transport/metadata.h index 99b15322c3..15ef9bb555 100644 --- a/src/core/transport/metadata.h +++ b/src/core/transport/metadata.h @@ -95,7 +95,7 @@ size_t grpc_mdctx_get_mdtab_free_test_only(grpc_mdctx *mdctx); /* Constructors for grpc_mdstr instances; take a variety of data types that clients may have handy */ -grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str); +grpc_mdstr *grpc_mdstr_from_string(grpc_mdctx *ctx, const char *str, int perform_key_canonicalization); /* Unrefs the slice. */ grpc_mdstr *grpc_mdstr_from_slice(grpc_mdctx *ctx, gpr_slice slice); grpc_mdstr *grpc_mdstr_from_buffer(grpc_mdctx *ctx, const gpr_uint8 *str, @@ -117,7 +117,8 @@ grpc_mdelem *grpc_mdelem_from_slices(grpc_mdctx *ctx, gpr_slice key, grpc_mdelem *grpc_mdelem_from_string_and_buffer(grpc_mdctx *ctx, const char *key, const gpr_uint8 *value, - size_t value_length); + size_t value_length, + int canonicalize_key); /* Mutator and accessor for grpc_mdelem user data. The destructor function is used as a type tag and is checked during user_data fetch. */ diff --git a/src/core/transport/stream_op.h b/src/core/transport/stream_op.h index f27ef1b66b..227320cf2a 100644 --- a/src/core/transport/stream_op.h +++ b/src/core/transport/stream_op.h @@ -108,7 +108,7 @@ void grpc_metadata_batch_move(grpc_metadata_batch *dst, grpc_metadata_batch *src); /** Add \a storage to the beginning of \a batch. storage->md is - assumed to be valid. + assumed to be valid. \a storage is owned by the caller and must survive for the lifetime of batch. This usually means it should be around for the lifetime of the call. */ diff --git a/src/core/transport/transport.c b/src/core/transport/transport.c index 69c00b6a4f..c0d92cf93f 100644 --- a/src/core/transport/transport.c +++ b/src/core/transport/transport.c @@ -32,6 +32,8 @@ */ #include "src/core/transport/transport.h" +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> #include "src/core/transport/transport_impl.h" size_t grpc_transport_stream_size(grpc_transport *transport) { @@ -83,12 +85,54 @@ void grpc_transport_stream_op_finish_with_failure( } void grpc_transport_stream_op_add_cancellation(grpc_transport_stream_op *op, - grpc_status_code status, - grpc_mdstr *message) { + grpc_status_code status) { + GPR_ASSERT(status != GRPC_STATUS_OK); if (op->cancel_with_status == GRPC_STATUS_OK) { op->cancel_with_status = status; } - if (message) { - GRPC_MDSTR_UNREF(message); + if (op->close_with_status != GRPC_STATUS_OK) { + op->close_with_status = GRPC_STATUS_OK; + if (op->optional_close_message != NULL) { + gpr_slice_unref(*op->optional_close_message); + op->optional_close_message = NULL; + } } } + +typedef struct { + gpr_slice message; + grpc_iomgr_closure *then_call; + grpc_iomgr_closure closure; +} close_message_data; + +static void free_message(void *p, int iomgr_success) { + close_message_data *cmd = p; + gpr_slice_unref(cmd->message); + if (cmd->then_call != NULL) { + cmd->then_call->cb(cmd->then_call->cb_arg, iomgr_success); + } + gpr_free(cmd); +} + +void grpc_transport_stream_op_add_close(grpc_transport_stream_op *op, + grpc_status_code status, + gpr_slice *optional_message) { + close_message_data *cmd; + GPR_ASSERT(status != GRPC_STATUS_OK); + if (op->cancel_with_status != GRPC_STATUS_OK || + op->close_with_status != GRPC_STATUS_OK) { + if (optional_message) { + gpr_slice_unref(*optional_message); + } + return; + } + if (optional_message) { + cmd = gpr_malloc(sizeof(*cmd)); + cmd->message = *optional_message; + cmd->then_call = op->on_consumed; + grpc_iomgr_closure_init(&cmd->closure, free_message, cmd); + op->on_consumed = &cmd->closure; + op->optional_close_message = &cmd->message; + } + op->close_with_status = status; +} diff --git a/src/core/transport/transport.h b/src/core/transport/transport.h index 553779602a..92c1f38c5e 100644 --- a/src/core/transport/transport.h +++ b/src/core/transport/transport.h @@ -80,8 +80,14 @@ typedef struct grpc_transport_stream_op { grpc_pollset *bind_pollset; + /** If != GRPC_STATUS_OK, cancel this stream */ grpc_status_code cancel_with_status; + /** If != GRPC_STATUS_OK, send grpc-status, grpc-message, and close this + stream for both reading and writing */ + grpc_status_code close_with_status; + gpr_slice *optional_close_message; + /* Indexes correspond to grpc_context_index enum values */ grpc_call_context_element *context; } grpc_transport_stream_op; @@ -109,6 +115,8 @@ typedef struct grpc_transport_op { void *set_accept_stream_user_data; /** add this transport to a pollset */ grpc_pollset *bind_pollset; + /** add this transport to a pollset_set */ + grpc_pollset_set *bind_pollset_set; /** send a ping, call this back if not NULL */ grpc_iomgr_closure *send_ping; } grpc_transport_op; @@ -146,8 +154,11 @@ void grpc_transport_destroy_stream(grpc_transport *transport, void grpc_transport_stream_op_finish_with_failure(grpc_transport_stream_op *op); void grpc_transport_stream_op_add_cancellation(grpc_transport_stream_op *op, - grpc_status_code status, - grpc_mdstr *message); + grpc_status_code status); + +void grpc_transport_stream_op_add_close(grpc_transport_stream_op *op, + grpc_status_code status, + gpr_slice *optional_message); char *grpc_transport_stream_op_string(grpc_transport_stream_op *op); diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c index 6156a39d09..609fc06ed5 100644 --- a/src/core/tsi/ssl_transport_security.c +++ b/src/core/tsi/ssl_transport_security.c @@ -1293,8 +1293,8 @@ tsi_result tsi_create_ssl_server_handshaker_factory( const size_t* pem_private_keys_sizes, const unsigned char** pem_cert_chains, const size_t* pem_cert_chains_sizes, size_t key_cert_pair_count, const unsigned char* pem_client_root_certs, - size_t pem_client_root_certs_size, const char* cipher_list, - const unsigned char** alpn_protocols, + size_t pem_client_root_certs_size, int force_client_auth, + const char* cipher_list, const unsigned char** alpn_protocols, const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols, tsi_ssl_handshaker_factory** factory) { tsi_ssl_server_handshaker_factory* impl = NULL; @@ -1349,6 +1349,7 @@ tsi_result tsi_create_ssl_server_handshaker_factory( if (result != TSI_OK) break; if (pem_client_root_certs != NULL) { + int flags = SSL_VERIFY_PEER; STACK_OF(X509_NAME)* root_names = NULL; result = ssl_ctx_load_verification_certs( impl->ssl_contexts[i], pem_client_root_certs, @@ -1358,7 +1359,8 @@ tsi_result tsi_create_ssl_server_handshaker_factory( break; } SSL_CTX_set_client_CA_list(impl->ssl_contexts[i], root_names); - SSL_CTX_set_verify(impl->ssl_contexts[i], SSL_VERIFY_PEER, NULL); + if (force_client_auth) flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + SSL_CTX_set_verify(impl->ssl_contexts[i], flags, NULL); /* TODO(jboeuf): Add revocation verification. */ } diff --git a/src/core/tsi/ssl_transport_security.h b/src/core/tsi/ssl_transport_security.h index b2aa2f393e..4bf6c81b75 100644 --- a/src/core/tsi/ssl_transport_security.h +++ b/src/core/tsi/ssl_transport_security.h @@ -107,10 +107,14 @@ tsi_result tsi_create_ssl_client_handshaker_factory( - key_cert_pair_count indicates the number of items in the private_key_files and cert_chain_files parameters. - pem_client_roots is the buffer containing the PEM encoding of the client - root certificates. This parameter may be NULL in which case the server - will not ask the client to authenticate itself with a certificate (server- - only authentication mode). - - pem_client_roots_size is the size of the associated buffer. + root certificates. This parameter may be NULL in which case the server will + not authenticate the client. If not NULL, the force_client_auth parameter + specifies if the server will accept only authenticated clients or both + authenticated and non-authenticated clients. + - pem_client_root_certs_size is the size of the associated buffer. + - force_client_auth, if set to non-zero will force the client to authenticate + with an SSL cert. Note that this option is ignored if pem_client_root_certs + is NULL or pem_client_roots_certs_size is 0 - cipher_suites contains an optional list of the ciphers that the server supports. The format of this string is described in: https://www.openssl.org/docs/apps/ciphers.html. @@ -131,8 +135,8 @@ tsi_result tsi_create_ssl_server_handshaker_factory( const size_t* pem_private_keys_sizes, const unsigned char** pem_cert_chains, const size_t* pem_cert_chains_sizes, size_t key_cert_pair_count, const unsigned char* pem_client_root_certs, - size_t pem_client_root_certs_size, const char* cipher_suites, - const unsigned char** alpn_protocols, + size_t pem_client_root_certs_size, int force_client_auth, + const char* cipher_suites, const unsigned char** alpn_protocols, const unsigned char* alpn_protocols_lengths, uint16_t num_alpn_protocols, tsi_ssl_handshaker_factory** factory); diff --git a/src/core/tsi/transport_security_interface.h b/src/core/tsi/transport_security_interface.h index 936b0c25b0..e27e6b9fc9 100644 --- a/src/core/tsi/transport_security_interface.h +++ b/src/core/tsi/transport_security_interface.h @@ -158,6 +158,8 @@ tsi_result tsi_frame_protector_protect_flush( value is expected to be at most max_protected_frame_size minus overhead which means that max_protected_frame_size is a safe bet. The output value is the number of bytes actually written. + If *unprotected_bytes_size is unchanged, there may be more data remaining + to unprotect, and the caller should call this function again. - This method returns TSI_OK in case of success. Success includes cases where there is not enough data to output a frame in which case diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc index da31d000b3..af7366eb01 100644 --- a/src/cpp/client/channel.cc +++ b/src/cpp/client/channel.cc @@ -39,7 +39,6 @@ #include <grpc/support/log.h> #include <grpc/support/slice.h> -#include "src/core/census/grpc_context.h" #include "src/core/profiling/timers.h" #include <grpc++/channel_arguments.h> #include <grpc++/client_context.h> @@ -49,27 +48,33 @@ #include <grpc++/impl/call.h> #include <grpc++/impl/rpc_method.h> #include <grpc++/status.h> +#include <grpc++/time.h> namespace grpc { -Channel::Channel(const grpc::string& target, grpc_channel* channel) - : target_(target), c_channel_(channel) {} +Channel::Channel(grpc_channel* channel) : c_channel_(channel) {} + +Channel::Channel(const grpc::string& host, grpc_channel* channel) + : host_(host), c_channel_(channel) {} Channel::~Channel() { grpc_channel_destroy(c_channel_); } Call Channel::CreateCall(const RpcMethod& method, ClientContext* context, CompletionQueue* cq) { - auto c_call = - method.channel_tag() && context->authority().empty() - ? grpc_channel_create_registered_call(c_channel_, cq->cq(), - method.channel_tag(), - context->raw_deadline()) - : grpc_channel_create_call(c_channel_, cq->cq(), method.name(), - context->authority().empty() - ? target_.c_str() - : context->authority().c_str(), - context->raw_deadline()); - grpc_census_call_set_context(c_call, context->get_census_context()); + const char* host_str = host_.empty() ? NULL : host_.c_str(); + auto c_call = method.channel_tag() && context->authority().empty() + ? grpc_channel_create_registered_call( + c_channel_, context->propagate_from_call_, + context->propagation_options_.c_bitmask(), cq->cq(), + method.channel_tag(), context->raw_deadline()) + : grpc_channel_create_call( + c_channel_, context->propagate_from_call_, + context->propagation_options_.c_bitmask(), cq->cq(), + method.name(), context->authority().empty() + ? host_str + : context->authority().c_str(), + context->raw_deadline()); + grpc_census_call_set_context(c_call, context->census_context()); GRPC_TIMER_MARK(GRPC_PTAG_CPP_CALL_CREATED, c_call); context->set_call(c_call, shared_from_this()); return Call(c_call, this, cq); @@ -87,7 +92,47 @@ void Channel::PerformOpsOnCall(CallOpSetInterface* ops, Call* call) { } void* Channel::RegisterMethod(const char* method) { - return grpc_channel_register_call(c_channel_, method, target_.c_str()); + return grpc_channel_register_call(c_channel_, method, + host_.empty() ? NULL : host_.c_str()); +} + +grpc_connectivity_state Channel::GetState(bool try_to_connect) { + return grpc_channel_check_connectivity_state(c_channel_, try_to_connect); +} + +namespace { +class TagSaver GRPC_FINAL : public CompletionQueueTag { + public: + explicit TagSaver(void* tag) : tag_(tag) {} + ~TagSaver() GRPC_OVERRIDE {} + bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE { + *tag = tag_; + delete this; + return true; + } + private: + void* tag_; +}; + +} // namespace + +void Channel::NotifyOnStateChangeImpl(grpc_connectivity_state last_observed, + gpr_timespec deadline, + CompletionQueue* cq, void* tag) { + TagSaver* tag_saver = new TagSaver(tag); + grpc_channel_watch_connectivity_state(c_channel_, last_observed, deadline, + cq->cq(), tag_saver); +} + +bool Channel::WaitForStateChangeImpl(grpc_connectivity_state last_observed, + gpr_timespec deadline) { + CompletionQueue cq; + bool ok = false; + void* tag = NULL; + NotifyOnStateChangeImpl(last_observed, deadline, &cq, NULL); + cq.Next(&tag, &ok); + GPR_ASSERT(tag == NULL); + return ok; } } // namespace grpc diff --git a/src/cpp/client/channel.h b/src/cpp/client/channel.h index 9108713c58..cb8e8d98d2 100644 --- a/src/cpp/client/channel.h +++ b/src/cpp/client/channel.h @@ -52,17 +52,27 @@ class StreamContextInterface; class Channel GRPC_FINAL : public GrpcLibrary, public ChannelInterface { public: - Channel(const grpc::string& target, grpc_channel* c_channel); + explicit Channel(grpc_channel* c_channel); + Channel(const grpc::string& host, grpc_channel* c_channel); ~Channel() GRPC_OVERRIDE; - virtual void* RegisterMethod(const char* method) GRPC_OVERRIDE; - virtual Call CreateCall(const RpcMethod& method, ClientContext* context, + void* RegisterMethod(const char* method) GRPC_OVERRIDE; + Call CreateCall(const RpcMethod& method, ClientContext* context, CompletionQueue* cq) GRPC_OVERRIDE; - virtual void PerformOpsOnCall(CallOpSetInterface* ops, + void PerformOpsOnCall(CallOpSetInterface* ops, Call* call) GRPC_OVERRIDE; + grpc_connectivity_state GetState(bool try_to_connect) GRPC_OVERRIDE; + private: - const grpc::string target_; + void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed, + gpr_timespec deadline, CompletionQueue* cq, + void* tag) GRPC_OVERRIDE; + + bool WaitForStateChangeImpl(grpc_connectivity_state last_observed, + gpr_timespec deadline) GRPC_OVERRIDE; + + const grpc::string host_; grpc_channel* const c_channel_; // owned }; diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc index 14ab772e50..1ed2d38961 100644 --- a/src/cpp/client/client_context.cc +++ b/src/cpp/client/client_context.cc @@ -34,8 +34,10 @@ #include <grpc++/client_context.h> #include <grpc/grpc.h> +#include <grpc/support/alloc.h> #include <grpc/support/string_util.h> #include <grpc++/credentials.h> +#include <grpc++/server_context.h> #include <grpc++/time.h> #include "src/core/channel/compress_filter.h" @@ -47,7 +49,8 @@ ClientContext::ClientContext() : initial_metadata_received_(false), call_(nullptr), cq_(nullptr), - deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)) {} + deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)), + propagate_from_call_(nullptr) {} ClientContext::~ClientContext() { if (call_) { @@ -63,6 +66,14 @@ ClientContext::~ClientContext() { } } +std::unique_ptr<ClientContext> ClientContext::FromServerContext( + const ServerContext& context, PropagationOptions options) { + std::unique_ptr<ClientContext> ctx(new ClientContext); + ctx->propagate_from_call_ = context.call_; + ctx->propagation_options_ = options; + return ctx; +} + void ClientContext::AddMetadata(const grpc::string& meta_key, const grpc::string& meta_value) { send_initial_metadata_.insert(std::make_pair(meta_key, meta_value)); @@ -104,4 +115,14 @@ void ClientContext::TryCancel() { } } +grpc::string ClientContext::peer() const { + grpc::string peer; + if (call_) { + char* c_peer = grpc_call_get_peer(call_); + peer = c_peer; + gpr_free(c_peer); + } + return peer; +} + } // namespace grpc diff --git a/src/cpp/client/create_channel.cc b/src/cpp/client/create_channel.cc index 62f179d361..21d01b739d 100644 --- a/src/cpp/client/create_channel.cc +++ b/src/cpp/client/create_channel.cc @@ -52,6 +52,6 @@ std::shared_ptr<ChannelInterface> CreateChannel( user_agent_prefix.str()); return creds ? creds->CreateChannel(target, cp_args) : std::shared_ptr<ChannelInterface>( - new Channel(target, grpc_lame_client_channel_create(NULL))); + new Channel(grpc_lame_client_channel_create(NULL))); } } // namespace grpc diff --git a/src/cpp/client/insecure_credentials.cc b/src/cpp/client/insecure_credentials.cc index 5ad8784567..d8dcaa1436 100644 --- a/src/cpp/client/insecure_credentials.cc +++ b/src/cpp/client/insecure_credentials.cc @@ -49,7 +49,7 @@ class InsecureCredentialsImpl GRPC_FINAL : public Credentials { grpc_channel_args channel_args; args.SetChannelArgs(&channel_args); return std::shared_ptr<ChannelInterface>(new Channel( - target, grpc_channel_create(target.c_str(), &channel_args))); + grpc_insecure_channel_create(target.c_str(), &channel_args))); } // InsecureCredentials should not be applied to a call. diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc index abf0cb387e..6cd6b77fcf 100644 --- a/src/cpp/client/secure_credentials.cc +++ b/src/cpp/client/secure_credentials.cc @@ -34,6 +34,7 @@ #include <grpc/support/log.h> #include <grpc++/channel_arguments.h> +#include <grpc++/impl/grpc_library.h> #include "src/cpp/client/channel.h" #include "src/cpp/client/secure_credentials.h" @@ -44,8 +45,7 @@ std::shared_ptr<grpc::ChannelInterface> SecureCredentials::CreateChannel( grpc_channel_args channel_args; args.SetChannelArgs(&channel_args); return std::shared_ptr<ChannelInterface>(new Channel( - args.GetSslTargetNameOverride().empty() ? target - : args.GetSslTargetNameOverride(), + args.GetSslTargetNameOverride(), grpc_secure_channel_create(c_creds_, target.c_str(), &channel_args))); } @@ -62,12 +62,14 @@ std::shared_ptr<Credentials> WrapCredentials(grpc_credentials* creds) { } // namespace std::shared_ptr<Credentials> GoogleDefaultCredentials() { + GrpcLibrary init; // To call grpc_init(). return WrapCredentials(grpc_google_default_credentials_create()); } // Builds SSL Credentials given SSL specific options std::shared_ptr<Credentials> SslCredentials( const SslCredentialsOptions& options) { + GrpcLibrary init; // To call grpc_init(). grpc_ssl_pem_key_cert_pair pem_key_cert_pair = { options.pem_private_key.c_str(), options.pem_cert_chain.c_str()}; @@ -79,6 +81,7 @@ std::shared_ptr<Credentials> SslCredentials( // Builds credentials for use when running in GCE std::shared_ptr<Credentials> ComputeEngineCredentials() { + GrpcLibrary init; // To call grpc_init(). return WrapCredentials(grpc_compute_engine_credentials_create()); } @@ -86,6 +89,7 @@ std::shared_ptr<Credentials> ComputeEngineCredentials() { std::shared_ptr<Credentials> ServiceAccountCredentials( const grpc::string& json_key, const grpc::string& scope, long token_lifetime_seconds) { + GrpcLibrary init; // To call grpc_init(). if (token_lifetime_seconds <= 0) { gpr_log(GPR_ERROR, "Trying to create ServiceAccountCredentials " @@ -101,6 +105,7 @@ std::shared_ptr<Credentials> ServiceAccountCredentials( // Builds JWT credentials. std::shared_ptr<Credentials> ServiceAccountJWTAccessCredentials( const grpc::string& json_key, long token_lifetime_seconds) { + GrpcLibrary init; // To call grpc_init(). if (token_lifetime_seconds <= 0) { gpr_log(GPR_ERROR, "Trying to create JWTCredentials with non-positive lifetime"); @@ -115,6 +120,7 @@ std::shared_ptr<Credentials> ServiceAccountJWTAccessCredentials( // Builds refresh token credentials. std::shared_ptr<Credentials> RefreshTokenCredentials( const grpc::string& json_refresh_token) { + GrpcLibrary init; // To call grpc_init(). return WrapCredentials( grpc_refresh_token_credentials_create(json_refresh_token.c_str())); } @@ -122,6 +128,7 @@ std::shared_ptr<Credentials> RefreshTokenCredentials( // Builds access token credentials. std::shared_ptr<Credentials> AccessTokenCredentials( const grpc::string& access_token) { + GrpcLibrary init; // To call grpc_init(). return WrapCredentials( grpc_access_token_credentials_create(access_token.c_str())); } @@ -130,6 +137,7 @@ std::shared_ptr<Credentials> AccessTokenCredentials( std::shared_ptr<Credentials> IAMCredentials( const grpc::string& authorization_token, const grpc::string& authority_selector) { + GrpcLibrary init; // To call grpc_init(). return WrapCredentials(grpc_iam_credentials_create( authorization_token.c_str(), authority_selector.c_str())); } diff --git a/src/cpp/common/auth_property_iterator.cc b/src/cpp/common/auth_property_iterator.cc index e706c6c921..ba88983515 100644 --- a/src/cpp/common/auth_property_iterator.cc +++ b/src/cpp/common/auth_property_iterator.cc @@ -31,7 +31,7 @@ * */ -#include <grpc++/auth_property_iterator.h> +#include <grpc++/auth_context.h> #include <grpc/grpc_security.h> diff --git a/src/cpp/server/insecure_server_credentials.cc b/src/cpp/server/insecure_server_credentials.cc index aca3568e59..800cd36caa 100644 --- a/src/cpp/server/insecure_server_credentials.cc +++ b/src/cpp/server/insecure_server_credentials.cc @@ -41,7 +41,7 @@ class InsecureServerCredentialsImpl GRPC_FINAL : public ServerCredentials { public: int AddPortToServer(const grpc::string& addr, grpc_server* server) GRPC_OVERRIDE { - return grpc_server_add_http2_port(server, addr.c_str()); + return grpc_server_add_insecure_http2_port(server, addr.c_str()); } }; } // namespace diff --git a/src/cpp/server/secure_server_credentials.cc b/src/cpp/server/secure_server_credentials.cc index 3e262dd74f..32c45e2280 100644 --- a/src/cpp/server/secure_server_credentials.cc +++ b/src/cpp/server/secure_server_credentials.cc @@ -51,7 +51,8 @@ std::shared_ptr<ServerCredentials> SslServerCredentials( } grpc_server_credentials* c_creds = grpc_ssl_server_credentials_create( options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(), - &pem_key_cert_pairs[0], pem_key_cert_pairs.size()); + &pem_key_cert_pairs[0], pem_key_cert_pairs.size(), + options.force_client_auth); return std::shared_ptr<ServerCredentials>( new SecureServerCredentials(c_creds)); } diff --git a/src/cpp/server/server_context.cc b/src/cpp/server/server_context.cc index bf7a4ba5ec..04373397f9 100644 --- a/src/cpp/server/server_context.cc +++ b/src/cpp/server/server_context.cc @@ -34,6 +34,7 @@ #include <grpc++/server_context.h> #include <grpc/grpc.h> +#include <grpc/support/alloc.h> #include <grpc/support/log.h> #include <grpc++/impl/call.h> #include <grpc++/impl/sync.h> @@ -49,16 +50,23 @@ namespace grpc { class ServerContext::CompletionOp GRPC_FINAL : public CallOpSetInterface { public: // initial refs: one in the server context, one in the cq - CompletionOp() : refs_(2), finalized_(false), cancelled_(0) {} + CompletionOp() : has_tag_(false), tag_(nullptr), refs_(2), finalized_(false), cancelled_(0) {} void FillOps(grpc_op* ops, size_t* nops) GRPC_OVERRIDE; bool FinalizeResult(void** tag, bool* status) GRPC_OVERRIDE; bool CheckCancelled(CompletionQueue* cq); + void set_tag(void* tag) { + has_tag_ = true; + tag_ = tag; + } + void Unref(); private: + bool has_tag_; + void* tag_; grpc::mutex mu_; int refs_; bool finalized_; @@ -89,18 +97,25 @@ void ServerContext::CompletionOp::FillOps(grpc_op* ops, size_t* nops) { bool ServerContext::CompletionOp::FinalizeResult(void** tag, bool* status) { grpc::unique_lock<grpc::mutex> lock(mu_); finalized_ = true; + bool ret = false; + if (has_tag_) { + *tag = tag_; + ret = true; + } if (!*status) cancelled_ = 1; if (--refs_ == 0) { lock.unlock(); delete this; } - return false; + return ret; } // ServerContext body ServerContext::ServerContext() : completion_op_(nullptr), + has_notify_when_done_tag_(false), + async_notify_when_done_tag_(nullptr), call_(nullptr), cq_(nullptr), sent_initial_metadata_(false) {} @@ -108,6 +123,8 @@ ServerContext::ServerContext() ServerContext::ServerContext(gpr_timespec deadline, grpc_metadata* metadata, size_t metadata_count) : completion_op_(nullptr), + has_notify_when_done_tag_(false), + async_notify_when_done_tag_(nullptr), deadline_(deadline), call_(nullptr), cq_(nullptr), @@ -132,6 +149,9 @@ ServerContext::~ServerContext() { void ServerContext::BeginCompletionOp(Call* call) { GPR_ASSERT(!completion_op_); completion_op_ = new CompletionOp(); + if (has_notify_when_done_tag_) { + completion_op_->set_tag(async_notify_when_done_tag_); + } call->PerformOps(completion_op_); } @@ -179,4 +199,18 @@ std::shared_ptr<const AuthContext> ServerContext::auth_context() const { return auth_context_; } +grpc::string ServerContext::peer() const { + grpc::string peer; + if (call_) { + char* c_peer = grpc_call_get_peer(call_); + peer = c_peer; + gpr_free(c_peer); + } + return peer; +} + +const struct census_context* ServerContext::census_context() const { + return grpc_census_call_get_context(call_); +} + } // namespace grpc diff --git a/src/csharp/Grpc.Auth/GoogleCredential.cs b/src/csharp/Grpc.Auth/GoogleCredential.cs deleted file mode 100644 index 9936cf583c..0000000000 --- a/src/csharp/Grpc.Auth/GoogleCredential.cs +++ /dev/null @@ -1,125 +0,0 @@ -#region Copyright notice and license - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#endregion - -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; - -using Google.Apis.Auth.OAuth2; -using Google.Apis.Auth.OAuth2.Responses; -using Newtonsoft.Json.Linq; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; - -namespace Grpc.Auth -{ - // TODO(jtattermusch): Remove this class once possible. - /// <summary> - /// A temporary placeholder for Google credential from - /// Google Auth library for .NET. It emulates the usage pattern - /// for Usable auth. - /// </summary> - public class GoogleCredential - { - private const string GoogleApplicationCredentialsEnvName = "GOOGLE_APPLICATION_CREDENTIALS"; - private const string ClientEmailFieldName = "client_email"; - private const string PrivateKeyFieldName = "private_key"; - - private ServiceCredential credential; - - private GoogleCredential(ServiceCredential credential) - { - this.credential = credential; - } - - public static GoogleCredential GetApplicationDefault() - { - return new GoogleCredential(null); - } - - public bool IsCreateScopedRequired - { - get - { - return true; - } - } - - public GoogleCredential CreateScoped(IEnumerable<string> scopes) - { - var credsPath = Environment.GetEnvironmentVariable(GoogleApplicationCredentialsEnvName); - if (credsPath == null) - { - // Default to ComputeCredentials if path to JSON key is not set. - // ComputeCredential is not scoped actually, but for our use case it's - // fine to treat is as such. - return new GoogleCredential(new ComputeCredential(new ComputeCredential.Initializer())); - } - - JObject jsonCredentialParameters = JObject.Parse(File.ReadAllText(credsPath)); - string clientEmail = jsonCredentialParameters.GetValue(ClientEmailFieldName).Value<string>(); - string privateKeyString = jsonCredentialParameters.GetValue(PrivateKeyFieldName).Value<string>(); - - var serviceCredential = new ServiceAccountCredential( - new ServiceAccountCredential.Initializer(clientEmail) - { - Scopes = scopes, - }.FromPrivateKey(privateKeyString)); - return new GoogleCredential(serviceCredential); - } - - public Task<bool> RequestAccessTokenAsync(CancellationToken taskCancellationToken) - { - return credential.RequestAccessTokenAsync(taskCancellationToken); - } - - public TokenResponse Token - { - get - { - return credential.Token; - } - } - - internal ServiceCredential InternalCredential - { - get - { - return credential; - } - } - } -} diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj index afb8204c07..930a34b0c3 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj +++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj @@ -11,7 +11,7 @@ <AssemblyName>Grpc.Auth</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <DocumentationFile>bin\$(Configuration)\Grpc.Auth.Xml</DocumentationFile> - <NuGetPackageImportStamp>9b408026</NuGetPackageImportStamp> + <NuGetPackageImportStamp>4f8487a9</NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -24,36 +24,49 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <ConsolePause>false</ConsolePause> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> - <Reference Include="BouncyCastle.Crypto"> + <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Auth, Version=1.9.2.27817, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.dll</HintPath> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.2.27820, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Core, Version=1.9.2.27816, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Core.1.9.2\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> + <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> </Reference> - <Reference Include="Microsoft.Threading.Tasks"> + <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> </Reference> - <Reference Include="Microsoft.Threading.Tasks.Extensions"> + <Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> </Reference> - <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop"> + <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> </Reference> <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> @@ -78,8 +91,7 @@ <Link>Version.cs</Link> </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="GoogleCredential.cs" /> - <Compile Include="OAuth2InterceptorFactory.cs" /> + <Compile Include="OAuth2Interceptors.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec index eeaa49aa92..2dc10d24c2 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec +++ b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec @@ -20,9 +20,9 @@ </dependencies> </metadata> <files> - <file src="bin/Release/Grpc.Auth.dll" target="lib/net45" /> - <file src="bin/Release/Grpc.Auth.pdb" target="lib/net45" /> - <file src="bin/Release/Grpc.Auth.xml" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Auth.dll" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Auth.pdb" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Auth.xml" target="lib/net45" /> <file src="**\*.cs" target="src" /> </files> </package> diff --git a/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs index 420c4cb537..d628a83246 100644 --- a/src/csharp/Grpc.Auth/OAuth2InterceptorFactory.cs +++ b/src/csharp/Grpc.Auth/OAuth2Interceptors.cs @@ -47,18 +47,32 @@ using Grpc.Core.Utils; namespace Grpc.Auth { - public static class OAuth2InterceptorFactory + public static class OAuth2Interceptors { /// <summary> - /// Creates OAuth2 interceptor. + /// Creates OAuth2 interceptor that will obtain access token from GoogleCredentials. /// </summary> - public static MetadataInterceptorDelegate Create(GoogleCredential googleCredential) + public static MetadataInterceptorDelegate FromCredential(GoogleCredential googleCredential) { - var interceptor = new OAuth2Interceptor(googleCredential.InternalCredential, SystemClock.Default); + var interceptor = new OAuth2Interceptor(googleCredential, SystemClock.Default); return new MetadataInterceptorDelegate(interceptor.InterceptHeaders); } /// <summary> + /// Creates OAuth2 interceptor that will use given OAuth2 token. + /// </summary> + /// <param name="oauth2Token"></param> + /// <returns></returns> + public static MetadataInterceptorDelegate FromAccessToken(string oauth2Token) + { + Preconditions.CheckNotNull(oauth2Token); + return new MetadataInterceptorDelegate((authUri, metadata) => + { + metadata.Add(OAuth2Interceptor.CreateBearerTokenHeader(oauth2Token)); + }); + } + + /// <summary> /// Injects OAuth2 authorization header into initial metadata (= request headers). /// </summary> private class OAuth2Interceptor @@ -66,10 +80,10 @@ namespace Grpc.Auth private const string AuthorizationHeader = "Authorization"; private const string Schema = "Bearer"; - private ServiceCredential credential; + private ITokenAccess credential; private IClock clock; - public OAuth2Interceptor(ServiceCredential credential, IClock clock) + public OAuth2Interceptor(ITokenAccess credential, IClock clock) { this.credential = credential; this.clock = clock; @@ -80,24 +94,21 @@ namespace Grpc.Auth /// </summary> /// <param name="cancellationToken"></param> /// <returns></returns> - public string GetAccessToken(CancellationToken cancellationToken) + public string GetAccessToken(string authUri, CancellationToken cancellationToken) + { + // TODO(jtattermusch): Rethink synchronous wait to obtain the result. + return credential.GetAccessTokenForRequestAsync(authUri, cancellationToken: cancellationToken).GetAwaiter().GetResult(); + } + + public void InterceptHeaders(string authUri, Metadata metadata) { - if (credential.Token == null || credential.Token.IsExpired(clock)) - { - // TODO(jtattermusch): Parallel requests will spawn multiple requests to refresh the token once the token expires. - // TODO(jtattermusch): Rethink synchronous wait to obtain the result. - if (!credential.RequestAccessTokenAsync(cancellationToken).Result) - { - throw new InvalidOperationException("The access token has expired but we can't refresh it"); - } - } - return credential.Token.AccessToken; + var accessToken = GetAccessToken(authUri, CancellationToken.None); + metadata.Add(CreateBearerTokenHeader(accessToken)); } - public void InterceptHeaders(Metadata metadata) + public static Metadata.Entry CreateBearerTokenHeader(string accessToken) { - var accessToken = GetAccessToken(CancellationToken.None); - metadata.Add(new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken)); + return new Metadata.Entry(AuthorizationHeader, Schema + " " + accessToken); } } } diff --git a/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs index 70cb32d5b2..83396c546b 100644 --- a/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs @@ -9,5 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - -[assembly: InternalsVisibleTo("Grpc.Auth.Tests")] diff --git a/src/csharp/Grpc.Auth/app.config b/src/csharp/Grpc.Auth/app.config index 0a82bb4f16..84d7534d65 100644 --- a/src/csharp/Grpc.Auth/app.config +++ b/src/csharp/Grpc.Auth/app.config @@ -10,6 +10,10 @@ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" /> </dependentAssembly> + <dependentAssembly> + <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" /> + <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" /> + </dependentAssembly> </assemblyBinding> </runtime> </configuration>
\ No newline at end of file diff --git a/src/csharp/Grpc.Auth/packages.config b/src/csharp/Grpc.Auth/packages.config index 29be953bf3..7a02c95db9 100644 --- a/src/csharp/Grpc.Auth/packages.config +++ b/src/csharp/Grpc.Auth/packages.config @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="BouncyCastle" version="1.7.0" targetFramework="net45" /> - <package id="Google.Apis.Auth" version="1.9.2" targetFramework="net45" /> - <package id="Google.Apis.Core" version="1.9.2" targetFramework="net45" /> + <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" /> + <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" /> <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" /> <package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> diff --git a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs index df09857efe..52be77c846 100644 --- a/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs +++ b/src/csharp/Grpc.Core.Tests/ChannelOptionsTest.cs @@ -67,9 +67,9 @@ namespace Grpc.Core.Internal.Tests [Test] public void ConstructorPreconditions() { - Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption(null, "abc"); }); - Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption(null, 1); }); - Assert.Throws(typeof(NullReferenceException), () => { new ChannelOption("abc", null); }); + Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption(null, "abc"); }); + Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption(null, 1); }); + Assert.Throws(typeof(ArgumentNullException), () => { new ChannelOption("abc", null); }); } [Test] diff --git a/src/csharp/Grpc.Core.Tests/ChannelTest.cs b/src/csharp/Grpc.Core.Tests/ChannelTest.cs new file mode 100644 index 0000000000..2787572924 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/ChannelTest.cs @@ -0,0 +1,91 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class ChannelTest + { + [TestFixtureTearDown] + public void CleanupClass() + { + GrpcEnvironment.Shutdown(); + } + + [Test] + public void Constructor_RejectsInvalidParams() + { + Assert.Throws(typeof(ArgumentNullException), () => new Channel(null, Credentials.Insecure)); + } + + [Test] + public void State_IdleAfterCreation() + { + using (var channel = new Channel("localhost", Credentials.Insecure)) + { + Assert.AreEqual(ChannelState.Idle, channel.State); + } + } + + [Test] + public void WaitForStateChangedAsync_InvalidArgument() + { + using (var channel = new Channel("localhost", Credentials.Insecure)) + { + Assert.Throws(typeof(ArgumentException), () => channel.WaitForStateChangedAsync(ChannelState.FatalFailure)); + } + } + + [Test] + public void ResolvedTarget() + { + using (var channel = new Channel("127.0.0.1", Credentials.Insecure)) + { + Assert.IsTrue(channel.ResolvedTarget.Contains("127.0.0.1")); + } + } + + [Test] + public void Dispose_IsIdempotent() + { + var channel = new Channel("localhost", Credentials.Insecure); + channel.Dispose(); + channel.Dispose(); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs index e286ea519f..e49fdb5268 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -46,42 +46,18 @@ namespace Grpc.Core.Tests public class ClientServerTest { const string Host = "127.0.0.1"; - const string ServiceName = "/tests.Test"; - - static readonly Method<string, string> EchoMethod = new Method<string, string>( - MethodType.Unary, - "/tests.Test/Echo", - Marshallers.StringMarshaller, - Marshallers.StringMarshaller); - - static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>( - MethodType.ClientStreaming, - "/tests.Test/ConcatAndEcho", - Marshallers.StringMarshaller, - Marshallers.StringMarshaller); - - static readonly Method<string, string> NonexistentMethod = new Method<string, string>( - MethodType.Unary, - "/tests.Test/NonexistentMethod", - Marshallers.StringMarshaller, - Marshallers.StringMarshaller); - - static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName) - .AddMethod(EchoMethod, EchoHandler) - .AddMethod(ConcatAndEchoMethod, ConcatAndEchoHandler) - .Build(); + MockServiceHelper helper; Server server; Channel channel; [SetUp] public void Init() { - server = new Server(); - server.AddServiceDefinition(ServiceDefinition); - int port = server.AddPort(Host, Server.PickUnusedPort, ServerCredentials.Insecure); + helper = new MockServiceHelper(Host); + server = helper.GetServer(); server.Start(); - channel = new Channel(Host, port, Credentials.Insecure); + channel = helper.GetChannel(); } [TearDown] @@ -98,124 +74,127 @@ namespace Grpc.Core.Tests } [Test] - public void UnaryCall() + public async Task UnaryCall() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - Assert.AreEqual("ABC", Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None)); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + return request; + }); + + Assert.AreEqual("ABC", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC")); + + Assert.AreEqual("ABC", await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "ABC")); } [Test] public void UnaryCall_ServerHandlerThrows() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - try - { - Calls.BlockingUnaryCall(internalCall, "THROW", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => { - Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); - } + throw new Exception("This was thrown on purpose by a test"); + }); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode); + + var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unknown, ex2.Status.StatusCode); } [Test] public void UnaryCall_ServerHandlerThrowsRpcException() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - try + helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) => { - Calls.BlockingUnaryCall(internalCall, "THROW_UNAUTHENTICATED", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode); - } + throw new RpcException(new Status(StatusCode.Unauthenticated, "")); + }); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode); + + var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode); } [Test] public void UnaryCall_ServerHandlerSetsStatus() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - try + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - Calls.BlockingUnaryCall(internalCall, "SET_UNAUTHENTICATED", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Unauthenticated, e.Status.StatusCode); - } - } + context.Status = new Status(StatusCode.Unauthenticated, ""); + return ""; + }); - [Test] - public void AsyncUnaryCall() - { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - var result = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None).ResponseAsync.Result; - Assert.AreEqual("ABC", result); - } + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode); - [Test] - public async Task AsyncUnaryCall_ServerHandlerThrows() - { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - try - { - await Calls.AsyncUnaryCall(internalCall, "THROW", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); - } + var ex2 = Assert.Throws<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc")); + Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode); } [Test] public async Task ClientStreamingCall() { - var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty); - var call = Calls.AsyncClientStreamingCall(internalCall, CancellationToken.None); + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + string result = ""; + await requestStream.ForEachAsync(async (request) => + { + result += request; + }); + await Task.Delay(100); + return result; + }); - await call.RequestStream.WriteAll(new string[] { "A", "B", "C" }); + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall()); + await call.RequestStream.WriteAllAsync(new string[] { "A", "B", "C" }); Assert.AreEqual("ABC", await call.ResponseAsync); } [Test] public async Task ClientStreamingCall_CancelAfterBegin() { - var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty); + var barrier = new TaskCompletionSource<object>(); + + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + barrier.SetResult(null); + await requestStream.ToListAsync(); + return ""; + }); var cts = new CancellationTokenSource(); - var call = Calls.AsyncClientStreamingCall(internalCall, cts.Token); + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token))); - // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it. - await Task.Delay(1000); + await barrier.Task; // make sure the handler has started. cts.Cancel(); - try - { - await call.ResponseAsync; - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode); - } + var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync); + Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode); } [Test] - public void AsyncUnaryCall_EchoMetadata() + public async Task AsyncUnaryCall_EchoMetadata() { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + foreach (Metadata.Entry metadataEntry in context.RequestHeaders) + { + if (metadataEntry.Key != "user-agent") + { + context.ResponseTrailers.Add(metadataEntry); + } + } + return ""; + }); + var headers = new Metadata { - new Metadata.Entry("asciiHeader", "abcdefg"), - new Metadata.Entry("binaryHeader-bin", new byte[] { 1, 2, 3, 0, 0xff }), + { "ascii-header", "abcdefg" }, + { "binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff } } }; - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, headers); - var call = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None); - - Assert.AreEqual("ABC", call.ResponseAsync.Result); + var call = Calls.AsyncUnaryCall(helper.CreateUnaryCall(new CallOptions(headers: headers)), "ABC"); + await call; Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode); @@ -232,102 +211,89 @@ namespace Grpc.Core.Tests public void UnaryCall_DisposedChannel() { channel.Dispose(); - - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None)); + Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "ABC")); } [Test] public void UnaryCallPerformance() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + return request; + }); + + var callDetails = helper.CreateUnaryCall(); BenchmarkUtil.RunBenchmark(100, 100, - () => { Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken)); }); + () => { Calls.BlockingUnaryCall(callDetails, "ABC"); }); } [Test] public void UnknownMethodHandler() { - var internalCall = new Call<string, string>(ServiceName, NonexistentMethod, channel, Metadata.Empty); - try - { - Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken)); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode); - } + var nonexistentMethod = new Method<string, string>( + MethodType.Unary, + MockServiceHelper.ServiceName, + "NonExistentMethod", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + var callDetails = new CallInvocationDetails<string, string>(channel, nonexistentMethod, new CallOptions()); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(callDetails, "abc")); + Assert.AreEqual(StatusCode.Unimplemented, ex.Status.StatusCode); } [Test] public void UserAgentStringPresent() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - string userAgent = Calls.BlockingUnaryCall(internalCall, "RETURN-USER-AGENT", CancellationToken.None); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value; + }); + + string userAgent = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); Assert.IsTrue(userAgent.StartsWith("grpc-csharp/")); } [Test] public void PeerInfoPresent() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - string peer = Calls.BlockingUnaryCall(internalCall, "RETURN-PEER", CancellationToken.None); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + return context.Peer; + }); + + string peer = Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"); Assert.IsTrue(peer.Contains(Host)); } - private static async Task<string> EchoHandler(string request, ServerCallContext context) + [Test] + public async Task Channel_WaitForStateChangedAsync() { - foreach (Metadata.Entry metadataEntry in context.RequestHeaders) - { - if (metadataEntry.Key != "user-agent") - { - context.ResponseTrailers.Add(metadataEntry); - } - } - - if (request == "RETURN-USER-AGENT") + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - return context.RequestHeaders.Where(entry => entry.Key == "user-agent").Single().Value; - } + return request; + }); - if (request == "RETURN-PEER") - { - return context.Peer; - } + Assert.Throws(typeof(TaskCanceledException), + async () => await channel.WaitForStateChangedAsync(channel.State, DateTime.UtcNow.AddMilliseconds(10))); - if (request == "THROW") - { - throw new Exception("This was thrown on purpose by a test"); - } + var stateChangedTask = channel.WaitForStateChangedAsync(channel.State); - if (request == "THROW_UNAUTHENTICATED") - { - throw new RpcException(new Status(StatusCode.Unauthenticated, "")); - } + await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"); - if (request == "SET_UNAUTHENTICATED") - { - context.Status = new Status(StatusCode.Unauthenticated, ""); - } - - return request; + await stateChangedTask; + Assert.AreEqual(ChannelState.Ready, channel.State); } - private static async Task<string> ConcatAndEchoHandler(IAsyncStreamReader<string> requestStream, ServerCallContext context) + [Test] + public async Task Channel_ConnectAsync() { - string result = ""; - await requestStream.ForEach(async (request) => - { - if (request == "THROW") - { - throw new Exception("This was thrown on purpose by a test"); - } - result += request; - }); - // simulate processing takes some time. - await Task.Delay(250); - return result; + await channel.ConnectAsync(); + Assert.AreEqual(ChannelState.Ready, channel.State); + + await channel.ConnectAsync(DateTime.UtcNow.AddMilliseconds(1000)); + Assert.AreEqual(ChannelState.Ready, channel.State); } } } diff --git a/src/csharp/Grpc.Core.Tests/CompressionTest.cs b/src/csharp/Grpc.Core.Tests/CompressionTest.cs new file mode 100644 index 0000000000..9547683f60 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/CompressionTest.cs @@ -0,0 +1,128 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class CompressionTest + { + MockServiceHelper helper; + Server server; + Channel channel; + + [SetUp] + public void Init() + { + helper = new MockServiceHelper(); + + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); + } + + [TearDown] + public void Cleanup() + { + channel.Dispose(); + server.ShutdownAsync().Wait(); + } + + [TestFixtureTearDown] + public void CleanupClass() + { + GrpcEnvironment.Shutdown(); + } + + [Test] + public void WriteOptions_Unary() + { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + context.WriteOptions = new WriteOptions(WriteFlags.NoCompress); + return request; + }); + + var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress)); + Calls.BlockingUnaryCall(helper.CreateUnaryCall(callOptions), "abc"); + } + + [Test] + public async Task WriteOptions_DuplexStreaming() + { + helper.DuplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) => + { + await requestStream.ToListAsync(); + + context.WriteOptions = new WriteOptions(WriteFlags.NoCompress); + + await context.WriteResponseHeadersAsync(new Metadata { { "ascii-header", "abcdefg" } }); + + await responseStream.WriteAsync("X"); + + responseStream.WriteOptions = null; + await responseStream.WriteAsync("Y"); + + responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress); + await responseStream.WriteAsync("Z"); + }); + + var callOptions = new CallOptions(writeOptions: new WriteOptions(WriteFlags.NoCompress)); + var call = Calls.AsyncDuplexStreamingCall(helper.CreateDuplexStreamingCall(callOptions)); + + // check that write options from call options are propagated to request stream. + Assert.IsTrue((call.RequestStream.WriteOptions.Flags & WriteFlags.NoCompress) != 0); + + call.RequestStream.WriteOptions = new WriteOptions(); + await call.RequestStream.WriteAsync("A"); + + call.RequestStream.WriteOptions = null; + await call.RequestStream.WriteAsync("B"); + + call.RequestStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress); + await call.RequestStream.WriteAsync("C"); + + await call.RequestStream.CompleteAsync(); + + await call.ResponseStream.ToListAsync(); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs new file mode 100644 index 0000000000..db5f953b0e --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/ContextPropagationTest.cs @@ -0,0 +1,153 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + public class ContextPropagationTest + { + MockServiceHelper helper; + Server server; + Channel channel; + + [SetUp] + public void Init() + { + helper = new MockServiceHelper(); + + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); + } + + [TearDown] + public void Cleanup() + { + channel.Dispose(); + server.ShutdownAsync().Wait(); + } + + [TestFixtureTearDown] + public void CleanupClass() + { + GrpcEnvironment.Shutdown(); + } + + [Test] + public async Task PropagateCancellation() + { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + // check that we didn't obtain the default cancellation token. + Assert.IsTrue(context.CancellationToken.CanBeCanceled); + return "PASS"; + }); + + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + var propagationToken = context.CreatePropagationToken(); + Assert.IsNotNull(propagationToken.ParentCall); + + var callOptions = new CallOptions(propagationToken: propagationToken); + return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + }); + + var cts = new CancellationTokenSource(); + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(cancellationToken: cts.Token))); + await call.RequestStream.CompleteAsync(); + Assert.AreEqual("PASS", await call); + } + + [Test] + public async Task PropagateDeadline() + { + var deadline = DateTime.UtcNow.AddDays(7); + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + Assert.IsTrue(context.Deadline < deadline.AddMinutes(1)); + Assert.IsTrue(context.Deadline > deadline.AddMinutes(-1)); + return "PASS"; + }); + + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + Assert.Throws(typeof(ArgumentException), () => + { + // Trying to override deadline while propagating deadline from parent call will throw. + Calls.BlockingUnaryCall(helper.CreateUnaryCall( + new CallOptions(deadline: DateTime.UtcNow.AddDays(8), + propagationToken: context.CreatePropagationToken())), ""); + }); + + var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken()); + return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + }); + + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: deadline))); + await call.RequestStream.CompleteAsync(); + Assert.AreEqual("PASS", await call); + } + + [Test] + public async Task SuppressDeadlinePropagation() + { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + Assert.AreEqual(DateTime.MaxValue, context.Deadline); + return "PASS"; + }); + + helper.ClientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + Assert.IsTrue(context.CancellationToken.CanBeCanceled); + + var callOptions = new CallOptions(propagationToken: context.CreatePropagationToken(new ContextPropagationOptions(propagateDeadline: false))); + return await Calls.AsyncUnaryCall(helper.CreateUnaryCall(callOptions), "xyz"); + }); + + var cts = new CancellationTokenSource(); + var call = Calls.AsyncClientStreamingCall(helper.CreateClientStreamingCall(new CallOptions(deadline: DateTime.UtcNow.AddDays(7)))); + await call.RequestStream.CompleteAsync(); + Assert.AreEqual("PASS", await call); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index 1c4cca8b69..97ee0454bb 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -17,15 +17,22 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="nunit.core"> @@ -69,6 +76,11 @@ <Compile Include="Internal\TimespecTest.cs" /> <Compile Include="TimeoutsTest.cs" /> <Compile Include="NUnitVersionTest.cs" /> + <Compile Include="ChannelTest.cs" /> + <Compile Include="MockServiceHelper.cs" /> + <Compile Include="ResponseHeadersTest.cs" /> + <Compile Include="CompressionTest.cs" /> + <Compile Include="ContextPropagationTest.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs index 9ae12776f3..4ed93c7eca 100644 --- a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs +++ b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs @@ -69,5 +69,13 @@ namespace Grpc.Core.Tests Assert.IsFalse(object.ReferenceEquals(env1, env2)); } + + [Test] + public void GetCoreVersionString() + { + var coreVersion = GrpcEnvironment.GetCoreVersionString(); + var parts = coreVersion.Split('.'); + Assert.AreEqual(4, parts.Length); + } } } diff --git a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs index 46469113c5..33534fdd3c 100644 --- a/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs +++ b/src/csharp/Grpc.Core.Tests/Internal/MetadataArraySafeHandleTest.cs @@ -53,8 +53,8 @@ namespace Grpc.Core.Internal.Tests { var metadata = new Metadata { - new Metadata.Entry("host", "somehost"), - new Metadata.Entry("header2", "header value"), + { "host", "somehost" }, + { "header2", "header value" }, }; var nativeMetadata = MetadataArraySafeHandle.Create(metadata); nativeMetadata.Dispose(); @@ -65,8 +65,8 @@ namespace Grpc.Core.Internal.Tests { var metadata = new Metadata { - new Metadata.Entry("host", "somehost"), - new Metadata.Entry("header2", "header value"), + { "host", "somehost" }, + { "header2", "header value" } }; var nativeMetadata = MetadataArraySafeHandle.Create(metadata); diff --git a/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs new file mode 100644 index 0000000000..bb69648d8b --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/MockServiceHelper.cs @@ -0,0 +1,244 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + /// <summary> + /// Allows setting up a mock service in the client-server tests easily. + /// </summary> + public class MockServiceHelper + { + public const string ServiceName = "tests.Test"; + + public static readonly Method<string, string> UnaryMethod = new Method<string, string>( + MethodType.Unary, + ServiceName, + "Unary", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + public static readonly Method<string, string> ClientStreamingMethod = new Method<string, string>( + MethodType.ClientStreaming, + ServiceName, + "ClientStreaming", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + public static readonly Method<string, string> ServerStreamingMethod = new Method<string, string>( + MethodType.ServerStreaming, + ServiceName, + "ServerStreaming", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + public static readonly Method<string, string> DuplexStreamingMethod = new Method<string, string>( + MethodType.DuplexStreaming, + ServiceName, + "DuplexStreaming", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + readonly string host; + readonly ServerServiceDefinition serviceDefinition; + + UnaryServerMethod<string, string> unaryHandler; + ClientStreamingServerMethod<string, string> clientStreamingHandler; + ServerStreamingServerMethod<string, string> serverStreamingHandler; + DuplexStreamingServerMethod<string, string> duplexStreamingHandler; + + Server server; + Channel channel; + + public MockServiceHelper(string host = null) + { + this.host = host ?? "localhost"; + + serviceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName) + .AddMethod(UnaryMethod, (request, context) => unaryHandler(request, context)) + .AddMethod(ClientStreamingMethod, (requestStream, context) => clientStreamingHandler(requestStream, context)) + .AddMethod(ServerStreamingMethod, (request, responseStream, context) => serverStreamingHandler(request, responseStream, context)) + .AddMethod(DuplexStreamingMethod, (requestStream, responseStream, context) => duplexStreamingHandler(requestStream, responseStream, context)) + .Build(); + + var defaultStatus = new Status(StatusCode.Unknown, "Default mock implementation. Please provide your own."); + + unaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + context.Status = defaultStatus; + return ""; + }); + + clientStreamingHandler = new ClientStreamingServerMethod<string, string>(async (requestStream, context) => + { + context.Status = defaultStatus; + return ""; + }); + + serverStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => + { + context.Status = defaultStatus; + }); + + duplexStreamingHandler = new DuplexStreamingServerMethod<string, string>(async (requestStream, responseStream, context) => + { + context.Status = defaultStatus; + }); + } + + /// <summary> + /// Returns the default server for this service and creates one if not yet created. + /// </summary> + public Server GetServer() + { + if (server == null) + { + server = new Server + { + Services = { serviceDefinition }, + Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } + }; + } + return server; + } + + /// <summary> + /// Returns the default channel for this service and creates one if not yet created. + /// </summary> + public Channel GetChannel() + { + if (channel == null) + { + channel = new Channel(Host, GetServer().Ports.Single().BoundPort, Credentials.Insecure); + } + return channel; + } + + public CallInvocationDetails<string, string> CreateUnaryCall(CallOptions options = default(CallOptions)) + { + return new CallInvocationDetails<string, string>(channel, UnaryMethod, options); + } + + public CallInvocationDetails<string, string> CreateClientStreamingCall(CallOptions options = default(CallOptions)) + { + return new CallInvocationDetails<string, string>(channel, ClientStreamingMethod, options); + } + + public CallInvocationDetails<string, string> CreateServerStreamingCall(CallOptions options = default(CallOptions)) + { + return new CallInvocationDetails<string, string>(channel, ServerStreamingMethod, options); + } + + public CallInvocationDetails<string, string> CreateDuplexStreamingCall(CallOptions options = default(CallOptions)) + { + return new CallInvocationDetails<string, string>(channel, DuplexStreamingMethod, options); + } + + public string Host + { + get + { + return this.host; + } + } + + public ServerServiceDefinition ServiceDefinition + { + get + { + return this.serviceDefinition; + } + } + + public UnaryServerMethod<string, string> UnaryHandler + { + get + { + return this.unaryHandler; + } + + set + { + unaryHandler = value; + } + } + + public ClientStreamingServerMethod<string, string> ClientStreamingHandler + { + get + { + return this.clientStreamingHandler; + } + + set + { + clientStreamingHandler = value; + } + } + + public ServerStreamingServerMethod<string, string> ServerStreamingHandler + { + get + { + return this.serverStreamingHandler; + } + + set + { + serverStreamingHandler = value; + } + } + + public DuplexStreamingServerMethod<string, string> DuplexStreamingHandler + { + get + { + return this.duplexStreamingHandler; + } + + set + { + duplexStreamingHandler = value; + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/NUnitVersionTest.cs b/src/csharp/Grpc.Core.Tests/NUnitVersionTest.cs index 600df1a18d..3fa6ad09c0 100644 --- a/src/csharp/Grpc.Core.Tests/NUnitVersionTest.cs +++ b/src/csharp/Grpc.Core.Tests/NUnitVersionTest.cs @@ -70,10 +70,8 @@ namespace Grpc.Core.Tests [Test] public async Task NUnitVersionTest2() { - testRunCount ++; + testRunCount++; await Task.Delay(10); } - - } } diff --git a/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs new file mode 100644 index 0000000000..981b8ea3c8 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/ResponseHeadersTest.cs @@ -0,0 +1,136 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Tests +{ + /// <summary> + /// Tests for response headers support. + /// </summary> + public class ResponseHeadersTest + { + MockServiceHelper helper; + Server server; + Channel channel; + + Metadata headers; + + [SetUp] + public void Init() + { + helper = new MockServiceHelper(); + + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); + + headers = new Metadata { { "ascii-header", "abcdefg" } }; + } + + [TearDown] + public void Cleanup() + { + channel.Dispose(); + server.ShutdownAsync().Wait(); + } + + [TestFixtureTearDown] + public void CleanupClass() + { + GrpcEnvironment.Shutdown(); + } + + [Test] + public void WriteResponseHeaders_NullNotAllowed() + { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + Assert.Throws(typeof(ArgumentNullException), async () => await context.WriteResponseHeadersAsync(null)); + return "PASS"; + }); + + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + } + + [Test] + public void WriteResponseHeaders_AllowedOnlyOnce() + { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + await context.WriteResponseHeadersAsync(headers); + try + { + await context.WriteResponseHeadersAsync(headers); + Assert.Fail(); + } + catch (InvalidOperationException expected) + { + } + return "PASS"; + }); + + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "")); + } + + [Test] + public async Task WriteResponseHeaders_NotAllowedAfterWrite() + { + helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) => + { + await responseStream.WriteAsync("A"); + try + { + await context.WriteResponseHeadersAsync(headers); + Assert.Fail(); + } + catch (InvalidOperationException expected) + { + } + await responseStream.WriteAsync("B"); + }); + + var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), ""); + var responses = await call.ResponseStream.ToListAsync(); + CollectionAssert.AreEqual(new[] { "A", "B" }, responses); + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/ServerTest.cs b/src/csharp/Grpc.Core.Tests/ServerTest.cs index ba9efae871..485006ebac 100644 --- a/src/csharp/Grpc.Core.Tests/ServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ServerTest.cs @@ -32,6 +32,7 @@ #endregion using System; +using System.Linq; using Grpc.Core; using Grpc.Core.Internal; using Grpc.Core.Utils; @@ -44,11 +45,45 @@ namespace Grpc.Core.Tests [Test] public void StartAndShutdownServer() { - Server server = new Server(); - server.AddPort("localhost", Server.PickUnusedPort, ServerCredentials.Insecure); + Server server = new Server + { + Ports = { new ServerPort("localhost", ServerPort.PickUnused, ServerCredentials.Insecure) } + }; server.Start(); server.ShutdownAsync().Wait(); GrpcEnvironment.Shutdown(); } + + [Test] + public void PickUnusedPort() + { + Server server = new Server + { + Ports = { new ServerPort("localhost", ServerPort.PickUnused, ServerCredentials.Insecure) } + }; + + var boundPort = server.Ports.Single(); + Assert.AreEqual(0, boundPort.Port); + Assert.Greater(boundPort.BoundPort, 0); + + server.Start(); + server.ShutdownAsync(); + GrpcEnvironment.Shutdown(); + } + + [Test] + public void CannotModifyAfterStarted() + { + Server server = new Server + { + Ports = { new ServerPort("localhost", ServerPort.PickUnused, ServerCredentials.Insecure) } + }; + server.Start(); + Assert.Throws(typeof(InvalidOperationException), () => server.Ports.Add("localhost", 9999, ServerCredentials.Insecure)); + Assert.Throws(typeof(InvalidOperationException), () => server.Services.Add(ServerServiceDefinition.CreateBuilder("serviceName").Build())); + + server.ShutdownAsync().Wait(); + GrpcEnvironment.Shutdown(); + } } } diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs index 010ffd898a..d875d601b9 100644 --- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs +++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs @@ -48,35 +48,18 @@ namespace Grpc.Core.Tests /// </summary> public class TimeoutsTest { - const string Host = "localhost"; - const string ServiceName = "/tests.Test"; - - static readonly Method<string, string> TestMethod = new Method<string, string>( - MethodType.Unary, - "/tests.Test/Test", - Marshallers.StringMarshaller, - Marshallers.StringMarshaller); - - static readonly ServerServiceDefinition ServiceDefinition = ServerServiceDefinition.CreateBuilder(ServiceName) - .AddMethod(TestMethod, TestMethodHandler) - .Build(); - - // provides a way how to retrieve an out-of-band result value from server handler - static TaskCompletionSource<string> stringFromServerHandlerTcs; - + MockServiceHelper helper; Server server; Channel channel; [SetUp] public void Init() { - server = new Server(); - server.AddServiceDefinition(ServiceDefinition); - int port = server.AddPort(Host, Server.PickUnusedPort, ServerCredentials.Insecure); - server.Start(); - channel = new Channel(Host, port, Credentials.Insecure); + helper = new MockServiceHelper(); - stringFromServerHandlerTcs = new TaskCompletionSource<string>(); + server = helper.GetServer(); + server.Start(); + channel = helper.GetChannel(); } [TearDown] @@ -95,113 +78,83 @@ namespace Grpc.Core.Tests [Test] public void InfiniteDeadline() { + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + Assert.AreEqual(DateTime.MaxValue, context.Deadline); + return "PASS"; + }); + // no deadline specified, check server sees infinite deadline - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty); - Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(internalCall, "RETURN_DEADLINE", CancellationToken.None)); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc")); // DateTime.MaxValue deadline specified, check server sees infinite deadline - var internalCall2 = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, DateTime.MaxValue); - Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(internalCall2, "RETURN_DEADLINE", CancellationToken.None)); + Assert.AreEqual("PASS", Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MaxValue)), "abc")); } [Test] public void DeadlineTransferredToServer() { - var remainingTimeClient = TimeSpan.FromDays(7); - var deadline = DateTime.UtcNow + remainingTimeClient; - Thread.Sleep(1000); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); - - var serverDeadlineTicksString = Calls.BlockingUnaryCall(internalCall, "RETURN_DEADLINE", CancellationToken.None); - var serverDeadline = new DateTime(long.Parse(serverDeadlineTicksString), DateTimeKind.Utc); - - // A fairly relaxed check that the deadline set by client and deadline seen by server - // are in agreement. C core takes care of the work with transferring deadline over the wire, - // so we don't need an exact check here. - Assert.IsTrue(Math.Abs((deadline - serverDeadline).TotalMilliseconds) < 5000); + var clientDeadline = DateTime.UtcNow + TimeSpan.FromDays(7); + + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => + { + // A fairly relaxed check that the deadline set by client and deadline seen by server + // are in agreement. C core takes care of the work with transferring deadline over the wire, + // so we don't need an exact check here. + Assert.IsTrue(Math.Abs((clientDeadline - context.Deadline).TotalMilliseconds) < 5000); + return "PASS"; + }); + Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: clientDeadline)), "abc"); } [Test] public void DeadlineInThePast() { - var deadline = DateTime.MinValue; - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); - - try - { - Calls.BlockingUnaryCall(internalCall, "TIMEOUT", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode); - } + await Task.Delay(60000); + return "FAIL"; + }); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.MinValue)), "abc")); + // We can't guarantee the status code always DeadlineExceeded. See issue #2685. + Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal }); } [Test] public void DeadlineExceededStatusOnTimeout() { - var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); - - try + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { - Calls.BlockingUnaryCall(internalCall, "TIMEOUT", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode); - } + await Task.Delay(60000); + return "FAIL"; + }); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc")); + // We can't guarantee the status code always DeadlineExceeded. See issue #2685. + Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal }); } [Test] - public void ServerReceivesCancellationOnTimeout() + public async Task ServerReceivesCancellationOnTimeout() { - var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); + var serverReceivedCancellationTcs = new TaskCompletionSource<bool>(); - try - { - Calls.BlockingUnaryCall(internalCall, "CHECK_CANCELLATION_RECEIVED", CancellationToken.None); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode); - } - Assert.AreEqual("CANCELLED", stringFromServerHandlerTcs.Task.Result); - } - - private static async Task<string> TestMethodHandler(string request, ServerCallContext context) - { - if (request == "TIMEOUT") - { - await Task.Delay(60000); - return ""; - } - - if (request == "RETURN_DEADLINE") - { - if (context.Deadline == DateTime.MaxValue) - { - return "DATETIME_MAXVALUE"; - } - - return context.Deadline.Ticks.ToString(); - } - - if (request == "CHECK_CANCELLATION_RECEIVED") + helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) => { // wait until cancellation token is fired. var tcs = new TaskCompletionSource<object>(); context.CancellationToken.Register(() => { tcs.SetResult(null); }); await tcs.Task; - stringFromServerHandlerTcs.SetResult("CANCELLED"); + serverReceivedCancellationTcs.SetResult(true); return ""; - } + }); + + var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(new CallOptions(deadline: DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)))), "abc")); + // We can't guarantee the status code always DeadlineExceeded. See issue #2685. + Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal }); - return ""; + Assert.IsTrue(await serverReceivedCancellationTcs.Task); } } } diff --git a/src/csharp/Grpc.Core/CallInvocationDetails.cs b/src/csharp/Grpc.Core/CallInvocationDetails.cs new file mode 100644 index 0000000000..6565073fc5 --- /dev/null +++ b/src/csharp/Grpc.Core/CallInvocationDetails.cs @@ -0,0 +1,171 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core +{ + /// <summary> + /// Details about a client-side call to be invoked. + /// </summary> + public struct CallInvocationDetails<TRequest, TResponse> + { + readonly Channel channel; + readonly string method; + readonly string host; + readonly Marshaller<TRequest> requestMarshaller; + readonly Marshaller<TResponse> responseMarshaller; + CallOptions options; + + /// <summary> + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// </summary> + /// <param name="channel">Channel to use for this call.</param> + /// <param name="method">Method to call.</param> + /// <param name="options">Call options.</param> + public CallInvocationDetails(Channel channel, Method<TRequest, TResponse> method, CallOptions options) : + this(channel, method, null, options) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// </summary> + /// <param name="channel">Channel to use for this call.</param> + /// <param name="method">Method to call.</param> + /// <param name="host">Host that contains the method. if <c>null</c>, default host will be used.</param> + /// <param name="options">Call options.</param> + public CallInvocationDetails(Channel channel, Method<TRequest, TResponse> method, string host, CallOptions options) : + this(channel, method.FullName, host, method.RequestMarshaller, method.ResponseMarshaller, options) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="Grpc.Core.CallInvocationDetails`2"/> struct. + /// </summary> + /// <param name="channel">Channel to use for this call.</param> + /// <param name="method">Qualified method name.</param> + /// <param name="host">Host that contains the method.</param> + /// <param name="requestMarshaller">Request marshaller.</param> + /// <param name="responseMarshaller">Response marshaller.</param> + /// <param name="options">Call options.</param> + public CallInvocationDetails(Channel channel, string method, string host, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller, CallOptions options) + { + this.channel = Preconditions.CheckNotNull(channel, "channel"); + this.method = Preconditions.CheckNotNull(method, "method"); + this.host = host; + this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller, "requestMarshaller"); + this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller, "responseMarshaller"); + this.options = options; + } + + /// <summary> + /// Get channel associated with this call. + /// </summary> + public Channel Channel + { + get + { + return this.channel; + } + } + + /// <summary> + /// Gets name of method to be called. + /// </summary> + public string Method + { + get + { + return this.method; + } + } + + /// <summary> + /// Get name of host. + /// </summary> + public string Host + { + get + { + return this.host; + } + } + + /// <summary> + /// Gets marshaller used to serialize requests. + /// </summary> + public Marshaller<TRequest> RequestMarshaller + { + get + { + return this.requestMarshaller; + } + } + + /// <summary> + /// Gets marshaller used to deserialized responses. + /// </summary> + public Marshaller<TResponse> ResponseMarshaller + { + get + { + return this.responseMarshaller; + } + } + + /// <summary> + /// Gets the call options. + /// </summary> + public CallOptions Options + { + get + { + return options; + } + } + + /// <summary> + /// Returns new instance of <see cref="CallInvocationDetails"/> with + /// <c>Options</c> set to the value provided. Values of all other fields are preserved. + /// </summary> + public CallInvocationDetails<TRequest, TResponse> WithOptions(CallOptions options) + { + var newDetails = this; + newDetails.options = options; + return newDetails; + } + } +} diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs new file mode 100644 index 0000000000..3dfe80b48c --- /dev/null +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -0,0 +1,178 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Threading; + +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core +{ + /// <summary> + /// Options for calls made by client. + /// </summary> + public struct CallOptions + { + Metadata headers; + DateTime? deadline; + CancellationToken cancellationToken; + WriteOptions writeOptions; + ContextPropagationToken propagationToken; + + /// <summary> + /// Creates a new instance of <c>CallOptions</c> struct. + /// </summary> + /// <param name="headers">Headers to be sent with the call.</param> + /// <param name="deadline">Deadline for the call to finish. null means no deadline.</param> + /// <param name="cancellationToken">Can be used to request cancellation of the call.</param> + /// <param name="writeOptions">Write options that will be used for this call.</param> + /// <param name="propagationToken">Context propagation token obtained from <see cref="ServerCallContext"/>.</param> + public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken), + WriteOptions writeOptions = null, ContextPropagationToken propagationToken = null) + { + this.headers = headers; + this.deadline = deadline; + this.cancellationToken = cancellationToken; + this.writeOptions = writeOptions; + this.propagationToken = propagationToken; + } + + /// <summary> + /// Headers to send at the beginning of the call. + /// </summary> + public Metadata Headers + { + get { return headers; } + } + + /// <summary> + /// Call deadline. + /// </summary> + public DateTime? Deadline + { + get { return deadline; } + } + + /// <summary> + /// Token that can be used for cancelling the call. + /// </summary> + public CancellationToken CancellationToken + { + get { return cancellationToken; } + } + + /// <summary> + /// Write options that will be used for this call. + /// </summary> + public WriteOptions WriteOptions + { + get + { + return this.writeOptions; + } + } + + /// <summary> + /// Token for propagating parent call context. + /// </summary> + public ContextPropagationToken PropagationToken + { + get + { + return this.propagationToken; + } + } + + /// <summary> + /// Returns new instance of <see cref="CallOptions"/> with + /// <c>Headers</c> set to the value provided. Values of all other fields are preserved. + /// </summary> + public CallOptions WithHeaders(Metadata headers) + { + var newOptions = this; + newOptions.headers = headers; + return newOptions; + } + + /// <summary> + /// Returns new instance of <see cref="CallOptions"/> with + /// <c>Deadline</c> set to the value provided. Values of all other fields are preserved. + /// </summary> + public CallOptions WithDeadline(DateTime deadline) + { + var newOptions = this; + newOptions.deadline = deadline; + return newOptions; + } + + /// <summary> + /// Returns new instance of <see cref="CallOptions"/> with + /// <c>CancellationToken</c> set to the value provided. Values of all other fields are preserved. + /// </summary> + public CallOptions WithCancellationToken(CancellationToken cancellationToken) + { + var newOptions = this; + newOptions.cancellationToken = cancellationToken; + return newOptions; + } + + /// <summary> + /// Returns a new instance of <see cref="CallOptions"/> with + /// all previously unset values set to their defaults and deadline and cancellation + /// token propagated when appropriate. + /// </summary> + internal CallOptions Normalize() + { + var newOptions = this; + if (propagationToken != null) + { + if (propagationToken.Options.IsPropagateDeadline) + { + Preconditions.CheckArgument(!newOptions.deadline.HasValue, + "Cannot propagate deadline from parent call. The deadline has already been set explicitly."); + newOptions.deadline = propagationToken.ParentDeadline; + } + if (propagationToken.Options.IsPropagateCancellation) + { + Preconditions.CheckArgument(!newOptions.cancellationToken.CanBeCanceled, + "Cannot propagate cancellation token from parent call. The cancellation token has already been set to a non-default value."); + } + } + + newOptions.headers = newOptions.headers ?? Metadata.Empty; + newOptions.deadline = newOptions.deadline ?? DateTime.MaxValue; + return newOptions; + } + } +} diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs index 054fc27491..7067456638 100644 --- a/src/csharp/Grpc.Core/Calls.cs +++ b/src/csharp/Grpc.Core/Calls.cs @@ -31,8 +31,6 @@ #endregion -using System; -using System.Threading; using System.Threading.Tasks; using Grpc.Core.Internal; @@ -40,73 +38,99 @@ namespace Grpc.Core { /// <summary> /// Helper methods for generated clients to make RPC calls. + /// Most users will use this class only indirectly and will be + /// making calls using client object generated from protocol + /// buffer definition files. /// </summary> public static class Calls { - public static TResponse BlockingUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + /// <summary> + /// Invokes a simple remote call in a blocking fashion. + /// </summary> + /// <returns>The response.</returns> + /// <param name="call">The call defintion.</param> + /// <param name="req">Request message.</param> + /// <typeparam name="TRequest">Type of request message.</typeparam> + /// <typeparam name="TResponse">The of response message.</typeparam> + public static TResponse BlockingUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - // TODO(jtattermusch): this gives a race that cancellation can be requested before the call even starts. - RegisterCancellationCallback(asyncCall, token); - return asyncCall.UnaryCall(call.Channel, call.Name, req, call.Headers, call.Deadline); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + return asyncCall.UnaryCall(req); } - public static AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + /// <summary> + /// Invokes a simple remote call asynchronously. + /// </summary> + /// <returns>An awaitable call object providing access to the response.</returns> + /// <param name="call">The call defintion.</param> + /// <param name="req">Request message.</param> + /// <typeparam name="TRequest">Type of request message.</typeparam> + /// <typeparam name="TResponse">The of response message.</typeparam> + public static AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - var asyncResult = asyncCall.UnaryCallAsync(req, call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + var asyncResult = asyncCall.UnaryCallAsync(req); return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + /// <summary> + /// Invokes a server streaming call asynchronously. + /// In server streaming scenario, client sends on request and server responds with a stream of responses. + /// </summary> + /// <returns>A call object providing access to the asynchronous response stream.</returns> + /// <param name="call">The call defintion.</param> + /// <param name="req">Request message.</param> + /// <typeparam name="TRequest">Type of request message.</typeparam> + /// <typeparam name="TResponse">The of response messages.</typeparam> + public static AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - asyncCall.StartServerStreamingCall(req, call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + asyncCall.StartServerStreamingCall(req); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token) + /// <summary> + /// Invokes a client streaming call asynchronously. + /// In client streaming scenario, client sends a stream of requests and server responds with a single response. + /// </summary> + /// <returns>An awaitable call object providing access to the response.</returns> + /// <typeparam name="TRequest">Type of request messages.</typeparam> + /// <typeparam name="TResponse">The of response message.</typeparam> + public static AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - var resultTask = asyncCall.ClientStreamingCallAsync(call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + var resultTask = asyncCall.ClientStreamingCallAsync(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token) + /// <summary> + /// Invokes a duplex streaming call asynchronously. + /// In duplex streaming scenario, client sends a stream of requests and server responds with a stream of responses. + /// The response stream is completely independent and both side can be sending messages at the same time. + /// </summary> + /// <returns>A call object providing access to the asynchronous request and response streams.</returns> + /// <param name="call">The call definition.</param> + /// <typeparam name="TRequest">Type of request messages.</typeparam> + /// <typeparam name="TResponse">Type of reponse messages.</typeparam> + public static AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - asyncCall.StartDuplexStreamingCall(call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + asyncCall.StartDuplexStreamingCall(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - - private static void RegisterCancellationCallback<TRequest, TResponse>(AsyncCall<TRequest, TResponse> asyncCall, CancellationToken token) - { - if (token.CanBeCanceled) - { - token.Register(() => asyncCall.Cancel()); - } - } } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index 18e6f2fda5..64c6adf2bf 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -37,6 +37,8 @@ using System.Threading; using System.Threading.Tasks; using Grpc.Core.Internal; +using Grpc.Core.Logging; +using Grpc.Core.Utils; namespace Grpc.Core { @@ -45,21 +47,24 @@ namespace Grpc.Core /// </summary> public class Channel : IDisposable { + static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Channel>(); + + readonly string target; readonly GrpcEnvironment environment; readonly ChannelSafeHandle handle; readonly List<ChannelOption> options; - readonly string target; bool disposed; /// <summary> /// Creates a channel that connects to a specific host. - /// Port will default to 80 for an unsecure channel and to 443 a secure channel. + /// Port will default to 80 for an unsecure channel and to 443 for a secure channel. /// </summary> - /// <param name="host">The DNS name of IP address of the host.</param> + /// <param name="target">Target of the channel.</param> /// <param name="credentials">Credentials to secure the channel.</param> /// <param name="options">Channel options.</param> - public Channel(string host, Credentials credentials, IEnumerable<ChannelOption> options = null) + public Channel(string target, Credentials credentials, IEnumerable<ChannelOption> options = null) { + this.target = Preconditions.CheckNotNull(target, "target"); this.environment = GrpcEnvironment.GetInstance(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); @@ -69,21 +74,20 @@ namespace Grpc.Core { if (nativeCredentials != null) { - this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, host, nativeChannelArgs); + this.handle = ChannelSafeHandle.CreateSecure(nativeCredentials, target, nativeChannelArgs); } else { - this.handle = ChannelSafeHandle.CreateInsecure(host, nativeChannelArgs); + this.handle = ChannelSafeHandle.CreateInsecure(target, nativeChannelArgs); } } - this.target = GetOverridenTarget(host, this.options); } /// <summary> /// Creates a channel that connects to a specific host and port. /// </summary> - /// <param name="host">DNS name or IP address</param> - /// <param name="port">the port</param> + /// <param name="host">The name or IP address of the host.</param> + /// <param name="port">The port.</param> /// <param name="credentials">Credentials to secure the channel.</param> /// <param name="options">Channel options.</param> public Channel(string host, int port, Credentials credentials, IEnumerable<ChannelOption> options = null) : @@ -91,41 +95,96 @@ namespace Grpc.Core { } - public void Dispose() + /// <summary> + /// Gets current connectivity state of this channel. + /// </summary> + public ChannelState State { - Dispose(true); - GC.SuppressFinalize(this); + get + { + return handle.CheckConnectivityState(false); + } } - internal string Target + /// <summary> + /// Returned tasks completes once channel state has become different from + /// given lastObservedState. + /// If deadline is reached or and error occurs, returned task is cancelled. + /// </summary> + public Task WaitForStateChangedAsync(ChannelState lastObservedState, DateTime? deadline = null) + { + Preconditions.CheckArgument(lastObservedState != ChannelState.FatalFailure, + "FatalFailure is a terminal state. No further state changes can occur."); + var tcs = new TaskCompletionSource<object>(); + var deadlineTimespec = deadline.HasValue ? Timespec.FromDateTime(deadline.Value) : Timespec.InfFuture; + var handler = new BatchCompletionDelegate((success, ctx) => + { + if (success) + { + tcs.SetResult(null); + } + else + { + tcs.SetCanceled(); + } + }); + handle.WatchConnectivityState(lastObservedState, deadlineTimespec, environment.CompletionQueue, environment.CompletionRegistry, handler); + return tcs.Task; + } + + /// <summary>Resolved address of the remote endpoint in URI format.</summary> + public string ResolvedTarget { get { - return target; + return handle.GetTarget(); } } - internal ChannelSafeHandle Handle + /// <summary>The original target used to create the channel.</summary> + public string Target { get { - return this.handle; + return this.target; } } - internal CompletionQueueSafeHandle CompletionQueue + /// <summary> + /// Allows explicitly requesting channel to connect without starting an RPC. + /// Returned task completes once state Ready was seen. If the deadline is reached, + /// or channel enters the FatalFailure state, the task is cancelled. + /// There is no need to call this explicitly unless your use case requires that. + /// Starting an RPC on a new channel will request connection implicitly. + /// </summary> + public async Task ConnectAsync(DateTime? deadline = null) { - get + var currentState = handle.CheckConnectivityState(true); + while (currentState != ChannelState.Ready) { - return this.environment.CompletionQueue; + if (currentState == ChannelState.FatalFailure) + { + throw new OperationCanceledException("Channel has reached FatalFailure state."); + } + await WaitForStateChangedAsync(currentState, deadline); + currentState = handle.CheckConnectivityState(false); } } - internal CompletionRegistry CompletionRegistry + /// <summary> + /// Destroys the underlying channel. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + internal ChannelSafeHandle Handle { get { - return this.environment.CompletionRegistry; + return this.handle; } } @@ -159,26 +218,5 @@ namespace Grpc.Core // TODO(jtattermusch): it would be useful to also provide .NET/mono version. return string.Format("grpc-csharp/{0}", VersionInfo.CurrentVersion); } - - /// <summary> - /// Look for SslTargetNameOverride option and return its value instead of originalTarget - /// if found. - /// </summary> - private static string GetOverridenTarget(string originalTarget, IEnumerable<ChannelOption> options) - { - if (options == null) - { - return originalTarget; - } - foreach (var option in options) - { - if (option.Type == ChannelOption.OptionType.String - && option.Name == ChannelOptions.SslTargetNameOverride) - { - return option.StringValue; - } - } - return originalTarget; - } } } diff --git a/src/csharp/Grpc.Core/ChannelOptions.cs b/src/csharp/Grpc.Core/ChannelOptions.cs index 9fe03d2805..0cb2953f2c 100644 --- a/src/csharp/Grpc.Core/ChannelOptions.cs +++ b/src/csharp/Grpc.Core/ChannelOptions.cs @@ -30,7 +30,6 @@ #endregion using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -64,8 +63,8 @@ namespace Grpc.Core public ChannelOption(string name, string stringValue) { this.type = OptionType.String; - this.name = Preconditions.CheckNotNull(name); - this.stringValue = Preconditions.CheckNotNull(stringValue); + this.name = Preconditions.CheckNotNull(name, "name"); + this.stringValue = Preconditions.CheckNotNull(stringValue, "stringValue"); } /// <summary> @@ -76,7 +75,7 @@ namespace Grpc.Core public ChannelOption(string name, int intValue) { this.type = OptionType.Integer; - this.name = Preconditions.CheckNotNull(name); + this.name = Preconditions.CheckNotNull(name, "name"); this.intValue = intValue; } @@ -135,6 +134,9 @@ namespace Grpc.Core /// <summary>Initial sequence number for http2 transports</summary> public const string Http2InitialSequenceNumber = "grpc.http2.initial_sequence_number"; + /// <summary>Default authority for calls.</summary> + public const string DefaultAuthority = "grpc.default_authority"; + /// <summary>Primary user agent: goes at the start of the user-agent metadata</summary> public const string PrimaryUserAgentString = "grpc.primary_user_agent"; diff --git a/src/csharp/Grpc.Core/Utils/ExceptionHelper.cs b/src/csharp/Grpc.Core/ChannelState.cs index c4d6bee058..d293b98f75 100644 --- a/src/csharp/Grpc.Core/Utils/ExceptionHelper.cs +++ b/src/csharp/Grpc.Core/ChannelState.cs @@ -33,25 +33,37 @@ using System; -namespace Grpc.Core.Utils +namespace Grpc.Core { - public static class ExceptionHelper + /// <summary> + /// Connectivity state of a channel. + /// Based on grpc_connectivity_state from grpc/grpc.h + /// </summary> + public enum ChannelState { /// <summary> - /// If inner exceptions contain RpcException, rethrows it. - /// Otherwise, rethrows the original aggregate exception. - /// Always throws, the exception return type is here only to make the. + /// Channel is idle /// </summary> - public static Exception UnwrapRpcException(AggregateException ae) - { - foreach (var e in ae.InnerExceptions) - { - if (e is RpcException) - { - throw e; - } - } - throw ae; - } + Idle, + + /// <summary> + /// Channel is connecting + /// </summary> + Connecting, + + /// <summary> + /// Channel is ready for work + /// </summary> + Ready, + + /// <summary> + /// Channel has seen a failure but expects to recover + /// </summary> + TransientFailure, + + /// <summary> + /// Channel has seen a failure that it cannot recover from + /// </summary> + FatalFailure } } diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index fd3473128a..f46184406c 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -35,21 +35,26 @@ using System; using System.Collections.Generic; using Grpc.Core.Internal; +using System.Text.RegularExpressions; namespace Grpc.Core { - public delegate void MetadataInterceptorDelegate(Metadata metadata); + public delegate void MetadataInterceptorDelegate(string authUri, Metadata metadata); /// <summary> /// Base class for client-side stubs. /// </summary> public abstract class ClientBase { + static readonly Regex TrailingPortPattern = new Regex(":[0-9]+/?$"); readonly Channel channel; + readonly string authUriBase; public ClientBase(Channel channel) { this.channel = channel; + // TODO(jtattermush): we shouldn't need to hand-curate the channel.Target contents. + this.authUriBase = "https://" + TrailingPortPattern.Replace(channel.Target, "") + "/"; } /// <summary> @@ -63,6 +68,18 @@ namespace Grpc.Core } /// <summary> + /// gRPC supports multiple "hosts" being served by a single server. + /// This property can be used to set the target host explicitly. + /// By default, this will be set to <c>null</c> with the meaning + /// "use default host". + /// </summary> + public string Host + { + get; + set; + } + + /// <summary> /// Channel associated with this client. /// </summary> public Channel Channel @@ -76,19 +93,21 @@ namespace Grpc.Core /// <summary> /// Creates a new call to given method. /// </summary> - protected Call<TRequest, TResponse> CreateCall<TRequest, TResponse>(string serviceName, Method<TRequest, TResponse> method, Metadata metadata, DateTime? deadline) + protected CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, CallOptions options) where TRequest : class where TResponse : class { var interceptor = HeaderInterceptor; if (interceptor != null) { - metadata = metadata ?? new Metadata(); - interceptor(metadata); - metadata.Freeze(); + if (options.Headers == null) + { + options = options.WithHeaders(new Metadata()); + } + var authUri = authUriBase + method.ServiceName; + interceptor(authUri, options.Headers); } - return new Call<TRequest, TResponse>(serviceName, method, channel, - metadata ?? Metadata.Empty, deadline ?? DateTime.MaxValue); + return new CallInvocationDetails<TRequest, TResponse>(channel, method, Host, options); } } } diff --git a/src/csharp/Grpc.Core/OperationFailedException.cs b/src/csharp/Grpc.Core/CompressionLevel.cs index 9b1c24d0c1..399652b85e 100644 --- a/src/csharp/Grpc.Core/OperationFailedException.cs +++ b/src/csharp/Grpc.Core/CompressionLevel.cs @@ -36,12 +36,28 @@ using System; namespace Grpc.Core { /// <summary> - /// Thrown when gRPC operation fails. + /// Compression level based on grpc_compression_level from grpc/compression.h /// </summary> - public class OperationFailedException : Exception + public enum CompressionLevel { - public OperationFailedException(string message) : base(message) - { - } + /// <summary> + /// No compression. + /// </summary> + None = 0, + + /// <summary> + /// Low compression. + /// </summary> + Low, + + /// <summary> + /// Medium compression. + /// </summary> + Medium, + + /// <summary> + /// High compression. + /// </summary> + High, } } diff --git a/src/csharp/Grpc.Core/ContextPropagationToken.cs b/src/csharp/Grpc.Core/ContextPropagationToken.cs new file mode 100644 index 0000000000..2e4bfc9e47 --- /dev/null +++ b/src/csharp/Grpc.Core/ContextPropagationToken.cs @@ -0,0 +1,171 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; +using System.Threading; + +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core +{ + /// <summary> + /// Token for propagating context of server side handlers to child calls. + /// In situations when a backend is making calls to another backend, + /// it makes sense to propagate properties like deadline and cancellation + /// token of the server call to the child call. + /// C core provides some other contexts (like tracing context) that + /// are not accessible to C# layer, but this token still allows propagating them. + /// </summary> + public class ContextPropagationToken + { + /// <summary> + /// Default propagation mask used by C core. + /// </summary> + private const ContextPropagationFlags DefaultCoreMask = (ContextPropagationFlags)0xffff; + + /// <summary> + /// Default propagation mask used by C# - we want to propagate deadline + /// and cancellation token by our own means. + /// </summary> + internal const ContextPropagationFlags DefaultMask = DefaultCoreMask + & ~ContextPropagationFlags.Deadline & ~ContextPropagationFlags.Cancellation; + + readonly CallSafeHandle parentCall; + readonly DateTime deadline; + readonly CancellationToken cancellationToken; + readonly ContextPropagationOptions options; + + internal ContextPropagationToken(CallSafeHandle parentCall, DateTime deadline, CancellationToken cancellationToken, ContextPropagationOptions options) + { + this.parentCall = Preconditions.CheckNotNull(parentCall); + this.deadline = deadline; + this.cancellationToken = cancellationToken; + this.options = options ?? ContextPropagationOptions.Default; + } + + /// <summary> + /// Gets the native handle of the parent call. + /// </summary> + internal CallSafeHandle ParentCall + { + get + { + return this.parentCall; + } + } + + /// <summary> + /// Gets the parent call's deadline. + /// </summary> + internal DateTime ParentDeadline + { + get + { + return this.deadline; + } + } + + /// <summary> + /// Gets the parent call's cancellation token. + /// </summary> + internal CancellationToken ParentCancellationToken + { + get + { + return this.cancellationToken; + } + } + + /// <summary> + /// Get the context propagation options. + /// </summary> + internal ContextPropagationOptions Options + { + get + { + return this.options; + } + } + } + + /// <summary> + /// Options for <see cref="ContextPropagationToken"/>. + /// </summary> + public class ContextPropagationOptions + { + /// <summary> + /// The context propagation options that will be used by default. + /// </summary> + public static readonly ContextPropagationOptions Default = new ContextPropagationOptions(); + + bool propagateDeadline; + bool propagateCancellation; + + + /// <summary> + /// Creates new context propagation options. + /// </summary> + /// <param name="propagateDeadline">If set to <c>true</c> parent call's deadline will be propagated to the child call.</param> + /// <param name="propagateCancellation">If set to <c>true</c> parent call's cancellation token will be propagated to the child call.</param> + public ContextPropagationOptions(bool propagateDeadline = true, bool propagateCancellation = true) + { + this.propagateDeadline = propagateDeadline; + this.propagateCancellation = propagateCancellation; + } + + /// <value><c>true</c> if parent call's deadline should be propagated to the child call.</value> + public bool IsPropagateDeadline + { + get { return this.propagateDeadline; } + } + + /// <value><c>true</c> if parent call's cancellation token should be propagated to the child call.</value> + public bool IsPropagateCancellation + { + get { return this.propagateCancellation; } + } + } + + /// <summary> + /// Context propagation flags from grpc/grpc.h. + /// </summary> + [Flags] + internal enum ContextPropagationFlags + { + Deadline = 1, + CensusStatsContext = 2, + CensusTracingContext = 4, + Cancellation = 8 + } +} diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index 0d879e9b1e..055aff1444 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -21,24 +21,29 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <DefineConstants>SIGNED</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Interactive.Async"> <HintPath>..\packages\Ix-Async.1.2.3\lib\net45\System.Interactive.Async.dll</HintPath> </Reference> - <Reference Include="System.Collections.Immutable"> - <HintPath>..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> <Compile Include="AsyncDuplexStreamingCall.cs" /> @@ -47,11 +52,11 @@ <Compile Include="IServerStreamWriter.cs" /> <Compile Include="IAsyncStreamWriter.cs" /> <Compile Include="IAsyncStreamReader.cs" /> + <Compile Include="ServerPort.cs" /> <Compile Include="Version.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="RpcException.cs" /> <Compile Include="Calls.cs" /> - <Compile Include="Call.cs" /> <Compile Include="AsyncClientStreamingCall.cs" /> <Compile Include="GrpcEnvironment.cs" /> <Compile Include="Status.cs" /> @@ -72,14 +77,12 @@ <Compile Include="ServerServiceDefinition.cs" /> <Compile Include="Utils\AsyncStreamExtensions.cs" /> <Compile Include="Utils\BenchmarkUtil.cs" /> - <Compile Include="Utils\ExceptionHelper.cs" /> <Compile Include="Internal\CredentialsSafeHandle.cs" /> <Compile Include="Credentials.cs" /> <Compile Include="Internal\ChannelArgsSafeHandle.cs" /> <Compile Include="Internal\AsyncCompletion.cs" /> <Compile Include="Internal\AsyncCallBase.cs" /> <Compile Include="Internal\AsyncCallServer.cs" /> - <Compile Include="OperationFailedException.cs" /> <Compile Include="Internal\AsyncCall.cs" /> <Compile Include="Utils\Preconditions.cs" /> <Compile Include="Internal\ServerCredentialsSafeHandle.cs" /> @@ -107,6 +110,12 @@ <Compile Include="Logging\ILogger.cs" /> <Compile Include="Logging\ConsoleLogger.cs" /> <Compile Include="Internal\NativeLogRedirector.cs" /> + <Compile Include="ChannelState.cs" /> + <Compile Include="CallInvocationDetails.cs" /> + <Compile Include="CallOptions.cs" /> + <Compile Include="CompressionLevel.cs" /> + <Compile Include="WriteOptions.cs" /> + <Compile Include="ContextPropagationToken.cs" /> </ItemGroup> <ItemGroup> <None Include="Grpc.Core.nuspec" /> @@ -137,7 +146,5 @@ </Target> <Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets')" /> <Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets')" /> - <ItemGroup> - <Folder Include="Logging\" /> - </ItemGroup> + <ItemGroup /> </Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.Core/Grpc.Core.nuspec b/src/csharp/Grpc.Core/Grpc.Core.nuspec index 5ace6dcf89..fe49efc7ec 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.nuspec +++ b/src/csharp/Grpc.Core/Grpc.Core.nuspec @@ -15,15 +15,14 @@ <copyright>Copyright 2015, Google Inc.</copyright> <tags>gRPC RPC Protocol HTTP/2</tags> <dependencies> - <dependency id="System.Collections.Immutable" version="1.1.36" /> <dependency id="Ix-Async" version="1.2.3" /> <dependency id="grpc.native.csharp_ext" version="$GrpcNativeCsharpExtVersion$" /> </dependencies> </metadata> <files> - <file src="bin/Release/Grpc.Core.dll" target="lib/net45" /> - <file src="bin/Release/Grpc.Core.pdb" target="lib/net45" /> - <file src="bin/Release/Grpc.Core.xml" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Core.dll" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Core.pdb" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.Core.xml" target="lib/net45" /> <file src="**\*.cs" target="src" /> </files> </package> diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs index 034a66be3c..30d8c80235 100644 --- a/src/csharp/Grpc.Core/GrpcEnvironment.cs +++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs @@ -53,6 +53,9 @@ namespace Grpc.Core [DllImport("grpc_csharp_ext.dll")] static extern void grpcsharp_shutdown(); + [DllImport("grpc_csharp_ext.dll")] + static extern IntPtr grpcsharp_version_string(); // returns not-owned const char* + static object staticLock = new object(); static GrpcEnvironment instance; @@ -112,7 +115,7 @@ namespace Grpc.Core /// </summary> public static void SetLogger(ILogger customLogger) { - Preconditions.CheckNotNull(customLogger); + Preconditions.CheckNotNull(customLogger, "customLogger"); logger = customLogger; } @@ -164,6 +167,15 @@ namespace Grpc.Core } /// <summary> + /// Gets version of gRPC C core. + /// </summary> + internal static string GetCoreVersionString() + { + var ptr = grpcsharp_version_string(); // the pointer is not owned + return Marshal.PtrToStringAnsi(ptr); + } + + /// <summary> /// Shuts down this environment. /// </summary> private void Close() @@ -180,23 +192,5 @@ namespace Grpc.Core Logger.Info("gRPC shutdown."); } - - /// <summary> - /// Shuts down this environment asynchronously. - /// </summary> - private Task CloseAsync() - { - return Task.Run(() => - { - try - { - Close(); - } - catch (Exception e) - { - Logger.Error(e, "Error occured while shutting down GrpcEnvironment."); - } - }); - } } } diff --git a/src/csharp/Grpc.Core/IAsyncStreamReader.cs b/src/csharp/Grpc.Core/IAsyncStreamReader.cs index 371fbf27ce..c0a0674e50 100644 --- a/src/csharp/Grpc.Core/IAsyncStreamReader.cs +++ b/src/csharp/Grpc.Core/IAsyncStreamReader.cs @@ -43,7 +43,7 @@ namespace Grpc.Core /// A stream of messages to be read. /// </summary> /// <typeparam name="T"></typeparam> - public interface IAsyncStreamReader<TResponse> : IAsyncEnumerator<TResponse> + public interface IAsyncStreamReader<T> : IAsyncEnumerator<T> { // TODO(jtattermusch): consider just using IAsyncEnumerator instead of this interface. } diff --git a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs index 2000210252..4e2acb9c71 100644 --- a/src/csharp/Grpc.Core/IAsyncStreamWriter.cs +++ b/src/csharp/Grpc.Core/IAsyncStreamWriter.cs @@ -50,5 +50,13 @@ namespace Grpc.Core /// </summary> /// <param name="message">the message to be written. Cannot be null.</param> Task WriteAsync(T message); + + /// <summary> + /// Write options that will be used for the next write. + /// If null, default options will be used. + /// Once set, this property maintains its value across subsequent + /// writes. + /// <value>The write options.</value> + WriteOptions WriteOptions { get; set; } } } diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index bfcb9366a1..2c3e3d75ea 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -50,7 +50,7 @@ namespace Grpc.Core.Internal { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>(); - Channel channel; + readonly CallInvocationDetails<TRequest, TResponse> details; // Completion of a pending unary response if not null. TaskCompletionSource<TResponse> unaryResponseTcs; @@ -60,26 +60,19 @@ namespace Grpc.Core.Internal bool readObserverCompleted; // True if readObserver has already been completed. - public AsyncCall(Func<TRequest, byte[]> serializer, Func<byte[], TResponse> deserializer) : base(serializer, deserializer) + public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails) + : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer) { - } - - public void Initialize(Channel channel, CompletionQueueSafeHandle cq, string methodName, Timespec deadline) - { - this.channel = channel; - var call = channel.Handle.CreateCall(channel.CompletionRegistry, cq, methodName, channel.Target, deadline); - channel.Environment.DebugStats.ActiveClientCalls.Increment(); - InitializeInternal(call); + this.details = callDetails.WithOptions(callDetails.Options.Normalize()); + this.initialMetadataSent = true; // we always send metadata at the very beginning of the call. } // TODO: this method is not Async, so it shouldn't be in AsyncCall class, but // it is reusing fair amount of code in this class, so we are leaving it here. - // TODO: for other calls, you need to call Initialize, this methods calls initialize - // on its own, so there's a usage inconsistency. /// <summary> /// Blocking unary request - unary response call. /// </summary> - public TResponse UnaryCall(Channel channel, string methodName, TRequest msg, Metadata headers, DateTime deadline) + public TResponse UnaryCall(TRequest msg) { using (CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create()) { @@ -89,17 +82,19 @@ namespace Grpc.Core.Internal lock (myLock) { - Initialize(channel, cq, methodName, Timespec.FromDateTime(deadline)); + Preconditions.CheckState(!started); started = true; + Initialize(cq); + halfcloseRequested = true; readingDone = true; } - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { using (var ctx = BatchContextSafeHandle.Create()) { - call.StartUnary(payload, ctx, metadataArray); + call.StartUnary(ctx, payload, metadataArray, GetWriteFlagsForCall()); var ev = cq.Pluck(ctx.Handle); bool success = (ev.success != 0); @@ -114,37 +109,33 @@ namespace Grpc.Core.Internal } } - try - { - // Once the blocking call returns, the result should be available synchronously. - return unaryResponseTcs.Task.Result; - } - catch (AggregateException ae) - { - throw ExceptionHelper.UnwrapRpcException(ae); - } + // Once the blocking call returns, the result should be available synchronously. + // Note that GetAwaiter().GetResult() doesn't wrap exceptions in AggregateException. + return unaryResponseTcs.Task.GetAwaiter().GetResult(); } } /// <summary> /// Starts a unary request - unary response call. /// </summary> - public Task<TResponse> UnaryCallAsync(TRequest msg, Metadata headers, DateTime deadline) + public Task<TResponse> UnaryCallAsync(TRequest msg) { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(details.Channel.Environment.CompletionQueue); + halfcloseRequested = true; readingDone = true; byte[] payload = UnsafeSerialize(msg); unaryResponseTcs = new TaskCompletionSource<TResponse>(); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { - call.StartUnary(payload, HandleUnaryResponse, metadataArray); + call.StartUnary(HandleUnaryResponse, payload, metadataArray, GetWriteFlagsForCall()); } return unaryResponseTcs.Task; } @@ -154,17 +145,19 @@ namespace Grpc.Core.Internal /// Starts a streamed request - unary response call. /// Use StartSendMessage and StartSendCloseFromClient to stream requests. /// </summary> - public Task<TResponse> ClientStreamingCallAsync(Metadata headers, DateTime deadline) + public Task<TResponse> ClientStreamingCallAsync() { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(details.Channel.Environment.CompletionQueue); + readingDone = true; unaryResponseTcs = new TaskCompletionSource<TResponse>(); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { call.StartClientStreaming(HandleUnaryResponse, metadataArray); } @@ -176,21 +169,23 @@ namespace Grpc.Core.Internal /// <summary> /// Starts a unary request - streamed response call. /// </summary> - public void StartServerStreamingCall(TRequest msg, Metadata headers, DateTime deadline) + public void StartServerStreamingCall(TRequest msg) { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(details.Channel.Environment.CompletionQueue); + halfcloseRequested = true; halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called. byte[] payload = UnsafeSerialize(msg); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { - call.StartServerStreaming(payload, HandleFinished, metadataArray); + call.StartServerStreaming(HandleFinished, payload, metadataArray, GetWriteFlagsForCall()); } } } @@ -199,15 +194,16 @@ namespace Grpc.Core.Internal /// Starts a streaming request - streaming response call. /// Use StartSendMessage and StartSendCloseFromClient to stream requests. /// </summary> - public void StartDuplexStreamingCall(Metadata headers, DateTime deadline) + public void StartDuplexStreamingCall() { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + Initialize(details.Channel.Environment.CompletionQueue); + + using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers)) { call.StartDuplexStreaming(HandleFinished, metadataArray); } @@ -218,9 +214,9 @@ namespace Grpc.Core.Internal /// Sends a streaming request. Only one pending send action is allowed at any given time. /// completionDelegate is called when the operation finishes. /// </summary> - public void StartSendMessage(TRequest msg, AsyncCompletionDelegate<object> completionDelegate) + public void StartSendMessage(TRequest msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate) { - StartSendMessageInternal(msg, completionDelegate); + StartSendMessageInternal(msg, writeFlags, completionDelegate); } /// <summary> @@ -277,6 +273,14 @@ namespace Grpc.Core.Internal } } + public CallInvocationDetails<TRequest, TResponse> Details + { + get + { + return this.details; + } + } + /// <summary> /// On client-side, we only fire readCompletionDelegate once all messages have been read /// and status has been received. @@ -309,7 +313,38 @@ namespace Grpc.Core.Internal protected override void OnReleaseResources() { - channel.Environment.DebugStats.ActiveClientCalls.Decrement(); + details.Channel.Environment.DebugStats.ActiveClientCalls.Decrement(); + } + + private void Initialize(CompletionQueueSafeHandle cq) + { + var parentCall = details.Options.PropagationToken != null ? details.Options.PropagationToken.ParentCall : CallSafeHandle.NullInstance; + + var call = details.Channel.Handle.CreateCall(details.Channel.Environment.CompletionRegistry, + parentCall, ContextPropagationToken.DefaultMask, cq, + details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value)); + details.Channel.Environment.DebugStats.ActiveClientCalls.Increment(); + InitializeInternal(call); + RegisterCancellationCallback(); + } + + // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called. + private void RegisterCancellationCallback() + { + var token = details.Options.CancellationToken; + if (token.CanBeCanceled) + { + token.Register(() => this.Cancel()); + } + } + + /// <summary> + /// Gets WriteFlags set in callDetails.Options.WriteOptions + /// </summary> + private WriteFlags GetWriteFlagsForCall() + { + var writeOptions = details.Options.WriteOptions; + return writeOptions != null ? writeOptions.Flags : default(WriteFlags); } /// <summary> diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs index 38f2a5baeb..6ca4bbdafc 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs @@ -71,6 +71,9 @@ namespace Grpc.Core.Internal protected bool halfclosed; protected bool finished; // True if close has been received from the peer. + protected bool initialMetadataSent; + protected long streamingWritesCounter; + public AsyncCallBase(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer) { this.serializer = Preconditions.CheckNotNull(serializer); @@ -123,7 +126,7 @@ namespace Grpc.Core.Internal /// Initiates sending a message. Only one send operation can be active at a time. /// completionDelegate is invoked upon completion. /// </summary> - protected void StartSendMessageInternal(TWrite msg, AsyncCompletionDelegate<object> completionDelegate) + protected void StartSendMessageInternal(TWrite msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate) { byte[] payload = UnsafeSerialize(msg); @@ -132,8 +135,11 @@ namespace Grpc.Core.Internal Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null"); CheckSendingAllowed(); - call.StartSendMessage(payload, HandleSendFinished); + call.StartSendMessage(HandleSendFinished, payload, writeFlags, !initialMetadataSent); + sendCompletionDelegate = completionDelegate; + initialMetadataSent = true; + streamingWritesCounter++; } } @@ -287,7 +293,7 @@ namespace Grpc.Core.Internal if (!success) { - FireCompletion(origCompletionDelegate, null, new OperationFailedException("Send failed")); + FireCompletion(origCompletionDelegate, null, new InvalidOperationException("Send failed")); } else { @@ -312,7 +318,7 @@ namespace Grpc.Core.Internal if (!success) { - FireCompletion(origCompletionDelegate, null, new OperationFailedException("Halfclose failed")); + FireCompletion(origCompletionDelegate, null, new InvalidOperationException("Halfclose failed")); } else { diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs index 513902ee36..3710a65d6b 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs @@ -83,9 +83,9 @@ namespace Grpc.Core.Internal /// Sends a streaming response. Only one pending send action is allowed at any given time. /// completionDelegate is called when the operation finishes. /// </summary> - public void StartSendMessage(TResponse msg, AsyncCompletionDelegate<object> completionDelegate) + public void StartSendMessage(TResponse msg, WriteFlags writeFlags, AsyncCompletionDelegate<object> completionDelegate) { - StartSendMessageInternal(msg, completionDelegate); + StartSendMessageInternal(msg, writeFlags, completionDelegate); } /// <summary> @@ -98,6 +98,35 @@ namespace Grpc.Core.Internal } /// <summary> + /// Initiates sending a initial metadata. + /// Even though C-core allows sending metadata in parallel to sending messages, we will treat sending metadata as a send message operation + /// to make things simpler. + /// completionDelegate is invoked upon completion. + /// </summary> + public void StartSendInitialMetadata(Metadata headers, AsyncCompletionDelegate<object> completionDelegate) + { + lock (myLock) + { + Preconditions.CheckNotNull(headers, "metadata"); + Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null"); + + Preconditions.CheckState(!initialMetadataSent, "Response headers can only be sent once per call."); + Preconditions.CheckState(streamingWritesCounter == 0, "Response headers can only be sent before the first write starts."); + CheckSendingAllowed(); + + Preconditions.CheckNotNull(completionDelegate, "Completion delegate cannot be null"); + + using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + { + call.StartSendInitialMetadata(HandleSendFinished, metadataArray); + } + + this.initialMetadataSent = true; + sendCompletionDelegate = completionDelegate; + } + } + + /// <summary> /// Sends call result status, also indicating server is done with streaming responses. /// Only one pending send action is allowed at any given time. /// completionDelegate is called when the operation finishes. @@ -111,7 +140,7 @@ namespace Grpc.Core.Internal using (var metadataArray = MetadataArraySafeHandle.Create(trailers)) { - call.StartSendStatusFromServer(status, HandleHalfclosed, metadataArray); + call.StartSendStatusFromServer(HandleHalfclosed, status, metadataArray, !initialMetadataSent); } halfcloseRequested = true; readingDone = true; diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs index 714749b171..3cb01e29bd 100644 --- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs @@ -42,6 +42,8 @@ namespace Grpc.Core.Internal /// </summary> internal class CallSafeHandle : SafeHandleZeroIsInvalid { + public static readonly CallSafeHandle NullInstance = new CallSafeHandle(); + const uint GRPC_WRITE_BUFFER_HINT = 1; CompletionRegistry completionRegistry; @@ -53,7 +55,7 @@ namespace Grpc.Core.Internal [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_start_unary(CallSafeHandle call, - BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray); + BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags); [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_start_client_streaming(CallSafeHandle call, @@ -62,7 +64,7 @@ namespace Grpc.Core.Internal [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_start_server_streaming(CallSafeHandle call, BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, - MetadataArraySafeHandle metadataArray); + MetadataArraySafeHandle metadataArray, WriteFlags writeFlags); [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_start_duplex_streaming(CallSafeHandle call, @@ -70,7 +72,7 @@ namespace Grpc.Core.Internal [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_send_message(CallSafeHandle call, - BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len); + BatchContextSafeHandle ctx, byte[] send_buffer, UIntPtr send_buffer_len, WriteFlags writeFlags, bool sendEmptyInitialMetadata); [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_send_close_from_client(CallSafeHandle call, @@ -78,7 +80,7 @@ namespace Grpc.Core.Internal [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_send_status_from_server(CallSafeHandle call, - BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray); + BatchContextSafeHandle ctx, StatusCode statusCode, string statusMessage, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata); [DllImport("grpc_csharp_ext.dll")] static extern GRPCCallError grpcsharp_call_recv_message(CallSafeHandle call, @@ -89,6 +91,10 @@ namespace Grpc.Core.Internal BatchContextSafeHandle ctx); [DllImport("grpc_csharp_ext.dll")] + static extern GRPCCallError grpcsharp_call_send_initial_metadata(CallSafeHandle call, + BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray); + + [DllImport("grpc_csharp_ext.dll")] static extern CStringSafeHandle grpcsharp_call_get_peer(CallSafeHandle call); [DllImport("grpc_csharp_ext.dll")] @@ -103,17 +109,17 @@ namespace Grpc.Core.Internal this.completionRegistry = completionRegistry; } - public void StartUnary(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartUnary(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { var ctx = BatchContextSafeHandle.Create(); completionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray) + grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags) .CheckOk(); } - public void StartUnary(byte[] payload, BatchContextSafeHandle ctx, MetadataArraySafeHandle metadataArray) + public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { - grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray) + grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags) .CheckOk(); } @@ -124,11 +130,11 @@ namespace Grpc.Core.Internal grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk(); } - public void StartServerStreaming(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartServerStreaming(BatchCompletionDelegate callback, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags) { var ctx = BatchContextSafeHandle.Create(); completionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray).CheckOk(); + grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags).CheckOk(); } public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) @@ -138,11 +144,11 @@ namespace Grpc.Core.Internal grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk(); } - public void StartSendMessage(byte[] payload, BatchCompletionDelegate callback) + public void StartSendMessage(BatchCompletionDelegate callback, byte[] payload, WriteFlags writeFlags, bool sendEmptyInitialMetadata) { var ctx = BatchContextSafeHandle.Create(); completionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length)).CheckOk(); + grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length), writeFlags, sendEmptyInitialMetadata).CheckOk(); } public void StartSendCloseFromClient(BatchCompletionDelegate callback) @@ -152,11 +158,11 @@ namespace Grpc.Core.Internal grpcsharp_call_send_close_from_client(this, ctx).CheckOk(); } - public void StartSendStatusFromServer(Status status, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + public void StartSendStatusFromServer(BatchCompletionDelegate callback, Status status, MetadataArraySafeHandle metadataArray, bool sendEmptyInitialMetadata) { var ctx = BatchContextSafeHandle.Create(); completionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray).CheckOk(); + grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail, metadataArray, sendEmptyInitialMetadata).CheckOk(); } public void StartReceiveMessage(BatchCompletionDelegate callback) @@ -173,6 +179,13 @@ namespace Grpc.Core.Internal grpcsharp_call_start_serverside(this, ctx).CheckOk(); } + public void StartSendInitialMetadata(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) + { + var ctx = BatchContextSafeHandle.Create(); + completionRegistry.RegisterBatchCompletion(ctx, callback); + grpcsharp_call_send_initial_metadata(this, ctx, metadataArray).CheckOk(); + } + public void Cancel() { grpcsharp_call_cancel(this).CheckOk(); diff --git a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs index 20815efbd3..7f03bf4ea5 100644 --- a/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs @@ -47,7 +47,17 @@ namespace Grpc.Core.Internal static extern ChannelSafeHandle grpcsharp_secure_channel_create(CredentialsSafeHandle credentials, string target, ChannelArgsSafeHandle channelArgs); [DllImport("grpc_csharp_ext.dll")] - static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline); + static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline); + + [DllImport("grpc_csharp_ext.dll")] + static extern ChannelState grpcsharp_channel_check_connectivity_state(ChannelSafeHandle channel, int tryToConnect); + + [DllImport("grpc_csharp_ext.dll")] + static extern void grpcsharp_channel_watch_connectivity_state(ChannelSafeHandle channel, ChannelState lastObservedState, + Timespec deadline, CompletionQueueSafeHandle cq, BatchContextSafeHandle ctx); + + [DllImport("grpc_csharp_ext.dll")] + static extern CStringSafeHandle grpcsharp_channel_get_target(ChannelSafeHandle call); [DllImport("grpc_csharp_ext.dll")] static extern void grpcsharp_channel_destroy(IntPtr channel); @@ -66,13 +76,34 @@ namespace Grpc.Core.Internal return grpcsharp_secure_channel_create(credentials, target, channelArgs); } - public CallSafeHandle CreateCall(CompletionRegistry registry, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline) + public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline) { - var result = grpcsharp_channel_create_call(this, cq, method, host, deadline); + var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline); result.SetCompletionRegistry(registry); return result; } + public ChannelState CheckConnectivityState(bool tryToConnect) + { + return grpcsharp_channel_check_connectivity_state(this, tryToConnect ? 1 : 0); + } + + public void WatchConnectivityState(ChannelState lastObservedState, Timespec deadline, CompletionQueueSafeHandle cq, + CompletionRegistry completionRegistry, BatchCompletionDelegate callback) + { + var ctx = BatchContextSafeHandle.Create(); + completionRegistry.RegisterBatchCompletion(ctx, callback); + grpcsharp_channel_watch_connectivity_state(this, lastObservedState, deadline, cq, ctx); + } + + public string GetTarget() + { + using (var cstring = grpcsharp_channel_get_target(this)) + { + return cstring.GetValue(); + } + } + protected override bool ReleaseHandle() { grpcsharp_channel_destroy(handle); diff --git a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs index 58f493463b..013f00ff6f 100644 --- a/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs +++ b/src/csharp/Grpc.Core/Internal/ClientRequestStream.cs @@ -40,16 +40,18 @@ namespace Grpc.Core.Internal internal class ClientRequestStream<TRequest, TResponse> : IClientStreamWriter<TRequest> { readonly AsyncCall<TRequest, TResponse> call; + WriteOptions writeOptions; public ClientRequestStream(AsyncCall<TRequest, TResponse> call) { this.call = call; + this.writeOptions = call.Details.Options.WriteOptions; } public Task WriteAsync(TRequest message) { var taskSource = new AsyncCompletionTaskSource<object>(); - call.StartSendMessage(message, taskSource.CompletionDelegate); + call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate); return taskSource.Task; } @@ -59,5 +61,24 @@ namespace Grpc.Core.Internal call.StartSendCloseFromClient(taskSource.CompletionDelegate); return taskSource.Task; } + + public WriteOptions WriteOptions + { + get + { + return this.writeOptions; + } + + set + { + writeOptions = value; + } + } + + private WriteFlags GetWriteFlags() + { + var options = writeOptions; + return options != null ? options.Flags : default(WriteFlags); + } } } diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index 19f0e3c57f..688f9f6fec 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -75,7 +75,7 @@ namespace Grpc.Core.Internal var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); Status status; - var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken); + var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { Preconditions.CheckArgument(await requestStream.MoveNext()); @@ -131,7 +131,7 @@ namespace Grpc.Core.Internal var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); Status status; - var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken); + var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { Preconditions.CheckArgument(await requestStream.MoveNext()); @@ -187,7 +187,7 @@ namespace Grpc.Core.Internal var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); Status status; - var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken); + var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { var result = await handler(requestStream, context); @@ -247,7 +247,7 @@ namespace Grpc.Core.Internal var responseStream = new ServerResponseStream<TRequest, TResponse>(asyncCall); Status status; - var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, asyncCall.CancellationToken); + var context = HandlerUtils.NewContext(newRpc, asyncCall.Peer, responseStream, asyncCall.CancellationToken); try { await handler(requestStream, responseStream, context); @@ -304,13 +304,14 @@ namespace Grpc.Core.Internal return new Status(StatusCode.Unknown, "Exception was thrown by handler."); } - public static ServerCallContext NewContext(ServerRpcNew newRpc, string peer, CancellationToken cancellationToken) + public static ServerCallContext NewContext<TRequest, TResponse>(ServerRpcNew newRpc, string peer, ServerResponseStream<TRequest, TResponse> serverResponseStream, CancellationToken cancellationToken) + where TRequest : class + where TResponse : class { DateTime realtimeDeadline = newRpc.Deadline.ToClockType(GPRClockType.Realtime).ToDateTime(); - return new ServerCallContext( - newRpc.Method, newRpc.Host, peer, realtimeDeadline, - newRpc.RequestMetadata, cancellationToken); + return new ServerCallContext(newRpc.Call, newRpc.Method, newRpc.Host, peer, realtimeDeadline, + newRpc.RequestMetadata, cancellationToken, serverResponseStream.WriteResponseHeadersAsync, serverResponseStream); } } } diff --git a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs index 59238a452c..37a4f5256b 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs @@ -42,7 +42,7 @@ namespace Grpc.Core.Internal internal class ServerCredentialsSafeHandle : SafeHandleZeroIsInvalid { [DllImport("grpc_csharp_ext.dll", CharSet = CharSet.Ansi)] - static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs); + static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, bool forceClientAuth); [DllImport("grpc_csharp_ext.dll")] static extern void grpcsharp_server_credentials_release(IntPtr credentials); @@ -51,12 +51,13 @@ namespace Grpc.Core.Internal { } - public static ServerCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray) + public static ServerCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, bool forceClientAuth) { Preconditions.CheckArgument(keyCertPairCertChainArray.Length == keyCertPairPrivateKeyArray.Length); return grpcsharp_ssl_server_credentials_create(pemRootCerts, keyCertPairCertChainArray, keyCertPairPrivateKeyArray, - new UIntPtr((ulong)keyCertPairCertChainArray.Length)); + new UIntPtr((ulong)keyCertPairCertChainArray.Length), + forceClientAuth); } protected override bool ReleaseHandle() diff --git a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs index 756dcee87f..03e39efc02 100644 --- a/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs +++ b/src/csharp/Grpc.Core/Internal/ServerResponseStream.cs @@ -38,11 +38,12 @@ namespace Grpc.Core.Internal /// <summary> /// Writes responses asynchronously to an underlying AsyncCallServer object. /// </summary> - internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse> + internal class ServerResponseStream<TRequest, TResponse> : IServerStreamWriter<TResponse>, IHasWriteOptions where TRequest : class where TResponse : class { readonly AsyncCallServer<TRequest, TResponse> call; + WriteOptions writeOptions; public ServerResponseStream(AsyncCallServer<TRequest, TResponse> call) { @@ -52,7 +53,7 @@ namespace Grpc.Core.Internal public Task WriteAsync(TResponse message) { var taskSource = new AsyncCompletionTaskSource<object>(); - call.StartSendMessage(message, taskSource.CompletionDelegate); + call.StartSendMessage(message, GetWriteFlags(), taskSource.CompletionDelegate); return taskSource.Task; } @@ -62,5 +63,31 @@ namespace Grpc.Core.Internal call.StartSendStatusFromServer(status, trailers, taskSource.CompletionDelegate); return taskSource.Task; } + + public Task WriteResponseHeadersAsync(Metadata responseHeaders) + { + var taskSource = new AsyncCompletionTaskSource<object>(); + call.StartSendInitialMetadata(responseHeaders, taskSource.CompletionDelegate); + return taskSource.Task; + } + + public WriteOptions WriteOptions + { + get + { + return writeOptions; + } + + set + { + writeOptions = value; + } + } + + private WriteFlags GetWriteFlags() + { + var options = writeOptions; + return options != null ? options.Flags : default(WriteFlags); + } } } diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs index e83d21f4a4..daf85d5f61 100644 --- a/src/csharp/Grpc.Core/Internal/Timespec.cs +++ b/src/csharp/Grpc.Core/Internal/Timespec.cs @@ -211,7 +211,7 @@ namespace Grpc.Core.Internal return Timespec.InfPast; } - Preconditions.CheckArgument(dateTime.Kind == DateTimeKind.Utc, "dateTime"); + Preconditions.CheckArgument(dateTime.Kind == DateTimeKind.Utc, "dateTime needs of kind DateTimeKind.Utc or be equal to DateTime.MaxValue or DateTime.MinValue."); try { diff --git a/src/csharp/Grpc.Core/KeyCertificatePair.cs b/src/csharp/Grpc.Core/KeyCertificatePair.cs index 7cea18618e..6f691975e9 100644 --- a/src/csharp/Grpc.Core/KeyCertificatePair.cs +++ b/src/csharp/Grpc.Core/KeyCertificatePair.cs @@ -33,7 +33,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using Grpc.Core.Internal; using Grpc.Core.Utils; @@ -55,8 +54,8 @@ namespace Grpc.Core /// <param name="privateKey">PEM encoded private key.</param> public KeyCertificatePair(string certificateChain, string privateKey) { - this.certificateChain = Preconditions.CheckNotNull(certificateChain); - this.privateKey = Preconditions.CheckNotNull(privateKey); + this.certificateChain = Preconditions.CheckNotNull(certificateChain, "certificateChain"); + this.privateKey = Preconditions.CheckNotNull(privateKey, "privateKey"); } /// <summary> diff --git a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs index c67765c78d..382481d871 100644 --- a/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs +++ b/src/csharp/Grpc.Core/Logging/ConsoleLogger.cs @@ -42,16 +42,21 @@ namespace Grpc.Core.Logging readonly Type forType; readonly string forTypeString; + /// <summary>Creates a console logger not associated to any specific type.</summary> public ConsoleLogger() : this(null) { } + /// <summary>Creates a console logger that logs messsage specific for given type.</summary> private ConsoleLogger(Type forType) { this.forType = forType; this.forTypeString = forType != null ? forType.FullName + " " : ""; } - + + /// <summary> + /// Returns a logger associated with the specified type. + /// </summary> public ILogger ForType<T>() { if (typeof(T) == forType) @@ -61,31 +66,37 @@ namespace Grpc.Core.Logging return new ConsoleLogger(typeof(T)); } + /// <summary>Logs a message with severity Debug.</summary> public void Debug(string message, params object[] formatArgs) { Log("D", message, formatArgs); } + /// <summary>Logs a message with severity Info.</summary> public void Info(string message, params object[] formatArgs) { Log("I", message, formatArgs); } + /// <summary>Logs a message with severity Warning.</summary> public void Warning(string message, params object[] formatArgs) { Log("W", message, formatArgs); } + /// <summary>Logs a message and an associated exception with severity Warning.</summary> public void Warning(Exception exception, string message, params object[] formatArgs) { Log("W", message + " " + exception, formatArgs); } + /// <summary>Logs a message with severity Error.</summary> public void Error(string message, params object[] formatArgs) { Log("E", message, formatArgs); } + /// <summary>Logs a message and an associated exception with severity Error.</summary> public void Error(Exception exception, string message, params object[] formatArgs) { Log("E", message + " " + exception, formatArgs); diff --git a/src/csharp/Grpc.Core/Logging/ILogger.cs b/src/csharp/Grpc.Core/Logging/ILogger.cs index 0d58f133e3..61e0c91388 100644 --- a/src/csharp/Grpc.Core/Logging/ILogger.cs +++ b/src/csharp/Grpc.Core/Logging/ILogger.cs @@ -42,16 +42,22 @@ namespace Grpc.Core.Logging /// <summary>Returns a logger associated with the specified type.</summary> ILogger ForType<T>(); + /// <summary>Logs a message with severity Debug.</summary> void Debug(string message, params object[] formatArgs); + /// <summary>Logs a message with severity Info.</summary> void Info(string message, params object[] formatArgs); + /// <summary>Logs a message with severity Warning.</summary> void Warning(string message, params object[] formatArgs); + /// <summary>Logs a message and an associated exception with severity Warning.</summary> void Warning(Exception exception, string message, params object[] formatArgs); + /// <summary>Logs a message with severity Error.</summary> void Error(string message, params object[] formatArgs); + /// <summary>Logs a message and an associated exception with severity Error.</summary> void Error(Exception exception, string message, params object[] formatArgs); } } diff --git a/src/csharp/Grpc.Core/Marshaller.cs b/src/csharp/Grpc.Core/Marshaller.cs index 8b1a929074..f38cb0863f 100644 --- a/src/csharp/Grpc.Core/Marshaller.cs +++ b/src/csharp/Grpc.Core/Marshaller.cs @@ -37,19 +37,27 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// For serializing and deserializing messages. + /// Encapsulates the logic for serializing and deserializing messages. /// </summary> public struct Marshaller<T> { readonly Func<T, byte[]> serializer; readonly Func<byte[], T> deserializer; + /// <summary> + /// Initializes a new marshaller. + /// </summary> + /// <param name="serializer">Function that will be used to serialize messages.</param> + /// <param name="deserializer">Function that will be used to deserialize messages.</param> public Marshaller(Func<T, byte[]> serializer, Func<byte[], T> deserializer) { - this.serializer = Preconditions.CheckNotNull(serializer); - this.deserializer = Preconditions.CheckNotNull(deserializer); + this.serializer = Preconditions.CheckNotNull(serializer, "serializer"); + this.deserializer = Preconditions.CheckNotNull(deserializer, "deserializer"); } + /// <summary> + /// Gets the serializer function. + /// </summary> public Func<T, byte[]> Serializer { get @@ -58,6 +66,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the deserializer function. + /// </summary> public Func<byte[], T> Deserializer { get @@ -72,11 +83,17 @@ namespace Grpc.Core /// </summary> public static class Marshallers { + /// <summary> + /// Creates a marshaller from specified serializer and deserializer. + /// </summary> public static Marshaller<T> Create<T>(Func<T, byte[]> serializer, Func<byte[], T> deserializer) { return new Marshaller<T>(serializer, deserializer); } + /// <summary> + /// Returns a marshaller for <c>string</c> type. This is useful for testing. + /// </summary> public static Marshaller<string> StringMarshaller { get diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs index 2f308cbb11..9db2abf46e 100644 --- a/src/csharp/Grpc.Core/Metadata.cs +++ b/src/csharp/Grpc.Core/Metadata.cs @@ -32,7 +32,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Text; @@ -115,6 +114,16 @@ namespace Grpc.Core entries.Add(item); } + public void Add(string key, string value) + { + Add(new Entry(key, value)); + } + + public void Add(string key, byte[] valueBytes) + { + Add(new Entry(key, valueBytes)); + } + public void Clear() { CheckWriteable(); @@ -177,15 +186,15 @@ namespace Grpc.Core public Entry(string key, byte[] valueBytes) { - this.key = Preconditions.CheckNotNull(key); + this.key = Preconditions.CheckNotNull(key, "key"); this.value = null; - this.valueBytes = Preconditions.CheckNotNull(valueBytes); + this.valueBytes = Preconditions.CheckNotNull(valueBytes, "valueBytes"); } public Entry(string key, string value) { - this.key = Preconditions.CheckNotNull(key); - this.value = Preconditions.CheckNotNull(value); + this.key = Preconditions.CheckNotNull(key, "key"); + this.value = Preconditions.CheckNotNull(value, "value"); this.valueBytes = null; } diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs index 77d36191c3..4c208b4a26 100644 --- a/src/csharp/Grpc.Core/Method.cs +++ b/src/csharp/Grpc.Core/Method.cs @@ -41,30 +41,52 @@ namespace Grpc.Core /// </summary> public enum MethodType { - Unary, // Unary request, unary response. - ClientStreaming, // Streaming request, unary response. - ServerStreaming, // Unary request, streaming response. - DuplexStreaming // Streaming request, streaming response. + /// <summary>Single request sent from client, single response received from server.</summary> + Unary, + + /// <summary>Stream of request sent from client, single response received from server.</summary> + ClientStreaming, + + /// <summary>Single request sent from client, stream of responses received from server.</summary> + ServerStreaming, + + /// <summary>Both server and client can stream arbitrary number of requests and responses simultaneously.</summary> + DuplexStreaming } /// <summary> - /// A description of a service method. + /// A description of a remote method. /// </summary> public class Method<TRequest, TResponse> { readonly MethodType type; + readonly string serviceName; readonly string name; readonly Marshaller<TRequest> requestMarshaller; readonly Marshaller<TResponse> responseMarshaller; + readonly string fullName; - public Method(MethodType type, string name, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller) + /// <summary> + /// Initializes a new instance of the <c>Method</c> class. + /// </summary> + /// <param name="type">Type of method.</param> + /// <param name="serviceName">Name of service this method belongs to.</param> + /// <param name="name">Unqualified name of the method.</param> + /// <param name="requestMarshaller">Marshaller used for request messages.</param> + /// <param name="responseMarshaller">Marshaller used for response messages.</param> + public Method(MethodType type, string serviceName, string name, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller) { this.type = type; - this.name = name; - this.requestMarshaller = requestMarshaller; - this.responseMarshaller = responseMarshaller; + this.serviceName = Preconditions.CheckNotNull(serviceName, "serviceName"); + this.name = Preconditions.CheckNotNull(name, "name"); + this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller, "requestMarshaller"); + this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller, "responseMarshaller"); + this.fullName = GetFullName(serviceName, name); } + /// <summary> + /// Gets the type of the method. + /// </summary> public MethodType Type { get @@ -72,7 +94,21 @@ namespace Grpc.Core return this.type; } } + + /// <summary> + /// Gets the name of the service to which this method belongs. + /// </summary> + public string ServiceName + { + get + { + return this.serviceName; + } + } + /// <summary> + /// Gets the unqualified name of the method. + /// </summary> public string Name { get @@ -81,6 +117,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the marshaller used for request messages. + /// </summary> public Marshaller<TRequest> RequestMarshaller { get @@ -89,6 +128,9 @@ namespace Grpc.Core } } + /// <summary> + /// Gets the marshaller used for response messages. + /// </summary> public Marshaller<TResponse> ResponseMarshaller { get @@ -96,13 +138,25 @@ namespace Grpc.Core return this.responseMarshaller; } } + + /// <summary> + /// Gets the fully qualified name of the method. On the server side, methods are dispatched + /// based on this name. + /// </summary> + public string FullName + { + get + { + return this.fullName; + } + } /// <summary> /// Gets full name of the method including the service name. /// </summary> - internal string GetFullName(string serviceName) + internal static string GetFullName(string serviceName, string methodName) { - return "/" + Preconditions.CheckNotNull(serviceName) + "/" + this.Name; + return "/" + serviceName + "/" + methodName; } } } diff --git a/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs index 2b3d7530f2..29db85d7aa 100644 --- a/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs @@ -10,4 +10,12 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +#if SIGNED +[assembly: InternalsVisibleTo("Grpc.Core.Tests,PublicKey=" + + "00240000048000009400000006020000002400005253413100040000010001002f5797a92c6fcde81bd4098f43" + + "0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" + + "27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" + + "71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")] +#else [assembly: InternalsVisibleTo("Grpc.Core.Tests")] +#endif
\ No newline at end of file diff --git a/src/csharp/Grpc.Core/RpcException.cs b/src/csharp/Grpc.Core/RpcException.cs index c58578286b..cac417e626 100644 --- a/src/csharp/Grpc.Core/RpcException.cs +++ b/src/csharp/Grpc.Core/RpcException.cs @@ -36,22 +36,34 @@ using System; namespace Grpc.Core { /// <summary> - /// Thrown when remote procedure call fails. + /// Thrown when remote procedure call fails. Every <c>RpcException</c> is associated with a resulting <see cref="Status"/> of the call. /// </summary> public class RpcException : Exception { private readonly Status status; + /// <summary> + /// Creates a new <c>RpcException</c> associated with given status. + /// </summary> + /// <param name="status">Resulting status of a call.</param> public RpcException(Status status) : base(status.ToString()) { this.status = status; } + /// <summary> + /// Creates a new <c>RpcException</c> associated with given status and message. + /// </summary> + /// <param name="status">Resulting status of a call.</param> + /// <param name="message">The exception message.</param> public RpcException(Status status, string message) : base(message) { this.status = status; } + /// <summary> + /// Resulting status of the call. + /// </summary> public Status Status { get diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs index 3217547cc4..c76f126026 100644 --- a/src/csharp/Grpc.Core/Server.cs +++ b/src/csharp/Grpc.Core/Server.cs @@ -32,7 +32,7 @@ #endregion using System; -using System.Collections.Concurrent; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; @@ -48,18 +48,17 @@ namespace Grpc.Core /// </summary> public class Server { - /// <summary> - /// Pass this value as port to have the server choose an unused listening port for you. - /// </summary> - public const int PickUnusedPort = 0; - static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<Server>(); + readonly ServiceDefinitionCollection serviceDefinitions; + readonly ServerPortCollection ports; readonly GrpcEnvironment environment; readonly List<ChannelOption> options; readonly ServerSafeHandle handle; readonly object myLock = new object(); + readonly List<ServerServiceDefinition> serviceDefinitionsList = new List<ServerServiceDefinition>(); + readonly List<ServerPort> serverPortList = new List<ServerPort>(); readonly Dictionary<string, IServerCallHandler> callHandlers = new Dictionary<string, IServerCallHandler>(); readonly TaskCompletionSource<object> shutdownTcs = new TaskCompletionSource<object>(); @@ -72,6 +71,8 @@ namespace Grpc.Core /// <param name="options">Channel options.</param> public Server(IEnumerable<ChannelOption> options = null) { + this.serviceDefinitions = new ServiceDefinitionCollection(this); + this.ports = new ServerPortCollection(this); this.environment = GrpcEnvironment.GetInstance(); this.options = options != null ? new List<ChannelOption>(options) : new List<ChannelOption>(); using (var channelArgs = ChannelOptions.CreateChannelArgs(this.options)) @@ -81,47 +82,26 @@ namespace Grpc.Core } /// <summary> - /// Adds a service definition to the server. This is how you register - /// handlers for a service with the server. - /// Only call this before Start(). + /// Services that will be exported by the server once started. Register a service with this + /// server by adding its definition to this collection. /// </summary> - public void AddServiceDefinition(ServerServiceDefinition serviceDefinition) + public ServiceDefinitionCollection Services { - lock (myLock) + get { - Preconditions.CheckState(!startRequested); - foreach (var entry in serviceDefinition.CallHandlers) - { - callHandlers.Add(entry.Key, entry.Value); - } + return serviceDefinitions; } } /// <summary> - /// Add a port on which server should listen. - /// Only call this before Start(). + /// Ports on which the server will listen once started. Register a port with this + /// server by adding its definition to this collection. /// </summary> - /// <returns>The port on which server will be listening.</returns> - /// <param name="host">the host</param> - /// <param name="port">the port. If zero, an unused port is chosen automatically.</param> - public int AddPort(string host, int port, ServerCredentials credentials) + public ServerPortCollection Ports { - lock (myLock) + get { - Preconditions.CheckNotNull(credentials); - Preconditions.CheckState(!startRequested); - var address = string.Format("{0}:{1}", host, port); - using (var nativeCredentials = credentials.ToNativeCredentials()) - { - if (nativeCredentials != null) - { - return handle.AddSecurePort(address, nativeCredentials); - } - else - { - return handle.AddInsecurePort(address); - } - } + return ports; } } @@ -190,6 +170,50 @@ namespace Grpc.Core } /// <summary> + /// Adds a service definition. + /// </summary> + private void AddServiceDefinitionInternal(ServerServiceDefinition serviceDefinition) + { + lock (myLock) + { + Preconditions.CheckState(!startRequested); + foreach (var entry in serviceDefinition.CallHandlers) + { + callHandlers.Add(entry.Key, entry.Value); + } + serviceDefinitionsList.Add(serviceDefinition); + } + } + + /// <summary> + /// Adds a listening port. + /// </summary> + private int AddPortInternal(ServerPort serverPort) + { + lock (myLock) + { + Preconditions.CheckNotNull(serverPort.Credentials, "serverPort"); + Preconditions.CheckState(!startRequested); + var address = string.Format("{0}:{1}", serverPort.Host, serverPort.Port); + int boundPort; + using (var nativeCredentials = serverPort.Credentials.ToNativeCredentials()) + { + if (nativeCredentials != null) + { + boundPort = handle.AddSecurePort(address, nativeCredentials); + } + else + { + boundPort = handle.AddInsecurePort(address); + } + } + var newServerPort = new ServerPort(serverPort, boundPort); + this.serverPortList.Add(newServerPort); + return boundPort; + } + } + + /// <summary> /// Allows one new RPC call to be received by server. /// </summary> private void AllowOneRpc() @@ -249,5 +273,82 @@ namespace Grpc.Core { shutdownTcs.SetResult(null); } + + /// <summary> + /// Collection of service definitions. + /// </summary> + public class ServiceDefinitionCollection : IEnumerable<ServerServiceDefinition> + { + readonly Server server; + + internal ServiceDefinitionCollection(Server server) + { + this.server = server; + } + + /// <summary> + /// Adds a service definition to the server. This is how you register + /// handlers for a service with the server. Only call this before Start(). + /// </summary> + public void Add(ServerServiceDefinition serviceDefinition) + { + server.AddServiceDefinitionInternal(serviceDefinition); + } + + public IEnumerator<ServerServiceDefinition> GetEnumerator() + { + return server.serviceDefinitionsList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return server.serviceDefinitionsList.GetEnumerator(); + } + } + + /// <summary> + /// Collection of server ports. + /// </summary> + public class ServerPortCollection : IEnumerable<ServerPort> + { + readonly Server server; + + internal ServerPortCollection(Server server) + { + this.server = server; + } + + /// <summary> + /// Adds a new port on which server should listen. + /// Only call this before Start(). + /// <returns>The port on which server will be listening.</returns> + /// </summary> + public int Add(ServerPort serverPort) + { + return server.AddPortInternal(serverPort); + } + + /// <summary> + /// Adds a new port on which server should listen. + /// <returns>The port on which server will be listening.</returns> + /// </summary> + /// <param name="host">the host</param> + /// <param name="port">the port. If zero, an unused port is chosen automatically.</param> + /// <param name="credentials">credentials to use to secure this port.</param> + public int Add(string host, int port, ServerCredentials credentials) + { + return Add(new ServerPort(host, port, credentials)); + } + + public IEnumerator<ServerPort> GetEnumerator() + { + return server.serverPortList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return server.serverPortList.GetEnumerator(); + } + } } } diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs index 0c48adaea5..75d81c64f3 100644 --- a/src/csharp/Grpc.Core/ServerCallContext.cs +++ b/src/csharp/Grpc.Core/ServerCallContext.cs @@ -36,15 +36,16 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Grpc.Core.Internal; + namespace Grpc.Core { /// <summary> /// Context for a server-side call. /// </summary> - public sealed class ServerCallContext + public class ServerCallContext { - // TODO(jtattermusch): expose method to send initial metadata back to client - + private readonly CallSafeHandle callHandle; private readonly string method; private readonly string host; private readonly string peer; @@ -54,18 +55,37 @@ namespace Grpc.Core private readonly Metadata responseTrailers = new Metadata(); private Status status = Status.DefaultSuccess; + private Func<Metadata, Task> writeHeadersFunc; + private IHasWriteOptions writeOptionsHolder; - public ServerCallContext(string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken) + internal ServerCallContext(CallSafeHandle callHandle, string method, string host, string peer, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken, + Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder) { + this.callHandle = callHandle; this.method = method; this.host = host; this.peer = peer; this.deadline = deadline; this.requestHeaders = requestHeaders; this.cancellationToken = cancellationToken; + this.writeHeadersFunc = writeHeadersFunc; + this.writeOptionsHolder = writeOptionsHolder; + } + + public Task WriteResponseHeadersAsync(Metadata responseHeaders) + { + return writeHeadersFunc(responseHeaders); + } + + /// <summary> + /// Creates a propagation token to be used to propagate call context to a child call. + /// </summary> + public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null) + { + return new ContextPropagationToken(callHandle, deadline, cancellationToken, options); } - /// <summary> Name of method called in this RPC. </summary> + /// <summary>Name of method called in this RPC.</summary> public string Method { get @@ -74,7 +94,7 @@ namespace Grpc.Core } } - /// <summary> Name of host called in this RPC. </summary> + /// <summary>Name of host called in this RPC.</summary> public string Host { get @@ -83,7 +103,7 @@ namespace Grpc.Core } } - /// <summary> Address of the remote endpoint in URI format. </summary> + /// <summary>Address of the remote endpoint in URI format.</summary> public string Peer { get @@ -92,7 +112,7 @@ namespace Grpc.Core } } - /// <summary> Deadline for this RPC. </summary> + /// <summary>Deadline for this RPC.</summary> public DateTime Deadline { get @@ -101,7 +121,7 @@ namespace Grpc.Core } } - /// <summary> Initial metadata sent by client. </summary> + /// <summary>Initial metadata sent by client.</summary> public Metadata RequestHeaders { get @@ -110,8 +130,7 @@ namespace Grpc.Core } } - // TODO(jtattermusch): support signalling cancellation. - /// <summary> Cancellation token signals when call is cancelled. </summary> + /// <summary>Cancellation token signals when call is cancelled.</summary> public CancellationToken CancellationToken { get @@ -120,7 +139,7 @@ namespace Grpc.Core } } - /// <summary> Trailers to send back to client after RPC finishes.</summary> + /// <summary>Trailers to send back to client after RPC finishes.</summary> public Metadata ResponseTrailers { get @@ -142,5 +161,31 @@ namespace Grpc.Core status = value; } } + + /// <summary> + /// Allows setting write options for the following write. + /// For streaming response calls, this property is also exposed as on IServerStreamWriter for convenience. + /// Both properties are backed by the same underlying value. + /// </summary> + public WriteOptions WriteOptions + { + get + { + return writeOptionsHolder.WriteOptions; + } + + set + { + writeOptionsHolder.WriteOptions = value; + } + } + } + + /// <summary> + /// Allows sharing write options between ServerCallContext and other objects. + /// </summary> + public interface IHasWriteOptions + { + WriteOptions WriteOptions { get; set; } } } diff --git a/src/csharp/Grpc.Core/ServerCredentials.cs b/src/csharp/Grpc.Core/ServerCredentials.cs index 32ed4b78a1..3c6703d30e 100644 --- a/src/csharp/Grpc.Core/ServerCredentials.cs +++ b/src/csharp/Grpc.Core/ServerCredentials.cs @@ -33,7 +33,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using Grpc.Core.Internal; using Grpc.Core.Utils; @@ -80,18 +79,26 @@ namespace Grpc.Core { readonly IList<KeyCertificatePair> keyCertificatePairs; readonly string rootCertificates; + readonly bool forceClientAuth; /// <summary> /// Creates server-side SSL credentials. /// </summary> - /// <param name="rootCertificates">PEM encoded client root certificates used to authenticate client.</param> /// <param name="keyCertificatePairs">Key-certificates to use.</param> - public SslServerCredentials(IEnumerable<KeyCertificatePair> keyCertificatePairs, string rootCertificates) + /// <param name="rootCertificates">PEM encoded client root certificates used to authenticate client.</param> + /// <param name="forceClientAuth">If true, client will be rejected unless it proves its unthenticity using against rootCertificates.</param> + public SslServerCredentials(IEnumerable<KeyCertificatePair> keyCertificatePairs, string rootCertificates, bool forceClientAuth) { this.keyCertificatePairs = new List<KeyCertificatePair>(keyCertificatePairs).AsReadOnly(); Preconditions.CheckArgument(this.keyCertificatePairs.Count > 0, - "At least one KeyCertificatePair needs to be provided"); + "At least one KeyCertificatePair needs to be provided."); + if (forceClientAuth) + { + Preconditions.CheckNotNull(rootCertificates, + "Cannot force client authentication unless you provide rootCertificates."); + } this.rootCertificates = rootCertificates; + this.forceClientAuth = forceClientAuth; } /// <summary> @@ -100,7 +107,7 @@ namespace Grpc.Core /// using client root certificates. /// </summary> /// <param name="keyCertificatePairs">Key-certificates to use.</param> - public SslServerCredentials(IEnumerable<KeyCertificatePair> keyCertificatePairs) : this(keyCertificatePairs, null) + public SslServerCredentials(IEnumerable<KeyCertificatePair> keyCertificatePairs) : this(keyCertificatePairs, null, false) { } @@ -126,6 +133,17 @@ namespace Grpc.Core } } + /// <summary> + /// If true, the authenticity of client check will be enforced. + /// </summary> + public bool ForceClientAuthentication + { + get + { + return this.forceClientAuth; + } + } + internal override ServerCredentialsSafeHandle ToNativeCredentials() { int count = keyCertificatePairs.Count; @@ -136,7 +154,7 @@ namespace Grpc.Core certChains[i] = keyCertificatePairs[i].CertificateChain; keys[i] = keyCertificatePairs[i].PrivateKey; } - return ServerCredentialsSafeHandle.CreateSslCredentials(rootCertificates, certChains, keys); + return ServerCredentialsSafeHandle.CreateSslCredentials(rootCertificates, certChains, keys, forceClientAuth); } } } diff --git a/src/csharp/Grpc.Core/ServerMethods.cs b/src/csharp/Grpc.Core/ServerMethods.cs index d457770203..1f119a80ff 100644 --- a/src/csharp/Grpc.Core/ServerMethods.cs +++ b/src/csharp/Grpc.Core/ServerMethods.cs @@ -31,12 +31,8 @@ #endregion -using System; -using System.Threading; using System.Threading.Tasks; -using Grpc.Core.Internal; - namespace Grpc.Core { /// <summary> diff --git a/src/csharp/Grpc.Core/Call.cs b/src/csharp/Grpc.Core/ServerPort.cs index 94c5e26082..598404d045 100644 --- a/src/csharp/Grpc.Core/Call.cs +++ b/src/csharp/Grpc.Core/ServerPort.cs @@ -32,89 +32,88 @@ #endregion using System; -using Grpc.Core.Internal; + using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// Abstraction of a call to be invoked on a client. + /// A port exposed by a server. /// </summary> - public class Call<TRequest, TResponse> + public class ServerPort { - readonly string name; - readonly Marshaller<TRequest> requestMarshaller; - readonly Marshaller<TResponse> responseMarshaller; - readonly Channel channel; - readonly Metadata headers; - readonly DateTime deadline; - - public Call(string serviceName, Method<TRequest, TResponse> method, Channel channel, Metadata headers) - : this(serviceName, method, channel, headers, DateTime.MaxValue) - { - } + /// <summary> + /// Pass this value as port to have the server choose an unused listening port for you. + /// Ports added to a server will contain the bound port in their <see cref="BoundPort"/> property. + /// </summary> + public const int PickUnused = 0; - public Call(string serviceName, Method<TRequest, TResponse> method, Channel channel, Metadata headers, DateTime deadline) - { - this.name = method.GetFullName(serviceName); - this.requestMarshaller = method.RequestMarshaller; - this.responseMarshaller = method.ResponseMarshaller; - this.channel = Preconditions.CheckNotNull(channel); - this.headers = Preconditions.CheckNotNull(headers); - this.deadline = deadline; - } + readonly string host; + readonly int port; + readonly ServerCredentials credentials; + readonly int boundPort; - public Channel Channel + /// <summary> + /// Creates a new port on which server should listen. + /// </summary> + /// <returns>The port on which server will be listening.</returns> + /// <param name="host">the host</param> + /// <param name="port">the port. If zero, an unused port is chosen automatically.</param> + /// <param name="credentials">credentials to use to secure this port.</param> + public ServerPort(string host, int port, ServerCredentials credentials) { - get - { - return this.channel; - } + this.host = Preconditions.CheckNotNull(host, "host"); + this.port = port; + this.credentials = Preconditions.CheckNotNull(credentials, "credentials"); } /// <summary> - /// Full methods name including the service name. + /// Creates a port from an existing <c>ServerPort</c> instance and boundPort value. /// </summary> - public string Name + internal ServerPort(ServerPort serverPort, int boundPort) { - get - { - return name; - } + this.host = serverPort.host; + this.port = serverPort.port; + this.credentials = serverPort.credentials; + this.boundPort = boundPort; } - /// <summary> - /// Headers to send at the beginning of the call. - /// </summary> - public Metadata Headers + /// <value>The host.</value> + public string Host { get { - return headers; + return host; } } - public DateTime Deadline + /// <value>The port.</value> + public int Port { get { - return this.deadline; + return port; } } - public Marshaller<TRequest> RequestMarshaller + /// <value>The server credentials.</value> + public ServerCredentials Credentials { get { - return requestMarshaller; + return credentials; } } - public Marshaller<TResponse> ResponseMarshaller + /// <value> + /// The port actually bound by the server. This is useful if you let server + /// pick port automatically. <see cref="PickUnused"/> + /// </value> + public int BoundPort { get { - return responseMarshaller; + return boundPort; } } } diff --git a/src/csharp/Grpc.Core/ServerServiceDefinition.cs b/src/csharp/Grpc.Core/ServerServiceDefinition.cs index b180186c12..94b0a320c3 100644 --- a/src/csharp/Grpc.Core/ServerServiceDefinition.cs +++ b/src/csharp/Grpc.Core/ServerServiceDefinition.cs @@ -33,7 +33,7 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.ObjectModel; using Grpc.Core.Internal; namespace Grpc.Core @@ -43,14 +43,14 @@ namespace Grpc.Core /// </summary> public class ServerServiceDefinition { - readonly ImmutableDictionary<string, IServerCallHandler> callHandlers; + readonly ReadOnlyDictionary<string, IServerCallHandler> callHandlers; - private ServerServiceDefinition(ImmutableDictionary<string, IServerCallHandler> callHandlers) + private ServerServiceDefinition(Dictionary<string, IServerCallHandler> callHandlers) { - this.callHandlers = callHandlers; + this.callHandlers = new ReadOnlyDictionary<string, IServerCallHandler>(callHandlers); } - internal ImmutableDictionary<string, IServerCallHandler> CallHandlers + internal IDictionary<string, IServerCallHandler> CallHandlers { get { @@ -79,7 +79,7 @@ namespace Grpc.Core where TRequest : class where TResponse : class { - callHandlers.Add(method.GetFullName(serviceName), ServerCalls.UnaryCall(method, handler)); + callHandlers.Add(method.FullName, ServerCalls.UnaryCall(method, handler)); return this; } @@ -89,7 +89,7 @@ namespace Grpc.Core where TRequest : class where TResponse : class { - callHandlers.Add(method.GetFullName(serviceName), ServerCalls.ClientStreamingCall(method, handler)); + callHandlers.Add(method.FullName, ServerCalls.ClientStreamingCall(method, handler)); return this; } @@ -99,7 +99,7 @@ namespace Grpc.Core where TRequest : class where TResponse : class { - callHandlers.Add(method.GetFullName(serviceName), ServerCalls.ServerStreamingCall(method, handler)); + callHandlers.Add(method.FullName, ServerCalls.ServerStreamingCall(method, handler)); return this; } @@ -109,13 +109,13 @@ namespace Grpc.Core where TRequest : class where TResponse : class { - callHandlers.Add(method.GetFullName(serviceName), ServerCalls.DuplexStreamingCall(method, handler)); + callHandlers.Add(method.FullName, ServerCalls.DuplexStreamingCall(method, handler)); return this; } public ServerServiceDefinition Build() { - return new ServerServiceDefinition(callHandlers.ToImmutableDictionary()); + return new ServerServiceDefinition(callHandlers); } } } diff --git a/src/csharp/Grpc.Core/Status.cs b/src/csharp/Grpc.Core/Status.cs index 754f6cb3ca..6bd8dc820b 100644 --- a/src/csharp/Grpc.Core/Status.cs +++ b/src/csharp/Grpc.Core/Status.cs @@ -29,13 +29,12 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion -using System; -using System.Runtime.InteropServices; +using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// Represents RPC result. + /// Represents RPC result, which consists of <see cref="StatusCode"/> and an optional detail string. /// </summary> public struct Status { @@ -52,6 +51,11 @@ namespace Grpc.Core readonly StatusCode statusCode; readonly string detail; + /// <summary> + /// Creates a new instance of <c>Status</c>. + /// </summary> + /// <param name="statusCode">Status code.</param> + /// <param name="detail">Detail.</param> public Status(StatusCode statusCode, string detail) { this.statusCode = statusCode; @@ -80,6 +84,9 @@ namespace Grpc.Core } } + /// <summary> + /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Status"/>. + /// </summary> public override string ToString() { return string.Format("Status(StatusCode={0}, Detail=\"{1}\")", statusCode, detail); diff --git a/src/csharp/Grpc.Core/StatusCode.cs b/src/csharp/Grpc.Core/StatusCode.cs index a9696fa469..90606955af 100644 --- a/src/csharp/Grpc.Core/StatusCode.cs +++ b/src/csharp/Grpc.Core/StatusCode.cs @@ -31,8 +31,6 @@ #endregion -using System; - namespace Grpc.Core { /// <summary> @@ -41,101 +39,101 @@ namespace Grpc.Core /// </summary> public enum StatusCode { - /* Not an error; returned on success */ + /// <summary>Not an error; returned on success.</summary> OK = 0, - /* The operation was cancelled (typically by the caller). */ + + /// <summary>The operation was cancelled (typically by the caller).</summary> Cancelled = 1, - /* Unknown error. An example of where this error may be returned is - if a Status value received from another address space belongs to - an error-space that is not known in this address space. Also - errors raised by APIs that do not return enough error information - may be converted to this error. */ + + /// <summary> + /// Unknown error. An example of where this error may be returned is + /// if a Status value received from another address space belongs to + /// an error-space that is not known in this address space. Also + /// errors raised by APIs that do not return enough error information + /// may be converted to this error. + /// </summary> Unknown = 2, - /* Client specified an invalid argument. Note that this differs - from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments - that are problematic regardless of the state of the system - (e.g., a malformed file name). */ + + /// <summary> + /// Client specified an invalid argument. Note that this differs + /// from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments + /// that are problematic regardless of the state of the system + /// (e.g., a malformed file name). + /// </summary> InvalidArgument = 3, - /* Deadline expired before operation could complete. For operations - that change the state of the system, this error may be returned - even if the operation has completed successfully. For example, a - successful response from a server could have been delayed long - enough for the deadline to expire. */ + + /// <summary> + /// Deadline expired before operation could complete. For operations + /// that change the state of the system, this error may be returned + /// even if the operation has completed successfully. For example, a + /// successful response from a server could have been delayed long + /// enough for the deadline to expire. + /// </summary> DeadlineExceeded = 4, - /* Some requested entity (e.g., file or directory) was not found. */ + + /// <summary>Some requested entity (e.g., file or directory) was not found.</summary> NotFound = 5, - /* Some entity that we attempted to create (e.g., file or directory) - already exists. */ + + /// <summary>Some entity that we attempted to create (e.g., file or directory) already exists.</summary> AlreadyExists = 6, - /* The caller does not have permission to execute the specified - operation. PERMISSION_DENIED must not be used for rejections - caused by exhausting some resource (use RESOURCE_EXHAUSTED - instead for those errors). PERMISSION_DENIED must not be - used if the caller can not be identified (use UNAUTHENTICATED - instead for those errors). */ + + /// <summary> + /// The caller does not have permission to execute the specified + /// operation. PERMISSION_DENIED must not be used for rejections + /// caused by exhausting some resource (use RESOURCE_EXHAUSTED + /// instead for those errors). PERMISSION_DENIED must not be + /// used if the caller can not be identified (use UNAUTHENTICATED + /// instead for those errors). + /// </summary> PermissionDenied = 7, - /* The request does not have valid authentication credentials for the - operation. */ + + /// <summary>The request does not have valid authentication credentials for the operation.</summary> Unauthenticated = 16, - /* Some resource has been exhausted, perhaps a per-user quota, or - perhaps the entire file system is out of space. */ + + /// <summary> + /// Some resource has been exhausted, perhaps a per-user quota, or + /// perhaps the entire file system is out of space. + /// </summary> ResourceExhausted = 8, - /* Operation was rejected because the system is not in a state - required for the operation's execution. For example, directory - to be deleted may be non-empty, an rmdir operation is applied to - a non-directory, etc. - - A litmus test that may help a service implementor in deciding - between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: - (a) Use UNAVAILABLE if the client can retry just the failing call. - (b) Use ABORTED if the client should retry at a higher-level - (e.g., restarting a read-modify-write sequence). - (c) Use FAILED_PRECONDITION if the client should not retry until - the system state has been explicitly fixed. E.g., if an "rmdir" - fails because the directory is non-empty, FAILED_PRECONDITION - should be returned since the client should not retry unless - they have first fixed up the directory by deleting files from it. - (d) Use FAILED_PRECONDITION if the client performs conditional - REST Get/Update/Delete on a resource and the resource on the - server does not match the condition. E.g., conflicting - read-modify-write on the same resource. */ + + /// <summary> + /// Operation was rejected because the system is not in a state + /// required for the operation's execution. For example, directory + /// to be deleted may be non-empty, an rmdir operation is applied to + /// a non-directory, etc. + /// </summary> FailedPrecondition = 9, - /* The operation was aborted, typically due to a concurrency issue - like sequencer check failures, transaction aborts, etc. - See litmus test above for deciding between FAILED_PRECONDITION, - ABORTED, and UNAVAILABLE. */ + /// <summary> + /// The operation was aborted, typically due to a concurrency issue + /// like sequencer check failures, transaction aborts, etc. + /// </summary> Aborted = 10, - /* Operation was attempted past the valid range. E.g., seeking or - reading past end of file. - - Unlike INVALID_ARGUMENT, this error indicates a problem that may - be fixed if the system state changes. For example, a 32-bit file - system will generate INVALID_ARGUMENT if asked to read at an - offset that is not in the range [0,2^32-1], but it will generate - OUT_OF_RANGE if asked to read from an offset past the current - file size. - - There is a fair bit of overlap between FAILED_PRECONDITION and - OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific - error) when it applies so that callers who are iterating through - a space can easily look for an OUT_OF_RANGE error to detect when - they are done. */ + + /// <summary> + /// Operation was attempted past the valid range. E.g., seeking or + /// reading past end of file. + /// </summary> OutOfRange = 11, - /* Operation is not implemented or not supported/enabled in this service. */ + + /// <summary>Operation is not implemented or not supported/enabled in this service.</summary> Unimplemented = 12, - /* Internal errors. Means some invariants expected by underlying - system has been broken. If you see one of these errors, - something is very broken. */ + + /// <summary> + /// Internal errors. Means some invariants expected by underlying + /// system has been broken. If you see one of these errors, + /// something is very broken. + /// </summary> Internal = 13, - /* The service is currently unavailable. This is a most likely a - transient condition and may be corrected by retrying with - a backoff. - See litmus test above for deciding between FAILED_PRECONDITION, - ABORTED, and UNAVAILABLE. */ + /// <summary> + /// The service is currently unavailable. This is a most likely a + /// transient condition and may be corrected by retrying with + /// a backoff. + /// </summary> Unavailable = 14, - /* Unrecoverable data loss or corruption. */ + + /// <summary>Unrecoverable data loss or corruption.</summary> DataLoss = 15 } } diff --git a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs index 8a748b45a8..cdf1e51026 100644 --- a/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs +++ b/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs @@ -33,7 +33,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Grpc.Core.Utils @@ -46,7 +45,7 @@ namespace Grpc.Core.Utils /// <summary> /// Reads the entire stream and executes an async action for each element. /// </summary> - public static async Task ForEach<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction) + public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction) where T : class { while (await streamReader.MoveNext()) @@ -58,7 +57,7 @@ namespace Grpc.Core.Utils /// <summary> /// Reads the entire stream and creates a list containing all the elements read. /// </summary> - public static async Task<List<T>> ToList<T>(this IAsyncStreamReader<T> streamReader) + public static async Task<List<T>> ToListAsync<T>(this IAsyncStreamReader<T> streamReader) where T : class { var result = new List<T>(); @@ -73,7 +72,7 @@ namespace Grpc.Core.Utils /// Writes all elements from given enumerable to the stream. /// Completes the stream afterwards unless close = false. /// </summary> - public static async Task WriteAll<T>(this IClientStreamWriter<T> streamWriter, IEnumerable<T> elements, bool complete = true) + public static async Task WriteAllAsync<T>(this IClientStreamWriter<T> streamWriter, IEnumerable<T> elements, bool complete = true) where T : class { foreach (var element in elements) @@ -89,7 +88,7 @@ namespace Grpc.Core.Utils /// <summary> /// Writes all elements from given enumerable to the stream. /// </summary> - public static async Task WriteAll<T>(this IServerStreamWriter<T> streamWriter, IEnumerable<T> elements) + public static async Task WriteAllAsync<T>(this IServerStreamWriter<T> streamWriter, IEnumerable<T> elements) where T : class { foreach (var element in elements) diff --git a/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs b/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs index 82653c3a1f..eb3a5b16e3 100644 --- a/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs +++ b/src/csharp/Grpc.Core/Utils/BenchmarkUtil.cs @@ -39,6 +39,9 @@ using System.Threading.Tasks; namespace Grpc.Core.Utils { + /// <summary> + /// Utility methods to run microbenchmarks. + /// </summary> public static class BenchmarkUtil { /// <summary> diff --git a/src/csharp/Grpc.Core/Utils/Preconditions.cs b/src/csharp/Grpc.Core/Utils/Preconditions.cs index aeb5d210a7..374262f87a 100644 --- a/src/csharp/Grpc.Core/Utils/Preconditions.cs +++ b/src/csharp/Grpc.Core/Utils/Preconditions.cs @@ -32,17 +32,16 @@ #endregion using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; namespace Grpc.Core.Utils { + /// <summary> + /// Utility methods to simplify checking preconditions in the code. + /// </summary> public static class Preconditions { /// <summary> - /// Throws ArgumentException if condition is false. + /// Throws <see cref="ArgumentException"/> if condition is false. /// </summary> public static void CheckArgument(bool condition) { @@ -53,7 +52,7 @@ namespace Grpc.Core.Utils } /// <summary> - /// Throws ArgumentException with given message if condition is false. + /// Throws <see cref="ArgumentException"/> with given message if condition is false. /// </summary> public static void CheckArgument(bool condition, string errorMessage) { @@ -64,31 +63,31 @@ namespace Grpc.Core.Utils } /// <summary> - /// Throws NullReferenceException if reference is null. + /// Throws <see cref="ArgumentNullException"/> if reference is null. /// </summary> public static T CheckNotNull<T>(T reference) { if (reference == null) { - throw new NullReferenceException(); + throw new ArgumentNullException(); } return reference; } /// <summary> - /// Throws NullReferenceException with given message if reference is null. + /// Throws <see cref="ArgumentNullException"/> if reference is null. /// </summary> - public static T CheckNotNull<T>(T reference, string errorMessage) + public static T CheckNotNull<T>(T reference, string paramName) { if (reference == null) { - throw new NullReferenceException(errorMessage); + throw new ArgumentNullException(paramName); } return reference; } /// <summary> - /// Throws InvalidOperationException if condition is false. + /// Throws <see cref="InvalidOperationException"/> if condition is false. /// </summary> public static void CheckState(bool condition) { @@ -99,7 +98,7 @@ namespace Grpc.Core.Utils } /// <summary> - /// Throws InvalidOperationException with given message if condition is false. + /// Throws <see cref="InvalidOperationException"/> with given message if condition is false. /// </summary> public static void CheckState(bool condition, string errorMessage) { diff --git a/src/csharp/Grpc.Core/Version.cs b/src/csharp/Grpc.Core/Version.cs index b5cb652945..d02b301cac 100644 --- a/src/csharp/Grpc.Core/Version.cs +++ b/src/csharp/Grpc.Core/Version.cs @@ -1,5 +1,37 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + using System.Reflection; -using System.Runtime.CompilerServices; // The current version of gRPC C#. -[assembly: AssemblyVersion(Grpc.Core.VersionInfo.CurrentVersion + ".*")] +[assembly: AssemblyVersion(Grpc.Core.VersionInfo.CurrentVersion + ".0")] diff --git a/src/csharp/Grpc.Core/VersionInfo.cs b/src/csharp/Grpc.Core/VersionInfo.cs index 656a3d47bb..b6dbd3b49c 100644 --- a/src/csharp/Grpc.Core/VersionInfo.cs +++ b/src/csharp/Grpc.Core/VersionInfo.cs @@ -1,13 +1,46 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion namespace Grpc.Core { + /// <summary> + /// Provides info about current version of gRPC. + /// </summary> public static class VersionInfo { /// <summary> /// Current version of gRPC /// </summary> - public const string CurrentVersion = "0.6.0"; + public const string CurrentVersion = "0.6.1"; } } diff --git a/src/csharp/Grpc.Core/WriteOptions.cs b/src/csharp/Grpc.Core/WriteOptions.cs new file mode 100644 index 0000000000..7ef3189d76 --- /dev/null +++ b/src/csharp/Grpc.Core/WriteOptions.cs @@ -0,0 +1,82 @@ +#region Copyright notice and license + +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#endregion + +using System; + +namespace Grpc.Core +{ + /// <summary> + /// Flags for write operations. + /// </summary> + [Flags] + public enum WriteFlags + { + /// <summary> + /// Hint that the write may be buffered and need not go out on the wire immediately. + /// gRPC is free to buffer the message until the next non-buffered + /// write, or until write stream completion, but it need not buffer completely or at all. + /// </summary> + BufferHint = 0x1, + + /// <summary> + /// Force compression to be disabled for a particular write. + /// </summary> + NoCompress = 0x2 + } + + /// <summary> + /// Options for write operations. + /// </summary> + public class WriteOptions + { + /// <summary> + /// Default write options. + /// </summary> + public static readonly WriteOptions Default = new WriteOptions(); + + private WriteFlags flags; + + public WriteOptions(WriteFlags flags = default(WriteFlags)) + { + this.flags = flags; + } + + public WriteFlags Flags + { + get + { + return this.flags; + } + } + } +} diff --git a/src/csharp/Grpc.Core/packages.config b/src/csharp/Grpc.Core/packages.config index 6cdcdf2656..9b12b9b096 100644 --- a/src/csharp/Grpc.Core/packages.config +++ b/src/csharp/Grpc.Core/packages.config @@ -3,5 +3,4 @@ <package id="grpc.dependencies.openssl.redist" version="1.0.2.2" targetFramework="net45" /> <package id="grpc.dependencies.zlib.redist" version="1.2.8.9" targetFramework="net45" /> <package id="Ix-Async" version="1.2.3" targetFramework="net45" /> - <package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj b/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj index 85996a570c..b603e3af3c 100644 --- a/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj +++ b/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj @@ -19,17 +19,22 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> - <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> - <PlatformTarget>AnyCPU</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj b/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj index 6c8856cc92..5f74b58773 100644 --- a/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj +++ b/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj @@ -19,17 +19,22 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> - <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> - <PlatformTarget>AnyCPU</PlatformTarget> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/src/csharp/Grpc.Examples.MathServer/MathServer.cs b/src/csharp/Grpc.Examples.MathServer/MathServer.cs index 468eefbe3e..5f7e717b0c 100644 --- a/src/csharp/Grpc.Examples.MathServer/MathServer.cs +++ b/src/csharp/Grpc.Examples.MathServer/MathServer.cs @@ -38,16 +38,19 @@ namespace math { class MainClass { + const string Host = "0.0.0.0"; + const int Port = 23456; + public static void Main(string[] args) { - string host = "0.0.0.0"; - - Server server = new Server(); - server.AddServiceDefinition(Math.BindService(new MathServiceImpl())); - int port = server.AddPort(host, 23456, ServerCredentials.Insecure); + Server server = new Server + { + Services = { Math.BindService(new MathServiceImpl()) }, + Ports = { { Host, Port, ServerCredentials.Insecure } } + }; server.Start(); - Console.WriteLine("MathServer listening on port " + port); + Console.WriteLine("MathServer listening on port " + Port); Console.WriteLine("Press any key to stop the server..."); Console.ReadKey(); diff --git a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj index d59d7515d1..9a8f780b24 100644 --- a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj +++ b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj @@ -19,15 +19,22 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="nunit.framework"> diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs index 26f332c1cf..fdef950f09 100644 --- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs +++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Grpc.Core; @@ -46,7 +47,7 @@ namespace math.Tests /// </summary> public class MathClientServerTest { - string host = "localhost"; + const string Host = "localhost"; Server server; Channel channel; Math.MathClient client; @@ -54,19 +55,14 @@ namespace math.Tests [TestFixtureSetUp] public void Init() { - server = new Server(); - server.AddServiceDefinition(Math.BindService(new MathServiceImpl())); - int port = server.AddPort(host, Server.PickUnusedPort, ServerCredentials.Insecure); - server.Start(); - channel = new Channel(host, port, Credentials.Insecure); - client = Math.NewClient(channel); - - // TODO(jtattermusch): get rid of the custom header here once we have dedicated tests - // for header support. - client.HeaderInterceptor = (metadata) => + server = new Server { - metadata.Add(new Metadata.Entry("customHeader", "abcdef")); + Services = { Math.BindService(new MathServiceImpl()) }, + Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } }; + server.Start(); + channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure); + client = Math.NewClient(channel); } [TestFixtureTearDown] @@ -96,15 +92,8 @@ namespace math.Tests [Test] public void DivByZero() { - try - { - DivReply response = client.Div(new DivArgs.Builder { Dividend = 0, Divisor = 0 }.Build()); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); - } + var ex = Assert.Throws<RpcException>(() => client.Div(new DivArgs.Builder { Dividend = 0, Divisor = 0 }.Build())); + Assert.AreEqual(StatusCode.Unknown, ex.Status.StatusCode); } [Test] @@ -120,7 +109,7 @@ namespace math.Tests { using (var call = client.Fib(new FibArgs.Builder { Limit = 6 }.Build())) { - var responses = await call.ResponseStream.ToList(); + var responses = await call.ResponseStream.ToListAsync(); CollectionAssert.AreEqual(new List<long> { 1, 1, 2, 3, 5, 8 }, responses.ConvertAll((n) => n.Num_)); } @@ -162,15 +151,10 @@ namespace math.Tests using (var call = client.Fib(new FibArgs.Builder { Limit = 0 }.Build(), deadline: DateTime.UtcNow.AddMilliseconds(500))) { - try - { - await call.ResponseStream.ToList(); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.DeadlineExceeded, e.Status.StatusCode); - } + var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.ToListAsync()); + + // We can't guarantee the status code always DeadlineExceeded. See issue #2685. + Assert.Contains(ex.Status.StatusCode, new[] { StatusCode.DeadlineExceeded, StatusCode.Internal }); } } @@ -183,7 +167,7 @@ namespace math.Tests var numbers = new List<long> { 10, 20, 30 }.ConvertAll( n => Num.CreateBuilder().SetNum_(n).Build()); - await call.RequestStream.WriteAll(numbers); + await call.RequestStream.WriteAllAsync(numbers); var result = await call.ResponseAsync; Assert.AreEqual(60, result.Num_); } @@ -201,8 +185,8 @@ namespace math.Tests using (var call = client.DivMany()) { - await call.RequestStream.WriteAll(divArgsList); - var result = await call.ResponseStream.ToList(); + await call.RequestStream.WriteAllAsync(divArgsList); + var result = await call.ResponseStream.ToListAsync(); CollectionAssert.AreEqual(new long[] { 3, 4, 3 }, result.ConvertAll((divReply) => divReply.Quotient)); CollectionAssert.AreEqual(new long[] { 1, 16, 1 }, result.ConvertAll((divReply) => divReply.Remainder)); diff --git a/src/csharp/Grpc.Examples/Grpc.Examples.csproj b/src/csharp/Grpc.Examples/Grpc.Examples.csproj index eaf24a253c..c1aa40500e 100644 --- a/src/csharp/Grpc.Examples/Grpc.Examples.csproj +++ b/src/csharp/Grpc.Examples/Grpc.Examples.csproj @@ -19,15 +19,22 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> </PropertyGroup> <ItemGroup> <Reference Include="System" /> diff --git a/src/csharp/Grpc.Examples/MathExamples.cs b/src/csharp/Grpc.Examples/MathExamples.cs index 06d81a4d83..dc1bf43995 100644 --- a/src/csharp/Grpc.Examples/MathExamples.cs +++ b/src/csharp/Grpc.Examples/MathExamples.cs @@ -54,7 +54,7 @@ namespace math { using (var call = client.Fib(new FibArgs.Builder { Limit = 5 }.Build())) { - List<Num> result = await call.ResponseStream.ToList(); + List<Num> result = await call.ResponseStream.ToListAsync(); Console.WriteLine("Fib Result: " + string.Join("|", result)); } } @@ -70,7 +70,7 @@ namespace math using (var call = client.Sum()) { - await call.RequestStream.WriteAll(numbers); + await call.RequestStream.WriteAllAsync(numbers); Console.WriteLine("Sum Result: " + await call.ResponseAsync); } } @@ -85,8 +85,8 @@ namespace math }; using (var call = client.DivMany()) { - await call.RequestStream.WriteAll(divArgsList); - Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToList())); + await call.RequestStream.WriteAllAsync(divArgsList); + Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToListAsync())); } } @@ -102,7 +102,7 @@ namespace math Num sum; using (var sumCall = client.Sum()) { - await sumCall.RequestStream.WriteAll(numbers); + await sumCall.RequestStream.WriteAllAsync(numbers); sum = await sumCall.ResponseAsync; } diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs index 67827e7b4f..4941ff35f7 100644 --- a/src/csharp/Grpc.Examples/MathGrpc.cs +++ b/src/csharp/Grpc.Examples/MathGrpc.cs @@ -19,24 +19,28 @@ namespace math { static readonly Method<global::math.DivArgs, global::math.DivReply> __Method_Div = new Method<global::math.DivArgs, global::math.DivReply>( MethodType.Unary, + __ServiceName, "Div", __Marshaller_DivArgs, __Marshaller_DivReply); static readonly Method<global::math.DivArgs, global::math.DivReply> __Method_DivMany = new Method<global::math.DivArgs, global::math.DivReply>( MethodType.DuplexStreaming, + __ServiceName, "DivMany", __Marshaller_DivArgs, __Marshaller_DivReply); static readonly Method<global::math.FibArgs, global::math.Num> __Method_Fib = new Method<global::math.FibArgs, global::math.Num>( MethodType.ServerStreaming, + __ServiceName, "Fib", __Marshaller_FibArgs, __Marshaller_Num); static readonly Method<global::math.Num, global::math.Num> __Method_Sum = new Method<global::math.Num, global::math.Num>( MethodType.ClientStreaming, + __ServiceName, "Sum", __Marshaller_Num, __Marshaller_Num); @@ -45,10 +49,15 @@ namespace math { public interface IMathClient { global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::math.DivReply Div(global::math.DivArgs request, CallOptions options); AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, CallOptions options); AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CallOptions options); AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CallOptions options); AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CallOptions options); } // server-side interface @@ -68,28 +77,53 @@ namespace math { } public global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Div, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::math.DivReply Div(global::math.DivArgs request, CallOptions options) + { + var call = CreateCall(__Method_Div, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Div, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, CallOptions options) + { + var call = CreateCall(__Method_Div, options); + return Calls.AsyncUnaryCall(call, request); } public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_DivMany, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_DivMany, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CallOptions options) + { + var call = CreateCall(__Method_DivMany, options); + return Calls.AsyncDuplexStreamingCall(call); } public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Fib, headers, deadline); - return Calls.AsyncServerStreamingCall(call, request, cancellationToken); + var call = CreateCall(__Method_Fib, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncServerStreamingCall(call, request); + } + public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CallOptions options) + { + var call = CreateCall(__Method_Fib, options); + return Calls.AsyncServerStreamingCall(call, request); } public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Sum, headers, deadline); - return Calls.AsyncClientStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_Sum, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncClientStreamingCall(call); + } + public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CallOptions options) + { + var call = CreateCall(__Method_Sum, options); + return Calls.AsyncClientStreamingCall(call); } } diff --git a/src/csharp/Grpc.Examples/MathServiceImpl.cs b/src/csharp/Grpc.Examples/MathServiceImpl.cs index dd26b1d350..7b2684615c 100644 --- a/src/csharp/Grpc.Examples/MathServiceImpl.cs +++ b/src/csharp/Grpc.Examples/MathServiceImpl.cs @@ -75,7 +75,7 @@ namespace math public async Task<Num> Sum(IAsyncStreamReader<Num> requestStream, ServerCallContext context) { long sum = 0; - await requestStream.ForEach(async num => + await requestStream.ForEachAsync(async num => { sum += num.Num_; }); @@ -84,10 +84,7 @@ namespace math public async Task DivMany(IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream, ServerCallContext context) { - await requestStream.ForEach(async divArgs => - { - await responseStream.WriteAsync(DivInternal(divArgs)); - }); + await requestStream.ForEachAsync(async divArgs => await responseStream.WriteAsync(DivInternal(divArgs))); } static DivReply DivInternal(DivArgs args) diff --git a/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj index 72e110302b..c922ddfb9e 100644 --- a/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj +++ b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj @@ -17,7 +17,6 @@ <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> @@ -25,10 +24,18 @@ <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> <Reference Include="Google.ProtocolBuffers"> <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.dll</HintPath> diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs index 9d89698a8f..024377e216 100644 --- a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs +++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs @@ -57,11 +57,13 @@ namespace Grpc.HealthCheck.Tests { serviceImpl = new HealthServiceImpl(); - server = new Server(); - server.AddServiceDefinition(Grpc.Health.V1Alpha.Health.BindService(serviceImpl)); - int port = server.AddPort(Host, Server.PickUnusedPort, ServerCredentials.Insecure); + server = new Server + { + Services = { Grpc.Health.V1Alpha.Health.BindService(serviceImpl) }, + Ports = { { Host, ServerPort.PickUnused, ServerCredentials.Insecure } } + }; server.Start(); - channel = new Channel(Host, port, Credentials.Insecure); + channel = new Channel(Host, server.Ports.Single().BoundPort, Credentials.Insecure); client = Grpc.Health.V1Alpha.Health.NewClient(channel); } diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs index 7184415655..c4caa3b57a 100644 --- a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs +++ b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs @@ -92,11 +92,11 @@ namespace Grpc.HealthCheck.Tests public void NullsRejected() { var impl = new HealthServiceImpl(); - Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus(null, "", HealthCheckResponse.Types.ServingStatus.SERVING)); - Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus("", null, HealthCheckResponse.Types.ServingStatus.SERVING)); + Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus(null, "", HealthCheckResponse.Types.ServingStatus.SERVING)); + Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus("", null, HealthCheckResponse.Types.ServingStatus.SERVING)); - Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus(null, "")); - Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus("", null)); + Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus(null, "")); + Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus("", null)); } private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string host, string service) diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj index 4ebb6446dd..0b7a7b91c6 100644 --- a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj +++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj @@ -18,7 +18,6 @@ <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> @@ -26,10 +25,18 @@ <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> <Reference Include="Google.ProtocolBuffers"> <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.dll</HintPath> diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec index ca35b36805..acdfba42c8 100644 --- a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec +++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec @@ -20,9 +20,9 @@ </dependencies> </metadata> <files> - <file src="bin/Release/Grpc.HealthCheck.dll" target="lib/net45" /> - <file src="bin/Release/Grpc.HealthCheck.pdb" target="lib/net45" /> - <file src="bin/Release/Grpc.HealthCheck.xml" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.HealthCheck.dll" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.HealthCheck.pdb" target="lib/net45" /> + <file src="bin/ReleaseSigned/Grpc.HealthCheck.xml" target="lib/net45" /> <file src="**\*.cs" target="src" /> </files> </package> diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs index 892cdb3f04..0dabc91f7c 100644 --- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs +++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs @@ -17,6 +17,7 @@ namespace Grpc.Health.V1Alpha { static readonly Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse> __Method_Check = new Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse>( MethodType.Unary, + __ServiceName, "Check", __Marshaller_HealthCheckRequest, __Marshaller_HealthCheckResponse); @@ -25,7 +26,9 @@ namespace Grpc.Health.V1Alpha { public interface IHealthClient { global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options); AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options); } // server-side interface @@ -42,13 +45,23 @@ namespace Grpc.Health.V1Alpha { } public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Check, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options) + { + var call = CreateCall(__Method_Check, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Check, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options) + { + var call = CreateCall(__Method_Check, options); + return Calls.AsyncUnaryCall(call, request); } } diff --git a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj index 37d53d61e0..2c38c9645c 100644 --- a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj @@ -18,18 +18,25 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> <Reference Include="System" /> </ItemGroup> diff --git a/src/csharp/Grpc.IntegrationTesting.Client/app.config b/src/csharp/Grpc.IntegrationTesting.Client/app.config index 0a82bb4f16..84d7534d65 100644 --- a/src/csharp/Grpc.IntegrationTesting.Client/app.config +++ b/src/csharp/Grpc.IntegrationTesting.Client/app.config @@ -10,6 +10,10 @@ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" /> </dependentAssembly> + <dependentAssembly> + <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" /> + <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" /> + </dependentAssembly> </assemblyBinding> </runtime> </configuration>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj index 0f3b9eb510..949ad61375 100644 --- a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj @@ -18,18 +18,25 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> <Reference Include="System" /> </ItemGroup> diff --git a/src/csharp/Grpc.IntegrationTesting.Server/app.config b/src/csharp/Grpc.IntegrationTesting.Server/app.config index 0a82bb4f16..84d7534d65 100644 --- a/src/csharp/Grpc.IntegrationTesting.Server/app.config +++ b/src/csharp/Grpc.IntegrationTesting.Server/app.config @@ -10,6 +10,10 @@ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" /> </dependentAssembly> + <dependentAssembly> + <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" /> + <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" /> + </dependentAssembly> </assemblyBinding> </runtime> </configuration>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj index db2e304d37..2020a76d39 100644 --- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj +++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj @@ -8,7 +8,7 @@ <RootNamespace>Grpc.IntegrationTesting</RootNamespace> <AssemblyName>Grpc.IntegrationTesting</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - <NuGetPackageImportStamp>041c163e</NuGetPackageImportStamp> + <NuGetPackageImportStamp>6566287f</NuGetPackageImportStamp> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -18,33 +18,53 @@ <DefineConstants>DEBUG;</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <Externalconsole>true</Externalconsole> <PlatformTarget>AnyCPU</PlatformTarget> </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseSigned|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\ReleaseSigned</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <SignAssembly>True</SignAssembly> + <AssemblyOriginatorKeyFile>C:\keys\Grpc.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> <ItemGroup> - <Reference Include="BouncyCastle.Crypto"> + <Reference Include="BouncyCastle.Crypto, Version=1.7.4137.9688, Culture=neutral, PublicKeyToken=a4292a325f69b123, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\BouncyCastle.1.7.0\lib\Net40-Client\BouncyCastle.Crypto.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Auth, Version=1.9.2.27817, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Google.Apis.Auth, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.3.19383, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Auth.1.9.3\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> + </Reference> + <Reference Include="Google.Apis.Core, Version=1.9.3.19379, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Google.Apis.Core.1.9.3\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.dll</HintPath> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Auth.PlatformServices, Version=1.9.2.27820, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Auth.1.9.2\lib\net40\Google.Apis.Auth.PlatformServices.dll</HintPath> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> </Reference> - <Reference Include="Google.Apis.Core, Version=1.9.2.27816, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL"> + <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\Google.Apis.Core.1.9.2\lib\portable-net40+sl50+win+wpa81+wp80\Google.Apis.Core.dll</HintPath> + <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> </Reference> <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -71,18 +91,6 @@ <HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> </Reference> <Reference Include="System.Net.Http.WebRequest" /> - <Reference Include="Microsoft.Threading.Tasks"> - <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> - </Reference> - <Reference Include="Microsoft.Threading.Tasks.Extensions"> - <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> - </Reference> - <Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop"> - <HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> - </Reference> - <Reference Include="System.Collections.Immutable"> - <HintPath>..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> <Compile Include="..\Grpc.Core\Version.cs"> diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs index 4f8a5a5da7..385ca92086 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs @@ -43,6 +43,7 @@ using Grpc.Auth; using Grpc.Core; using Grpc.Core.Utils; using NUnit.Framework; +using Google.Apis.Auth.OAuth2; namespace Grpc.IntegrationTesting { @@ -97,10 +98,10 @@ namespace Grpc.IntegrationTesting } var interopClient = new InteropClient(options); - interopClient.Run(); + interopClient.Run().Wait(); } - private void Run() + private async Task Run() { Credentials credentials = null; if (options.useTls) @@ -120,17 +121,7 @@ namespace Grpc.IntegrationTesting using (Channel channel = new Channel(options.serverHost, options.serverPort.Value, credentials, channelOptions)) { TestService.TestServiceClient client = new TestService.TestServiceClient(channel); - if (options.testCase == "service_account_creds" || options.testCase == "compute_engine_creds") - { - var credential = GoogleCredential.GetApplicationDefault(); - if (credential.IsCreateScopedRequired) - { - credential = credential.CreateScoped(new[] { AuthScope }); - } - client.HeaderInterceptor = OAuth2InterceptorFactory.Create(credential); - } - - RunTestCaseAsync(options.testCase, client).Wait(); + await RunTestCaseAsync(options.testCase, client); } GrpcEnvironment.Shutdown(); } @@ -158,16 +149,19 @@ namespace Grpc.IntegrationTesting await RunEmptyStreamAsync(client); break; case "service_account_creds": - RunServiceAccountCreds(client); + await RunServiceAccountCredsAsync(client); break; case "compute_engine_creds": - RunComputeEngineCreds(client); + await RunComputeEngineCredsAsync(client); + break; + case "jwt_token_creds": + await RunJwtTokenCredsAsync(client); break; case "oauth2_auth_token": - RunOAuth2AuthToken(client); + await RunOAuth2AuthTokenAsync(client); break; case "per_rpc_creds": - RunPerRpcCreds(client); + await RunPerRpcCredsAsync(client); break; case "cancel_after_begin": await RunCancelAfterBeginAsync(client); @@ -215,7 +209,7 @@ namespace Grpc.IntegrationTesting using (var call = client.StreamingInputCall()) { - await call.RequestStream.WriteAll(bodySizes); + await call.RequestStream.WriteAllAsync(bodySizes); var response = await call.ResponseAsync; Assert.AreEqual(74922, response.AggregatedPayloadSize); @@ -237,7 +231,7 @@ namespace Grpc.IntegrationTesting using (var call = client.StreamingOutputCall(request)) { - var responseList = await call.ResponseStream.ToList(); + var responseList = await call.ResponseStream.ToListAsync(); foreach (var res in responseList) { Assert.AreEqual(PayloadType.COMPRESSABLE, res.Payload.Type); @@ -303,15 +297,19 @@ namespace Grpc.IntegrationTesting { await call.RequestStream.CompleteAsync(); - var responseList = await call.ResponseStream.ToList(); + var responseList = await call.ResponseStream.ToListAsync(); Assert.AreEqual(0, responseList.Count); } Console.WriteLine("Passed!"); } - public static void RunServiceAccountCreds(TestService.ITestServiceClient client) + public static async Task RunServiceAccountCredsAsync(TestService.TestServiceClient client) { Console.WriteLine("running service_account_creds"); + var credential = await GoogleCredential.GetApplicationDefaultAsync(); + credential = credential.CreateScoped(new[] { AuthScope }); + client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + var request = SimpleRequest.CreateBuilder() .SetResponseType(PayloadType.COMPRESSABLE) .SetResponseSize(314159) @@ -329,9 +327,13 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - public static void RunComputeEngineCreds(TestService.ITestServiceClient client) + public static async Task RunComputeEngineCredsAsync(TestService.TestServiceClient client) { Console.WriteLine("running compute_engine_creds"); + var credential = await GoogleCredential.GetApplicationDefaultAsync(); + Assert.IsFalse(credential.IsCreateScopedRequired); + client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + var request = SimpleRequest.CreateBuilder() .SetResponseType(PayloadType.COMPRESSABLE) .SetResponseSize(314159) @@ -349,18 +351,37 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - public static void RunOAuth2AuthToken(TestService.TestServiceClient client) + public static async Task RunJwtTokenCredsAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running jwt_token_creds"); + var credential = await GoogleCredential.GetApplicationDefaultAsync(); + // check this a credential with scope support, but don't add the scope. + Assert.IsTrue(credential.IsCreateScopedRequired); + client.HeaderInterceptor = OAuth2Interceptors.FromCredential(credential); + + var request = SimpleRequest.CreateBuilder() + .SetResponseType(PayloadType.COMPRESSABLE) + .SetResponseSize(314159) + .SetPayload(CreateZerosPayload(271828)) + .SetFillUsername(true) + .SetFillOauthScope(true) + .Build(); + + var response = client.UnaryCall(request); + + Assert.AreEqual(PayloadType.COMPRESSABLE, response.Payload.Type); + Assert.AreEqual(314159, response.Payload.Body.Length); + Assert.AreEqual(ServiceAccountUser, response.Username); + Console.WriteLine("Passed!"); + } + + public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client) { Console.WriteLine("running oauth2_auth_token"); - var credential = GoogleCredential.GetApplicationDefault().CreateScoped(new[] { AuthScope }); - Assert.IsTrue(credential.RequestAccessTokenAsync(CancellationToken.None).Result); - string oauth2Token = credential.Token.AccessToken; + ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope }); + string oauth2Token = await credential.GetAccessTokenForRequestAsync(); - // Intercept calls with an OAuth2 token obtained out-of-band. - client.HeaderInterceptor = new MetadataInterceptorDelegate((metadata) => - { - metadata.Add(new Metadata.Entry("Authorization", "Bearer " + oauth2Token)); - }); + client.HeaderInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token); var request = SimpleRequest.CreateBuilder() .SetFillUsername(true) @@ -374,20 +395,22 @@ namespace Grpc.IntegrationTesting Console.WriteLine("Passed!"); } - public static void RunPerRpcCreds(TestService.TestServiceClient client) + public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client) { Console.WriteLine("running per_rpc_creds"); - var credential = GoogleCredential.GetApplicationDefault().CreateScoped(new[] { AuthScope }); - Assert.IsTrue(credential.RequestAccessTokenAsync(CancellationToken.None).Result); - string oauth2Token = credential.Token.AccessToken; + ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { AuthScope }); + string oauth2Token = await credential.GetAccessTokenForRequestAsync(); + var headerInterceptor = OAuth2Interceptors.FromAccessToken(oauth2Token); var request = SimpleRequest.CreateBuilder() .SetFillUsername(true) .SetFillOauthScope(true) .Build(); - var response = client.UnaryCall(request, headers: new Metadata { new Metadata.Entry("Authorization", "Bearer " + oauth2Token) }); + var headers = new Metadata(); + headerInterceptor("", headers); + var response = client.UnaryCall(request, headers: headers); Assert.AreEqual(AuthScopeResponse, response.OauthScope); Assert.AreEqual(ServiceAccountUser, response.Username); @@ -405,15 +428,8 @@ namespace Grpc.IntegrationTesting await Task.Delay(1000); cts.Cancel(); - try - { - var response = await call.ResponseAsync; - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode); - } + var ex = Assert.Throws<RpcException>(async () => await call.ResponseAsync); + Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode); } Console.WriteLine("Passed!"); } @@ -436,15 +452,8 @@ namespace Grpc.IntegrationTesting cts.Cancel(); - try - { - await call.ResponseStream.MoveNext(); - Assert.Fail(); - } - catch (RpcException e) - { - Assert.AreEqual(StatusCode.Cancelled, e.Status.StatusCode); - } + var ex = Assert.Throws<RpcException>(async () => await call.ResponseStream.MoveNext()); + Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode); } Console.WriteLine("Passed!"); } diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs index 2756ce97aa..6fa721bc1c 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using grpc.testing; @@ -47,7 +48,7 @@ namespace Grpc.IntegrationTesting /// </summary> public class InteropClientServerTest { - string host = "localhost"; + const string Host = "localhost"; Server server; Channel channel; TestService.ITestServiceClient client; @@ -55,16 +56,19 @@ namespace Grpc.IntegrationTesting [TestFixtureSetUp] public void Init() { - server = new Server(); - server.AddServiceDefinition(TestService.BindService(new TestServiceImpl())); - int port = server.AddPort(host, Server.PickUnusedPort, TestCredentials.CreateTestServerCredentials()); + server = new Server + { + Services = { TestService.BindService(new TestServiceImpl()) }, + Ports = { { Host, ServerPort.PickUnused, TestCredentials.CreateTestServerCredentials() } } + }; server.Start(); var options = new List<ChannelOption> { new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride) }; - channel = new Channel(host, port, TestCredentials.CreateTestClientCredentials(true), options); + int port = server.Ports.Single().BoundPort; + channel = new Channel(Host, port, TestCredentials.CreateTestClientCredentials(true), options); client = TestService.NewClient(channel); } diff --git a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs index bf6947e09d..504fd11857 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs @@ -88,18 +88,20 @@ namespace Grpc.IntegrationTesting private void Run() { - var server = new Server(); - server.AddServiceDefinition(TestService.BindService(new TestServiceImpl())); + var server = new Server + { + Services = { TestService.BindService(new TestServiceImpl()) } + }; string host = "0.0.0.0"; int port = options.port.Value; if (options.useTls) { - server.AddPort(host, port, TestCredentials.CreateTestServerCredentials()); + server.Ports.Add(host, port, TestCredentials.CreateTestServerCredentials()); } else { - server.AddPort(host, options.port.Value, ServerCredentials.Insecure); + server.Ports.Add(host, options.port.Value, ServerCredentials.Insecure); } Console.WriteLine("Running server on " + string.Format("{0}:{1}", host, port)); server.Start(); diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs index 1baf40eea2..1c398eb84e 100644 --- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs @@ -34,6 +34,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using grpc.testing; @@ -49,7 +50,7 @@ namespace Grpc.IntegrationTesting /// </summary> public class SslCredentialsTest { - string host = "localhost"; + const string Host = "localhost"; Server server; Channel channel; TestService.ITestServiceClient client; @@ -62,12 +63,14 @@ namespace Grpc.IntegrationTesting File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)); - var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert); + var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, true); var clientCredentials = new SslCredentials(rootCert, keyCertPair); - server = new Server(); - server.AddServiceDefinition(TestService.BindService(new TestServiceImpl())); - int port = server.AddPort(host, Server.PickUnusedPort, serverCredentials); + server = new Server + { + Services = { TestService.BindService(new TestServiceImpl()) }, + Ports = { { Host, ServerPort.PickUnused, serverCredentials } } + }; server.Start(); var options = new List<ChannelOption> @@ -75,7 +78,7 @@ namespace Grpc.IntegrationTesting new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride) }; - channel = new Channel(host, port, clientCredentials, options); + channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options); client = TestService.NewClient(channel); } diff --git a/src/csharp/Grpc.IntegrationTesting/TestCredentials.cs b/src/csharp/Grpc.IntegrationTesting/TestCredentials.cs index 54d8587713..da0b7fb910 100644 --- a/src/csharp/Grpc.IntegrationTesting/TestCredentials.cs +++ b/src/csharp/Grpc.IntegrationTesting/TestCredentials.cs @@ -33,7 +33,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs index ddcd0c2958..697acb53d8 100644 --- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs +++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs @@ -22,36 +22,42 @@ namespace grpc.testing { static readonly Method<global::grpc.testing.Empty, global::grpc.testing.Empty> __Method_EmptyCall = new Method<global::grpc.testing.Empty, global::grpc.testing.Empty>( MethodType.Unary, + __ServiceName, "EmptyCall", __Marshaller_Empty, __Marshaller_Empty); static readonly Method<global::grpc.testing.SimpleRequest, global::grpc.testing.SimpleResponse> __Method_UnaryCall = new Method<global::grpc.testing.SimpleRequest, global::grpc.testing.SimpleResponse>( MethodType.Unary, + __ServiceName, "UnaryCall", __Marshaller_SimpleRequest, __Marshaller_SimpleResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_StreamingOutputCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.ServerStreaming, + __ServiceName, "StreamingOutputCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); static readonly Method<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> __Method_StreamingInputCall = new Method<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse>( MethodType.ClientStreaming, + __ServiceName, "StreamingInputCall", __Marshaller_StreamingInputCallRequest, __Marshaller_StreamingInputCallResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_FullDuplexCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.DuplexStreaming, + __ServiceName, "FullDuplexCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_HalfDuplexCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.DuplexStreaming, + __ServiceName, "HalfDuplexCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); @@ -60,13 +66,21 @@ namespace grpc.testing { public interface ITestServiceClient { global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CallOptions options); AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CallOptions options); global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CallOptions options); AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CallOptions options); AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CallOptions options); AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options); AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options); AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options); } // server-side interface @@ -88,43 +102,83 @@ namespace grpc.testing { } public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_EmptyCall, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CallOptions options) + { + var call = CreateCall(__Method_EmptyCall, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_EmptyCall, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CallOptions options) + { + var call = CreateCall(__Method_EmptyCall, options); + return Calls.AsyncUnaryCall(call, request); } public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_UnaryCall, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CallOptions options) + { + var call = CreateCall(__Method_UnaryCall, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_UnaryCall, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CallOptions options) + { + var call = CreateCall(__Method_UnaryCall, options); + return Calls.AsyncUnaryCall(call, request); } public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_StreamingOutputCall, headers, deadline); - return Calls.AsyncServerStreamingCall(call, request, cancellationToken); + var call = CreateCall(__Method_StreamingOutputCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncServerStreamingCall(call, request); + } + public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CallOptions options) + { + var call = CreateCall(__Method_StreamingOutputCall, options); + return Calls.AsyncServerStreamingCall(call, request); } public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_StreamingInputCall, headers, deadline); - return Calls.AsyncClientStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_StreamingInputCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncClientStreamingCall(call); + } + public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options) + { + var call = CreateCall(__Method_StreamingInputCall, options); + return Calls.AsyncClientStreamingCall(call); } public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_FullDuplexCall, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_FullDuplexCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options) + { + var call = CreateCall(__Method_FullDuplexCall, options); + return Calls.AsyncDuplexStreamingCall(call); } public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_HalfDuplexCall, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_HalfDuplexCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options) + { + var call = CreateCall(__Method_HalfDuplexCall, options); + return Calls.AsyncDuplexStreamingCall(call); } } diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs index ccf9fe6ced..ceebd5dd8c 100644 --- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs +++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs @@ -71,7 +71,7 @@ namespace grpc.testing public async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context) { int sum = 0; - await requestStream.ForEach(async request => + await requestStream.ForEachAsync(async request => { sum += request.Payload.Body.Length; }); @@ -80,7 +80,7 @@ namespace grpc.testing public async Task FullDuplexCall(IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream, ServerCallContext context) { - await requestStream.ForEach(async request => + await requestStream.ForEachAsync(async request => { foreach (var responseParam in request.ResponseParametersList) { diff --git a/src/csharp/Grpc.IntegrationTesting/app.config b/src/csharp/Grpc.IntegrationTesting/app.config index 0a82bb4f16..84d7534d65 100644 --- a/src/csharp/Grpc.IntegrationTesting/app.config +++ b/src/csharp/Grpc.IntegrationTesting/app.config @@ -10,6 +10,10 @@ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.2.28.0" newVersion="4.0.0.0" /> </dependentAssembly> + <dependentAssembly> + <assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" /> + <bindingRedirect oldVersion="0.0.0.0-1.9.2.38523" newVersion="1.9.2.38523" /> + </dependentAssembly> </assemblyBinding> </runtime> </configuration>
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting/packages.config b/src/csharp/Grpc.IntegrationTesting/packages.config index 746133a7a5..0867b091b9 100644 --- a/src/csharp/Grpc.IntegrationTesting/packages.config +++ b/src/csharp/Grpc.IntegrationTesting/packages.config @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="BouncyCastle" version="1.7.0" targetFramework="net45" /> - <package id="Google.Apis.Auth" version="1.9.2" targetFramework="net45" /> - <package id="Google.Apis.Core" version="1.9.2" targetFramework="net45" /> + <package id="Google.Apis.Auth" version="1.9.3" targetFramework="net45" /> + <package id="Google.Apis.Core" version="1.9.3" targetFramework="net45" /> <package id="Google.ProtocolBuffers" version="2.4.1.521" targetFramework="net45" /> <package id="Ix-Async" version="1.2.3" targetFramework="net45" /> <package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> @@ -11,5 +11,4 @@ <package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> <package id="NUnit" version="2.6.4" targetFramework="net45" /> - <package id="System.Collections.Immutable" version="1.1.36" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln index 0cd8aaef6d..f19f29c6a2 100644 --- a/src/csharp/Grpc.sln +++ b/src/csharp/Grpc.sln @@ -36,58 +36,81 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
+ ReleaseSigned|Any CPU = ReleaseSigned|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|Any CPU.Build.0 = Release|Any CPU
- {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|Any CPU.Build.0 = Release|Any CPU
- {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|Any CPU.Build.0 = Release|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7DC1433E-3225-42C7-B7EA-546D56E27A4B}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {7DC1433E-3225-42C7-B7EA-546D56E27A4B}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {86EC5CB4-4EA2-40A2-8057-86542A0353BB}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {86EC5CB4-4EA2-40A2-8057-86542A0353BB}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {3D166931-BA2D-416E-95A3-D36E8F6E90B9}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Release|Any CPU.Build.0 = Release|Any CPU
- {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|Any CPU.Build.0 = Release|Any CPU
- {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A654F3B8-E859-4E6A-B30D-227527DBEF0D}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {A654F3B8-E859-4E6A-B30D-227527DBEF0D}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
{BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|Any CPU.Build.0 = Release|Any CPU
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|Any CPU.Build.0 = Release|Any CPU
- {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.ReleaseSigned|Any CPU.ActiveCfg = ReleaseSigned|Any CPU
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.ReleaseSigned|Any CPU.Build.0 = ReleaseSigned|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/csharp/build_packages.bat b/src/csharp/build_packages.bat index c3e5fe8817..8a11d01430 100644 --- a/src/csharp/build_packages.bat +++ b/src/csharp/build_packages.bat @@ -1,8 +1,8 @@ @rem Builds gRPC NuGet packages @rem Current package versions -set VERSION=0.6.0 -set CORE_VERSION=0.10.0 +set VERSION=0.6.1 +set CORE_VERSION=0.10.1 @rem Adjust the location of nuget.exe set NUGET=C:\nuget\nuget.exe @@ -12,7 +12,7 @@ cd ..\..\vsprojects\nuget_package @call buildall.bat || goto :error endlocal -@call buildall.bat || goto :error +@call buildall.bat BUILD_SIGNED || goto :error %NUGET% pack ..\..\vsprojects\nuget_package\grpc.native.csharp_ext.nuspec -Version %CORE_VERSION% || goto :error %NUGET% pack Grpc.Auth\Grpc.Auth.nuspec -Symbols -Version %VERSION% || goto :error diff --git a/src/csharp/buildall.bat b/src/csharp/buildall.bat index 16860aec3c..d85896c255 100644 --- a/src/csharp/buildall.bat +++ b/src/csharp/buildall.bat @@ -9,10 +9,15 @@ cd /d %~dp0 @call "%VS120COMNTOOLS%\..\..\vc\vcvarsall.bat" x86 @rem Build the C# native extension -msbuild ..\..\vsprojects\grpc.sln /t:grpc_csharp_ext /p:PlatformToolset=v120 || goto :error +msbuild ..\..\vsprojects\grpc_csharp_ext.sln /p:PlatformToolset=v120 || goto :error msbuild Grpc.sln /p:Configuration=Debug || goto :error msbuild Grpc.sln /p:Configuration=Release || goto :error + +if "%1" == "BUILD_SIGNED" ( +msbuild Grpc.sln /p:Configuration=ReleaseSigned || goto :error +) + endlocal goto :EOF diff --git a/src/csharp/doc/README.md b/src/csharp/doc/README.md new file mode 100644 index 0000000000..585500b5ca --- /dev/null +++ b/src/csharp/doc/README.md @@ -0,0 +1,2 @@ + +SandCastle project files to generate HTML reference documentation.
\ No newline at end of file diff --git a/src/csharp/doc/grpc_csharp_public.shfbproj b/src/csharp/doc/grpc_csharp_public.shfbproj new file mode 100644 index 0000000000..05c93f4a13 --- /dev/null +++ b/src/csharp/doc/grpc_csharp_public.shfbproj @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <!-- The configuration and platform will be used to determine which assemblies to include from solution and + project documentation sources --> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{77e3da09-fc92-486f-a90a-99ca788e8b59}</ProjectGuid> + <SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion> + <!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway --> + <AssemblyName>Documentation</AssemblyName> + <RootNamespace>Documentation</RootNamespace> + <Name>Documentation</Name> + <!-- SHFB properties --> + <FrameworkVersion>.NET Framework 4.5</FrameworkVersion> + <OutputPath>..\..\..\doc\ref\csharp\html</OutputPath> + <Language>en-US</Language> + <DocumentationSources> + <DocumentationSource sourceFile="..\Grpc.Auth\Grpc.Auth.csproj" /> +<DocumentationSource sourceFile="..\Grpc.Core\Grpc.Core.csproj" /></DocumentationSources> + <BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity> + <HelpFileFormat>Website</HelpFileFormat> + <IndentHtml>False</IndentHtml> + <KeepLogFile>True</KeepLogFile> + <DisableCodeBlockComponent>False</DisableCodeBlockComponent> + <CleanIntermediates>True</CleanIntermediates> + <HelpFileVersion>1.0.0.0</HelpFileVersion> + <MaximumGroupParts>2</MaximumGroupParts> + <NamespaceGrouping>False</NamespaceGrouping> + <SyntaxFilters>Standard</SyntaxFilters> + <SdkLinkTarget>Blank</SdkLinkTarget> + <RootNamespaceContainer>True</RootNamespaceContainer> + <PresentationStyle>VS2013</PresentationStyle> + <Preliminary>False</Preliminary> + <NamingMethod>MemberName</NamingMethod> + <HelpTitle>gRPC C#</HelpTitle> + <ContentPlacement>AboveNamespaces</ContentPlacement> + <HtmlHelpName>Documentation</HtmlHelpName> + </PropertyGroup> + <!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform + the build. The others are optional common platform types that may appear. --> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|Win32' "> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|Win32' "> + </PropertyGroup> + <!-- Import the SHFB build targets --> + <Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" /> + <!-- The pre-build and post-build event properties must appear *after* the targets file import in order to be + evaluated correctly. --> + <PropertyGroup> + <PreBuildEvent> + </PreBuildEvent> + <PostBuildEvent> + </PostBuildEvent> + <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent> + </PropertyGroup> +</Project>
\ No newline at end of file diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 1c5891bbff..9379ae01f1 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -169,7 +169,7 @@ grpcsharp_metadata_array_add(grpc_metadata_array *array, const char *key, GPR_EXPORT gpr_intptr GPR_CALLTYPE grpcsharp_metadata_array_count(grpc_metadata_array *array) { - return (gpr_intptr) array->count; + return (gpr_intptr)array->count; } GPR_EXPORT const char *GPR_CALLTYPE @@ -184,10 +184,10 @@ grpcsharp_metadata_array_get_value(grpc_metadata_array *array, size_t index) { return array->metadata[index].value; } -GPR_EXPORT gpr_intptr GPR_CALLTYPE -grpcsharp_metadata_array_get_value_length(grpc_metadata_array *array, size_t index) { +GPR_EXPORT gpr_intptr GPR_CALLTYPE grpcsharp_metadata_array_get_value_length( + grpc_metadata_array *array, size_t index) { GPR_ASSERT(index < array->count); - return (gpr_intptr) array->metadata[index].value_length; + return (gpr_intptr)array->metadata[index].value_length; } /* Move contents of metadata array */ @@ -306,8 +306,7 @@ grpcsharp_batch_context_server_rpc_new_method( return ctx->server_rpc_new.call_details.method; } -GPR_EXPORT const char *GPR_CALLTYPE -grpcsharp_batch_context_server_rpc_new_host( +GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_batch_context_server_rpc_new_host( const grpcsharp_batch_context *ctx) { return ctx->server_rpc_new.call_details.host; } @@ -367,8 +366,9 @@ grpcsharp_completion_queue_pluck(grpc_completion_queue *cq, void *tag) { /* Channel */ GPR_EXPORT grpc_channel *GPR_CALLTYPE + grpcsharp_insecure_channel_create(const char *target, const grpc_channel_args *args) { - return grpc_channel_create(target, args); + return grpc_insecure_channel_create(target, args); } GPR_EXPORT void GPR_CALLTYPE grpcsharp_channel_destroy(grpc_channel *channel) { @@ -376,10 +376,29 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_channel_destroy(grpc_channel *channel) { } GPR_EXPORT grpc_call *GPR_CALLTYPE -grpcsharp_channel_create_call(grpc_channel *channel, grpc_completion_queue *cq, +grpcsharp_channel_create_call(grpc_channel *channel, grpc_call *parent_call, + gpr_uint32 propagation_mask, + grpc_completion_queue *cq, const char *method, const char *host, gpr_timespec deadline) { - return grpc_channel_create_call(channel, cq, method, host, deadline); + return grpc_channel_create_call(channel, parent_call, propagation_mask, cq, + method, host, deadline); +} + +GPR_EXPORT grpc_connectivity_state GPR_CALLTYPE +grpcsharp_channel_check_connectivity_state(grpc_channel *channel, gpr_int32 try_to_connect) { + return grpc_channel_check_connectivity_state(channel, try_to_connect); +} + +GPR_EXPORT void GPR_CALLTYPE grpcsharp_channel_watch_connectivity_state( + grpc_channel *channel, grpc_connectivity_state last_observed_state, + gpr_timespec deadline, grpc_completion_queue *cq, grpcsharp_batch_context *ctx) { + grpc_channel_watch_connectivity_state(channel, last_observed_state, + deadline, cq, ctx); +} + +GPR_EXPORT char *GPR_CALLTYPE grpcsharp_channel_get_target(grpc_channel *channel) { + return grpc_channel_get_target(channel); } /* Channel args */ @@ -480,7 +499,7 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_call_destroy(grpc_call *call) { GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_unary(grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer, size_t send_buffer_len, - grpc_metadata_array *initial_metadata) { + grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) { /* TODO: don't use magic number */ grpc_op ops[6]; ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; @@ -494,7 +513,7 @@ grpcsharp_call_start_unary(grpc_call *call, grpcsharp_batch_context *ctx, ops[1].op = GRPC_OP_SEND_MESSAGE; ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len); ops[1].data.send_message = ctx->send_message; - ops[1].flags = 0; + ops[1].flags = write_flags; ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; ops[2].flags = 0; @@ -561,7 +580,7 @@ grpcsharp_call_start_client_streaming(grpc_call *call, GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming( grpc_call *call, grpcsharp_batch_context *ctx, const char *send_buffer, - size_t send_buffer_len, grpc_metadata_array *initial_metadata) { + size_t send_buffer_len, grpc_metadata_array *initial_metadata, gpr_uint32 write_flags) { /* TODO: don't use magic number */ grpc_op ops[5]; ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; @@ -575,7 +594,7 @@ GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_server_streaming( ops[1].op = GRPC_OP_SEND_MESSAGE; ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len); ops[1].data.send_message = ctx->send_message; - ops[1].flags = 0; + ops[1].flags = write_flags; ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; ops[2].flags = 0; @@ -634,15 +653,22 @@ grpcsharp_call_start_duplex_streaming(grpc_call *call, GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_message(grpc_call *call, grpcsharp_batch_context *ctx, - const char *send_buffer, size_t send_buffer_len) { + const char *send_buffer, size_t send_buffer_len, + gpr_uint32 write_flags, + gpr_int32 send_empty_initial_metadata) { /* TODO: don't use magic number */ - grpc_op ops[1]; + grpc_op ops[2]; + size_t nops = send_empty_initial_metadata ? 2 : 1; ops[0].op = GRPC_OP_SEND_MESSAGE; ctx->send_message = string_to_byte_buffer(send_buffer, send_buffer_len); ops[0].data.send_message = ctx->send_message; - ops[0].flags = 0; + ops[0].flags = write_flags; + ops[1].op = GRPC_OP_SEND_INITIAL_METADATA; + ops[1].data.send_initial_metadata.count = 0; + ops[1].data.send_initial_metadata.metadata = NULL; + ops[1].flags = 0; - return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx); + return grpc_call_start_batch(call, ops, nops, ctx); } GPR_EXPORT grpc_call_error GPR_CALLTYPE @@ -656,27 +682,30 @@ grpcsharp_call_send_close_from_client(grpc_call *call, return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx); } -GPR_EXPORT grpc_call_error GPR_CALLTYPE -grpcsharp_call_send_status_from_server(grpc_call *call, - grpcsharp_batch_context *ctx, - grpc_status_code status_code, - const char *status_details, - grpc_metadata_array *trailing_metadata) { +GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_send_status_from_server( + grpc_call *call, grpcsharp_batch_context *ctx, grpc_status_code status_code, + const char *status_details, grpc_metadata_array *trailing_metadata, + gpr_int32 send_empty_initial_metadata) { /* TODO: don't use magic number */ - grpc_op ops[1]; + grpc_op ops[2]; + size_t nops = send_empty_initial_metadata ? 2 : 1; ops[0].op = GRPC_OP_SEND_STATUS_FROM_SERVER; ops[0].data.send_status_from_server.status = status_code; ops[0].data.send_status_from_server.status_details = gpr_strdup(status_details); - grpcsharp_metadata_array_move(&(ctx->send_status_from_server.trailing_metadata), - trailing_metadata); + grpcsharp_metadata_array_move( + &(ctx->send_status_from_server.trailing_metadata), trailing_metadata); ops[0].data.send_status_from_server.trailing_metadata_count = ctx->send_status_from_server.trailing_metadata.count; ops[0].data.send_status_from_server.trailing_metadata = ctx->send_status_from_server.trailing_metadata.metadata; ops[0].flags = 0; + ops[1].op = GRPC_OP_SEND_INITIAL_METADATA; + ops[1].data.send_initial_metadata.count = 0; + ops[1].data.send_initial_metadata.metadata = NULL; + ops[1].flags = 0; - return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx); + return grpc_call_start_batch(call, ops, nops, ctx); } GPR_EXPORT grpc_call_error GPR_CALLTYPE @@ -692,16 +721,28 @@ grpcsharp_call_recv_message(grpc_call *call, grpcsharp_batch_context *ctx) { GPR_EXPORT grpc_call_error GPR_CALLTYPE grpcsharp_call_start_serverside(grpc_call *call, grpcsharp_batch_context *ctx) { /* TODO: don't use magic number */ - grpc_op ops[2]; - ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; - ops[0].data.send_initial_metadata.count = 0; - ops[0].data.send_initial_metadata.metadata = NULL; + grpc_op ops[1]; + ops[0].op = GRPC_OP_RECV_CLOSE_ON_SERVER; + ops[0].data.recv_close_on_server.cancelled = + (&ctx->recv_close_on_server_cancelled); ops[0].flags = 0; - ops[1].op = GRPC_OP_RECV_CLOSE_ON_SERVER; - ops[1].data.recv_close_on_server.cancelled = - (&ctx->recv_close_on_server_cancelled); - ops[1].flags = 0; + return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx); +} + +GPR_EXPORT grpc_call_error GPR_CALLTYPE +grpcsharp_call_send_initial_metadata(grpc_call *call, + grpcsharp_batch_context *ctx, + grpc_metadata_array *initial_metadata) { + /* TODO: don't use magic number */ + grpc_op ops[1]; + ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; + grpcsharp_metadata_array_move(&(ctx->send_initial_metadata), + initial_metadata); + ops[0].data.send_initial_metadata.count = ctx->send_initial_metadata.count; + ops[0].data.send_initial_metadata.metadata = + ctx->send_initial_metadata.metadata; + ops[0].flags = 0; return grpc_call_start_batch(call, ops, sizeof(ops) / sizeof(ops[0]), ctx); } @@ -718,7 +759,7 @@ grpcsharp_server_create(grpc_completion_queue *cq, GPR_EXPORT gpr_int32 GPR_CALLTYPE grpcsharp_server_add_insecure_http2_port(grpc_server *server, const char *addr) { - return grpc_server_add_http2_port(server, addr); + return grpc_server_add_insecure_http2_port(server, addr); } GPR_EXPORT void GPR_CALLTYPE grpcsharp_server_start(grpc_server *server) { @@ -779,7 +820,8 @@ grpcsharp_secure_channel_create(grpc_credentials *creds, const char *target, GPR_EXPORT grpc_server_credentials *GPR_CALLTYPE grpcsharp_ssl_server_credentials_create( const char *pem_root_certs, const char **key_cert_pair_cert_chain_array, - const char **key_cert_pair_private_key_array, size_t num_key_cert_pairs) { + const char **key_cert_pair_private_key_array, size_t num_key_cert_pairs, + int force_client_auth) { size_t i; grpc_server_credentials *creds; grpc_ssl_pem_key_cert_pair *key_cert_pairs = @@ -795,7 +837,8 @@ grpcsharp_ssl_server_credentials_create( } } creds = grpc_ssl_server_credentials_create(pem_root_certs, key_cert_pairs, - num_key_cert_pairs); + num_key_cert_pairs, + force_client_auth); gpr_free(key_cert_pairs); return creds; } @@ -833,6 +876,11 @@ GPR_EXPORT void GPR_CALLTYPE grpcsharp_redirect_log(grpcsharp_log_func func) { typedef void(GPR_CALLTYPE *test_callback_funcptr)(gpr_int32 success); +/* Version info */ +GPR_EXPORT const char *GPR_CALLTYPE grpcsharp_version_string() { + return grpc_version_string(); +} + /* For testing */ GPR_EXPORT void GPR_CALLTYPE grpcsharp_test_callback(test_callback_funcptr callback) { diff --git a/src/csharp/keys/Grpc.public.snk b/src/csharp/keys/Grpc.public.snk Binary files differnew file mode 100644 index 0000000000..bac3046b36 --- /dev/null +++ b/src/csharp/keys/Grpc.public.snk diff --git a/src/csharp/keys/README.md b/src/csharp/keys/README.md new file mode 100644 index 0000000000..f3e9a3cb56 --- /dev/null +++ b/src/csharp/keys/README.md @@ -0,0 +1,5 @@ +Contents +-------- + +- Grpc.public.snk: + Public key to verify strong name of gRPC assemblies.
\ No newline at end of file diff --git a/src/node/binding.gyp b/src/node/binding.gyp index 6ba233388a..734dc8410b 100644 --- a/src/node/binding.gyp +++ b/src/node/binding.gyp @@ -11,7 +11,8 @@ '-pedantic', '-g', '-zdefs', - '-Werror' + '-Werror', + '-Wno-error=deprecated-declarations' ], 'ldflags': [ '-g' diff --git a/src/node/examples/math_server.js b/src/node/examples/math_server.js index b1f8a6323f..31892c65df 100644 --- a/src/node/examples/math_server.js +++ b/src/node/examples/math_server.js @@ -115,7 +115,7 @@ server.addProtoService(math.Math.service, { }); if (require.main === module) { - server.bind('0.0.0.0:50051'); + server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); server.start(); } diff --git a/src/node/examples/perf_test.js b/src/node/examples/perf_test.js index da919eced5..214b9384d5 100644 --- a/src/node/examples/perf_test.js +++ b/src/node/examples/perf_test.js @@ -41,7 +41,8 @@ var interop_server = require('../interop/interop_server.js'); function runTest(iterations, callback) { var testServer = interop_server.getServer(0, false); testServer.server.listen(); - var client = new testProto.TestService('localhost:' + testServer.port); + var client = new testProto.TestService('localhost:' + testServer.port, + grpc.Credentials.createInsecure()); function runIterations(finish) { var start = process.hrtime(); @@ -62,7 +63,7 @@ function runTest(iterations, callback) { var timeDiff = process.hrtime(startTime); intervals[i] = timeDiff[0] * 1000000 + timeDiff[1] / 1000; next(i+1); - }, {}, deadline); + }, {}, {deadline: deadline}); } } next(0); diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js index 00293b464a..1ce4dbe070 100644 --- a/src/node/examples/qps_test.js +++ b/src/node/examples/qps_test.js @@ -61,7 +61,8 @@ var interop_server = require('../interop/interop_server.js'); function runTest(concurrent_calls, seconds, callback) { var testServer = interop_server.getServer(0, false); testServer.server.listen(); - var client = new testProto.TestService('localhost:' + testServer.port); + var client = new testProto.TestService('localhost:' + testServer.port, + grpc.Credentials.createInsecure()); var warmup_num = 100; diff --git a/src/node/examples/route_guide_client.js b/src/node/examples/route_guide_client.js index 8cd532fef1..647f3ffaba 100644 --- a/src/node/examples/route_guide_client.js +++ b/src/node/examples/route_guide_client.js @@ -40,7 +40,8 @@ var path = require('path'); var _ = require('lodash'); var grpc = require('..'); var examples = grpc.load(__dirname + '/route_guide.proto').examples; -var client = new examples.RouteGuide('localhost:50051'); +var client = new examples.RouteGuide('localhost:50051', + grpc.Credentials.createInsecure()); var COORD_FACTOR = 1e7; diff --git a/src/node/examples/route_guide_server.js b/src/node/examples/route_guide_server.js index 70044a322c..bb8e79b5bd 100644 --- a/src/node/examples/route_guide_server.js +++ b/src/node/examples/route_guide_server.js @@ -239,7 +239,7 @@ function getServer() { if (require.main === module) { // If this is run as a script, start a server on an unused port var routeServer = getServer(); - routeServer.bind('0.0.0.0:50051'); + routeServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); var argv = parseArgs(process.argv, { string: 'db_path' }); diff --git a/src/node/examples/stock_client.js b/src/node/examples/stock_client.js index b37e66df07..ab9b050e9b 100644 --- a/src/node/examples/stock_client.js +++ b/src/node/examples/stock_client.js @@ -38,7 +38,8 @@ var examples = grpc.load(__dirname + '/stock.proto').examples; * This exports a client constructor for the Stock service. The usage looks like * * var StockClient = require('stock_client.js'); - * var stockClient = new StockClient(server_address); + * var stockClient = new StockClient(server_address, + * grpc.Credentials.createInsecure()); * stockClient.getLastTradePrice({symbol: 'GOOG'}, function(error, response) { * console.log(error || response); * }); diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js index f2eb6ad4ab..dfcfe30eb4 100644 --- a/src/node/examples/stock_server.js +++ b/src/node/examples/stock_server.js @@ -80,7 +80,7 @@ stockServer.addProtoService(examples.Stock.service, { }); if (require.main === module) { - stockServer.bind('0.0.0.0:50051'); + stockServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); stockServer.listen(); } diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc index dc45c8d8ae..c5c8313385 100644 --- a/src/node/ext/call.cc +++ b/src/node/ext/call.cc @@ -510,9 +510,21 @@ NAN_METHOD(Call::New) { NanUtf8String method(args[1]); double deadline = args[2]->NumberValue(); grpc_channel *wrapped_channel = channel->GetWrappedChannel(); - grpc_call *wrapped_call = grpc_channel_create_call( - wrapped_channel, CompletionQueueAsyncWorker::GetQueue(), *method, - channel->GetHost(), MillisecondsToTimespec(deadline)); + grpc_call *wrapped_call; + if (args[3]->IsString()) { + NanUtf8String host_override(args[3]); + wrapped_call = grpc_channel_create_call( + wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS, + CompletionQueueAsyncWorker::GetQueue(), *method, + *host_override, MillisecondsToTimespec(deadline)); + } else if (args[3]->IsUndefined() || args[3]->IsNull()) { + wrapped_call = grpc_channel_create_call( + wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS, + CompletionQueueAsyncWorker::GetQueue(), *method, + NULL, MillisecondsToTimespec(deadline)); + } else { + return NanThrowTypeError("Call's fourth argument must be a string"); + } call = new Call(wrapped_call); args.This()->SetHiddenValue(NanNew("channel_"), channel_object); } diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc index 0b7333e450..457a58c057 100644 --- a/src/node/ext/channel.cc +++ b/src/node/ext/channel.cc @@ -59,14 +59,12 @@ using v8::Value; NanCallback *Channel::constructor; Persistent<FunctionTemplate> Channel::fun_tpl; -Channel::Channel(grpc_channel *channel, NanUtf8String *host) - : wrapped_channel(channel), host(host) {} +Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {} Channel::~Channel() { if (wrapped_channel != NULL) { grpc_channel_destroy(wrapped_channel); } - delete host; } void Channel::Init(Handle<Object> exports) { @@ -91,38 +89,31 @@ bool Channel::HasInstance(Handle<Value> val) { grpc_channel *Channel::GetWrappedChannel() { return this->wrapped_channel; } -char *Channel::GetHost() { return **this->host; } - NAN_METHOD(Channel::New) { NanScope(); if (args.IsConstructCall()) { if (!args[0]->IsString()) { - return NanThrowTypeError("Channel expects a string and an object"); + return NanThrowTypeError( + "Channel expects a string, a credential and an object"); } grpc_channel *wrapped_channel; // Owned by the Channel object - NanUtf8String *host = new NanUtf8String(args[0]); - NanUtf8String *host_override = NULL; - if (args[1]->IsUndefined()) { - wrapped_channel = grpc_channel_create(**host, NULL); - } else if (args[1]->IsObject()) { - grpc_credentials *creds = NULL; - Handle<Object> args_hash(args[1]->ToObject()->Clone()); - if (args_hash->HasOwnProperty(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))) { - host_override = new NanUtf8String(args_hash->Get(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))); - } - if (args_hash->HasOwnProperty(NanNew("credentials"))) { - Handle<Value> creds_value = args_hash->Get(NanNew("credentials")); - if (!Credentials::HasInstance(creds_value)) { - return NanThrowTypeError( - "credentials arg must be a Credentials object"); - } - Credentials *creds_object = - ObjectWrap::Unwrap<Credentials>(creds_value->ToObject()); - creds = creds_object->GetWrappedCredentials(); - args_hash->Delete(NanNew("credentials")); - } + NanUtf8String host(args[0]); + grpc_credentials *creds; + if (!Credentials::HasInstance(args[1])) { + return NanThrowTypeError( + "Channel's second argument must be a credential"); + } + Credentials *creds_object = ObjectWrap::Unwrap<Credentials>( + args[1]->ToObject()); + creds = creds_object->GetWrappedCredentials(); + grpc_channel_args *channel_args_ptr; + if (args[2]->IsUndefined()) { + channel_args_ptr = NULL; + wrapped_channel = grpc_insecure_channel_create(*host, NULL); + } else if (args[2]->IsObject()) { + Handle<Object> args_hash(args[2]->ToObject()->Clone()); Handle<Array> keys(args_hash->GetOwnPropertyNames()); grpc_channel_args channel_args; channel_args.num_args = keys->Length(); @@ -149,27 +140,25 @@ NAN_METHOD(Channel::New) { return NanThrowTypeError("Arg values must be strings"); } } - if (creds == NULL) { - wrapped_channel = grpc_channel_create(**host, &channel_args); - } else { - wrapped_channel = - grpc_secure_channel_create(creds, **host, &channel_args); - } - free(channel_args.args); + channel_args_ptr = &channel_args; } else { return NanThrowTypeError("Channel expects a string and an object"); } - Channel *channel; - if (host_override == NULL) { - channel = new Channel(wrapped_channel, host); + if (creds == NULL) { + wrapped_channel = grpc_insecure_channel_create(*host, channel_args_ptr); } else { - channel = new Channel(wrapped_channel, host_override); + wrapped_channel = + grpc_secure_channel_create(creds, *host, channel_args_ptr); + } + if (channel_args_ptr != NULL) { + free(channel_args_ptr->args); } + Channel *channel = new Channel(wrapped_channel); channel->Wrap(args.This()); NanReturnValue(args.This()); } else { - const int argc = 2; - Local<Value> argv[argc] = {args[0], args[1]}; + const int argc = 3; + Local<Value> argv[argc] = {args[0], args[1], args[2]}; NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv)); } } diff --git a/src/node/ext/channel.h b/src/node/ext/channel.h index 6725ebb03f..e2182cb45c 100644 --- a/src/node/ext/channel.h +++ b/src/node/ext/channel.h @@ -53,11 +53,8 @@ class Channel : public ::node::ObjectWrap { /* Returns the grpc_channel struct that this object wraps */ grpc_channel *GetWrappedChannel(); - /* Return the hostname that this channel connects to */ - char *GetHost(); - private: - explicit Channel(grpc_channel *channel, NanUtf8String *host); + explicit Channel(grpc_channel *channel); ~Channel(); // Prevent copying @@ -71,7 +68,6 @@ class Channel : public ::node::ObjectWrap { static v8::Persistent<v8::FunctionTemplate> fun_tpl; grpc_channel *wrapped_channel; - NanUtf8String *host; }; } // namespace node diff --git a/src/node/ext/credentials.cc b/src/node/ext/credentials.cc index d6cff0631d..21d61f1a7f 100644 --- a/src/node/ext/credentials.cc +++ b/src/node/ext/credentials.cc @@ -81,6 +81,8 @@ void Credentials::Init(Handle<Object> exports) { NanNew<FunctionTemplate>(CreateGce)->GetFunction()); ctr->Set(NanNew("createIam"), NanNew<FunctionTemplate>(CreateIam)->GetFunction()); + ctr->Set(NanNew("createInsecure"), + NanNew<FunctionTemplate>(CreateInsecure)->GetFunction()); constructor = new NanCallback(ctr); exports->Set(NanNew("Credentials"), ctr); } @@ -92,9 +94,6 @@ bool Credentials::HasInstance(Handle<Value> val) { Handle<Value> Credentials::WrapStruct(grpc_credentials *credentials) { NanEscapableScope(); - if (credentials == NULL) { - return NanEscapeScope(NanNull()); - } const int argc = 1; Handle<Value> argv[argc] = { NanNew<External>(reinterpret_cast<void *>(credentials))}; @@ -128,7 +127,11 @@ NAN_METHOD(Credentials::New) { NAN_METHOD(Credentials::CreateDefault) { NanScope(); - NanReturnValue(WrapStruct(grpc_google_default_credentials_create())); + grpc_credentials *creds = grpc_google_default_credentials_create(); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); } NAN_METHOD(Credentials::CreateSsl) { @@ -152,9 +155,12 @@ NAN_METHOD(Credentials::CreateSsl) { return NanThrowTypeError( "createSSl's third argument must be a Buffer if provided"); } - - NanReturnValue(WrapStruct(grpc_ssl_credentials_create( - root_certs, key_cert_pair.private_key == NULL ? NULL : &key_cert_pair))); + grpc_credentials *creds = grpc_ssl_credentials_create( + root_certs, key_cert_pair.private_key == NULL ? NULL : &key_cert_pair); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); } NAN_METHOD(Credentials::CreateComposite) { @@ -169,13 +175,21 @@ NAN_METHOD(Credentials::CreateComposite) { } Credentials *creds1 = ObjectWrap::Unwrap<Credentials>(args[0]->ToObject()); Credentials *creds2 = ObjectWrap::Unwrap<Credentials>(args[1]->ToObject()); - NanReturnValue(WrapStruct(grpc_composite_credentials_create( - creds1->wrapped_credentials, creds2->wrapped_credentials))); + grpc_credentials *creds = grpc_composite_credentials_create( + creds1->wrapped_credentials, creds2->wrapped_credentials); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); } NAN_METHOD(Credentials::CreateGce) { NanScope(); - NanReturnValue(WrapStruct(grpc_compute_engine_credentials_create())); + grpc_credentials *creds = grpc_compute_engine_credentials_create(); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); } NAN_METHOD(Credentials::CreateIam) { @@ -188,8 +202,17 @@ NAN_METHOD(Credentials::CreateIam) { } NanUtf8String auth_token(args[0]); NanUtf8String auth_selector(args[1]); - NanReturnValue( - WrapStruct(grpc_iam_credentials_create(*auth_token, *auth_selector))); + grpc_credentials *creds = grpc_iam_credentials_create(*auth_token, + *auth_selector); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); +} + +NAN_METHOD(Credentials::CreateInsecure) { + NanScope(); + NanReturnValue(WrapStruct(NULL)); } } // namespace node diff --git a/src/node/ext/credentials.h b/src/node/ext/credentials.h index 794736fe1a..62957e61c3 100644 --- a/src/node/ext/credentials.h +++ b/src/node/ext/credentials.h @@ -68,6 +68,7 @@ class Credentials : public ::node::ObjectWrap { static NAN_METHOD(CreateGce); static NAN_METHOD(CreateFake); static NAN_METHOD(CreateIam); + static NAN_METHOD(CreateInsecure); static NanCallback *constructor; // Used for typechecking instances of this javascript class static v8::Persistent<v8::FunctionTemplate> fun_tpl; diff --git a/src/node/ext/server.cc b/src/node/ext/server.cc index 8554fce777..1dc179db3d 100644 --- a/src/node/ext/server.cc +++ b/src/node/ext/server.cc @@ -136,10 +136,6 @@ void Server::Init(Handle<Object> exports) { tpl, "addHttp2Port", NanNew<FunctionTemplate>(AddHttp2Port)->GetFunction()); - NanSetPrototypeTemplate( - tpl, "addSecureHttp2Port", - NanNew<FunctionTemplate>(AddSecureHttp2Port)->GetFunction()); - NanSetPrototypeTemplate(tpl, "start", NanNew<FunctionTemplate>(Start)->GetFunction()); @@ -248,43 +244,35 @@ NAN_METHOD(Server::RequestCall) { NAN_METHOD(Server::AddHttp2Port) { NanScope(); if (!HasInstance(args.This())) { - return NanThrowTypeError("addHttp2Port can only be called on a Server"); - } - if (!args[0]->IsString()) { - return NanThrowTypeError("addHttp2Port's argument must be a String"); - } - Server *server = ObjectWrap::Unwrap<Server>(args.This()); - if (server->wrapped_server == NULL) { - return NanThrowError("addHttp2Port cannot be called on a shut down Server"); - } - NanReturnValue(NanNew<Number>(grpc_server_add_http2_port( - server->wrapped_server, *NanUtf8String(args[0])))); -} - -NAN_METHOD(Server::AddSecureHttp2Port) { - NanScope(); - if (!HasInstance(args.This())) { return NanThrowTypeError( - "addSecureHttp2Port can only be called on a Server"); + "addHttp2Port can only be called on a Server"); } if (!args[0]->IsString()) { return NanThrowTypeError( - "addSecureHttp2Port's first argument must be a String"); + "addHttp2Port's first argument must be a String"); } if (!ServerCredentials::HasInstance(args[1])) { return NanThrowTypeError( - "addSecureHttp2Port's second argument must be ServerCredentials"); + "addHttp2Port's second argument must be ServerCredentials"); } Server *server = ObjectWrap::Unwrap<Server>(args.This()); if (server->wrapped_server == NULL) { return NanThrowError( - "addSecureHttp2Port cannot be called on a shut down Server"); + "addHttp2Port cannot be called on a shut down Server"); } - ServerCredentials *creds = ObjectWrap::Unwrap<ServerCredentials>( + ServerCredentials *creds_object = ObjectWrap::Unwrap<ServerCredentials>( args[1]->ToObject()); - NanReturnValue(NanNew<Number>(grpc_server_add_secure_http2_port( - server->wrapped_server, *NanUtf8String(args[0]), - creds->GetWrappedServerCredentials()))); + grpc_server_credentials *creds = creds_object->GetWrappedServerCredentials(); + int port; + if (creds == NULL) { + port = grpc_server_add_insecure_http2_port(server->wrapped_server, + *NanUtf8String(args[0])); + } else { + port = grpc_server_add_secure_http2_port(server->wrapped_server, + *NanUtf8String(args[0]), + creds); + } + NanReturnValue(NanNew<Number>(port)); } NAN_METHOD(Server::Start) { diff --git a/src/node/ext/server.h b/src/node/ext/server.h index 5b4b18a0e0..faab7e3418 100644 --- a/src/node/ext/server.h +++ b/src/node/ext/server.h @@ -66,7 +66,6 @@ class Server : public ::node::ObjectWrap { static NAN_METHOD(New); static NAN_METHOD(RequestCall); static NAN_METHOD(AddHttp2Port); - static NAN_METHOD(AddSecureHttp2Port); static NAN_METHOD(Start); static NAN_METHOD(Shutdown); static NanCallback *constructor; diff --git a/src/node/ext/server_credentials.cc b/src/node/ext/server_credentials.cc index 66aaa3300f..1b8e7b43fb 100644 --- a/src/node/ext/server_credentials.cc +++ b/src/node/ext/server_credentials.cc @@ -73,6 +73,8 @@ void ServerCredentials::Init(Handle<Object> exports) { Handle<Function> ctr = tpl->GetFunction(); ctr->Set(NanNew("createSsl"), NanNew<FunctionTemplate>(CreateSsl)->GetFunction()); + ctr->Set(NanNew("createInsecure"), + NanNew<FunctionTemplate>(CreateInsecure)->GetFunction()); constructor = new NanCallback(ctr); exports->Set(NanNew("ServerCredentials"), ctr); } @@ -85,9 +87,6 @@ bool ServerCredentials::HasInstance(Handle<Value> val) { Handle<Value> ServerCredentials::WrapStruct( grpc_server_credentials *credentials) { NanEscapableScope(); - if (credentials == NULL) { - return NanEscapeScope(NanNull()); - } const int argc = 1; Handle<Value> argv[argc] = { NanNew<External>(reinterpret_cast<void *>(credentials))}; @@ -138,8 +137,19 @@ NAN_METHOD(ServerCredentials::CreateSsl) { return NanThrowTypeError("createSsl's third argument must be a Buffer"); } key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]); - NanReturnValue(WrapStruct( - grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1))); + // TODO Add a force_client_auth parameter and pass it as the last parameter + // here. + grpc_server_credentials *creds = + grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1, 0); + if (creds == NULL) { + NanReturnNull(); + } + NanReturnValue(WrapStruct(creds)); +} + +NAN_METHOD(ServerCredentials::CreateInsecure) { + NanScope(); + NanReturnValue(WrapStruct(NULL)); } } // namespace node diff --git a/src/node/ext/server_credentials.h b/src/node/ext/server_credentials.h index 80747504a1..63903f663c 100644 --- a/src/node/ext/server_credentials.h +++ b/src/node/ext/server_credentials.h @@ -63,6 +63,7 @@ class ServerCredentials : public ::node::ObjectWrap { static NAN_METHOD(New); static NAN_METHOD(CreateSsl); + static NAN_METHOD(CreateInsecure); static NanCallback *constructor; // Used for typechecking instances of this javascript class static v8::Persistent<v8::FunctionTemplate> fun_tpl; diff --git a/src/node/index.js b/src/node/index.js index d81e780443..b26ab35f2c 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -48,7 +48,7 @@ var grpc = require('bindings')('grpc'); * @param {ProtoBuf.Reflect.Namespace} value The ProtoBuf object to load. * @return {Object<string, *>} The resulting gRPC object */ -function loadObject(value) { +exports.loadObject = function loadObject(value) { var result = {}; if (value.className === 'Namespace') { _.each(value.children, function(child) { @@ -62,7 +62,9 @@ function loadObject(value) { } else { return value; } -} +}; + +var loadObject = exports.loadObject; /** * Load a gRPC object from a .proto file. @@ -71,7 +73,7 @@ function loadObject(value) { * 'json'. Defaults to 'proto' * @return {Object<string, *>} The resulting gRPC object */ -function load(filename, format) { +exports.load = function load(filename, format) { if (!format) { format = 'proto'; } @@ -88,7 +90,7 @@ function load(filename, format) { } return loadObject(builder.ns); -} +}; /** * Get a function that a client can use to update metadata with authentication @@ -97,7 +99,7 @@ function load(filename, format) { * @param {Object} credential The credential object to use * @return {function(Object, callback)} Metadata updater function */ -function getGoogleAuthDelegate(credential) { +exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) { /** * Update a metadata object with authentication information. * @param {string} authURI The uri to authenticate to @@ -120,20 +122,10 @@ function getGoogleAuthDelegate(credential) { callback(null, metadata); }); }; -} - -/** - * See docs for loadObject - */ -exports.loadObject = loadObject; +}; /** - * See docs for load - */ -exports.load = load; - -/** - * See docs for Server + * @see module:src/server.Server */ exports.Server = server.Server; @@ -141,6 +133,7 @@ exports.Server = server.Server; * Status name to code number mapping */ exports.status = grpc.status; + /** * Call error name to code number mapping */ @@ -156,6 +149,7 @@ exports.Credentials = grpc.Credentials; */ exports.ServerCredentials = grpc.ServerCredentials; -exports.getGoogleAuthDelegate = getGoogleAuthDelegate; - +/** + * @see module:src/client.makeClientConstructor + */ exports.makeGenericClientConstructor = client.makeClientConstructor; diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js index e810e68e45..6d6f9a349e 100644 --- a/src/node/interop/interop_client.js +++ b/src/node/interop/interop_client.js @@ -67,11 +67,8 @@ function zeroBuffer(size) { * primarily for use with mocha */ function emptyUnary(client, done) { - var call = client.emptyCall({}, function(err, resp) { + client.emptyCall({}, function(err, resp) { assert.ifError(err); - }); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); if (done) { done(); } @@ -92,13 +89,10 @@ function largeUnary(client, done) { body: zeroBuffer(271828) } }; - var call = client.unaryCall(arg, function(err, resp) { + client.unaryCall(arg, function(err, resp) { assert.ifError(err); assert.strictEqual(resp.payload.type, 'COMPRESSABLE'); assert.strictEqual(resp.payload.body.length, 314159); - }); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); if (done) { done(); } @@ -115,9 +109,6 @@ function clientStreaming(client, done) { var call = client.streamingInputCall(function(err, resp) { assert.ifError(err); assert.strictEqual(resp.aggregated_payload_size, 74922); - }); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); if (done) { done(); } @@ -268,7 +259,7 @@ function cancelAfterFirstResponse(client, done) { function timeoutOnSleepingServer(client, done) { var deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 1); - var call = client.fullDuplexCall(null, deadline); + var call = client.fullDuplexCall(null, {deadline: deadline}); call.write({ payload: {body: zeroBuffer(27182)} }); @@ -302,15 +293,14 @@ function authTest(expected_user, scope, client, done) { fill_username: true, fill_oauth_scope: true }; - var call = client.unaryCall(arg, function(err, resp) { + client.unaryCall(arg, function(err, resp) { assert.ifError(err); assert.strictEqual(resp.payload.type, 'COMPRESSABLE'); assert.strictEqual(resp.payload.body.length, 314159); assert.strictEqual(resp.username, expected_user); - assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); - }); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); + if (scope) { + assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); + } if (done) { done(); } @@ -340,17 +330,14 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) { }; var makeTestCall = function(error, client_metadata) { assert.ifError(error); - var call = client.unaryCall(arg, function(err, resp) { + client.unaryCall(arg, function(err, resp) { assert.ifError(err); assert.strictEqual(resp.username, expected_user); assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); - }); - call.on('status', function(status) { - assert.strictEqual(status.code, grpc.status.OK); if (done) { done(); } - }); + }, client_metadata); }; if (per_rpc) { updateMetadata('', {}, makeTestCall); @@ -358,7 +345,6 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) { client.updateMetadata = updateMetadata; makeTestCall(null, {}); } - }); }); } @@ -397,6 +383,7 @@ var test_cases = { function runTest(address, host_override, test_case, tls, test_ca, done) { // TODO(mlumish): enable TLS functionality var options = {}; + var creds; if (tls) { var ca_path; if (test_ca) { @@ -405,13 +392,15 @@ function runTest(address, host_override, test_case, tls, test_ca, done) { ca_path = process.env.SSL_CERT_FILE; } var ca_data = fs.readFileSync(ca_path); - var creds = grpc.Credentials.createSsl(ca_data); - options.credentials = creds; + creds = grpc.Credentials.createSsl(ca_data); if (host_override) { options['grpc.ssl_target_name_override'] = host_override; + options['grpc.default_authority'] = host_override; } + } else { + creds = grpc.Credentials.createInsecure(); } - var client = new testProto.TestService(address, options); + var client = new testProto.TestService(address, creds, options); test_cases[test_case](client, done); } diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js index 505c6bb537..ece22cce31 100644 --- a/src/node/interop/interop_server.js +++ b/src/node/interop/interop_server.js @@ -161,7 +161,7 @@ function handleHalfDuplex(call) { function getServer(port, tls) { // TODO(mlumish): enable TLS functionality var options = {}; - var server_creds = null; + var server_creds; if (tls) { var key_path = path.join(__dirname, '../test/data/server1.key'); var pem_path = path.join(__dirname, '../test/data/server1.pem'); @@ -171,6 +171,8 @@ function getServer(port, tls) { server_creds = grpc.ServerCredentials.createSsl(null, key_data, pem_data); + } else { + server_creds = grpc.ServerCredentials.createInsecure(); } var server = new grpc.Server(options); server.addProtoService(testProto.TestService.service, { diff --git a/src/node/jsdoc_conf.json b/src/node/jsdoc_conf.json new file mode 100644 index 0000000000..876a8e19c6 --- /dev/null +++ b/src/node/jsdoc_conf.json @@ -0,0 +1,22 @@ +{ + "tags": { + "allowUnknownTags": true + }, + "source": { + "include": [ "index.js", "src" ], + "includePattern": ".+\\.js(doc)?$", + "excludePattern": "(^|\\/|\\\\)_" + }, + "opts": { + "package": "package.json", + "readme": "README.md" + }, + "plugins": [], + "templates": { + "cleverLinks": false, + "monospaceLinks": false, + "default": { + "outputSourceFiles": true + } + } +} diff --git a/src/node/package.json b/src/node/package.json index 1caf158792..756d41b0af 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -21,7 +21,8 @@ }, "scripts": { "lint": "node ./node_modules/jshint/bin/jshint src test examples interop index.js", - "test": "node ./node_modules/mocha/bin/mocha && npm run-script lint" + "test": "node ./node_modules/mocha/bin/mocha && npm run-script lint", + "gen_docs": "./node_modules/.bin/jsdoc -c jsdoc_conf.json" }, "dependencies": { "bindings": "^1.2.0", @@ -32,6 +33,7 @@ "devDependencies": { "async": "^0.9.0", "google-auth-library": "^0.9.2", + "jsdoc": "^3.3.2", "jshint": "^2.5.0", "minimist": "^1.1.0", "mocha": "~1.21.0", diff --git a/src/node/src/client.js b/src/node/src/client.js index d89c656c07..5cde438572 100644 --- a/src/node/src/client.js +++ b/src/node/src/client.js @@ -31,6 +31,11 @@ * */ +/** + * Server module + * @module + */ + 'use strict'; var _ = require('lodash'); @@ -72,6 +77,7 @@ function ClientWritableStream(call, serialize) { /** * Attempt to write the given chunk. Calls the callback when done. This is an * implementation of a method needed for implementing stream.Writable. + * @access private * @param {Buffer} chunk The chunk to write * @param {string} encoding Ignored * @param {function(Error=)} callback Called when the write is complete @@ -110,6 +116,7 @@ function ClientReadableStream(call, deserialize) { /** * Read the next object from the stream. + * @access private * @param {*} size Ignored because we use objectMode=true */ function _read(size) { @@ -201,6 +208,25 @@ ClientWritableStream.prototype.getPeer = getPeer; ClientDuplexStream.prototype.getPeer = getPeer; /** + * Get a call object built with the provided options. Keys for options are + * 'deadline', which takes a date or number, and 'host', which takes a string + * and overrides the hostname to connect to. + * @param {Object} options Options map. + */ +function getCall(channel, method, options) { + var deadline; + var host; + if (options) { + deadline = options.deadline; + host = options.host; + } + if (deadline === undefined) { + deadline = Infinity; + } + return new grpc.Call(channel, method, deadline, host); +} + +/** * Get a function that can make unary requests to the specified method. * @param {string} method The name of the method to request * @param {function(*):Buffer} serialize The serialization function for inputs @@ -219,17 +245,13 @@ function makeUnaryRequestFunction(method, serialize, deserialize) { * response is received * @param {array=} metadata Array of metadata key/value pairs to add to the * call - * @param {(number|Date)=} deadline The deadline for processing this request. - * Defaults to infinite future + * @param {Object=} options Options map * @return {EventEmitter} An event emitter for stream related events */ - function makeUnaryRequest(argument, callback, metadata, deadline) { + function makeUnaryRequest(argument, callback, metadata, options) { /* jshint validthis: true */ - if (deadline === undefined) { - deadline = Infinity; - } var emitter = new EventEmitter(); - var call = new grpc.Call(this.channel, method, deadline); + var call = getCall(this.channel, method, options); if (metadata === null || metadata === undefined) { metadata = {}; } @@ -293,16 +315,12 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) { * response is received * @param {array=} metadata Array of metadata key/value pairs to add to the * call - * @param {(number|Date)=} deadline The deadline for processing this request. - * Defaults to infinite future + * @param {Object=} options Options map * @return {EventEmitter} An event emitter for stream related events */ - function makeClientStreamRequest(callback, metadata, deadline) { + function makeClientStreamRequest(callback, metadata, options) { /* jshint validthis: true */ - if (deadline === undefined) { - deadline = Infinity; - } - var call = new grpc.Call(this.channel, method, deadline); + var call = getCall(this.channel, method, options); if (metadata === null || metadata === undefined) { metadata = {}; } @@ -367,16 +385,12 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) { * serialize * @param {array=} metadata Array of metadata key/value pairs to add to the * call - * @param {(number|Date)=} deadline The deadline for processing this request. - * Defaults to infinite future + * @param {Object} options Options map * @return {EventEmitter} An event emitter for stream related events */ - function makeServerStreamRequest(argument, metadata, deadline) { + function makeServerStreamRequest(argument, metadata, options) { /* jshint validthis: true */ - if (deadline === undefined) { - deadline = Infinity; - } - var call = new grpc.Call(this.channel, method, deadline); + var call = getCall(this.channel, method, options); if (metadata === null || metadata === undefined) { metadata = {}; } @@ -439,16 +453,12 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) { * @this {SurfaceClient} Client object. Must have a channel member. * @param {array=} metadata Array of metadata key/value pairs to add to the * call - * @param {(number|Date)=} deadline The deadline for processing this request. - * Defaults to infinite future + * @param {Options} options Options map * @return {EventEmitter} An event emitter for stream related events */ - function makeBidiStreamRequest(metadata, deadline) { + function makeBidiStreamRequest(metadata, options) { /* jshint validthis: true */ - if (deadline === undefined) { - deadline = Infinity; - } - var call = new grpc.Call(this.channel, method, deadline); + var call = getCall(this.channel, method, options); if (metadata === null || metadata === undefined) { metadata = {}; } @@ -516,19 +526,21 @@ var requester_makers = { * requestSerialize: function to serialize request objects * responseDeserialize: function to deserialize response objects * @param {Object} methods An object mapping method names to method attributes - * @param {string} serviceName The name of the service + * @param {string} serviceName The fully qualified name of the service * @return {function(string, Object)} New client constructor */ -function makeClientConstructor(methods, serviceName) { +exports.makeClientConstructor = function(methods, serviceName) { /** * Create a client with the given methods * @constructor * @param {string} address The address of the server to connect to + * @param {grpc.Credentials} credentials Credentials to use to connect + * to the server * @param {Object} options Options to pass to the underlying channel * @param {function(string, Object, function)=} updateMetadata function to * update the metadata for each request */ - function Client(address, options, updateMetadata) { + function Client(address, credentials, options, updateMetadata) { if (!updateMetadata) { updateMetadata = function(uri, metadata, callback) { callback(null, metadata); @@ -538,9 +550,11 @@ function makeClientConstructor(methods, serviceName) { options = {}; } options['grpc.primary_user_agent'] = 'grpc-node/' + version; - this.channel = new grpc.Channel(address, options); - this.server_address = address.replace(/\/$/, ''); - this.auth_uri = this.server_address + '/' + serviceName; + this.channel = new grpc.Channel(address, credentials, options); + // Remove the optional DNS scheme, trailing port, and trailing backslash + address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2'); + this.server_address = address; + this.auth_uri = 'https://' + this.server_address + '/' + serviceName; this.updateMetadata = updateMetadata; } @@ -568,7 +582,7 @@ function makeClientConstructor(methods, serviceName) { }); return Client; -} +}; /** * Creates a constructor for clients for the given service @@ -576,22 +590,19 @@ function makeClientConstructor(methods, serviceName) { * for * @return {function(string, Object)} New client constructor */ -function makeProtobufClientConstructor(service) { +exports.makeProtobufClientConstructor = function(service) { var method_attrs = common.getProtobufServiceAttrs(service, service.name); - var Client = makeClientConstructor(method_attrs); + var Client = exports.makeClientConstructor( + method_attrs, common.fullyQualifiedName(service)); Client.service = service; - return Client; -} - -exports.makeClientConstructor = makeClientConstructor; - -exports.makeProtobufClientConstructor = makeProtobufClientConstructor; +}; /** - * See docs for client.status + * Map of status code names to status codes */ exports.status = grpc.status; + /** * See docs for client.callError */ diff --git a/src/node/src/common.js b/src/node/src/common.js index feaa859a4f..5551ebeec8 100644 --- a/src/node/src/common.js +++ b/src/node/src/common.js @@ -31,6 +31,10 @@ * */ +/** + * @module + */ + 'use strict'; var _ = require('lodash'); @@ -40,7 +44,7 @@ var _ = require('lodash'); * @param {function()} cls The constructor of the message type to deserialize * @return {function(Buffer):cls} The deserialization function */ -function deserializeCls(cls) { +exports.deserializeCls = function deserializeCls(cls) { /** * Deserialize a buffer to a message object * @param {Buffer} arg_buf The buffer to deserialize @@ -51,14 +55,16 @@ function deserializeCls(cls) { // and longs as strings (second argument) return cls.decode(arg_buf).toRaw(false, true); }; -} +}; + +var deserializeCls = exports.deserializeCls; /** * Get a function that serializes objects to a buffer by protobuf class. * @param {function()} Cls The constructor of the message type to serialize * @return {function(Cls):Buffer} The serialization function */ -function serializeCls(Cls) { +exports.serializeCls = function serializeCls(Cls) { /** * Serialize an object to a Buffer * @param {Object} arg The object to serialize @@ -67,14 +73,16 @@ function serializeCls(Cls) { return function serialize(arg) { return new Buffer(new Cls(arg).encode().toBuffer()); }; -} +}; + +var serializeCls = exports.serializeCls; /** * Get the fully qualified (dotted) name of a ProtoBuf.Reflect value. * @param {ProtoBuf.Reflect.Namespace} value The value to get the name of * @return {string} The fully qualified name of the value */ -function fullyQualifiedName(value) { +exports.fullyQualifiedName = function fullyQualifiedName(value) { if (value === null || value === undefined) { return ''; } @@ -89,7 +97,9 @@ function fullyQualifiedName(value) { } } return name; -} +}; + +var fullyQualifiedName = exports.fullyQualifiedName; /** * Wrap a function to pass null-like values through without calling it. If no @@ -97,7 +107,7 @@ function fullyQualifiedName(value) { * @param {?function} func The function to wrap * @return {function} The wrapped function */ -function wrapIgnoreNull(func) { +exports.wrapIgnoreNull = function wrapIgnoreNull(func) { if (!func) { return _.identity; } @@ -107,14 +117,14 @@ function wrapIgnoreNull(func) { } return func(arg); }; -} +}; /** * Return a map from method names to method attributes for the service. * @param {ProtoBuf.Reflect.Service} service The service to get attributes for * @return {Object} The attributes map */ -function getProtobufServiceAttrs(service) { +exports.getProtobufServiceAttrs = function getProtobufServiceAttrs(service) { var prefix = '/' + fullyQualifiedName(service) + '/'; return _.object(_.map(service.children, function(method) { return [_.camelCase(method.name), { @@ -127,26 +137,4 @@ function getProtobufServiceAttrs(service) { responseDeserialize: deserializeCls(method.resolvedResponseType.build()) }]; })); -} - -/** - * See docs for deserializeCls - */ -exports.deserializeCls = deserializeCls; - -/** - * See docs for serializeCls - */ -exports.serializeCls = serializeCls; - -/** - * See docs for fullyQualifiedName - */ -exports.fullyQualifiedName = fullyQualifiedName; - -/** - * See docs for wrapIgnoreNull - */ -exports.wrapIgnoreNull = wrapIgnoreNull; - -exports.getProtobufServiceAttrs = getProtobufServiceAttrs; +}; diff --git a/src/node/src/server.js b/src/node/src/server.js index e876313d96..5c62f5990c 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -31,6 +31,11 @@ * */ +/** + * Server module + * @module + */ + 'use strict'; var _ = require('lodash'); @@ -50,6 +55,7 @@ var EventEmitter = require('events').EventEmitter; /** * Handle an error on a call by sending it as a status + * @access private * @param {grpc.Call} call The call to send the error on * @param {Object} error The error object */ @@ -82,6 +88,7 @@ function handleError(call, error) { /** * Wait for the client to close, then emit a cancelled event if the client * cancelled. + * @access private * @param {grpc.Call} call The call object to wait on * @param {EventEmitter} emitter The event emitter to emit the cancelled event * on @@ -102,6 +109,7 @@ function waitForCancel(call, emitter) { /** * Send a response to a unary or client streaming call. + * @access private * @param {grpc.Call} call The call to respond on * @param {*} value The value to respond with * @param {function(*):Buffer=} serialize Serialization function for the @@ -130,6 +138,7 @@ function sendUnaryResponse(call, value, serialize, metadata) { /** * Initialize a writable stream. This is used for both the writable and duplex * stream constructors. + * @access private * @param {Writable} stream The stream to set up * @param {function(*):Buffer=} Serialization function for responses */ @@ -203,6 +212,7 @@ function setUpWritable(stream, serialize) { /** * Initialize a readable stream. This is used for both the readable and duplex * stream constructors. + * @access private * @param {Readable} stream The stream to initialize * @param {function(Buffer):*=} deserialize Deserialization function for * incoming data. @@ -242,6 +252,7 @@ function ServerWritableStream(call, serialize) { /** * Start writing a chunk of data. This is an implementation of a method required * for implementing stream.Writable. + * @access private * @param {Buffer} chunk The chunk of data to write * @param {string} encoding Ignored * @param {function(Error=)} callback Callback to indicate that the write is @@ -266,6 +277,11 @@ function _write(chunk, encoding, callback) { ServerWritableStream.prototype._write = _write; +/** + * Send the initial metadata for a writable stream. + * @param {Object<String, Array<(String|Buffer)>>} responseMetadata Metadata + * to send + */ function sendMetadata(responseMetadata) { /* jshint validthis: true */ if (!this.call.metadataSent) { @@ -281,6 +297,10 @@ function sendMetadata(responseMetadata) { } } +/** + * @inheritdoc + * @alias module:src/server~ServerWritableStream#sendMetadata + */ ServerWritableStream.prototype.sendMetadata = sendMetadata; util.inherits(ServerReadableStream, Readable); @@ -301,6 +321,7 @@ function ServerReadableStream(call, deserialize) { /** * Start reading from the gRPC data source. This is an implementation of a * method required for implementing stream.Readable + * @access private * @param {number} size Ignored */ function _read(size) { @@ -388,6 +409,7 @@ ServerDuplexStream.prototype.getPeer = getPeer; /** * Fully handle a unary call + * @access private * @param {grpc.Call} call The call to handle * @param {Object} handler Request handler object for the method that was called * @param {Object} metadata Metadata from the client @@ -442,6 +464,7 @@ function handleUnary(call, handler, metadata) { /** * Fully handle a server streaming call + * @access private * @param {grpc.Call} call The call to handle * @param {Object} handler Request handler object for the method that was called * @param {Object} metadata Metadata from the client @@ -470,6 +493,7 @@ function handleServerStreaming(call, handler, metadata) { /** * Fully handle a client streaming call + * @access private * @param {grpc.Call} call The call to handle * @param {Object} handler Request handler object for the method that was called * @param {Object} metadata Metadata from the client @@ -504,6 +528,7 @@ function handleClientStreaming(call, handler, metadata) { /** * Fully handle a bidirectional streaming call + * @access private * @param {grpc.Call} call The call to handle * @param {Object} handler Request handler object for the method that was called * @param {Object} metadata Metadata from the client @@ -587,7 +612,8 @@ function Server(options) { } server.requestCall(handleNewCall); }; - /** Shuts down the server. + /** + * Shuts down the server. */ this.shutdown = function() { server.shutdown(); @@ -621,6 +647,15 @@ Server.prototype.register = function(name, handler, serialize, deserialize, return true; }; +/** + * Add a service to the server, with a corresponding implementation. If you are + * generating this from a proto file, you should instead use + * addProtoService. + * @param {Object<String, *>} service The service descriptor, as + * {@link module:src/common.getProtobufServiceAttrs} returns + * @param {Object<String, function>} implementation Map of method names to + * method implementation for the provided service. + */ Server.prototype.addService = function(service, implementation) { if (this.started) { throw new Error('Can\'t add a service to a started server.'); @@ -658,6 +693,12 @@ Server.prototype.addService = function(service, implementation) { }); }; +/** + * Add a proto service to the server, with a corresponding implementation + * @param {Protobuf.Reflect.Service} service The proto service descriptor + * @param {Object<String, function>} implementation Map of method names to + * method implementation for the provided service. + */ Server.prototype.addProtoService = function(service, implementation) { this.addService(common.getProtobufServiceAttrs(service), implementation); }; @@ -673,14 +714,10 @@ Server.prototype.bind = function(port, creds) { if (this.started) { throw new Error('Can\'t bind an already running server to an address'); } - if (creds) { - return this._server.addSecureHttp2Port(port, creds); - } else { - return this._server.addHttp2Port(port); - } + return this._server.addHttp2Port(port, creds); }; /** - * See documentation for Server + * @see module:src/server~Server */ exports.Server = Server; diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js index 942c31ac68..8d0f20b074 100644 --- a/src/node/test/call_test.js +++ b/src/node/test/call_test.js @@ -48,14 +48,17 @@ function getDeadline(timeout_secs) { return deadline; } +var insecureCreds = grpc.Credentials.createInsecure(); + describe('call', function() { var channel; var server; before(function() { server = new grpc.Server(); - var port = server.addHttp2Port('localhost:0'); + var port = server.addHttp2Port('localhost:0', + grpc.ServerCredentials.createInsecure()); server.start(); - channel = new grpc.Channel('localhost:' + port); + channel = new grpc.Channel('localhost:' + port, insecureCreds); }); after(function() { server.shutdown(); @@ -81,8 +84,13 @@ describe('call', function() { new grpc.Call(channel, 'method', 0); }); }); + it('should accept an optional fourth string parameter', function() { + assert.doesNotThrow(function() { + new grpc.Call(channel, 'method', new Date(), 'host_override'); + }); + }); it('should fail with a closed channel', function() { - var local_channel = new grpc.Channel('hostname'); + var local_channel = new grpc.Channel('hostname', insecureCreds); local_channel.close(); assert.throws(function() { new grpc.Call(channel, 'method'); diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js index 3e61d3bbc6..c991d7b25b 100644 --- a/src/node/test/channel_test.js +++ b/src/node/test/channel_test.js @@ -36,11 +36,13 @@ var assert = require('assert'); var grpc = require('bindings')('grpc.node'); +var insecureCreds = grpc.Credentials.createInsecure(); + describe('channel', function() { describe('constructor', function() { it('should require a string for the first argument', function() { assert.doesNotThrow(function() { - new grpc.Channel('hostname'); + new grpc.Channel('hostname', insecureCreds); }); assert.throws(function() { new grpc.Channel(); @@ -49,38 +51,49 @@ describe('channel', function() { new grpc.Channel(5); }); }); - it('should accept an object for the second parameter', function() { + it('should require a credential for the second argument', function() { assert.doesNotThrow(function() { - new grpc.Channel('hostname', {}); + new grpc.Channel('hostname', insecureCreds); }); assert.throws(function() { new grpc.Channel('hostname', 5); }); + assert.throws(function() { + new grpc.Channel('hostname'); + }); + }); + it('should accept an object for the third argument', function() { + assert.doesNotThrow(function() { + new grpc.Channel('hostname', insecureCreds, {}); + }); + assert.throws(function() { + new grpc.Channel('hostname', insecureCreds, 'abc'); + }); }); it('should only accept objects with string or int values', function() { assert.doesNotThrow(function() { - new grpc.Channel('hostname', {'key' : 'value'}); + new grpc.Channel('hostname', insecureCreds,{'key' : 'value'}); }); assert.doesNotThrow(function() { - new grpc.Channel('hostname', {'key' : 5}); + new grpc.Channel('hostname', insecureCreds, {'key' : 5}); }); assert.throws(function() { - new grpc.Channel('hostname', {'key' : null}); + new grpc.Channel('hostname', insecureCreds, {'key' : null}); }); assert.throws(function() { - new grpc.Channel('hostname', {'key' : new Date()}); + new grpc.Channel('hostname', insecureCreds, {'key' : new Date()}); }); }); }); describe('close', function() { it('should succeed silently', function() { - var channel = new grpc.Channel('hostname', {}); + var channel = new grpc.Channel('hostname', insecureCreds, {}); assert.doesNotThrow(function() { channel.close(); }); }); it('should be idempotent', function() { - var channel = new grpc.Channel('hostname', {}); + var channel = new grpc.Channel('hostname', insecureCreds, {}); assert.doesNotThrow(function() { channel.close(); channel.close(); @@ -89,7 +102,7 @@ describe('channel', function() { }); describe('getTarget', function() { it('should return a string', function() { - var channel = new grpc.Channel('localhost', {}); + var channel = new grpc.Channel('localhost', insecureCreds, {}); assert.strictEqual(typeof channel.getTarget(), 'string'); }); }); diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js index 5d3baf823d..7574d98b8a 100644 --- a/src/node/test/end_to_end_test.js +++ b/src/node/test/end_to_end_test.js @@ -57,22 +57,23 @@ function multiDone(done, count) { }; } +var insecureCreds = grpc.Credentials.createInsecure(); + describe('end-to-end', function() { var server; var channel; before(function() { server = new grpc.Server(); - var port_num = server.addHttp2Port('0.0.0.0:0'); + var port_num = server.addHttp2Port('0.0.0.0:0', + grpc.ServerCredentials.createInsecure()); server.start(); - channel = new grpc.Channel('localhost:' + port_num); + channel = new grpc.Channel('localhost:' + port_num, insecureCreds); }); after(function() { server.shutdown(); }); it('should start and end a request without error', function(complete) { var done = multiDone(complete, 2); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 3); var status_text = 'xyz'; var call = new grpc.Call(channel, 'dummy_method', @@ -123,8 +124,6 @@ describe('end-to-end', function() { }); it('should successfully send and receive metadata', function(complete) { var done = multiDone(complete, 2); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 3); var status_text = 'xyz'; var call = new grpc.Call(channel, 'dummy_method', @@ -181,8 +180,6 @@ describe('end-to-end', function() { var req_text = 'client_request'; var reply_text = 'server_response'; var done = multiDone(complete, 2); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 3); var status_text = 'success'; var call = new grpc.Call(channel, 'dummy_method', @@ -238,8 +235,6 @@ describe('end-to-end', function() { it('should send multiple messages', function(complete) { var done = multiDone(complete, 2); var requests = ['req1', 'req2']; - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 3); var status_text = 'xyz'; var call = new grpc.Call(channel, 'dummy_method', diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js index bb700cc46c..be4ef1d251 100644 --- a/src/node/test/health_test.js +++ b/src/node/test/health_test.js @@ -54,9 +54,11 @@ describe('Health Checking', function() { new health.Implementation(statusMap)); var healthClient; before(function() { - var port_num = healthServer.bind('0.0.0.0:0'); + var port_num = healthServer.bind('0.0.0.0:0', + grpc.ServerCredentials.createInsecure()); healthServer.start(); - healthClient = new health.Client('localhost:' + port_num); + healthClient = new health.Client('localhost:' + port_num, + grpc.Credentials.createInsecure()); }); after(function() { healthServer.shutdown(); diff --git a/src/node/test/math_client_test.js b/src/node/test/math_client_test.js index f2751857ff..ef01870a4c 100644 --- a/src/node/test/math_client_test.js +++ b/src/node/test/math_client_test.js @@ -51,9 +51,11 @@ var server = require('../examples/math_server.js'); describe('Math client', function() { before(function(done) { - var port_num = server.bind('0.0.0.0:0'); + var port_num = server.bind('0.0.0.0:0', + grpc.ServerCredentials.createInsecure()); server.start(); - math_client = new math.Math('localhost:' + port_num); + math_client = new math.Math('localhost:' + port_num, + grpc.Credentials.createInsecure()); done(); }); after(function() { diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js index 9c7bb465aa..a9df43909e 100644 --- a/src/node/test/server_test.js +++ b/src/node/test/server_test.js @@ -59,16 +59,11 @@ describe('server', function() { it('should bind to an unused port', function() { var port; assert.doesNotThrow(function() { - port = server.addHttp2Port('0.0.0.0:0'); + port = server.addHttp2Port('0.0.0.0:0', + grpc.ServerCredentials.createInsecure()); }); assert(port > 0); }); - }); - describe('addSecureHttp2Port', function() { - var server; - before(function() { - server = new grpc.Server(); - }); it('should bind to an unused port with ssl credentials', function() { var port; var key_path = path.join(__dirname, '../test/data/server1.key'); @@ -77,16 +72,22 @@ describe('server', function() { var pem_data = fs.readFileSync(pem_path); var creds = grpc.ServerCredentials.createSsl(null, key_data, pem_data); assert.doesNotThrow(function() { - port = server.addSecureHttp2Port('0.0.0.0:0', creds); + port = server.addHttp2Port('0.0.0.0:0', creds); }); assert(port > 0); }); }); + describe('addSecureHttp2Port', function() { + var server; + before(function() { + server = new grpc.Server(); + }); + }); describe('listen', function() { var server; before(function() { server = new grpc.Server(); - server.addHttp2Port('0.0.0.0:0'); + server.addHttp2Port('0.0.0.0:0', grpc.ServerCredentials.createInsecure()); }); after(function() { server.shutdown(); diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js index 9005cbd505..dda2f8d127 100644 --- a/src/node/test/surface_test.js +++ b/src/node/test/surface_test.js @@ -47,6 +47,8 @@ var mathService = math_proto.lookup('math.Math'); var _ = require('lodash'); +var server_insecure_creds = grpc.ServerCredentials.createInsecure(); + describe('File loader', function() { it('Should load a proto file by default', function() { assert.doesNotThrow(function() { @@ -122,9 +124,9 @@ describe('Echo service', function() { callback(null, call.request); } }); - var port = server.bind('localhost:0'); + var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(echo_service); - client = new Client('localhost:' + port); + client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); server.start(); }); after(function() { @@ -166,10 +168,11 @@ describe('Generic client and server', function() { callback(null, _.capitalize(call.request)); } }); - var port = server.bind('localhost:0'); + var port = server.bind('localhost:0', server_insecure_creds); server.start(); var Client = grpc.makeGenericClientConstructor(string_service_attrs); - client = new Client('localhost:' + port); + client = new Client('localhost:' + port, + grpc.Credentials.createInsecure()); }); after(function() { server.shutdown(); @@ -214,9 +217,9 @@ describe('Echo metadata', function() { }); } }); - var port = server.bind('localhost:0'); + var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(test_service); - client = new Client('localhost:' + port); + client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); server.start(); }); after(function() { @@ -260,9 +263,8 @@ describe('Echo metadata', function() { }); it('shows the correct user-agent string', function(done) { var version = require('../package.json').version; - var call = client.unary({}, function(err, data) { - assert.ifError(err); - }, {key: ['value']}); + var call = client.unary({}, function(err, data) { assert.ifError(err); }, + {key: ['value']}); call.on('metadata', function(metadata) { assert(_.startsWith(metadata['user-agent'], 'grpc-node/' + version)); done(); @@ -336,9 +338,9 @@ describe('Other conditions', function() { }); } }); - port = server.bind('localhost:0'); + port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(test_service); - client = new Client('localhost:' + port); + client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); server.start(); }); after(function() { @@ -383,7 +385,8 @@ describe('Other conditions', function() { }; var Client = surface_client.makeClientConstructor(test_service_attrs, 'TestService'); - misbehavingClient = new Client('localhost:' + port); + misbehavingClient = new Client('localhost:' + port, + grpc.Credentials.createInsecure()); }); it('should respond correctly to a unary call', function(done) { misbehavingClient.unary(badArg, function(err, data) { @@ -601,9 +604,9 @@ describe('Cancelling surface client', function() { 'fib': function(stream) {}, 'sum': function(stream) {} }); - var port = server.bind('localhost:0'); + var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(mathService); - client = new Client('localhost:' + port); + client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); server.start(); }); after(function() { diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h new file mode 100644 index 0000000000..2e379a7157 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h @@ -0,0 +1,49 @@ +/* + * + * 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. + * + */ + +#import "GRPCCall.h" + +// Helpers for setting and reading headers compatible with OAuth2. +@interface GRPCCall (OAuth2) + +// Setting this property is equivalent to setting "Bearer <passed token>" as the value of the +// request header with key "authorization" (the authorization header). Setting it to nil removes the +// authorization header from the request. +// The value obtained by getting the property is the OAuth2 bearer token if the authorization header +// of the request has the form "Bearer <token>", or nil otherwise. +@property(atomic, copy) NSString *oauth2AccessToken; + +// Returns the value (if any) of the "www-authenticate" response header (the challenge header). +@property(atomic, readonly) NSString *oauth2ChallengeHeader; + +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.m b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m new file mode 100644 index 0000000000..ed39d4b0f7 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.m @@ -0,0 +1,63 @@ +/* + * + * 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. + * + */ + +#import "GRPCCall+OAuth2.h" + +static NSString * const kAuthorizationHeader = @"authorization"; +static NSString * const kBearerPrefix = @"Bearer "; +static NSString * const kChallengeHeader = @"www-authenticate"; + +@implementation GRPCCall (OAuth2) + +- (NSString *)oauth2AccessToken { + NSString *headerValue = self.requestMetadata[kAuthorizationHeader]; + if ([headerValue hasPrefix:kBearerPrefix]) { + return [headerValue substringFromIndex:kBearerPrefix.length]; + } else { + return nil; + } +} + +- (void)setOauth2AccessToken:(NSString *)token { + if (token) { + self.requestMetadata[kAuthorizationHeader] = [kBearerPrefix stringByAppendingString:token]; + } else { + [self.requestMetadata removeObjectForKey:kAuthorizationHeader]; + } +} + +- (NSString *)oauth2ChallengeHeader { + return self.responseMetadata[kChallengeHeader]; +} + +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.h b/src/objective-c/GRPCClient/GRPCCall+Tests.h new file mode 100644 index 0000000000..cca1614606 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.h @@ -0,0 +1,54 @@ +/* + * + * 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. + * + */ + +#import "GRPCCall.h" + +// Methods to let tune down the security of gRPC connections for specific hosts. These shouldn't be +// used in releases, but are sometimes needed for testing. +@interface GRPCCall (Tests) + +// Establish all SSL connections to the provided host using the passed SSL target name and the root +// certificates found in the file at |certsPath|. +// +// Must be called before any gRPC call to that host is made. It's illegal to pass the same host to +// more than one invocation of the methods of this category. ++ (void)useTestCertsPath:(NSString *)certsPath + testName:(NSString *)testName + forHost:(NSString *)host; + +// Establish all connections to the provided host using cleartext instead of SSL. +// +// Must be called before any gRPC call to that host is made. It's illegal to pass the same host to +// more than one invocation of the methods of this category. ++ (void)useInsecureConnectionsForHost:(NSString *)host; +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.m b/src/objective-c/GRPCClient/GRPCCall+Tests.m new file mode 100644 index 0000000000..bade0b2920 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.m @@ -0,0 +1,53 @@ +/* + * + * 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. + * + */ + +#import "GRPCCall+Tests.h" + +#import "private/GRPCHost.h" + +@implementation GRPCCall (Tests) + ++ (void)useTestCertsPath:(NSString *)certsPath + testName:(NSString *)testName + forHost:(NSString *)host { + GRPCHost *hostConfig = [GRPCHost hostWithAddress:host]; + hostConfig.pathToCertificates = certsPath; + hostConfig.hostNameOverride = testName; +} + ++ (void)useInsecureConnectionsForHost:(NSString *)host { + GRPCHost *hostConfig = [GRPCHost hostWithAddress:host]; + hostConfig.secure = NO; +} + +@end diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 9435bf2b35..0f4c811ce4 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -37,8 +37,6 @@ #include <grpc/support/time.h> #import <RxLibrary/GRXConcurrentWriteable.h> -#import "private/GRPCChannel.h" -#import "private/GRPCCompletionQueue.h" #import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" #import "private/NSDictionary+GRPC.h" @@ -71,19 +69,25 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; GRPCWrappedCall *_wrappedCall; dispatch_once_t _callAlreadyInvoked; - GRPCChannel *_channel; - GRPCCompletionQueue *_completionQueue; - // The C gRPC library has less guarantees on the ordering of events than we // do. Particularly, in the face of errors, there's no ordering guarantee at // all. This wrapper over our actual writeable ensures thread-safety and // correct ordering. GRXConcurrentWriteable *_responseWriteable; + + // The network thread wants the requestWriter to resume (when the server is ready for more input), + // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop + // it. Because a writer isn't thread-safe, we'll synchronize those operations on it. + // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or + // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to + // pause the writer immediately on writeValue:, so we need our locking to be recursive. GRXWriter *_requestWriter; // To create a retain cycle when a call is started, up until it finishes. See - // |startWithWriteable:| and |finishWithError:|. - GRPCCall *_self; + // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a + // reference to the call object if all they're interested in is the handler being executed when + // the response arrives. + GRPCCall *_retainSelf; NSMutableDictionary *_requestMetadata; NSMutableDictionary *_responseMetadata; @@ -100,24 +104,17 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; path:(NSString *)path requestsWriter:(GRXWriter *)requestWriter { if (!host || !path) { - [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."]; + [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; } if (requestWriter.state != GRXWriterStateNotStarted) { - [NSException raise:NSInvalidArgumentException format:@"The requests writer can't be already started."]; + [NSException raise:NSInvalidArgumentException + format:@"The requests writer can't be already started."]; } if ((self = [super init])) { - static dispatch_once_t initialization; - dispatch_once(&initialization, ^{ - grpc_init(); - }); - - _completionQueue = [GRPCCompletionQueue completionQueue]; - - _channel = [GRPCChannel channelToHost:host]; - - _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel - path:path - host:host]; + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:host path:path]; + if (!_wrappedCall) { + return nil; + } // Serial queue to invoke the non-reentrant methods of the grpc_call object. _callQueue = dispatch_queue_create("org.grpc.call", NULL); @@ -148,11 +145,12 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; - (void)finishWithError:(NSError *)errorOrNil { // If the call isn't retained anywhere else, it can be deallocated now. - _self = nil; + _retainSelf = nil; // If there were still request messages coming, stop them. - _requestWriter.state = GRXWriterStateFinished; - _requestWriter = nil; + @synchronized(_requestWriter) { + _requestWriter.state = GRXWriterStateFinished; + } if (errorOrNil) { [_responseWriteable cancelWithError:errorOrNil]; @@ -252,12 +250,14 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; // Resume the request writer. GRPCCall *strongSelf = weakSelf; if (strongSelf) { - strongSelf->_requestWriter.state = GRXWriterStateStarted; + @synchronized(strongSelf->_requestWriter) { + strongSelf->_requestWriter.state = GRXWriterStateStarted; + } } }; - [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] - initWithMessage:message - handler:resumingHandler]] errorHandler:errorHandler]; + [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message + handler:resumingHandler]] + errorHandler:errorHandler]; } - (void)writeValue:(id)value { @@ -265,7 +265,9 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; // Pause the input and only resume it when the C layer notifies us that writes // can proceed. - _requestWriter.state = GRXWriterStatePaused; + @synchronized(_requestWriter) { + _requestWriter.state = GRXWriterStatePaused; + } __weak GRPCCall *weakSelf = self; dispatch_async(_callQueue, ^{ @@ -285,7 +287,6 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; } - (void)writesFinishedWithError:(NSError *)errorOrNil { - _requestWriter = nil; if (errorOrNil) { [self cancel]; } else { @@ -339,7 +340,9 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; } }]; // Now that the RPC has been initiated, request writes can start. - [_requestWriter startWithWriteable:self]; + @synchronized(_requestWriter) { + [_requestWriter startWithWriteable:self]; + } } #pragma mark GRXWriter implementation @@ -350,7 +353,7 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; // before being autoreleased). // Care is taken not to retain self strongly in any of the blocks used in this implementation, so // that the life of the instance is determined by this retain cycle. - _self = self; + _retainSelf = self; _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable]; [self sendHeaders:_requestMetadata]; diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h index bc6a47d469..2a7b701576 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCChannel.h @@ -35,17 +35,12 @@ struct grpc_channel; -// Each separate instance of this class represents at least one TCP -// connection to the provided host. To create a grpc_call, pass the -// value of the unmanagedChannel property to grpc_channel_create_call. -// Release this object when the call is finished. +// Each separate instance of this class represents at least one TCP connection to the provided host. +// Create them using one of the subclasses |GRPCSecureChannel| and |GRPCUnsecuredChannel|. @interface GRPCChannel : NSObject @property(nonatomic, readonly) struct grpc_channel *unmanagedChannel; -// Convenience constructor to allow for reuse of connections. -+ (instancetype)channelToHost:(NSString *)host; - -- (instancetype)initWithHost:(NSString *)host NS_DESIGNATED_INITIALIZER; - +// This initializer takes ownership of the passed channel, and will destroy it when this object is +// deallocated. It's illegal to pass the same grpc_channel to two different GRPCChannel objects. - (instancetype)initWithChannel:(struct grpc_channel *)unmanagedChannel NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index af4326332f..4366e63320 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -35,53 +35,17 @@ #include <grpc/grpc.h> -#import "GRPCSecureChannel.h" -#import "GRPCUnsecuredChannel.h" - @implementation GRPCChannel -+ (instancetype)channelToHost:(NSString *)host { - // TODO(mlumish): Investigate whether a cache with strong links is a good idea - static NSMutableDictionary *channelCache; - static dispatch_once_t cacheInitialization; - dispatch_once(&cacheInitialization, ^{ - channelCache = [NSMutableDictionary dictionary]; - }); - GRPCChannel *channel = channelCache[host]; - if (!channel) { - channel = [[self alloc] initWithHost:host]; - channelCache[host] = channel; - } - return channel; -} - - (instancetype)init { - return [self initWithHost:nil]; + return [self initWithChannel:NULL]; } -- (instancetype)initWithHost:(NSString *)host { - if (![host rangeOfString:@"://"].length) { - // No scheme provided; assume https. - host = [@"https://" stringByAppendingString:host]; - } - NSURL *hostURL = [NSURL URLWithString:host]; - if (!hostURL) { - [NSException raise:NSInvalidArgumentException format:@"Invalid URL: %@", host]; +// Designated initializer +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + if (!unmanagedChannel) { + return nil; } - if ([hostURL.scheme isEqualToString:@"https"]) { - host = [@[hostURL.host, hostURL.port ?: @443] componentsJoinedByString:@":"]; - return [[GRPCSecureChannel alloc] initWithHost:host]; - } - if ([hostURL.scheme isEqualToString:@"http"]) { - host = [@[hostURL.host, hostURL.port ?: @80] componentsJoinedByString:@":"]; - return [[GRPCUnsecuredChannel alloc] initWithHost:host]; - } - [NSException raise:NSInvalidArgumentException - format:@"URL scheme %@ isn't supported.", hostURL.scheme]; - return nil; // silence warning. -} - -- (instancetype)initWithChannel:(struct grpc_channel *)unmanagedChannel { if ((self = [super init])) { _unmanagedChannel = unmanagedChannel; } @@ -89,12 +53,8 @@ } - (void)dealloc { - // _unmanagedChannel is NULL when deallocating an object of the base class (because the - // initializer returns a different object). - if (_unmanagedChannel) { - // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, - // as in the past that made this call to crash. - grpc_channel_destroy(_unmanagedChannel); - } + // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, + // as in the past that made this call to crash. + grpc_channel_destroy(_unmanagedChannel); } @end diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h index 603bf01939..ab8d714d22 100644 --- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h +++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h @@ -36,15 +36,15 @@ typedef void(^GRPCQueueCompletionHandler)(bool success); -// This class lets one more easily use grpc_completion_queue. To use it, pass -// the value of the unmanagedQueue property of an instance of this class to -// grpc_call_start_invoke. Then for every grpc_call_* method that accepts a tag, -// you can pass a block of type GRPCEventHandler (remembering to cast it using -// __bridge_retained). The block is guaranteed to eventually be called, by a -// concurrent queue, and then released. Each such block is passed a pointer to -// the grpc_event that carried it (in event->tag). -// Release the GRPCCompletionQueue object only after you are not going to pass -// any more blocks to the grpc_call that's using it. +// This class lets one more easily use |grpc_completion_queue|. To use it, pass the value of the +// |unmanagedQueue| property of an instance of this class to |grpc_channel_create_call|. Then for +// every |grpc_call_*| method that accepts a tag, you can pass a block of type +// |GRPCQueueCompletionHandler| (remembering to cast it using |__bridge_retained|). The block is +// guaranteed to eventually be called, by a concurrent queue, and then released. Each such block is +// passed a |bool| that tells if the operation was successful. +// +// Release the GRPCCompletionQueue object only after you are not going to pass any more blocks to +// the |grpc_call| that's using it. @interface GRPCCompletionQueue : NSObject @property(nonatomic, readonly) grpc_completion_queue *unmanagedQueue; diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m index 12535c9616..696069c200 100644 --- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m +++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m @@ -38,8 +38,6 @@ @implementation GRPCCompletionQueue + (instancetype)completionQueue { - // TODO(jcanizales): Reuse completion queues to consume only one thread, - // instead of one per call. return [[self alloc] init]; } diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h new file mode 100644 index 0000000000..f0bbd53023 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCHost.h @@ -0,0 +1,58 @@ +/* + * + * 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. + * + */ + +#import <Foundation/Foundation.h> + +@class GRPCCompletionQueue; +struct grpc_call; + +@interface GRPCHost : NSObject + +@property(nonatomic, readonly) NSString *address; + +// The following properties should only be modified for testing: + +@property(nonatomic, getter=isSecure) BOOL secure; + +@property(nonatomic, copy) NSString *pathToCertificates; +@property(nonatomic, copy) NSString *hostNameOverride; + +// Host objects initialized with the same address are the same. ++ (instancetype)hostWithAddress:(NSString *)address; +- (instancetype)initWithAddress:(NSString *)address NS_DESIGNATED_INITIALIZER; + +// Create a grpc_call object to the provided path on this host. +- (struct grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue; + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m new file mode 100644 index 0000000000..d902f95b51 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -0,0 +1,126 @@ +/* + * + * 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. + * + */ + +#import "GRPCHost.h" + +#include <grpc/grpc.h> + +#import "GRPCChannel.h" +#import "GRPCCompletionQueue.h" +#import "GRPCSecureChannel.h" +#import "GRPCUnsecuredChannel.h" + +@interface GRPCHost () +// TODO(mlumish): Investigate whether caching channels with strong links is a good idea. +@property(nonatomic, strong) GRPCChannel *channel; +@end + +@implementation GRPCHost + ++ (instancetype)hostWithAddress:(NSString *)address { + return [[self alloc] initWithAddress:address]; +} + +- (instancetype)init { + return [self initWithAddress:nil]; +} + +// Default initializer. +- (instancetype)initWithAddress:(NSString *)address { + + // To provide a default port, we try to interpret the address. If it's just a host name without + // scheme and without port, we'll use port 443. If it has a scheme, we pass it untouched to the C + // gRPC library. + // TODO(jcanizales): Add unit tests for the types of addresses we want to let pass untouched. + NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:address]]; + if (hostURL && !hostURL.port) { + address = [hostURL.host stringByAppendingString:@":443"]; + } + + // Look up the GRPCHost in the cache. + static NSMutableDictionary *hostCache; + static dispatch_once_t cacheInitialization; + dispatch_once(&cacheInitialization, ^{ + hostCache = [NSMutableDictionary dictionary]; + }); + @synchronized(hostCache) { + GRPCHost *cachedHost = hostCache[address]; + if (cachedHost) { + return cachedHost; + } + + if ((self = [super init])) { + _address = address; + _secure = YES; + hostCache[address] = self; + } + } + return self; +} + +- (grpc_call *)unmanagedCallWithPath:(NSString *)path completionQueue:(GRPCCompletionQueue *)queue { + if (!queue || !path || !self.channel) { + return NULL; + } + return grpc_channel_create_call(self.channel.unmanagedChannel, + NULL, GRPC_PROPAGATE_DEFAULTS, + queue.unmanagedQueue, + path.UTF8String, + self.hostName.UTF8String, + gpr_inf_future(GPR_CLOCK_REALTIME)); +} + +- (GRPCChannel *)channel { + // Create it lazily, because we don't want to open a connection just because someone is + // configuring a host. + if (!_channel) { + if (_secure) { + _channel = [[GRPCSecureChannel alloc] initWithHost:_address + pathToCertificates:_pathToCertificates + hostNameOverride:_hostNameOverride]; + } else { + _channel = [[GRPCUnsecuredChannel alloc] initWithHost:_address]; + } + } + return _channel; +} + +- (NSString *)hostName { + // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified. + return _hostNameOverride ?: _address; +} + +// TODO(jcanizales): Don't let set |secure| to |NO| if |pathToCertificates| or |hostNameOverride| +// have been set. Don't let set either of the latter if |secure| has been set to |NO|. + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.h b/src/objective-c/GRPCClient/private/GRPCSecureChannel.h index d34ceaea0c..74257eb058 100644 --- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.h @@ -31,8 +31,23 @@ * */ +#include <grpc/grpc.h> + #import "GRPCChannel.h" +struct grpc_credentials; + @interface GRPCSecureChannel : GRPCChannel +- (instancetype)initWithHost:(NSString *)host; + +// Only in tests shouldn't pathToCertificates or hostNameOverride be nil. Passing nil for +// pathToCertificates results in using the default root certificates distributed with the library. +- (instancetype)initWithHost:(NSString *)host + pathToCertificates:(NSString *)path + hostNameOverride:(NSString *)hostNameOverride; +// The passed arguments aren't required to be valid beyond the invocation of this initializer. +- (instancetype)initWithHost:(NSString *)host + credentials:(struct grpc_credentials *)credentials + args:(grpc_channel_args *)args NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m index 43a8bd539e..0a54804bb2 100644 --- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m @@ -33,28 +33,83 @@ #import "GRPCSecureChannel.h" -#import <grpc/grpc_security.h> +#include <grpc/grpc_security.h> + +// Returns NULL if the file at path couldn't be read. In that case, if errorPtr isn't NULL, +// *errorPtr will be an object describing what went wrong. +static grpc_credentials *CertificatesAtPath(NSString *path, NSError **errorPtr) { + // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the + // issuer). Load them as UTF8 and produce an ASCII equivalent. + NSString *contentInUTF8 = [NSString stringWithContentsOfFile:path + encoding:NSUTF8StringEncoding + error:errorPtr]; + NSData *contentInASCII = [contentInUTF8 dataUsingEncoding:NSASCIIStringEncoding + allowLossyConversion:YES]; + if (!contentInASCII.bytes) { + // Passing NULL to grpc_ssl_credentials_create produces behavior we don't want, so return. + return NULL; + } + return grpc_ssl_credentials_create(contentInASCII.bytes, NULL); +} @implementation GRPCSecureChannel - (instancetype)initWithHost:(NSString *)host { - static grpc_credentials *kCredentials; + return [self initWithHost:host pathToCertificates:nil hostNameOverride:nil]; +} + +- (instancetype)initWithHost:(NSString *)host + pathToCertificates:(NSString *)path + hostNameOverride:(NSString *)hostNameOverride { + // Load default SSL certificates once. + static grpc_credentials *kDefaultCertificates; static dispatch_once_t loading; dispatch_once(&loading, ^{ + NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem // Do not use NSBundle.mainBundle, as it's nil for tests of library projects. NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *certsPath = [bundle pathForResource:@"gRPCCertificates.bundle/roots" ofType:@"pem"]; - NSAssert(certsPath.length, - @"gRPCCertificates.bundle/roots.pem not found under %@. This file, with the root " - "certificates, is needed to establish TLS (HTTPS) connections.", bundle.bundlePath); - NSData *certsData = [NSData dataWithContentsOfFile:certsPath]; - NSAssert(certsData.length, @"No data read from %@", certsPath); - NSString *certsString = [[NSString alloc] initWithData:certsData encoding:NSUTF8StringEncoding]; - kCredentials = grpc_ssl_credentials_create(certsString.UTF8String, NULL); + NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; + NSError *error; + kDefaultCertificates = CertificatesAtPath(path, &error); + NSAssert(kDefaultCertificates, @"Could not read %@/%@.pem. This file, with the root " + "certificates, is needed to establish secure (TLS) connections. Because the file is " + "distributed with the gRPC library, this error is usually a sign that the library " + "wasn't configured correctly for your project. Error: %@", + bundle.bundlePath, defaultPath, error); }); - return (self = [super initWithChannel:grpc_secure_channel_create(kCredentials, - host.UTF8String, - NULL)]); + + //TODO(jcanizales): Add NSError** parameter to the initializer. + grpc_credentials *certificates = path ? CertificatesAtPath(path, NULL) : kDefaultCertificates; + if (!certificates) { + return nil; + } + + // Ritual to pass the SSL host name override to the C library. + grpc_channel_args channelArgs; + grpc_arg nameOverrideArg; + channelArgs.num_args = 1; + channelArgs.args = &nameOverrideArg; + nameOverrideArg.type = GRPC_ARG_STRING; + nameOverrideArg.key = GRPC_SSL_TARGET_NAME_OVERRIDE_ARG; + // Cast const away. Hope C gRPC doesn't modify it! + nameOverrideArg.value.string = (char *) hostNameOverride.UTF8String; + grpc_channel_args *args = hostNameOverride ? &channelArgs : NULL; + + return [self initWithHost:host credentials:certificates args:args]; +} + +- (instancetype)initWithHost:(NSString *)host + credentials:(grpc_credentials *)credentials + args:(grpc_channel_args *)args { + return (self = + [super initWithChannel:grpc_secure_channel_create(credentials, host.UTF8String, args)]); +} + +// TODO(jcanizales): GRPCSecureChannel and GRPCUnsecuredChannel are just convenience initializers +// for GRPCChannel. Move them into GRPCChannel, which will make the following unnecessary. +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + [NSException raise:NSInternalInconsistencyException format:@"use another initializer"]; + return [self initWithHost:nil]; // silence warnings } @end diff --git a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h index 9d89cfb541..8528be44c0 100644 --- a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h @@ -34,5 +34,5 @@ #import "GRPCChannel.h" @interface GRPCUnsecuredChannel : GRPCChannel - +- (instancetype)initWithHost:(NSString *)host NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m index d27f7ca565..070a529629 100644 --- a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m @@ -38,7 +38,13 @@ @implementation GRPCUnsecuredChannel - (instancetype)initWithHost:(NSString *)host { - return (self = [super initWithChannel:grpc_channel_create(host.UTF8String, NULL)]); + return (self = [super initWithChannel:grpc_insecure_channel_create(host.UTF8String, NULL)]); } +// TODO(jcanizales): GRPCSecureChannel and GRPCUnsecuredChannel are just convenience initializers +// for GRPCChannel. Move them into GRPCChannel, which will make the following unnecessary. +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + [NSException raise:NSInternalInconsistencyException format:@"use the other initializer"]; + return [self initWithHost:nil]; // silence warnings +} @end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index 18f8bb5531..da11cbb761 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -81,11 +81,12 @@ @end +#pragma mark GRPCWrappedCall + @interface GRPCWrappedCall : NSObject -- (instancetype)initWithChannel:(GRPCChannel *)channel - path:(NSString *)path - host:(NSString *)host NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path NS_DESIGNATED_INITIALIZER; - (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void(^)())errorHandler; diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m index 1db63df77f..951c051036 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -32,11 +32,14 @@ */ #import "GRPCWrappedCall.h" + #import <Foundation/Foundation.h> #include <grpc/grpc.h> #include <grpc/byte_buffer.h> #include <grpc/support/alloc.h> + #import "GRPCCompletionQueue.h" +#import "GRPCHost.h" #import "NSDictionary+GRPC.h" #import "NSData+GRPC.h" #import "NSError+GRPC.h" @@ -219,38 +222,36 @@ @end -@implementation GRPCWrappedCall{ - grpc_call *_call; +#pragma mark GRPCWrappedCall + +@implementation GRPCWrappedCall { GRPCCompletionQueue *_queue; + grpc_call *_call; } - (instancetype)init { - return [self initWithChannel:nil path:nil host:nil]; + return [self initWithHost:nil path:nil]; } -- (instancetype)initWithChannel:(GRPCChannel *)channel - path:(NSString *)path - host:(NSString *)host { - if (!channel || !path || !host) { +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path { + if (!path || !host) { [NSException raise:NSInvalidArgumentException - format:@"channel, method, and host cannot be nil."]; + format:@"path and host cannot be nil."]; } - + if (self = [super init]) { static dispatch_once_t initialization; dispatch_once(&initialization, ^{ grpc_init(); }); - + + // Each completion queue consumes one thread. There's a trade to be made between creating and + // consuming too many threads and having contention of multiple calls in a single completion + // queue. Currently we favor latency and use one per call. _queue = [GRPCCompletionQueue completionQueue]; - if (!_queue) { - return nil; - } - _call = grpc_channel_create_call(channel.unmanagedChannel, - _queue.unmanagedQueue, - path.UTF8String, - host.UTF8String, - gpr_inf_future(GPR_CLOCK_REALTIME)); + + _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path completionQueue:_queue]; if (_call == NULL) { return nil; } @@ -299,4 +300,4 @@ grpc_call_destroy(_call); } -@end
\ No newline at end of file +@end diff --git a/src/objective-c/RxLibrary/GRXBufferedPipe.h b/src/objective-c/RxLibrary/GRXBufferedPipe.h index b6296e1ed7..ca94ce275f 100644 --- a/src/objective-c/RxLibrary/GRXBufferedPipe.h +++ b/src/objective-c/RxLibrary/GRXBufferedPipe.h @@ -36,13 +36,11 @@ #import "GRXWriteable.h" #import "GRXWriter.h" -// A buffered pipe is a Writeable that also acts as a Writer (to whichever other writeable is passed -// to -startWithWriteable:). +// A buffered pipe is a Writer that also acts as a Writeable. // Once it is started, whatever values are written into it (via -writeValue:) will be propagated // immediately, unless flow control prevents it. // If it is throttled and keeps receiving values, as well as if it receives values before being -// started, it will buffer them and propagate them in order as soon as its state becomes -// GRXWriterStateStarted. +// started, it will buffer them and propagate them in order as soon as its state becomes Started. // If it receives an error (via -writesFinishedWithError:), it will drop any buffered values and // propagate the error immediately. // @@ -51,6 +49,9 @@ // pipe will keep buffering all data written to it, your application could run out of memory and // crash. If you want to react to flow control signals to prevent that, instead of using this class // you can implement an object that conforms to GRXWriter. +// +// Thread-safety: +// The methods of an object of this class should not be called concurrently from different threads. @interface GRXBufferedPipe : GRXWriter<GRXWriteable> // Convenience constructor. diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.h b/src/objective-c/RxLibrary/GRXForwardingWriter.h index d004333d2b..f310832284 100644 --- a/src/objective-c/RxLibrary/GRXForwardingWriter.h +++ b/src/objective-c/RxLibrary/GRXForwardingWriter.h @@ -33,11 +33,17 @@ #import "GRXWriter.h" -// A "proxy" class that simply forwards values, completion, and errors from its -// input writer to its writeable. +// A "proxy" class that simply forwards values, completion, and errors from its input writer to its +// writeable. // It is useful as a superclass for pipes that act as a transformation of their // input writer, and for classes that represent objects with input and // output sequences of values, like an RPC. +// +// Thread-safety: +// All messages sent to this object need to be serialized. When it is started, the writer it wraps +// is started in the same thread. Manual state changes are propagated to the wrapped writer in the +// same thread too. Importantly, all messages the wrapped writer sends to its writeable need to be +// serialized with any message sent to this object. @interface GRXForwardingWriter : GRXWriter - (instancetype)initWithWriter:(GRXWriter *)writer NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.m b/src/objective-c/RxLibrary/GRXForwardingWriter.m index 2342f51ab3..a72be9ace2 100644 --- a/src/objective-c/RxLibrary/GRXForwardingWriter.m +++ b/src/objective-c/RxLibrary/GRXForwardingWriter.m @@ -48,7 +48,11 @@ // Designated initializer - (instancetype)initWithWriter:(GRXWriter *)writer { if (!writer) { - [NSException raise:NSInvalidArgumentException format:@"writer can't be nil."]; + return nil; + } + if (writer.state != GRXWriterStateNotStarted) { + [NSException raise:NSInvalidArgumentException + format:@"The writer argument must not have already started."]; } if ((self = [super init])) { _writer = writer; diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.h b/src/objective-c/RxLibrary/GRXImmediateWriter.h index b171f0c760..3fcc259434 100644 --- a/src/objective-c/RxLibrary/GRXImmediateWriter.h +++ b/src/objective-c/RxLibrary/GRXImmediateWriter.h @@ -36,10 +36,17 @@ #import "GRXWriter.h" // Utility to construct GRXWriter instances from values that are immediately available when -// required. The returned writers all support pausing and early termination. +// required. // -// Unless the writeable callback pauses them or stops them early, these writers will do all their -// interactions with the writeable before the start method returns. +// Thread-safety: +// +// An object of this class shouldn't be messaged concurrently by more than one thread. It will start +// messaging the writeable before |startWithWriteable:| returns, in the same thread. That is the +// only place where the writer can be paused or stopped prematurely. +// +// If a paused writer of this class is resumed, it will start messaging the writeable, in the same +// thread, before |setState:| returns. Because the object can't be legally accessed concurrently, +// that's the only place where it can be paused again (or stopped). @interface GRXImmediateWriter : GRXWriter // Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to diff --git a/src/objective-c/RxLibrary/GRXWriteable.h b/src/objective-c/RxLibrary/GRXWriteable.h index 216de30735..45613d6dd0 100644 --- a/src/objective-c/RxLibrary/GRXWriteable.h +++ b/src/objective-c/RxLibrary/GRXWriteable.h @@ -48,15 +48,15 @@ typedef void (^GRXValueHandler)(id value); typedef void (^GRXCompletionHandler)(NSError *errorOrNil); -typedef void (^GRXSingleValueHandler)(id value, NSError *errorOrNil); -typedef void (^GRXStreamHandler)(BOOL done, id value, NSError *error); +typedef void (^GRXSingleHandler)(id value, NSError *errorOrNil); +typedef void (^GRXEventHandler)(BOOL done, id value, NSError *error); // Utility to create objects that conform to the GRXWriteable protocol, from // blocks that handle each of the two methods of the protocol. @interface GRXWriteable : NSObject<GRXWriteable> -+ (instancetype)writeableWithSingleValueHandler:(GRXSingleValueHandler)handler; -+ (instancetype)writeableWithStreamHandler:(GRXStreamHandler)handler; ++ (instancetype)writeableWithSingleHandler:(GRXSingleHandler)handler; ++ (instancetype)writeableWithEventHandler:(GRXEventHandler)handler; - (instancetype)initWithValueHandler:(GRXValueHandler)valueHandler completionHandler:(GRXCompletionHandler)completionHandler diff --git a/src/objective-c/RxLibrary/GRXWriteable.m b/src/objective-c/RxLibrary/GRXWriteable.m index 63f7c3e7f3..2729d62b72 100644 --- a/src/objective-c/RxLibrary/GRXWriteable.m +++ b/src/objective-c/RxLibrary/GRXWriteable.m @@ -38,7 +38,7 @@ GRXCompletionHandler _completionHandler; } -+ (instancetype)writeableWithSingleValueHandler:(GRXSingleValueHandler)handler { ++ (instancetype)writeableWithSingleHandler:(GRXSingleHandler)handler { if (!handler) { return [[self alloc] init]; } @@ -51,7 +51,7 @@ }]; } -+ (instancetype)writeableWithStreamHandler:(GRXStreamHandler)handler { ++ (instancetype)writeableWithEventHandler:(GRXEventHandler)handler { if (!handler) { return [[self alloc] init]; } diff --git a/src/objective-c/RxLibrary/GRXWriter.h b/src/objective-c/RxLibrary/GRXWriter.h index 5d6e1a472a..b1c994aa38 100644 --- a/src/objective-c/RxLibrary/GRXWriter.h +++ b/src/objective-c/RxLibrary/GRXWriter.h @@ -35,84 +35,73 @@ #import "GRXWriteable.h" +// States of a writer. typedef NS_ENUM(NSInteger, GRXWriterState) { - // The writer has not yet been given a writeable to which it can push its - // values. To have an writer transition to the Started state, send it a - // startWithWriteable: message. + // The writer has not yet been given a writeable to which it can push its values. To have a writer + // transition to the Started state, send it a startWithWriteable: message. // - // An writer's state cannot be manually set to this value. + // A writer's state cannot be manually set to this value. GRXWriterStateNotStarted, // The writer might push values to the writeable at any moment. GRXWriterStateStarted, - // The writer is temporarily paused, and won't send any more values to the - // writeable unless its state is set back to Started. The writer might still - // transition to the Finished state at any moment, and is allowed to send - // writesFinishedWithError: to its writeable. - // - // Not all implementations of writer have to support pausing, and thus - // trying to set an writer's state to this value might have no effect. + // The writer is temporarily paused, and won't send any more values to the writeable unless its + // state is set back to Started. The writer might still transition to the Finished state at any + // moment, and is allowed to send writesFinishedWithError: to its writeable. GRXWriterStatePaused, // The writer has released its writeable and won't interact with it anymore. // - // One seldomly wants to set an writer's state to this value, as its - // writeable isn't notified with a writesFinishedWithError: message. Instead, sending - // finishWithError: to the writer will make it notify the writeable and then - // transition to this state. + // One seldomly wants to set a writer's state to this value, as its writeable isn't notified with + // a writesFinishedWithError: message. Instead, sending finishWithError: to the writer will make + // it notify the writeable and then transition to this state. GRXWriterStateFinished }; -// An object that conforms to this protocol can produce, on demand, a sequence -// of values. The sequence may be produced asynchronously, and it may consist of -// any number of elements, including none or an infinite number. +// An GRXWriter object can produce, on demand, a sequence of values. The sequence may be produced +// asynchronously, and it may consist of any number of elements, including none or an infinite +// number. +// +// GRXWriter is the active dual of NSEnumerator. The difference between them is thus whether the +// object plays an active or passive role during usage: A user of NSEnumerator pulls values off it, +// and passes the values to a writeable. A user of GRXWriter, though, just gives it a writeable, and +// the GRXWriter instance pushes values to the writeable. This makes this protocol suitable to +// represent a sequence of future values, as well as collections with internal iteration. // -// GRXWriter is the active dual of NSEnumerator. The difference between them -// is thus whether the object plays an active or passive role during usage: A -// user of NSEnumerator pulls values off it, and passes the values to a writeable. -// A user of GRXWriter, though, just gives it a writeable, and the -// GRXWriter instance pushes values to the writeable. This makes this protocol -// suitable to represent a sequence of future values, as well as collections -// with internal iteration. +// An instance of GRXWriter can start producing values after a writeable is passed to it. It can +// also be commanded to finish the sequence immediately (with an optional error). Finally, it can be +// asked to pause, and resumed later. All GRXWriter objects support pausing and early termination. // -// An instance of GRXWriter can start producing values after a writeable is -// passed to it. It can also be commanded to finish the sequence immediately -// (with an optional error). Finally, it can be asked to pause, but the -// conforming instance is not required to oblige. +// Thread-safety: // -// Unless otherwise indicated by a conforming class, no messages should be sent -// concurrently to a GRXWriter. I.e., conforming classes aren't required to -// be thread-safe. +// State transitions take immediate effect if the object is used from a single thread. Subclasses +// might offer stronger guarantees. +// +// Unless otherwise indicated by a conforming subclass, no messages should be sent concurrently to a +// GRXWriter. I.e., conforming classes aren't required to be thread-safe. @interface GRXWriter : NSObject -// This property can be used to query the current state of the writer, which -// determines how it might currently use its writeable. Some state transitions can -// be triggered by setting this property to the corresponding value, and that's -// useful for advanced use cases like pausing an writer. For more details, -// see the documentation of the enum. +// This property can be used to query the current state of the writer, which determines how it might +// currently use its writeable. Some state transitions can be triggered by setting this property to +// the corresponding value, and that's useful for advanced use cases like pausing an writer. For +// more details, see the documentation of the enum further down. @property(nonatomic) GRXWriterState state; -// Start sending messages to the writeable. Messages may be sent before the method -// returns, or they may be sent later in the future. See GRXWriteable.h for the -// different messages a writeable can receive. +// Transition to the Started state, and start sending messages to the writeable (a reference to it +// is retained). Messages to the writeable may be sent before the method returns, or they may be +// sent later in the future. See GRXWriteable.h for the different messages a writeable can receive. // -// If this writer draws its values from an external source (e.g. from the -// filesystem or from a server), calling this method will commonly trigger side -// effects (like network connections). +// If this writer draws its values from an external source (e.g. from the filesystem or from a +// server), calling this method will commonly trigger side effects (like network connections). // // This method might only be called on writers in the NotStarted state. - (void)startWithWriteable:(id<GRXWriteable>)writeable; -// Send writesFinishedWithError:errorOrNil immediately to the writeable, and don't send -// any more messages to it. -// -// This method might only be called on writers in the Started or Paused -// state. +// Send writesFinishedWithError:errorOrNil to the writeable. Then release the reference to it and +// transition to the Finished state. // -// TODO(jcanizales): Consider adding some guarantee about the immediacy of that -// stopping. I know I've relied on it in part of the code that uses this, but -// can't remember the details in the presence of concurrency. +// This method might only be called on writers in the Started or Paused state. - (void)finishWithError:(NSError *)errorOrNil; @end diff --git a/src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec b/src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec index 7cc9a040fe..8710753e59 100644 --- a/src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec +++ b/src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec @@ -8,11 +8,10 @@ Pod::Spec.new do |s| # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients. s.prepare_command = <<-CMD - cd ../../../.. - # TODO(jcanizales): Make only Objective-C plugin. - make plugins - cd - - protoc --plugin=protoc-gen-grpc=../../../../bins/opt/grpc_objective_c_plugin --objc_out=. --grpc_out=. *.proto + BINDIR=../../../../bins/$CONFIG + PROTOC=$BINDIR/protobuf/protoc + PLUGIN=$BINDIR/grpc_objective_c_plugin + $PROTOC --plugin=protoc-gen-grpc=$PLUGIN --objc_out=. --grpc_out=. *.proto CMD s.subspec "Messages" do |ms| diff --git a/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec b/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec index 0e8dacd1c4..23ccffe69d 100644 --- a/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec +++ b/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec @@ -8,11 +8,10 @@ Pod::Spec.new do |s| # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients. s.prepare_command = <<-CMD - cd ../../../.. - # TODO(jcanizales): Make only Objective-C plugin. - make plugins - cd - - protoc --plugin=protoc-gen-grpc=../../../../bins/opt/grpc_objective_c_plugin --objc_out=. --grpc_out=. *.proto + BINDIR=../../../../bins/$CONFIG + PROTOC=$BINDIR/protobuf/protoc + PLUGIN=$BINDIR/grpc_objective_c_plugin + $PROTOC --plugin=protoc-gen-grpc=$PLUGIN --objc_out=. --grpc_out=. *.proto CMD s.subspec "Messages" do |ms| diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m index 3210ad7050..f23102988b 100644 --- a/src/objective-c/tests/GRPCClientTests.m +++ b/src/objective-c/tests/GRPCClientTests.m @@ -35,6 +35,8 @@ #import <XCTest/XCTest.h> #import <GRPCClient/GRPCCall.h> +#import <GRPCClient/GRPCCall+OAuth2.h> +#import <GRPCClient/GRPCCall+Tests.h> #import <ProtoRPC/ProtoMethod.h> #import <RemoteTest/Messages.pbobjc.h> #import <RxLibrary/GRXWriteable.h> @@ -43,7 +45,7 @@ // These are a few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) // rather than a generated proto library on top of it. -static NSString * const kHostAddress = @"grpc-test.sandbox.google.com"; +static NSString * const kHostAddress = @"localhost:5050"; static NSString * const kPackage = @"grpc.testing"; static NSString * const kService = @"TestService"; @@ -57,6 +59,9 @@ static ProtoMethod *kUnaryCallMethod; @implementation GRPCClientTests - (void)setUp { + // Register test server as non-SSL. + [GRPCCall useInsecureConnectionsForHost:kHostAddress]; + // This method isn't implemented by the remote server. kInexistentMethod = [[ProtoMethod alloc] initWithPackage:kPackage service:kService @@ -109,7 +114,7 @@ static ProtoMethod *kUnaryCallMethod; [call startWithWriteable:responsesWriteable]; - [self waitForExpectationsWithTimeout:4 handler:nil]; + [self waitForExpectationsWithTimeout:8 handler:nil]; } - (void)testSimpleProtoRPC { @@ -141,7 +146,7 @@ static ProtoMethod *kUnaryCallMethod; [call startWithWriteable:responsesWriteable]; - [self waitForExpectationsWithTimeout:4 handler:nil]; + [self waitForExpectationsWithTimeout:8 handler:nil]; } - (void)testMetadata { @@ -156,7 +161,7 @@ static ProtoMethod *kUnaryCallMethod; path:kUnaryCallMethod.HTTPPath requestsWriter:requestsWriter]; - call.requestMetadata[@"Authorization"] = @"Bearer bogusToken"; + call.oauth2AccessToken = @"bogusToken"; id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { XCTFail(@"Received unexpected response: %@", value); @@ -165,7 +170,7 @@ static ProtoMethod *kUnaryCallMethod; XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil); XCTAssertEqualObjects(call.responseMetadata, errorOrNil.userInfo[kGRPCStatusMetadataKey], @"Metadata in the NSError object and call object differ."); - NSString *challengeHeader = call.responseMetadata[@"www-authenticate"]; + NSString *challengeHeader = call.oauth2ChallengeHeader; XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@", call.responseMetadata); [expectation fulfill]; diff --git a/src/core/httpcli/httpcli_security_connector.h b/src/objective-c/tests/InteropTests.h index c50f25905e..1045c3d124 100644 --- a/src/core/httpcli/httpcli_security_connector.h +++ b/src/objective-c/tests/InteropTests.h @@ -31,13 +31,13 @@ * */ -#ifndef GRPC_INTERNAL_CORE_HTTPCLI_HTTPCLI_SECURITY_CONNECTOR_H -#define GRPC_INTERNAL_CORE_HTTPCLI_HTTPCLI_SECURITY_CONNECTOR_H +#import <XCTest/XCTest.h> -#include "src/core/security/security_connector.h" +// Implements tests as described here: +// https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md -grpc_security_status grpc_httpcli_ssl_channel_security_connector_create( - const unsigned char *pem_root_certs, size_t pem_root_certs_size, - const char *secure_peer_name, grpc_channel_security_connector **sc); - -#endif /* GRPC_INTERNAL_CORE_HTTPCLI_HTTPCLI_SECURITY_CONNECTOR_H */ +@interface InteropTests : XCTestCase +// Returns @"grpc-test.sandbox.google.com". +// Override in a subclass to perform the same tests against a different address. ++ (NSString *)host; +@end diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m index 501f33317a..1b63fe2059 100644 --- a/src/objective-c/tests/InteropTests.m +++ b/src/objective-c/tests/InteropTests.m @@ -31,11 +31,11 @@ * */ -#include <grpc/status.h> +#import "InteropTests.h" -#import <UIKit/UIKit.h> -#import <XCTest/XCTest.h> +#include <grpc/status.h> +#import <GRPCClient/GRPCCall+Tests.h> #import <ProtoRPC/ProtoRPC.h> #import <RemoteTest/Empty.pbobjc.h> #import <RemoteTest/Messages.pbobjc.h> @@ -76,18 +76,21 @@ } @end -@interface InteropTests : XCTestCase -@end +#pragma mark Tests + +static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com"; @implementation InteropTests { RMTTestService *_service; } -- (void)setUp { - _service = [[RMTTestService alloc] initWithHost:@"grpc-test.sandbox.google.com"]; ++ (NSString *)host { + return kRemoteSSLHost; } -// Tests as described here: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md +- (void)setUp { + _service = [[RMTTestService alloc] initWithHost:self.class.host]; +} - (void)testEmptyUnaryRPC { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"]; @@ -125,7 +128,7 @@ [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:8 handler:nil]; + [self waitForExpectationsWithTimeout:16 handler:nil]; } - (void)testClientStreamingRPC { diff --git a/src/core/census/grpc_context.h b/src/objective-c/tests/InteropTestsLocalCleartext.m index 4637e7218e..2d7d3c4b2c 100644 --- a/src/core/census/grpc_context.h +++ b/src/objective-c/tests/InteropTestsLocalCleartext.m @@ -31,27 +31,29 @@ * */ -/* GRPC <--> CENSUS context interface */ +// Repeat of the tests in InteropTests.m, but sending the RPCs to a local cleartext server instead +// of the remote SSL one. -#ifndef CENSUS_GRPC_CONTEXT_H -#define CENSUS_GRPC_CONTEXT_H +#import <GRPCClient/GRPCCall+Tests.h> -#include <grpc/census.h> -#include "src/core/surface/call.h" +#import "InteropTests.h" -#ifdef __cplusplus -extern "C" { -#endif +static NSString * const kLocalCleartextHost = @"localhost:5050"; -/* Set census context for the call; Must be called before first call to - grpc_call_start_batch(). */ -void grpc_census_call_set_context(grpc_call *call, census_context *context); +@interface InteropTestsLocalCleartext : InteropTests +@end -/* Retrieve the calls current census context. */ -census_context *grpc_census_call_get_context(grpc_call *call); +@implementation InteropTestsLocalCleartext -#ifdef __cplusplus ++ (NSString *)host { + return kLocalCleartextHost; } -#endif -#endif /* CENSUS_GRPC_CONTEXT_H */ +- (void)setUp { + // Register test server as non-SSL. + [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost]; + + [super setUp]; +} + +@end diff --git a/src/objective-c/tests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTestsLocalSSL.m new file mode 100644 index 0000000000..f69f806dcf --- /dev/null +++ b/src/objective-c/tests/InteropTestsLocalSSL.m @@ -0,0 +1,62 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Repeat of the tests in InteropTests.m, but sending the RPCs to a local SSL server instead of the +// remote one. + +#import <GRPCClient/GRPCCall+Tests.h> + +#import "InteropTests.h" + +static NSString * const kLocalSSLHost = @"localhost:5051"; + +@interface InteropTestsLocalSSL : InteropTests +@end + +@implementation InteropTestsLocalSSL + ++ (NSString *)host { + return kLocalSSLHost; +} + +- (void)setUp { + // Register test server certificates and name. + NSBundle *bundle = [NSBundle bundleForClass:self.class]; + NSString *certsPath = [bundle pathForResource:@"TestCertificates.bundle/test-certificates" + ofType:@"pem"]; + [GRPCCall useTestCertsPath:certsPath testName:@"foo.test.google.fr" forHost:kLocalSSLHost]; + + [super setUp]; +} + +@end diff --git a/src/objective-c/tests/RxLibraryUnitTests.m b/src/objective-c/tests/RxLibraryUnitTests.m index 5e3162875a..a67a4c6cd9 100644 --- a/src/objective-c/tests/RxLibraryUnitTests.m +++ b/src/objective-c/tests/RxLibraryUnitTests.m @@ -55,7 +55,7 @@ return [[self alloc] init]; } -- (GRXSingleValueHandler)block { +- (GRXSingleHandler)block { return ^(id value, NSError *errorOrNil) { ++_timesCalled; _value = value; @@ -71,13 +71,13 @@ #pragma mark Writeable -- (void)testWriteableSingleValueHandlerIsCalledForValue { +- (void)testWriteableSingleHandlerIsCalledForValue { // Given: CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler]; id anyValue = @7; // If: - id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block]; + id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block]; [writeable writeValue:anyValue]; // Then: @@ -86,13 +86,13 @@ XCTAssertEqualObjects(handler.errorOrNil, nil); } -- (void)testWriteableSingleValueHandlerIsCalledForError { +- (void)testWriteableSingleHandlerIsCalledForError { // Given: CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler]; NSError *anyError = [NSError errorWithDomain:@"domain" code:7 userInfo:nil]; // If: - id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block]; + id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block]; [writeable writesFinishedWithError:anyError]; // Then: @@ -106,7 +106,7 @@ - (void)testBufferedPipePropagatesValue { // Given: CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler]; - id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block]; + id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block]; id anyValue = @7; // If: @@ -123,7 +123,7 @@ - (void)testBufferedPipePropagatesError { // Given: CapturingSingleValueHandler *handler = [CapturingSingleValueHandler handler]; - id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleValueHandler:handler.block]; + id<GRXWriteable> writeable = [GRXWriteable writeableWithSingleHandler:handler.block]; NSError *anyError = [NSError errorWithDomain:@"domain" code:7 userInfo:nil]; // If: diff --git a/src/python/interop/interop/credentials/ca.pem b/src/objective-c/tests/TestCertificates.bundle/test-certificates.pem index 6c8511a73c..6c8511a73c 100755..100644 --- a/src/python/interop/interop/credentials/ca.pem +++ b/src/objective-c/tests/TestCertificates.bundle/test-certificates.pem diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj index f13fb8288b..3a1c3d940a 100644 --- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj @@ -13,6 +13,9 @@ 63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; }; 635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; }; 635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; }; + 63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; }; + 63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; }; + 63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; }; 7D8A186224D39101F90230F6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; }; /* End PBXBuildFile section */ @@ -49,6 +52,10 @@ 635697CC1B14FC11007A7283 /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; }; 635697D81B14FC11007A7283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTests.m; sourceTree = "<group>"; }; + 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalCleartext.m; sourceTree = "<group>"; }; + 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteropTests.h; sourceTree = "<group>"; }; + 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; }; + 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; }; FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -93,6 +100,7 @@ isa = PBXGroup; children = ( 635697C91B14FC11007A7283 /* Tests */, + 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */, 635697C81B14FC11007A7283 /* Products */, 51E4650F34F854F41FF053B3 /* Pods */, 136D535E19727099B941D7B1 /* Frameworks */, @@ -112,9 +120,12 @@ isa = PBXGroup; children = ( 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */, - 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */, + 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */, 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */, + 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */, + 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */, 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */, + 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */, 635697CC1B14FC11007A7283 /* Tests.m */, 635697D71B14FC11007A7283 /* Supporting Files */, ); @@ -209,6 +220,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -252,8 +264,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */, 63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */, 63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */, + 63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */, 6312AE4E1B1BF49B00341DEE /* GRPCClientTests.m in Sources */, 635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */, ); diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme index 3a6e2c3591..a7e0ed110e 100644 --- a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme +++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme @@ -39,6 +39,12 @@ </BuildableReference> <SkippedTests> <Test + Identifier = "GRPCClientTests/testConnectionToRemoteServer"> + </Test> + <Test + Identifier = "GRPCClientTests/testMetadata"> + </Test> + <Test Identifier = "LocalClearTextTests"> </Test> <Test diff --git a/src/objective-c/tests/build_tests.sh b/src/objective-c/tests/build_tests.sh new file mode 100755 index 0000000000..e7ad31e403 --- /dev/null +++ b/src/objective-c/tests/build_tests.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Don't run this script standalone. Instead, run from the repository root: +# ./tools/run_tests/run_tests.py -l objc + +set -e + +cd $(dirname $0) + +hash pod 2>/dev/null || { echo >&2 "Cocoapods needs to be installed."; exit 1; } +hash xcodebuild 2>/dev/null || { + echo >&2 "XCode command-line tools need to be installed." + exit 1 +} + +BINDIR=../../../bins/$CONFIG + +if [ ! -f $BINDIR/protobuf/protoc ]; then + hash protoc 2>/dev/null || { + echo >&2 "Can't find protoc. Make sure run_tests.py is making" \ + "grpc_objective_c_plugin before calling this script." + exit 1 + } + # When protoc is already installed, make doesn't compile one. Put a link + # there so the podspecs can do codegen using that path. + mkdir -p $BINDIR/protobuf + ln -s `which protoc` $BINDIR/protobuf/protoc +fi + +[ -f $BINDIR/interop_server ] || { + echo >&2 "Can't find the test server. Make sure run_tests.py is making" \ + "interop_server before calling this script. It needs to be done" \ + "before because pod install of gRPC renames some C gRPC files" \ + "and not the server's code references to them." + exit 1 +} + +pod install diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh index 37fced3a62..7b133c1782 100755 --- a/src/objective-c/tests/run_tests.sh +++ b/src/objective-c/tests/run_tests.sh @@ -28,13 +28,18 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# Don't run this script standalone. Instead, run from the repository root: +# ./tools/run_tests/run_tests.py -l objc + set -e cd $(dirname $0) -# TODO(jcanizales): Remove when Cocoapods issue #3823 is resolved. -export COCOAPODS_DISABLE_DETERMINISTIC_UUIDS=YES -pod install +# Run the tests server. +../../../bins/$CONFIG/interop_server --port=5050 & +../../../bins/$CONFIG/interop_server --port=5051 --enable_ssl & +# Kill them when this script exits. +trap 'kill -9 `jobs -p`' EXIT # xcodebuild is very verbose. We filter its output and tell Bash to fail if any # element of the pipe fails. diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c index b67bae7568..01ec909b79 100644 --- a/src/php/ext/grpc/call.c +++ b/src/php/ext/grpc/call.c @@ -240,8 +240,8 @@ PHP_METHOD(Call, __construct) { (wrapped_grpc_timeval *)zend_object_store_get_object( deadline_obj TSRMLS_CC); call->wrapped = grpc_channel_create_call( - channel->wrapped, completion_queue, method, channel->target, - deadline->wrapped); + channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, method, + channel->target, deadline->wrapped); } /** @@ -473,6 +473,16 @@ cleanup: } /** + * Get the endpoint this call/stream is connected to + * @return string The URI of the endpoint + */ +PHP_METHOD(Call, getPeer) { + wrapped_grpc_call *call = + (wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC); + RETURN_STRING(grpc_call_get_peer(call->wrapped), 1); +} + +/** * Cancel the call. This will cause the call to end with STATUS_CANCELLED if it * has not already ended with another status. */ @@ -485,7 +495,9 @@ PHP_METHOD(Call, cancel) { static zend_function_entry call_methods[] = { PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) PHP_ME(Call, startBatch, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC) PHP_FE_END}; + PHP_ME(Call, getPeer, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END}; void grpc_init_call(TSRMLS_D) { zend_class_entry ce; diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c index b8262db162..7d8a6f87ab 100644 --- a/src/php/ext/grpc/channel.c +++ b/src/php/ext/grpc/channel.c @@ -152,7 +152,7 @@ PHP_METHOD(Channel, __construct) { override = target; override_len = target_length; if (args_array == NULL) { - channel->wrapped = grpc_channel_create(target, NULL); + channel->wrapped = grpc_insecure_channel_create(target, NULL); } else { array_hash = Z_ARRVAL_P(args_array); if (zend_hash_find(array_hash, "credentials", sizeof("credentials"), @@ -182,7 +182,7 @@ PHP_METHOD(Channel, __construct) { } php_grpc_read_args_array(args_array, &args); if (creds == NULL) { - channel->wrapped = grpc_channel_create(target, &args); + channel->wrapped = grpc_insecure_channel_create(target, &args); } else { gpr_log(GPR_DEBUG, "Initialized secure channel"); channel->wrapped = @@ -195,6 +195,16 @@ PHP_METHOD(Channel, __construct) { } /** + * Get the endpoint this call/stream is connected to + * @return string The URI of the endpoint + */ +PHP_METHOD(Channel, getTarget) { + wrapped_grpc_channel *channel = + (wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC); + RETURN_STRING(grpc_channel_get_target(channel->wrapped), 1); +} + +/** * Close the channel */ PHP_METHOD(Channel, close) { @@ -208,7 +218,9 @@ PHP_METHOD(Channel, close) { static zend_function_entry channel_methods[] = { PHP_ME(Channel, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) - PHP_ME(Channel, close, NULL, ZEND_ACC_PUBLIC) PHP_FE_END}; + PHP_ME(Channel, getTarget, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Channel, close, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END}; void grpc_init_channel(TSRMLS_D) { zend_class_entry ce; diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c index 8b8d5b2f47..d58aa884ca 100644 --- a/src/php/ext/grpc/server.c +++ b/src/php/ext/grpc/server.c @@ -182,7 +182,7 @@ PHP_METHOD(Server, addHttp2Port) { "add_http2_port expects a string", 1 TSRMLS_CC); return; } - RETURN_LONG(grpc_server_add_http2_port(server->wrapped, addr)); + RETURN_LONG(grpc_server_add_insecure_http2_port(server->wrapped, addr)); } PHP_METHOD(Server, addSecureHttp2Port) { diff --git a/src/php/ext/grpc/server_credentials.c b/src/php/ext/grpc/server_credentials.c index 4c4a598cb0..e9183c4598 100644 --- a/src/php/ext/grpc/server_credentials.c +++ b/src/php/ext/grpc/server_credentials.c @@ -115,8 +115,10 @@ PHP_METHOD(ServerCredentials, createSsl) { "createSsl expects 3 strings", 1 TSRMLS_CC); return; } - grpc_server_credentials *creds = - grpc_ssl_server_credentials_create(pem_root_certs, &pem_key_cert_pair, 1); + /* TODO: add a force_client_auth field in ServerCredentials and pass it as + * the last parameter. */ + grpc_server_credentials *creds = grpc_ssl_server_credentials_create( + pem_root_certs, &pem_key_cert_pair, 1, 0); zval *creds_object = grpc_php_wrap_server_credentials(creds); RETURN_DESTROY_ZVAL(creds_object); } diff --git a/src/php/lib/Grpc/AbstractCall.php b/src/php/lib/Grpc/AbstractCall.php index 5b28417a0d..35057224f8 100644 --- a/src/php/lib/Grpc/AbstractCall.php +++ b/src/php/lib/Grpc/AbstractCall.php @@ -68,6 +68,13 @@ abstract class AbstractCall { } /** + * @return string The URI of the endpoint. + */ + public function getPeer() { + return $this->call->getPeer(); + } + + /** * Cancels the call */ public function cancel() { diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php index 8c438e4bf9..a0c677908c 100755 --- a/src/php/lib/Grpc/BaseStub.php +++ b/src/php/lib/Grpc/BaseStub.php @@ -68,6 +68,13 @@ class BaseStub { } /** + * @return string The URI of the endpoint. + */ + public function getTarget() { + return $this->channel->getTarget(); + } + + /** * Close the communication channel associated with this stub */ public function close() { diff --git a/src/php/tests/generated_code/AbstractGeneratedCodeTest.php b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php index 6102aaf0a8..8b7e67f57c 100644 --- a/src/php/tests/generated_code/AbstractGeneratedCodeTest.php +++ b/src/php/tests/generated_code/AbstractGeneratedCodeTest.php @@ -43,7 +43,9 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase { $div_arg = new math\DivArgs(); $div_arg->setDividend(7); $div_arg->setDivisor(4); - list($response, $status) = self::$client->Div($div_arg)->wait(); + $call = self::$client->Div($div_arg); + $this->assertTrue(is_string($call->getPeer())); + list($response, $status) = $call->wait(); $this->assertSame(1, $response->getQuotient()); $this->assertSame(3, $response->getRemainder()); $this->assertSame(\Grpc\STATUS_OK, $status->code); @@ -53,6 +55,7 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase { $fib_arg = new math\FibArgs(); $fib_arg->setLimit(7); $call = self::$client->Fib($fib_arg); + $this->assertTrue(is_string($call->getPeer())); $result_array = iterator_to_array($call->responses()); $extract_num = function($num){ return $num->getNum(); @@ -72,6 +75,7 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase { } }; $call = self::$client->Sum($num_iter()); + $this->assertTrue(is_string($call->getPeer())); list($response, $status) = $call->wait(); $this->assertSame(21, $response->getNum()); $this->assertSame(\Grpc\STATUS_OK, $status->code); @@ -79,6 +83,7 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase { public function testBidiStreaming() { $call = self::$client->DivMany(); + $this->assertTrue(is_string($call->getPeer())); for ($i = 0; $i < 7; $i++) { $div_arg = new math\DivArgs(); $div_arg->setDividend(2 * $i + 1); diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php index 2041577557..44e6242c29 100755 --- a/src/php/tests/interop/interop_client.php +++ b/src/php/tests/interop/interop_client.php @@ -332,10 +332,11 @@ if (in_array($args['test_case'], array( $opts['update_metadata'] = $auth->getUpdateMetadataFunc(); } -$stub = new grpc\testing\TestServiceClient( - new Grpc\BaseStub( - $server_address, - $opts)); +$internal_stub = new Grpc\BaseStub($server_address, $opts); +hardAssert(is_string($internal_stub->getTarget()), + 'Unexpected target URI value'); + +$stub = new grpc\testing\TestServiceClient($internal_stub); echo "Connecting to $server_address\n"; echo "Running test case $args[test_case]\n"; diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php index 77a2d86ce4..caff15ee11 100755 --- a/src/php/tests/unit_tests/CallTest.php +++ b/src/php/tests/unit_tests/CallTest.php @@ -79,4 +79,8 @@ class CallTest extends PHPUnit_Framework_TestCase{ $result = $this->call->startBatch($batch); $this->assertTrue($result->send_metadata); } + + public function testGetPeer() { + $this->assertTrue(is_string($this->call->getPeer())); + } } diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php index 2980dca4a7..27e27cdfdf 100755 --- a/src/php/tests/unit_tests/EndToEndTest.php +++ b/src/php/tests/unit_tests/EndToEndTest.php @@ -34,8 +34,8 @@ class EndToEndTest extends PHPUnit_Framework_TestCase{ public function setUp() { $this->server = new Grpc\Server([]); - $port = $this->server->addHttp2Port('0.0.0.0:0'); - $this->channel = new Grpc\Channel('localhost:' . $port, []); + $this->port = $this->server->addHttp2Port('0.0.0.0:0'); + $this->channel = new Grpc\Channel('localhost:' . $this->port, []); $this->server->start(); } @@ -149,4 +149,8 @@ class EndToEndTest extends PHPUnit_Framework_TestCase{ unset($call); unset($server_call); } + + public function testGetTarget() { + $this->assertTrue(is_string($this->channel->getTarget())); + } } diff --git a/src/python/grpcio/.gitignore b/src/python/grpcio/.gitignore new file mode 100644 index 0000000000..4c02b8d14d --- /dev/null +++ b/src/python/grpcio/.gitignore @@ -0,0 +1,9 @@ +MANIFEST +*.egg-info/ +build/ +dist/ +*.egg +*.egg/ +*.eggs/ +doc/ +_grpcio_metadata.py diff --git a/src/python/grpcio/MANIFEST.in b/src/python/grpcio/MANIFEST.in new file mode 100644 index 0000000000..9583dc7768 --- /dev/null +++ b/src/python/grpcio/MANIFEST.in @@ -0,0 +1,3 @@ +graft grpc +include commands.py +include requirements.txt diff --git a/src/python/src/README.rst b/src/python/grpcio/README.rst index 00bdecf56f..00bdecf56f 100644 --- a/src/python/src/README.rst +++ b/src/python/grpcio/README.rst diff --git a/src/python/grpcio/commands.py b/src/python/grpcio/commands.py new file mode 100644 index 0000000000..89c0fbf0f3 --- /dev/null +++ b/src/python/grpcio/commands.py @@ -0,0 +1,102 @@ +# 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. + +"""Provides distutils command classes for the GRPC Python setup process.""" + +import os +import os.path +import sys + +import setuptools +from setuptools.command import build_py + +_CONF_PY_ADDENDUM = """ +extensions.append('sphinx.ext.napoleon') +napoleon_google_docstring = True +napoleon_numpy_docstring = True + +html_theme = 'sphinx_rtd_theme' +""" + + +class SphinxDocumentation(setuptools.Command): + """Command to generate documentation via sphinx.""" + + description = '' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + # We import here to ensure that setup.py has had a chance to install the + # relevant package eggs first. + import sphinx + import sphinx.apidoc + metadata = self.distribution.metadata + src_dir = os.path.join( + os.getcwd(), self.distribution.package_dir['grpc']) + sys.path.append(src_dir) + sphinx.apidoc.main([ + '', '--force', '--full', '-H', metadata.name, '-A', metadata.author, + '-V', metadata.version, '-R', metadata.version, + '-o', os.path.join('doc', 'src'), src_dir]) + conf_filepath = os.path.join('doc', 'src', 'conf.py') + with open(conf_filepath, 'a') as conf_file: + conf_file.write(_CONF_PY_ADDENDUM) + sphinx.main(['', os.path.join('doc', 'src'), os.path.join('doc', 'build')]) + + +class BuildProjectMetadata(setuptools.Command): + """Command to generate project metadata in a module.""" + + description = '' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + with open('grpc/_grpcio_metadata.py', 'w') as module_file: + module_file.write('__version__ = """{}"""'.format( + self.distribution.get_version())) + + +class BuildPy(build_py.build_py): + """Custom project build command.""" + + def run(self): + self.run_command('build_project_metadata') + build_py.build_py.run(self) diff --git a/src/python/interop/interop/__init__.py b/src/python/grpcio/grpc/__init__.py index 7086519106..7086519106 100644 --- a/src/python/interop/interop/__init__.py +++ b/src/python/grpcio/grpc/__init__.py diff --git a/src/python/src/grpc/_adapter/.gitignore b/src/python/grpcio/grpc/_adapter/.gitignore index a6f96cd6db..a6f96cd6db 100644 --- a/src/python/src/grpc/_adapter/.gitignore +++ b/src/python/grpcio/grpc/_adapter/.gitignore diff --git a/src/python/src/grpc/__init__.py b/src/python/grpcio/grpc/_adapter/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/__init__.py +++ b/src/python/grpcio/grpc/_adapter/__init__.py diff --git a/src/python/src/grpc/_adapter/_c/module.c b/src/python/grpcio/grpc/_adapter/_c/module.c index 1f3aedd9d8..9b93b051f6 100644 --- a/src/python/src/grpc/_adapter/_c/module.c +++ b/src/python/grpcio/grpc/_adapter/_c/module.c @@ -53,6 +53,12 @@ PyMODINIT_FUNC init_c(void) { return; } + if (PyModule_AddStringConstant( + module, "PRIMARY_USER_AGENT_KEY", + GRPC_ARG_PRIMARY_USER_AGENT_STRING) < 0) { + return; + } + /* GRPC maintains an internal counter of how many times it has been initialized and handles multiple pairs of grpc_init()/grpc_shutdown() invocations accordingly. */ diff --git a/src/python/src/grpc/_adapter/_c/types.c b/src/python/grpcio/grpc/_adapter/_c/types.c index 8855c32ca6..8855c32ca6 100644 --- a/src/python/src/grpc/_adapter/_c/types.c +++ b/src/python/grpcio/grpc/_adapter/_c/types.c diff --git a/src/python/src/grpc/_adapter/_c/types.h b/src/python/grpcio/grpc/_adapter/_c/types.h index 4e0da4a28a..f646465c63 100644 --- a/src/python/src/grpc/_adapter/_c/types.h +++ b/src/python/grpcio/grpc/_adapter/_c/types.h @@ -113,6 +113,7 @@ Call *pygrpc_Call_new_empty(CompletionQueue *cq); void pygrpc_Call_dealloc(Call *self); PyObject *pygrpc_Call_start_batch(Call *self, PyObject *args, PyObject *kwargs); PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs); +PyObject *pygrpc_Call_peer(Call *self); extern PyTypeObject pygrpc_Call_type; @@ -129,6 +130,11 @@ Channel *pygrpc_Channel_new( void pygrpc_Channel_dealloc(Channel *self); Call *pygrpc_Channel_create_call( Channel *self, PyObject *args, PyObject *kwargs); +PyObject *pygrpc_Channel_check_connectivity_state(Channel *self, PyObject *args, + PyObject *kwargs); +PyObject *pygrpc_Channel_watch_connectivity_state(Channel *self, PyObject *args, + PyObject *kwargs); +PyObject *pygrpc_Channel_target(Channel *self); extern PyTypeObject pygrpc_Channel_type; @@ -181,6 +187,9 @@ pygrpc_tag *pygrpc_produce_request_tag(PyObject *user_tag, Call *empty_call); /* Construct a tag associated with a server shutdown. */ pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag); +/* Construct a tag associated with a channel state change. */ +pygrpc_tag *pygrpc_produce_channel_state_change_tag(PyObject *user_tag); + /* Frees all resources owned by the tag and the tag itself. */ void pygrpc_discard_tag(pygrpc_tag *tag); diff --git a/src/python/src/grpc/_adapter/_c/types/call.c b/src/python/grpcio/grpc/_adapter/_c/types/call.c index 0739070044..5e46605c45 100644 --- a/src/python/src/grpc/_adapter/_c/types/call.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/call.c @@ -42,6 +42,7 @@ PyMethodDef pygrpc_Call_methods[] = { {"start_batch", (PyCFunction)pygrpc_Call_start_batch, METH_KEYWORDS, ""}, {"cancel", (PyCFunction)pygrpc_Call_cancel, METH_KEYWORDS, ""}, + {"peer", (PyCFunction)pygrpc_Call_peer, METH_NOARGS, ""}, {NULL} }; const char pygrpc_Call_doc[] = "See grpc._adapter._types.Call."; @@ -161,3 +162,10 @@ PyObject *pygrpc_Call_cancel(Call *self, PyObject *args, PyObject *kwargs) { } return PyInt_FromLong(errcode); } + +PyObject *pygrpc_Call_peer(Call *self) { + char *peer = grpc_call_get_peer(self->c_call); + PyObject *py_peer = PyString_FromString(peer); + gpr_free(peer); + return py_peer; +} diff --git a/src/python/src/grpc/_adapter/_c/types/channel.c b/src/python/grpcio/grpc/_adapter/_c/types/channel.c index c235597466..eb9d43d154 100644 --- a/src/python/src/grpc/_adapter/_c/types/channel.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/channel.c @@ -36,10 +36,14 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> #include <grpc/grpc.h> +#include <grpc/support/alloc.h> PyMethodDef pygrpc_Channel_methods[] = { {"create_call", (PyCFunction)pygrpc_Channel_create_call, METH_KEYWORDS, ""}, + {"check_connectivity_state", (PyCFunction)pygrpc_Channel_check_connectivity_state, METH_KEYWORDS, ""}, + {"watch_connectivity_state", (PyCFunction)pygrpc_Channel_watch_connectivity_state, METH_KEYWORDS, ""}, + {"target", (PyCFunction)pygrpc_Channel_target, METH_NOARGS, ""}, {NULL} }; const char pygrpc_Channel_doc[] = "See grpc._adapter._types.Channel."; @@ -104,7 +108,7 @@ Channel *pygrpc_Channel_new( if (creds) { self->c_chan = grpc_secure_channel_create(creds->c_creds, target, &c_args); } else { - self->c_chan = grpc_channel_create(target, &c_args); + self->c_chan = grpc_insecure_channel_create(target, &c_args); } pygrpc_discard_channel_args(c_args); return self; @@ -122,13 +126,61 @@ Call *pygrpc_Channel_create_call( const char *host; double deadline; char *keywords[] = {"cq", "method", "host", "deadline", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ssd:create_call", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!szd:create_call", keywords, &pygrpc_CompletionQueue_type, &cq, &method, &host, &deadline)) { return NULL; } call = pygrpc_Call_new_empty(cq); call->c_call = grpc_channel_create_call( - self->c_chan, cq->c_cq, method, host, + self->c_chan, NULL, GRPC_PROPAGATE_DEFAULTS, cq->c_cq, method, host, pygrpc_cast_double_to_gpr_timespec(deadline)); return call; } + +PyObject *pygrpc_Channel_check_connectivity_state( + Channel *self, PyObject *args, PyObject *kwargs) { + PyObject *py_try_to_connect; + int try_to_connect; + char *keywords[] = {"try_to_connect", NULL}; + grpc_connectivity_state state; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:connectivity_state", keywords, + &py_try_to_connect)) { + return NULL; + } + if (!PyBool_Check(py_try_to_connect)) { + Py_XDECREF(py_try_to_connect); + return NULL; + } + try_to_connect = Py_True == py_try_to_connect; + Py_DECREF(py_try_to_connect); + state = grpc_channel_check_connectivity_state(self->c_chan, try_to_connect); + return PyInt_FromLong(state); +} + +PyObject *pygrpc_Channel_watch_connectivity_state( + Channel *self, PyObject *args, PyObject *kwargs) { + PyObject *tag; + double deadline; + int last_observed_state; + CompletionQueue *completion_queue; + char *keywords[] = {"last_observed_state", "deadline", + "completion_queue", "tag"}; + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "idO!O:watch_connectivity_state", keywords, + &last_observed_state, &deadline, &pygrpc_CompletionQueue_type, + &completion_queue, &tag)) { + return NULL; + } + grpc_channel_watch_connectivity_state( + self->c_chan, (grpc_connectivity_state)last_observed_state, + pygrpc_cast_double_to_gpr_timespec(deadline), completion_queue->c_cq, + pygrpc_produce_channel_state_change_tag(tag)); + Py_RETURN_NONE; +} + +PyObject *pygrpc_Channel_target(Channel *self) { + char *target = grpc_channel_get_target(self->c_chan); + PyObject *py_target = PyString_FromString(target); + gpr_free(target); + return py_target; +} diff --git a/src/python/src/grpc/_adapter/_c/types/client_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c index e314c15324..e314c15324 100644 --- a/src/python/src/grpc/_adapter/_c/types/client_credentials.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/client_credentials.c diff --git a/src/python/src/grpc/_adapter/_c/types/completion_queue.c b/src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c index 2dd44b6ddd..2dd44b6ddd 100644 --- a/src/python/src/grpc/_adapter/_c/types/completion_queue.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/completion_queue.c diff --git a/src/python/src/grpc/_adapter/_c/types/server.c b/src/python/grpcio/grpc/_adapter/_c/types/server.c index 2a00f34039..2a11d09d21 100644 --- a/src/python/src/grpc/_adapter/_c/types/server.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/server.c @@ -96,7 +96,7 @@ Server *pygrpc_Server_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *py_args; grpc_channel_args c_args; char *keywords[] = {"cq", "args", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Channel", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Server", keywords, &pygrpc_CompletionQueue_type, &cq, &py_args)) { return NULL; } @@ -155,7 +155,7 @@ PyObject *pygrpc_Server_add_http2_port( port = grpc_server_add_secure_http2_port( self->c_serv, addr, creds->c_creds); } else { - port = grpc_server_add_http2_port(self->c_serv, addr); + port = grpc_server_add_insecure_http2_port(self->c_serv, addr); } return PyInt_FromLong(port); diff --git a/src/python/src/grpc/_adapter/_c/types/server_credentials.c b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c index f22edbf187..f6859b79d7 100644 --- a/src/python/src/grpc/_adapter/_c/types/server_credentials.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/server_credentials.c @@ -128,8 +128,10 @@ ServerCredentials *pygrpc_ServerCredentials_ssl( } self = (ServerCredentials *)type->tp_alloc(type, 0); + /* TODO: Add a force_client_auth parameter in the python object and pass it + here as the last arg. */ self->c_creds = grpc_ssl_server_credentials_create( - root_certs, key_cert_pairs, num_key_cert_pairs); + root_certs, key_cert_pairs, num_key_cert_pairs, 0); gpr_free(key_cert_pairs); return self; } diff --git a/src/python/src/grpc/_adapter/_c/utility.c b/src/python/grpcio/grpc/_adapter/_c/utility.c index 51f3c9be01..2eea0e18ef 100644 --- a/src/python/src/grpc/_adapter/_c/utility.c +++ b/src/python/grpcio/grpc/_adapter/_c/utility.c @@ -88,6 +88,19 @@ pygrpc_tag *pygrpc_produce_server_shutdown_tag(PyObject *user_tag) { return tag; } +pygrpc_tag *pygrpc_produce_channel_state_change_tag(PyObject *user_tag) { + pygrpc_tag *tag = gpr_malloc(sizeof(pygrpc_tag)); + tag->user_tag = user_tag; + Py_XINCREF(tag->user_tag); + tag->call = NULL; + tag->ops = NULL; + tag->nops = 0; + grpc_call_details_init(&tag->request_call_details); + grpc_metadata_array_init(&tag->request_metadata); + tag->is_new_call = 0; + return tag; +} + void pygrpc_discard_tag(pygrpc_tag *tag) { if (!tag) { return; @@ -139,7 +152,7 @@ PyObject *pygrpc_consume_event(grpc_event event) { } int pygrpc_produce_op(PyObject *op, grpc_op *result) { - static const int OP_TUPLE_SIZE = 5; + static const int OP_TUPLE_SIZE = 6; static const int STATUS_TUPLE_SIZE = 2; static const int TYPE_INDEX = 0; static const int INITIAL_METADATA_INDEX = 1; @@ -148,6 +161,7 @@ int pygrpc_produce_op(PyObject *op, grpc_op *result) { static const int STATUS_INDEX = 4; static const int STATUS_CODE_INDEX = 0; static const int STATUS_DETAILS_INDEX = 1; + static const int WRITE_FLAGS_INDEX = 5; int type; Py_ssize_t message_size; char *message; @@ -170,7 +184,10 @@ int pygrpc_produce_op(PyObject *op, grpc_op *result) { return 0; } c_op.op = type; - c_op.flags = 0; + c_op.flags = PyInt_AsLong(PyTuple_GET_ITEM(op, WRITE_FLAGS_INDEX)); + if (PyErr_Occurred()) { + return 0; + } switch (type) { case GRPC_OP_SEND_INITIAL_METADATA: if (!pygrpc_cast_pyseq_to_send_metadata( diff --git a/src/python/src/grpc/_adapter/_common.py b/src/python/grpcio/grpc/_adapter/_common.py index 492849f4cb..492849f4cb 100644 --- a/src/python/src/grpc/_adapter/_common.py +++ b/src/python/grpcio/grpc/_adapter/_common.py diff --git a/src/python/src/grpc/_adapter/_intermediary_low.py b/src/python/grpcio/grpc/_adapter/_intermediary_low.py index 3c7f0a2619..e7bf9dc462 100644 --- a/src/python/src/grpc/_adapter/_intermediary_low.py +++ b/src/python/grpcio/grpc/_adapter/_intermediary_low.py @@ -127,7 +127,7 @@ class Call(object): def write(self, message, tag): return self._internal.start_batch([ - _types.OpArgs.send_message(message) + _types.OpArgs.send_message(message, 0) ], _TagAdapter(tag, Event.Kind.WRITE_ACCEPTED)) def complete(self, tag): diff --git a/src/python/src/grpc/_adapter/_low.py b/src/python/grpcio/grpc/_adapter/_low.py index dcf67dbc11..147086e725 100644 --- a/src/python/src/grpc/_adapter/_low.py +++ b/src/python/grpcio/grpc/_adapter/_low.py @@ -27,9 +27,12 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from grpc import _grpcio_metadata from grpc._adapter import _c from grpc._adapter import _types +_USER_AGENT = 'Python-gRPC-{}'.format(_grpcio_metadata.__version__) + ClientCredentials = _c.ClientCredentials ServerCredentials = _c.ServerCredentials @@ -72,10 +75,14 @@ class Call(_types.Call): else: return self.call.cancel(code, details) + def peer(self): + return self.call.peer() + class Channel(_types.Channel): def __init__(self, target, args, creds=None): + args = list(args) + [(_c.PRIMARY_USER_AGENT_KEY, _USER_AGENT)] if creds is None: self.channel = _c.Channel(target, args) else: @@ -84,6 +91,17 @@ class Channel(_types.Channel): def create_call(self, completion_queue, method, host, deadline=None): return Call(self.channel.create_call(completion_queue.completion_queue, method, host, deadline)) + def check_connectivity_state(self, try_to_connect): + return self.channel.check_connectivity_state(try_to_connect) + + def watch_connectivity_state(self, last_observed_state, deadline, + completion_queue, tag): + self.channel.watch_connectivity_state( + last_observed_state, deadline, completion_queue.completion_queue, tag) + + def target(self): + return self.channel.target() + _NO_TAG = object() diff --git a/src/python/src/grpc/_adapter/_types.py b/src/python/grpcio/grpc/_adapter/_types.py index 5ddb1774ea..5470d2de4a 100644 --- a/src/python/src/grpc/_adapter/_types.py +++ b/src/python/grpcio/grpc/_adapter/_types.py @@ -31,13 +31,12 @@ import abc import collections import enum -# TODO(atash): decide whether or not to move these enums to the _c module to -# force build errors with upstream changes. class GrpcChannelArgumentKeys(enum.Enum): """Mirrors keys used in grpc_channel_args for GRPC-specific arguments.""" SSL_TARGET_NAME_OVERRIDE = 'grpc.ssl_target_name_override' + @enum.unique class CallError(enum.IntEnum): """Mirrors grpc_call_error in the C core.""" @@ -53,6 +52,7 @@ class CallError(enum.IntEnum): ERROR_INVALID_FLAGS = 9 ERROR_INVALID_METADATA = 10 + @enum.unique class StatusCode(enum.IntEnum): """Mirrors grpc_status_code in the C core.""" @@ -74,6 +74,14 @@ class StatusCode(enum.IntEnum): DATA_LOSS = 15 UNAUTHENTICATED = 16 + +@enum.unique +class OpWriteFlags(enum.IntEnum): + """Mirrors defined write-flag constants in the C core.""" + WRITE_BUFFER_HINT = 1 + WRITE_NO_COMPRESS = 2 + + @enum.unique class OpType(enum.IntEnum): """Mirrors grpc_op_type in the C core.""" @@ -86,12 +94,24 @@ class OpType(enum.IntEnum): RECV_STATUS_ON_CLIENT = 6 RECV_CLOSE_ON_SERVER = 7 + @enum.unique class EventType(enum.IntEnum): """Mirrors grpc_completion_type in the C core.""" - QUEUE_SHUTDOWN = 0 - QUEUE_TIMEOUT = 1 # if seen on the Python side, something went horridly wrong - OP_COMPLETE = 2 + QUEUE_SHUTDOWN = 0 + QUEUE_TIMEOUT = 1 # if seen on the Python side, something went horridly wrong + OP_COMPLETE = 2 + + +@enum.unique +class ConnectivityState(enum.IntEnum): + """Mirrors grpc_connectivity_state in the C core.""" + IDLE = 0 + CONNECTING = 1 + READY = 2 + TRANSIENT_FAILURE = 3 + FATAL_FAILURE = 4 + class Status(collections.namedtuple( 'Status', [ @@ -105,6 +125,7 @@ class Status(collections.namedtuple( details (str): ... """ + class CallDetails(collections.namedtuple( 'CallDetails', [ 'method', @@ -119,6 +140,7 @@ class CallDetails(collections.namedtuple( deadline (float): ... """ + class OpArgs(collections.namedtuple( 'OpArgs', [ 'type', @@ -126,6 +148,7 @@ class OpArgs(collections.namedtuple( 'trailing_metadata', 'message', 'status', + 'write_flags', ])): """Arguments passed into a GRPC operation. @@ -138,39 +161,40 @@ class OpArgs(collections.namedtuple( message (bytes): Only valid if type == OpType.SEND_MESSAGE, else is None. status (Status): Only valid if type == OpType.SEND_STATUS_FROM_SERVER, else is None. + write_flags (int): a bit OR'ing of 0 or more OpWriteFlags values. """ @staticmethod def send_initial_metadata(initial_metadata): - return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None) + return OpArgs(OpType.SEND_INITIAL_METADATA, initial_metadata, None, None, None, 0) @staticmethod - def send_message(message): - return OpArgs(OpType.SEND_MESSAGE, None, None, message, None) + def send_message(message, flags): + return OpArgs(OpType.SEND_MESSAGE, None, None, message, None, flags) @staticmethod def send_close_from_client(): - return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None) + return OpArgs(OpType.SEND_CLOSE_FROM_CLIENT, None, None, None, None, 0) @staticmethod def send_status_from_server(trailing_metadata, status_code, status_details): - return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details)) + return OpArgs(OpType.SEND_STATUS_FROM_SERVER, None, trailing_metadata, None, Status(status_code, status_details), 0) @staticmethod def recv_initial_metadata(): - return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None); + return OpArgs(OpType.RECV_INITIAL_METADATA, None, None, None, None, 0); @staticmethod def recv_message(): - return OpArgs(OpType.RECV_MESSAGE, None, None, None, None) + return OpArgs(OpType.RECV_MESSAGE, None, None, None, None, 0) @staticmethod def recv_status_on_client(): - return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None) + return OpArgs(OpType.RECV_STATUS_ON_CLIENT, None, None, None, None, 0) @staticmethod def recv_close_on_server(): - return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None) + return OpArgs(OpType.RECV_CLOSE_ON_SERVER, None, None, None, None, 0) class OpResult(collections.namedtuple( @@ -290,6 +314,15 @@ class Call: """ return CallError.ERROR + @abc.abstractmethod + def peer(self): + """Get the peer of this call. + + Returns: + str: the peer of this call. + """ + return None + class Channel: __metaclass__ = abc.ABCMeta @@ -321,6 +354,40 @@ class Channel: """ return None + @abc.abstractmethod + def check_connectivity_state(self, try_to_connect): + """Check and optionally repair the connectivity state of the channel. + + Args: + try_to_connect (bool): whether or not to try to connect the channel if + disconnected. + + Returns: + ConnectivityState: state of the channel at the time of this invocation. + """ + return None + + @abc.abstractmethod + def watch_connectivity_state(self, last_observed_state, deadline, + completion_queue, tag): + """Watch for connectivity state changes from the last_observed_state. + + Args: + last_observed_state (ConnectivityState): ... + deadline (float): ... + completion_queue (CompletionQueue): ... + tag (object) ... + """ + + @abc.abstractmethod + def target(self): + """Get the target of this channel. + + Returns: + str: the target of this channel. + """ + return None + class Server: __metaclass__ = abc.ABCMeta diff --git a/src/python/src/grpc/_adapter/fore.py b/src/python/grpcio/grpc/_adapter/fore.py index 7d88bda263..7d88bda263 100644 --- a/src/python/src/grpc/_adapter/fore.py +++ b/src/python/grpcio/grpc/_adapter/fore.py diff --git a/src/python/src/grpc/_adapter/rear.py b/src/python/grpcio/grpc/_adapter/rear.py index fd6f45f7a7..fd6f45f7a7 100644 --- a/src/python/src/grpc/_adapter/rear.py +++ b/src/python/grpcio/grpc/_adapter/rear.py diff --git a/src/python/src/grpc/_cython/.gitignore b/src/python/grpcio/grpc/_cython/.gitignore index c315029288..c315029288 100644 --- a/src/python/src/grpc/_cython/.gitignore +++ b/src/python/grpcio/grpc/_cython/.gitignore diff --git a/src/python/src/grpc/_cython/README.rst b/src/python/grpcio/grpc/_cython/README.rst index c0e66734e8..c0e66734e8 100644 --- a/src/python/src/grpc/_cython/README.rst +++ b/src/python/grpcio/grpc/_cython/README.rst diff --git a/src/python/src/grpc/_cython/__init__.py b/src/python/grpcio/grpc/_cython/__init__.py index b89398809f..b89398809f 100644 --- a/src/python/src/grpc/_cython/__init__.py +++ b/src/python/grpcio/grpc/_cython/__init__.py diff --git a/src/python/src/grpc/_cython/_cygrpc/__init__.py b/src/python/grpcio/grpc/_cython/_cygrpc/__init__.py index b89398809f..b89398809f 100644 --- a/src/python/src/grpc/_cython/_cygrpc/__init__.py +++ b/src/python/grpcio/grpc/_cython/_cygrpc/__init__.py diff --git a/src/python/src/grpc/_cython/_cygrpc/call.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/call.pxd index fe9b81e3d3..fe9b81e3d3 100644 --- a/src/python/src/grpc/_cython/_cygrpc/call.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/call.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/call.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/call.pyx index 4349786b3a..4349786b3a 100644 --- a/src/python/src/grpc/_cython/_cygrpc/call.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/call.pyx diff --git a/src/python/src/grpc/_cython/_cygrpc/channel.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd index 3e341bf222..3e341bf222 100644 --- a/src/python/src/grpc/_cython/_cygrpc/channel.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/channel.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx index b20313818d..b20313818d 100644 --- a/src/python/src/grpc/_cython/_cygrpc/channel.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/channel.pyx diff --git a/src/python/src/grpc/_cython/_cygrpc/completion_queue.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd index fd562ad75b..fd562ad75b 100644 --- a/src/python/src/grpc/_cython/_cygrpc/completion_queue.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/completion_queue.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx index 886d85360a..886d85360a 100644 --- a/src/python/src/grpc/_cython/_cygrpc/completion_queue.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/completion_queue.pyx diff --git a/src/python/src/grpc/_cython/_cygrpc/credentials.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd index 6b74a267e0..6b74a267e0 100644 --- a/src/python/src/grpc/_cython/_cygrpc/credentials.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/credentials.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx index 2d74702fbd..2d74702fbd 100644 --- a/src/python/src/grpc/_cython/_cygrpc/credentials.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx diff --git a/src/python/src/grpc/_cython/_cygrpc/grpc.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd index d065383587..d065383587 100644 --- a/src/python/src/grpc/_cython/_cygrpc/grpc.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/records.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/records.pxd index 9ee487882a..9ee487882a 100644 --- a/src/python/src/grpc/_cython/_cygrpc/records.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/records.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/records.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx index 4814769fd2..4814769fd2 100644 --- a/src/python/src/grpc/_cython/_cygrpc/records.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/records.pyx diff --git a/src/python/src/grpc/_cython/_cygrpc/server.pxd b/src/python/grpcio/grpc/_cython/_cygrpc/server.pxd index 0257542a03..0257542a03 100644 --- a/src/python/src/grpc/_cython/_cygrpc/server.pxd +++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pxd diff --git a/src/python/src/grpc/_cython/_cygrpc/server.pyx b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx index dcf9d38337..dcf9d38337 100644 --- a/src/python/src/grpc/_cython/_cygrpc/server.pyx +++ b/src/python/grpcio/grpc/_cython/_cygrpc/server.pyx diff --git a/src/python/src/grpc/_cython/adapter_low.py b/src/python/grpcio/grpc/_cython/adapter_low.py index 2bb468eece..2bb468eece 100644 --- a/src/python/src/grpc/_cython/adapter_low.py +++ b/src/python/grpcio/grpc/_cython/adapter_low.py diff --git a/src/python/src/grpc/_cython/cygrpc.pyx b/src/python/grpcio/grpc/_cython/cygrpc.pyx index f4d9661580..f4d9661580 100644 --- a/src/python/src/grpc/_cython/cygrpc.pyx +++ b/src/python/grpcio/grpc/_cython/cygrpc.pyx diff --git a/src/python/src/grpc/_adapter/__init__.py b/src/python/grpcio/grpc/_links/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/_adapter/__init__.py +++ b/src/python/grpcio/grpc/_links/__init__.py diff --git a/src/python/src/grpc/_links/invocation.py b/src/python/grpcio/grpc/_links/invocation.py index 0058ae91f8..0058ae91f8 100644 --- a/src/python/src/grpc/_links/invocation.py +++ b/src/python/grpcio/grpc/_links/invocation.py diff --git a/src/python/src/grpc/_links/service.py b/src/python/grpcio/grpc/_links/service.py index 7783e91824..7783e91824 100644 --- a/src/python/src/grpc/_links/service.py +++ b/src/python/grpcio/grpc/_links/service.py diff --git a/src/python/src/grpc/_junkdrawer/__init__.py b/src/python/grpcio/grpc/early_adopter/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/_junkdrawer/__init__.py +++ b/src/python/grpcio/grpc/early_adopter/__init__.py diff --git a/src/python/src/grpc/early_adopter/implementations.py b/src/python/grpcio/grpc/early_adopter/implementations.py index 10919fae69..10919fae69 100644 --- a/src/python/src/grpc/early_adopter/implementations.py +++ b/src/python/grpcio/grpc/early_adopter/implementations.py diff --git a/src/python/src/grpc/_links/__init__.py b/src/python/grpcio/grpc/framework/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/_links/__init__.py +++ b/src/python/grpcio/grpc/framework/__init__.py diff --git a/src/python/src/grpc/framework/alpha/__init__.py b/src/python/grpcio/grpc/framework/alpha/__init__.py index b89398809f..b89398809f 100644 --- a/src/python/src/grpc/framework/alpha/__init__.py +++ b/src/python/grpcio/grpc/framework/alpha/__init__.py diff --git a/src/python/src/grpc/framework/alpha/_face_utilities.py b/src/python/grpcio/grpc/framework/alpha/_face_utilities.py index fb0cfe426d..fb0cfe426d 100644 --- a/src/python/src/grpc/framework/alpha/_face_utilities.py +++ b/src/python/grpcio/grpc/framework/alpha/_face_utilities.py diff --git a/src/python/src/grpc/framework/alpha/_reexport.py b/src/python/grpcio/grpc/framework/alpha/_reexport.py index 198cb95ad5..198cb95ad5 100644 --- a/src/python/src/grpc/framework/alpha/_reexport.py +++ b/src/python/grpcio/grpc/framework/alpha/_reexport.py diff --git a/src/python/src/grpc/framework/alpha/exceptions.py b/src/python/grpcio/grpc/framework/alpha/exceptions.py index 5234d3b91c..5234d3b91c 100644 --- a/src/python/src/grpc/framework/alpha/exceptions.py +++ b/src/python/grpcio/grpc/framework/alpha/exceptions.py diff --git a/src/python/src/grpc/framework/alpha/interfaces.py b/src/python/grpcio/grpc/framework/alpha/interfaces.py index 8380567c97..8380567c97 100644 --- a/src/python/src/grpc/framework/alpha/interfaces.py +++ b/src/python/grpcio/grpc/framework/alpha/interfaces.py diff --git a/src/python/src/grpc/framework/alpha/utilities.py b/src/python/grpcio/grpc/framework/alpha/utilities.py index 7d7f78f5e4..7d7f78f5e4 100644 --- a/src/python/src/grpc/framework/alpha/utilities.py +++ b/src/python/grpcio/grpc/framework/alpha/utilities.py diff --git a/src/python/src/grpc/early_adopter/__init__.py b/src/python/grpcio/grpc/framework/base/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/early_adopter/__init__.py +++ b/src/python/grpcio/grpc/framework/base/__init__.py diff --git a/src/python/src/grpc/framework/base/_cancellation.py b/src/python/grpcio/grpc/framework/base/_cancellation.py index ffbc90668f..ffbc90668f 100644 --- a/src/python/src/grpc/framework/base/_cancellation.py +++ b/src/python/grpcio/grpc/framework/base/_cancellation.py diff --git a/src/python/src/grpc/framework/base/_constants.py b/src/python/grpcio/grpc/framework/base/_constants.py index 8fbdc82782..8fbdc82782 100644 --- a/src/python/src/grpc/framework/base/_constants.py +++ b/src/python/grpcio/grpc/framework/base/_constants.py diff --git a/src/python/src/grpc/framework/base/_context.py b/src/python/grpcio/grpc/framework/base/_context.py index d84871d639..d84871d639 100644 --- a/src/python/src/grpc/framework/base/_context.py +++ b/src/python/grpcio/grpc/framework/base/_context.py diff --git a/src/python/src/grpc/framework/base/_emission.py b/src/python/grpcio/grpc/framework/base/_emission.py index 1829669a72..1829669a72 100644 --- a/src/python/src/grpc/framework/base/_emission.py +++ b/src/python/grpcio/grpc/framework/base/_emission.py diff --git a/src/python/src/grpc/framework/base/_ends.py b/src/python/grpcio/grpc/framework/base/_ends.py index 176f3ac06e..176f3ac06e 100644 --- a/src/python/src/grpc/framework/base/_ends.py +++ b/src/python/grpcio/grpc/framework/base/_ends.py diff --git a/src/python/src/grpc/framework/base/_expiration.py b/src/python/grpcio/grpc/framework/base/_expiration.py index 17acbef4c1..17acbef4c1 100644 --- a/src/python/src/grpc/framework/base/_expiration.py +++ b/src/python/grpcio/grpc/framework/base/_expiration.py diff --git a/src/python/src/grpc/framework/base/_ingestion.py b/src/python/grpcio/grpc/framework/base/_ingestion.py index 06d5b92f0b..06d5b92f0b 100644 --- a/src/python/src/grpc/framework/base/_ingestion.py +++ b/src/python/grpcio/grpc/framework/base/_ingestion.py diff --git a/src/python/src/grpc/framework/base/_interfaces.py b/src/python/grpcio/grpc/framework/base/_interfaces.py index d88cf76590..d88cf76590 100644 --- a/src/python/src/grpc/framework/base/_interfaces.py +++ b/src/python/grpcio/grpc/framework/base/_interfaces.py diff --git a/src/python/src/grpc/framework/base/_reception.py b/src/python/grpcio/grpc/framework/base/_reception.py index dd428964f1..dd428964f1 100644 --- a/src/python/src/grpc/framework/base/_reception.py +++ b/src/python/grpcio/grpc/framework/base/_reception.py diff --git a/src/python/src/grpc/framework/base/_termination.py b/src/python/grpcio/grpc/framework/base/_termination.py index ddcbc60293..ddcbc60293 100644 --- a/src/python/src/grpc/framework/base/_termination.py +++ b/src/python/grpcio/grpc/framework/base/_termination.py diff --git a/src/python/src/grpc/framework/base/_transmission.py b/src/python/grpcio/grpc/framework/base/_transmission.py index 6845129234..6845129234 100644 --- a/src/python/src/grpc/framework/base/_transmission.py +++ b/src/python/grpcio/grpc/framework/base/_transmission.py diff --git a/src/python/src/grpc/framework/base/exceptions.py b/src/python/grpcio/grpc/framework/base/exceptions.py index b8f4752184..b8f4752184 100644 --- a/src/python/src/grpc/framework/base/exceptions.py +++ b/src/python/grpcio/grpc/framework/base/exceptions.py diff --git a/src/python/src/grpc/framework/base/implementations.py b/src/python/grpcio/grpc/framework/base/implementations.py index 5656f9f981..5656f9f981 100644 --- a/src/python/src/grpc/framework/base/implementations.py +++ b/src/python/grpcio/grpc/framework/base/implementations.py diff --git a/src/python/src/grpc/framework/base/in_memory.py b/src/python/grpcio/grpc/framework/base/in_memory.py index c92d0bc663..c92d0bc663 100644 --- a/src/python/src/grpc/framework/base/in_memory.py +++ b/src/python/grpcio/grpc/framework/base/in_memory.py diff --git a/src/python/src/grpc/framework/base/interfaces.py b/src/python/grpcio/grpc/framework/base/interfaces.py index e22c10d975..e22c10d975 100644 --- a/src/python/src/grpc/framework/base/interfaces.py +++ b/src/python/grpcio/grpc/framework/base/interfaces.py diff --git a/src/python/src/grpc/framework/base/null.py b/src/python/grpcio/grpc/framework/base/null.py index 1e30d4557b..1e30d4557b 100644 --- a/src/python/src/grpc/framework/base/null.py +++ b/src/python/grpcio/grpc/framework/base/null.py diff --git a/src/python/src/grpc/framework/base/util.py b/src/python/grpcio/grpc/framework/base/util.py index c832c826cf..c832c826cf 100644 --- a/src/python/src/grpc/framework/base/util.py +++ b/src/python/grpcio/grpc/framework/base/util.py diff --git a/src/python/src/grpc/framework/__init__.py b/src/python/grpcio/grpc/framework/common/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/__init__.py +++ b/src/python/grpcio/grpc/framework/common/__init__.py diff --git a/src/python/src/grpc/framework/common/cardinality.py b/src/python/grpcio/grpc/framework/common/cardinality.py index 610425e803..610425e803 100644 --- a/src/python/src/grpc/framework/common/cardinality.py +++ b/src/python/grpcio/grpc/framework/common/cardinality.py diff --git a/src/python/src/grpc/framework/common/style.py b/src/python/grpcio/grpc/framework/common/style.py index 6ae694bdcb..6ae694bdcb 100644 --- a/src/python/src/grpc/framework/common/style.py +++ b/src/python/grpcio/grpc/framework/common/style.py diff --git a/src/python/src/grpc/framework/base/__init__.py b/src/python/grpcio/grpc/framework/face/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/base/__init__.py +++ b/src/python/grpcio/grpc/framework/face/__init__.py diff --git a/src/python/src/grpc/framework/face/_calls.py b/src/python/grpcio/grpc/framework/face/_calls.py index 87edeb0f0e..87edeb0f0e 100644 --- a/src/python/src/grpc/framework/face/_calls.py +++ b/src/python/grpcio/grpc/framework/face/_calls.py diff --git a/src/python/src/grpc/framework/face/_control.py b/src/python/grpcio/grpc/framework/face/_control.py index e918907b74..e918907b74 100644 --- a/src/python/src/grpc/framework/face/_control.py +++ b/src/python/grpcio/grpc/framework/face/_control.py diff --git a/src/python/src/grpc/framework/face/_service.py b/src/python/grpcio/grpc/framework/face/_service.py index cdf413356a..cdf413356a 100644 --- a/src/python/src/grpc/framework/face/_service.py +++ b/src/python/grpcio/grpc/framework/face/_service.py diff --git a/src/python/src/grpc/framework/face/demonstration.py b/src/python/grpcio/grpc/framework/face/demonstration.py index f6b4b609ff..f6b4b609ff 100644 --- a/src/python/src/grpc/framework/face/demonstration.py +++ b/src/python/grpcio/grpc/framework/face/demonstration.py diff --git a/src/python/src/grpc/framework/face/exceptions.py b/src/python/grpcio/grpc/framework/face/exceptions.py index f112df70bc..f112df70bc 100644 --- a/src/python/src/grpc/framework/face/exceptions.py +++ b/src/python/grpcio/grpc/framework/face/exceptions.py diff --git a/src/python/src/grpc/framework/face/implementations.py b/src/python/grpcio/grpc/framework/face/implementations.py index 4a6de52974..4a6de52974 100644 --- a/src/python/src/grpc/framework/face/implementations.py +++ b/src/python/grpcio/grpc/framework/face/implementations.py diff --git a/src/python/src/grpc/framework/face/interfaces.py b/src/python/grpcio/grpc/framework/face/interfaces.py index b7cc4c1169..b7cc4c1169 100644 --- a/src/python/src/grpc/framework/face/interfaces.py +++ b/src/python/grpcio/grpc/framework/face/interfaces.py diff --git a/src/python/src/grpc/framework/face/utilities.py b/src/python/grpcio/grpc/framework/face/utilities.py index a63fe8c60d..a63fe8c60d 100644 --- a/src/python/src/grpc/framework/face/utilities.py +++ b/src/python/grpcio/grpc/framework/face/utilities.py diff --git a/src/python/src/grpc/framework/common/__init__.py b/src/python/grpcio/grpc/framework/foundation/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/common/__init__.py +++ b/src/python/grpcio/grpc/framework/foundation/__init__.py diff --git a/src/python/src/grpc/framework/foundation/_timer_future.py b/src/python/grpcio/grpc/framework/foundation/_timer_future.py index 2c9996aa9d..2c9996aa9d 100644 --- a/src/python/src/grpc/framework/foundation/_timer_future.py +++ b/src/python/grpcio/grpc/framework/foundation/_timer_future.py diff --git a/src/python/src/grpc/framework/foundation/abandonment.py b/src/python/grpcio/grpc/framework/foundation/abandonment.py index 960b4d06b4..960b4d06b4 100644 --- a/src/python/src/grpc/framework/foundation/abandonment.py +++ b/src/python/grpcio/grpc/framework/foundation/abandonment.py diff --git a/src/python/src/grpc/framework/foundation/activated.py b/src/python/grpcio/grpc/framework/foundation/activated.py index 426a71c705..426a71c705 100644 --- a/src/python/src/grpc/framework/foundation/activated.py +++ b/src/python/grpcio/grpc/framework/foundation/activated.py diff --git a/src/python/src/grpc/framework/foundation/callable_util.py b/src/python/grpcio/grpc/framework/foundation/callable_util.py index 32b0751a01..32b0751a01 100644 --- a/src/python/src/grpc/framework/foundation/callable_util.py +++ b/src/python/grpcio/grpc/framework/foundation/callable_util.py diff --git a/src/python/src/grpc/framework/foundation/future.py b/src/python/grpcio/grpc/framework/foundation/future.py index bfc16fc1ea..bfc16fc1ea 100644 --- a/src/python/src/grpc/framework/foundation/future.py +++ b/src/python/grpcio/grpc/framework/foundation/future.py diff --git a/src/python/src/grpc/framework/foundation/later.py b/src/python/grpcio/grpc/framework/foundation/later.py index 1d1e065041..1d1e065041 100644 --- a/src/python/src/grpc/framework/foundation/later.py +++ b/src/python/grpcio/grpc/framework/foundation/later.py diff --git a/src/python/src/grpc/framework/foundation/logging_pool.py b/src/python/grpcio/grpc/framework/foundation/logging_pool.py index 7c7a6eebfc..7c7a6eebfc 100644 --- a/src/python/src/grpc/framework/foundation/logging_pool.py +++ b/src/python/grpcio/grpc/framework/foundation/logging_pool.py diff --git a/src/python/src/grpc/framework/foundation/relay.py b/src/python/grpcio/grpc/framework/foundation/relay.py index 9c23946552..9c23946552 100644 --- a/src/python/src/grpc/framework/foundation/relay.py +++ b/src/python/grpcio/grpc/framework/foundation/relay.py diff --git a/src/python/src/grpc/framework/foundation/stream.py b/src/python/grpcio/grpc/framework/foundation/stream.py index 75c0cf145b..75c0cf145b 100644 --- a/src/python/src/grpc/framework/foundation/stream.py +++ b/src/python/grpcio/grpc/framework/foundation/stream.py diff --git a/src/python/src/grpc/framework/foundation/stream_util.py b/src/python/grpcio/grpc/framework/foundation/stream_util.py index 2210e4efcf..2210e4efcf 100644 --- a/src/python/src/grpc/framework/foundation/stream_util.py +++ b/src/python/grpcio/grpc/framework/foundation/stream_util.py diff --git a/src/python/src/grpc/framework/face/__init__.py b/src/python/grpcio/grpc/framework/interfaces/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/face/__init__.py +++ b/src/python/grpcio/grpc/framework/interfaces/__init__.py diff --git a/src/python/src/grpc/framework/face/testing/__init__.py b/src/python/grpcio/grpc/framework/interfaces/links/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/face/testing/__init__.py +++ b/src/python/grpcio/grpc/framework/interfaces/links/__init__.py diff --git a/src/python/src/grpc/framework/interfaces/links/links.py b/src/python/grpcio/grpc/framework/interfaces/links/links.py index 5ebbac8a6f..5ebbac8a6f 100644 --- a/src/python/src/grpc/framework/interfaces/links/links.py +++ b/src/python/grpcio/grpc/framework/interfaces/links/links.py diff --git a/src/python/src/grpc/framework/interfaces/links/utilities.py b/src/python/grpcio/grpc/framework/interfaces/links/utilities.py index 6e4fd76d93..6e4fd76d93 100644 --- a/src/python/src/grpc/framework/interfaces/links/utilities.py +++ b/src/python/grpcio/grpc/framework/interfaces/links/utilities.py diff --git a/src/python/requirements.txt b/src/python/grpcio/requirements.txt index 43395df03b..43395df03b 100644 --- a/src/python/requirements.txt +++ b/src/python/grpcio/requirements.txt diff --git a/src/python/grpcio/setup.cfg b/src/python/grpcio/setup.cfg new file mode 100644 index 0000000000..8f69613632 --- /dev/null +++ b/src/python/grpcio/setup.cfg @@ -0,0 +1,2 @@ +[build_ext] +inplace=1 diff --git a/src/python/src/setup.py b/src/python/grpcio/setup.py index a857ae98cc..caa71a4f7c 100644 --- a/src/python/src/setup.py +++ b/src/python/grpcio/setup.py @@ -30,11 +30,17 @@ """A setup module for the GRPC Python package.""" import os +import os.path import sys from distutils import core as _core import setuptools +# Ensure we're in the proper directory whether or not we're being used by pip. +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +# Break import-style to ensure we can actually find our commands module. +import commands # Use environment variables to determine whether or not the Cython extension # should *use* Cython or use the generated C files. Note that this requires the @@ -73,29 +79,27 @@ _C_EXTENSION_MODULE = _core.Extension( _EXTENSION_MODULES = [_C_EXTENSION_MODULE] _PACKAGES = ( - 'grpc', - 'grpc._adapter', - 'grpc._junkdrawer', - 'grpc._links', - 'grpc.early_adopter', - 'grpc.framework', - 'grpc.framework.alpha', - 'grpc.framework.base', - 'grpc.framework.common', - 'grpc.framework.face', - 'grpc.framework.face.testing', - 'grpc.framework.foundation', - 'grpc.framework.interfaces', - 'grpc.framework.interfaces.links', + setuptools.find_packages('.', exclude=['*._cython', '*._cython.*']) ) _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', + '': '.', +} + +_INSTALL_REQUIRES = ( + 'enum34==1.0.4', + 'futures==2.2.0', + 'protobuf==3.0.0a3', +) + +_SETUP_REQUIRES = ( + 'sphinx>=1.3', +) + _INSTALL_REQUIRES + +_COMMAND_CLASS = { + 'doc': commands.SphinxDocumentation, + 'build_project_metadata': commands.BuildProjectMetadata, + 'build_py': commands.BuildPy, } setuptools.setup( @@ -104,9 +108,7 @@ setuptools.setup( ext_modules=_EXTENSION_MODULES, packages=list(_PACKAGES), package_dir=_PACKAGE_DIRECTORIES, - install_requires=[ - 'enum34==1.0.4', - 'futures==2.2.0', - 'protobuf==3.0.0a3' - ] + install_requires=_INSTALL_REQUIRES, + setup_requires=_SETUP_REQUIRES, + cmdclass=_COMMAND_CLASS ) diff --git a/src/python/grpcio_test/.gitignore b/src/python/grpcio_test/.gitignore new file mode 100644 index 0000000000..218e3a15ab --- /dev/null +++ b/src/python/grpcio_test/.gitignore @@ -0,0 +1,10 @@ +MANIFEST +*.egg-info/ +build/ +dist/ +*.egg +*.egg/ +*.eggs/ +.coverage +.coverage.* +nosetests.xml diff --git a/src/python/grpcio_test/MANIFEST.in b/src/python/grpcio_test/MANIFEST.in new file mode 100644 index 0000000000..c9327307dc --- /dev/null +++ b/src/python/grpcio_test/MANIFEST.in @@ -0,0 +1,4 @@ +graft grpc_interop +graft grpc_test +include commands.py +include requirements.txt diff --git a/src/python/grpcio_test/commands.py b/src/python/grpcio_test/commands.py new file mode 100644 index 0000000000..c796d94c76 --- /dev/null +++ b/src/python/grpcio_test/commands.py @@ -0,0 +1,57 @@ +# 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. + +"""Provides distutils command classes for the GRPC Python test setup process.""" + +import os +import os.path +import sys + +import setuptools + + +class RunTests(setuptools.Command): + """Command to run all tests via py.test.""" + + description = '' + user_options = [('pytest-args=', 'a', 'arguments to pass to py.test')] + + def initialize_options(self): + self.pytest_args = [] + + def finalize_options(self): + pass + + def run(self): + # We import here to ensure that setup.py has had a chance to install the + # relevant package eggs first. + import pytest + result = pytest.main(self.pytest_args) + if result != 0: + raise SystemExit(result) diff --git a/src/python/src/grpc/framework/foundation/__init__.py b/src/python/grpcio_test/grpc_interop/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/foundation/__init__.py +++ b/src/python/grpcio_test/grpc_interop/__init__.py diff --git a/src/python/interop/interop/_insecure_interop_test.py b/src/python/grpcio_test/grpc_interop/_insecure_interop_test.py index 98ea3a6648..825988a072 100644 --- a/src/python/interop/interop/_insecure_interop_test.py +++ b/src/python/grpcio_test/grpc_interop/_insecure_interop_test.py @@ -33,8 +33,8 @@ import unittest from grpc.early_adopter import implementations -from interop import _interop_test_case -from interop import methods +from grpc_interop import _interop_test_case +from grpc_interop import methods class InsecureInteropTest( diff --git a/src/python/interop/interop/_interop_test_case.py b/src/python/grpcio_test/grpc_interop/_interop_test_case.py index f40ef0ec83..b6d06b300d 100644 --- a/src/python/interop/interop/_interop_test_case.py +++ b/src/python/grpcio_test/grpc_interop/_interop_test_case.py @@ -29,7 +29,7 @@ """Common code for unit tests of the interoperability test code.""" -from interop import methods +from grpc_interop import methods class InteropTestCase(object): @@ -59,3 +59,6 @@ class InteropTestCase(object): def testCancelAfterFirstResponse(self): methods.TestCase.CANCEL_AFTER_FIRST_RESPONSE.test_interoperability(self.stub, None) + + def testTimeoutOnSleepingServer(self): + methods.TestCase.TIMEOUT_ON_SLEEPING_SERVER.test_interoperability(self.stub, None) diff --git a/src/python/interop/interop/_secure_interop_test.py b/src/python/grpcio_test/grpc_interop/_secure_interop_test.py index be7618f549..a2682dee99 100644 --- a/src/python/interop/interop/_secure_interop_test.py +++ b/src/python/grpcio_test/grpc_interop/_secure_interop_test.py @@ -33,9 +33,9 @@ import unittest from grpc.early_adopter import implementations -from interop import _interop_test_case -from interop import methods -from interop import resources +from grpc_interop import _interop_test_case +from grpc_interop import methods +from grpc_interop import resources _SERVER_HOST_OVERRIDE = 'foo.test.google.fr' diff --git a/src/python/interop/interop/client.py b/src/python/grpcio_test/grpc_interop/client.py index 41f0d94539..2dd2103cbe 100644 --- a/src/python/interop/interop/client.py +++ b/src/python/grpcio_test/grpc_interop/client.py @@ -34,8 +34,8 @@ from oauth2client import client as oauth2client_client from grpc.early_adopter import implementations -from interop import methods -from interop import resources +from grpc_interop import methods +from grpc_interop import resources _ONE_DAY_IN_SECONDS = 60 * 60 * 24 diff --git a/src/python/interop/interop/credentials/README b/src/python/grpcio_test/grpc_interop/credentials/README index cb20dcb49f..cb20dcb49f 100644 --- a/src/python/interop/interop/credentials/README +++ b/src/python/grpcio_test/grpc_interop/credentials/README diff --git a/src/python/grpcio_test/grpc_interop/credentials/ca.pem b/src/python/grpcio_test/grpc_interop/credentials/ca.pem new file mode 100755 index 0000000000..6c8511a73c --- /dev/null +++ b/src/python/grpcio_test/grpc_interop/credentials/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/src/python/interop/interop/credentials/server1.key b/src/python/grpcio_test/grpc_interop/credentials/server1.key index 143a5b8765..143a5b8765 100755 --- a/src/python/interop/interop/credentials/server1.key +++ b/src/python/grpcio_test/grpc_interop/credentials/server1.key diff --git a/src/python/interop/interop/credentials/server1.pem b/src/python/grpcio_test/grpc_interop/credentials/server1.pem index 8e582e571f..8e582e571f 100755 --- a/src/python/interop/interop/credentials/server1.pem +++ b/src/python/grpcio_test/grpc_interop/credentials/server1.pem diff --git a/src/python/interop/interop/empty_pb2.py b/src/python/grpcio_test/grpc_interop/empty_pb2.py index 8c1ce2f13e..8c1ce2f13e 100644 --- a/src/python/interop/interop/empty_pb2.py +++ b/src/python/grpcio_test/grpc_interop/empty_pb2.py diff --git a/src/python/interop/interop/messages_pb2.py b/src/python/grpcio_test/grpc_interop/messages_pb2.py index 0bf3d86a31..0bf3d86a31 100644 --- a/src/python/interop/interop/messages_pb2.py +++ b/src/python/grpcio_test/grpc_interop/messages_pb2.py diff --git a/src/python/interop/interop/methods.py b/src/python/grpcio_test/grpc_interop/methods.py index 194afadb17..7a831f3cbd 100644 --- a/src/python/interop/interop/methods.py +++ b/src/python/grpcio_test/grpc_interop/methods.py @@ -33,13 +33,15 @@ import enum import json import os import threading +import time from oauth2client import client as oauth2client_client from grpc.framework.alpha import utilities +from grpc.framework.alpha import exceptions -from interop import empty_pb2 -from interop import messages_pb2 +from grpc_interop import empty_pb2 +from grpc_interop import messages_pb2 _TIMEOUT = 7 @@ -318,6 +320,24 @@ def _cancel_after_first_response(stub): raise ValueError('expected call to be cancelled') +def _timeout_on_sleeping_server(stub): + request_payload_size = 27182 + with stub, _Pipe() as pipe: + response_iterator = stub.FullDuplexCall(pipe, 0.001) + + request = messages_pb2.StreamingOutputCallRequest( + response_type=messages_pb2.COMPRESSABLE, + payload=messages_pb2.Payload(body=b'\x00' * request_payload_size)) + pipe.add(request) + time.sleep(0.1) + try: + next(response_iterator) + except exceptions.ExpirationError: + pass + else: + raise ValueError('expected call to exceed deadline') + + def _compute_engine_creds(stub, args): response = _large_unary_common_behavior(stub, True, True) if args.default_service_account != response.username: @@ -351,6 +371,7 @@ class TestCase(enum.Enum): CANCEL_AFTER_FIRST_RESPONSE = 'cancel_after_first_response' COMPUTE_ENGINE_CREDS = 'compute_engine_creds' SERVICE_ACCOUNT_CREDS = 'service_account_creds' + TIMEOUT_ON_SLEEPING_SERVER = 'timeout_on_sleeping_server' def test_interoperability(self, stub, args): if self is TestCase.EMPTY_UNARY: @@ -367,6 +388,8 @@ class TestCase(enum.Enum): _cancel_after_begin(stub) elif self is TestCase.CANCEL_AFTER_FIRST_RESPONSE: _cancel_after_first_response(stub) + elif self is TestCase.TIMEOUT_ON_SLEEPING_SERVER: + _timeout_on_sleeping_server(stub) elif self is TestCase.COMPUTE_ENGINE_CREDS: _compute_engine_creds(stub, args) elif self is TestCase.SERVICE_ACCOUNT_CREDS: diff --git a/src/python/interop/interop/resources.py b/src/python/grpcio_test/grpc_interop/resources.py index 2c3045313d..2c3045313d 100644 --- a/src/python/interop/interop/resources.py +++ b/src/python/grpcio_test/grpc_interop/resources.py diff --git a/src/python/interop/interop/server.py b/src/python/grpcio_test/grpc_interop/server.py index a67d412038..60f630a6be 100644 --- a/src/python/interop/interop/server.py +++ b/src/python/grpcio_test/grpc_interop/server.py @@ -35,8 +35,8 @@ import time from grpc.early_adopter import implementations -from interop import methods -from interop import resources +from grpc_interop import methods +from grpc_interop import resources _ONE_DAY_IN_SECONDS = 60 * 60 * 24 diff --git a/src/python/interop/interop/test_pb2.py b/src/python/grpcio_test/grpc_interop/test_pb2.py index 71325d5a9f..71325d5a9f 100644 --- a/src/python/interop/interop/test_pb2.py +++ b/src/python/grpcio_test/grpc_interop/test_pb2.py diff --git a/src/python/src/grpc/framework/interfaces/__init__.py b/src/python/grpcio_test/grpc_protoc_plugin/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/interfaces/__init__.py +++ b/src/python/grpcio_test/grpc_protoc_plugin/__init__.py diff --git a/src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py b/src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py new file mode 100644 index 0000000000..b200d129a9 --- /dev/null +++ b/src/python/grpcio_test/grpc_protoc_plugin/python_plugin_test.py @@ -0,0 +1,541 @@ +# 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. + +import argparse +import contextlib +import distutils.spawn +import errno +import itertools +import os +import pkg_resources +import shutil +import subprocess +import sys +import tempfile +import threading +import time +import unittest + +from grpc.framework.alpha import exceptions +from grpc.framework.foundation import future + +# Identifiers of entities we expect to find in the generated module. +SERVICER_IDENTIFIER = 'EarlyAdopterTestServiceServicer' +SERVER_IDENTIFIER = 'EarlyAdopterTestServiceServer' +STUB_IDENTIFIER = 'EarlyAdopterTestServiceStub' +SERVER_FACTORY_IDENTIFIER = 'early_adopter_create_TestService_server' +STUB_FACTORY_IDENTIFIER = 'early_adopter_create_TestService_stub' + +# The timeout used in tests of RPCs that are supposed to expire. +SHORT_TIMEOUT = 2 +# The timeout used in tests of RPCs that are not supposed to expire. The +# absurdly large value doesn't matter since no passing execution of this test +# module will ever wait the duration. +LONG_TIMEOUT = 600 +NO_DELAY = 0 + + +class _ServicerMethods(object): + + def __init__(self, test_pb2, delay): + self._condition = threading.Condition() + self._delay = delay + self._paused = False + self._fail = False + self._test_pb2 = test_pb2 + + @contextlib.contextmanager + def pause(self): # pylint: disable=invalid-name + with self._condition: + self._paused = True + yield + with self._condition: + self._paused = False + self._condition.notify_all() + + @contextlib.contextmanager + def fail(self): # pylint: disable=invalid-name + with self._condition: + self._fail = True + yield + with self._condition: + self._fail = False + + def _control(self): # pylint: disable=invalid-name + with self._condition: + if self._fail: + raise ValueError() + while self._paused: + self._condition.wait() + time.sleep(self._delay) + + def UnaryCall(self, request, unused_rpc_context): + response = self._test_pb2.SimpleResponse() + response.payload.payload_type = self._test_pb2.COMPRESSABLE + response.payload.payload_compressable = 'a' * request.response_size + self._control() + return response + + def StreamingOutputCall(self, request, unused_rpc_context): + for parameter in request.response_parameters: + response = self._test_pb2.StreamingOutputCallResponse() + response.payload.payload_type = self._test_pb2.COMPRESSABLE + response.payload.payload_compressable = 'a' * parameter.size + self._control() + yield response + + def StreamingInputCall(self, request_iter, unused_rpc_context): + response = self._test_pb2.StreamingInputCallResponse() + aggregated_payload_size = 0 + for request in request_iter: + aggregated_payload_size += len(request.payload.payload_compressable) + response.aggregated_payload_size = aggregated_payload_size + self._control() + return response + + def FullDuplexCall(self, request_iter, unused_rpc_context): + for request in request_iter: + for parameter in request.response_parameters: + response = self._test_pb2.StreamingOutputCallResponse() + response.payload.payload_type = self._test_pb2.COMPRESSABLE + response.payload.payload_compressable = 'a' * parameter.size + self._control() + yield response + + def HalfDuplexCall(self, request_iter, unused_rpc_context): + responses = [] + for request in request_iter: + for parameter in request.response_parameters: + response = self._test_pb2.StreamingOutputCallResponse() + response.payload.payload_type = self._test_pb2.COMPRESSABLE + response.payload.payload_compressable = 'a' * parameter.size + self._control() + responses.append(response) + for response in responses: + yield response + + +@contextlib.contextmanager +def _CreateService(test_pb2, delay): + """Provides a servicer backend and a stub. + + The servicer is just the implementation + of the actual servicer passed to the face player of the python RPC + implementation; the two are detached. + + Non-zero delay puts a delay on each call to the servicer, representative of + communication latency. Timeout is the default timeout for the stub while + waiting for the service. + + Args: + test_pb2: The test_pb2 module generated by this test. + delay: Delay in seconds per response from the servicer. + + Yields: + A (servicer_methods, servicer, stub) three-tuple where servicer_methods is + the back-end of the service bound to the stub and the server and stub + are both activated and ready for use. + """ + servicer_methods = _ServicerMethods(test_pb2, delay) + + class Servicer(getattr(test_pb2, SERVICER_IDENTIFIER)): + + def UnaryCall(self, request, context): + return servicer_methods.UnaryCall(request, context) + + def StreamingOutputCall(self, request, context): + return servicer_methods.StreamingOutputCall(request, context) + + def StreamingInputCall(self, request_iter, context): + return servicer_methods.StreamingInputCall(request_iter, context) + + def FullDuplexCall(self, request_iter, context): + return servicer_methods.FullDuplexCall(request_iter, context) + + def HalfDuplexCall(self, request_iter, context): + return servicer_methods.HalfDuplexCall(request_iter, context) + + servicer = Servicer() + server = getattr( + test_pb2, SERVER_FACTORY_IDENTIFIER)(servicer, 0) + with server: + port = server.port() + stub = getattr(test_pb2, STUB_FACTORY_IDENTIFIER)('localhost', port) + with stub: + yield servicer_methods, stub, server + + +def _streaming_input_request_iterator(test_pb2): + for _ in range(3): + request = test_pb2.StreamingInputCallRequest() + request.payload.payload_type = test_pb2.COMPRESSABLE + request.payload.payload_compressable = 'a' + yield request + + +def _streaming_output_request(test_pb2): + request = test_pb2.StreamingOutputCallRequest() + sizes = [1, 2, 3] + request.response_parameters.add(size=sizes[0], interval_us=0) + request.response_parameters.add(size=sizes[1], interval_us=0) + request.response_parameters.add(size=sizes[2], interval_us=0) + return request + + +def _full_duplex_request_iterator(test_pb2): + request = test_pb2.StreamingOutputCallRequest() + request.response_parameters.add(size=1, interval_us=0) + yield request + request = test_pb2.StreamingOutputCallRequest() + request.response_parameters.add(size=2, interval_us=0) + request.response_parameters.add(size=3, interval_us=0) + yield request + + +class PythonPluginTest(unittest.TestCase): + """Test case for the gRPC Python protoc-plugin. + + While reading these tests, remember that the futures API + (`stub.method.async()`) only gives futures for the *non-streaming* responses, + else it behaves like its blocking cousin. + """ + + def setUp(self): + # Assume that the appropriate protoc and grpc_python_plugins are on the + # path. + protoc_command = 'protoc' + protoc_plugin_filename = distutils.spawn.find_executable( + 'grpc_python_plugin') + test_proto_filename = pkg_resources.resource_filename( + 'grpc_protoc_plugin', 'test.proto') + if not os.path.isfile(protoc_command): + # Assume that if we haven't built protoc that it's on the system. + protoc_command = 'protoc' + + # Ensure that the output directory exists. + self.outdir = tempfile.mkdtemp() + + # Invoke protoc with the plugin. + cmd = [ + protoc_command, + '--plugin=protoc-gen-python-grpc=%s' % protoc_plugin_filename, + '-I .', + '--python_out=%s' % self.outdir, + '--python-grpc_out=%s' % self.outdir, + os.path.basename(test_proto_filename), + ] + subprocess.check_call(' '.join(cmd), shell=True, env=os.environ, + cwd=os.path.dirname(test_proto_filename)) + sys.path.append(self.outdir) + + def tearDown(self): + try: + shutil.rmtree(self.outdir) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + + # TODO(atash): Figure out which of these tests is hanging flakily with small + # probability. + + def testImportAttributes(self): + # check that we can access the generated module and its members. + import test_pb2 # pylint: disable=g-import-not-at-top + self.assertIsNotNone(getattr(test_pb2, SERVICER_IDENTIFIER, None)) + self.assertIsNotNone(getattr(test_pb2, SERVER_IDENTIFIER, None)) + self.assertIsNotNone(getattr(test_pb2, STUB_IDENTIFIER, None)) + self.assertIsNotNone(getattr(test_pb2, SERVER_FACTORY_IDENTIFIER, None)) + self.assertIsNotNone(getattr(test_pb2, STUB_FACTORY_IDENTIFIER, None)) + + def testUpDown(self): + import test_pb2 + with _CreateService( + test_pb2, NO_DELAY) as (servicer, stub, unused_server): + request = test_pb2.SimpleRequest(response_size=13) + + def testUnaryCall(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + timeout = 6 # TODO(issue 2039): LONG_TIMEOUT like the other methods. + request = test_pb2.SimpleRequest(response_size=13) + response = stub.UnaryCall(request, timeout) + expected_response = methods.UnaryCall(request, 'not a real RpcContext!') + self.assertEqual(expected_response, response) + + def testUnaryCallAsync(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = test_pb2.SimpleRequest(response_size=13) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + # Check that the call does not block waiting for the server to respond. + with methods.pause(): + response_future = stub.UnaryCall.async(request, LONG_TIMEOUT) + response = response_future.result() + expected_response = methods.UnaryCall(request, 'not a real RpcContext!') + self.assertEqual(expected_response, response) + + def testUnaryCallAsyncExpired(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + request = test_pb2.SimpleRequest(response_size=13) + with methods.pause(): + response_future = stub.UnaryCall.async(request, SHORT_TIMEOUT) + with self.assertRaises(exceptions.ExpirationError): + response_future.result() + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testUnaryCallAsyncCancelled(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = test_pb2.SimpleRequest(response_size=13) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + response_future = stub.UnaryCall.async(request, 1) + response_future.cancel() + self.assertTrue(response_future.cancelled()) + + def testUnaryCallAsyncFailed(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = test_pb2.SimpleRequest(response_size=13) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.fail(): + response_future = stub.UnaryCall.async(request, LONG_TIMEOUT) + self.assertIsNotNone(response_future.exception()) + + def testStreamingOutputCall(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = _streaming_output_request(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + responses = stub.StreamingOutputCall(request, LONG_TIMEOUT) + expected_responses = methods.StreamingOutputCall( + request, 'not a real RpcContext!') + for expected_response, response in itertools.izip_longest( + expected_responses, responses): + self.assertEqual(expected_response, response) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testStreamingOutputCallExpired(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = _streaming_output_request(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + responses = stub.StreamingOutputCall(request, SHORT_TIMEOUT) + with self.assertRaises(exceptions.ExpirationError): + list(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testStreamingOutputCallCancelled(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = _streaming_output_request(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as ( + unused_methods, stub, unused_server): + responses = stub.StreamingOutputCall(request, SHORT_TIMEOUT) + next(responses) + responses.cancel() + with self.assertRaises(future.CancelledError): + next(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this times out ' + 'instead of raising the proper error.') + def testStreamingOutputCallFailed(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request = _streaming_output_request(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.fail(): + responses = stub.StreamingOutputCall(request, 1) + self.assertIsNotNone(responses) + with self.assertRaises(exceptions.ServicerError): + next(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testStreamingInputCall(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + response = stub.StreamingInputCall( + _streaming_input_request_iterator(test_pb2), LONG_TIMEOUT) + expected_response = methods.StreamingInputCall( + _streaming_input_request_iterator(test_pb2), 'not a real RpcContext!') + self.assertEqual(expected_response, response) + + def testStreamingInputCallAsync(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + response_future = stub.StreamingInputCall.async( + _streaming_input_request_iterator(test_pb2), LONG_TIMEOUT) + response = response_future.result() + expected_response = methods.StreamingInputCall( + _streaming_input_request_iterator(test_pb2), 'not a real RpcContext!') + self.assertEqual(expected_response, response) + + def testStreamingInputCallAsyncExpired(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + response_future = stub.StreamingInputCall.async( + _streaming_input_request_iterator(test_pb2), SHORT_TIMEOUT) + with self.assertRaises(exceptions.ExpirationError): + response_future.result() + self.assertIsInstance( + response_future.exception(), exceptions.ExpirationError) + + def testStreamingInputCallAsyncCancelled(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + timeout = 6 # TODO(issue 2039): LONG_TIMEOUT like the other methods. + response_future = stub.StreamingInputCall.async( + _streaming_input_request_iterator(test_pb2), timeout) + response_future.cancel() + self.assertTrue(response_future.cancelled()) + with self.assertRaises(future.CancelledError): + response_future.result() + + def testStreamingInputCallAsyncFailed(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.fail(): + response_future = stub.StreamingInputCall.async( + _streaming_input_request_iterator(test_pb2), SHORT_TIMEOUT) + self.assertIsNotNone(response_future.exception()) + + def testFullDuplexCall(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + responses = stub.FullDuplexCall( + _full_duplex_request_iterator(test_pb2), LONG_TIMEOUT) + expected_responses = methods.FullDuplexCall( + _full_duplex_request_iterator(test_pb2), 'not a real RpcContext!') + for expected_response, response in itertools.izip_longest( + expected_responses, responses): + self.assertEqual(expected_response, response) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testFullDuplexCallExpired(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request_iterator = _full_duplex_request_iterator(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.pause(): + responses = stub.FullDuplexCall(request_iterator, SHORT_TIMEOUT) + with self.assertRaises(exceptions.ExpirationError): + list(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testFullDuplexCallCancelled(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + request_iterator = _full_duplex_request_iterator(test_pb2) + responses = stub.FullDuplexCall(request_iterator, LONG_TIMEOUT) + next(responses) + responses.cancel() + with self.assertRaises(future.CancelledError): + next(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this hangs forever ' + 'and fix.') + def testFullDuplexCallFailed(self): + import test_pb2 # pylint: disable=g-import-not-at-top + request_iterator = _full_duplex_request_iterator(test_pb2) + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + with methods.fail(): + responses = stub.FullDuplexCall(request_iterator, LONG_TIMEOUT) + self.assertIsNotNone(responses) + with self.assertRaises(exceptions.ServicerError): + next(responses) + + @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs ' + 'forever and fix.') + def testHalfDuplexCall(self): + import test_pb2 # pylint: disable=g-import-not-at-top + with _CreateService(test_pb2, NO_DELAY) as ( + methods, stub, unused_server): + def half_duplex_request_iterator(): + request = test_pb2.StreamingOutputCallRequest() + request.response_parameters.add(size=1, interval_us=0) + yield request + request = test_pb2.StreamingOutputCallRequest() + request.response_parameters.add(size=2, interval_us=0) + request.response_parameters.add(size=3, interval_us=0) + yield request + responses = stub.HalfDuplexCall( + half_duplex_request_iterator(), LONG_TIMEOUT) + expected_responses = methods.HalfDuplexCall( + half_duplex_request_iterator(), 'not a real RpcContext!') + for check in itertools.izip_longest(expected_responses, responses): + expected_response, response = check + self.assertEqual(expected_response, response) + + def testHalfDuplexCallWedged(self): + import test_pb2 # pylint: disable=g-import-not-at-top + condition = threading.Condition() + wait_cell = [False] + @contextlib.contextmanager + def wait(): # pylint: disable=invalid-name + # Where's Python 3's 'nonlocal' statement when you need it? + with condition: + wait_cell[0] = True + yield + with condition: + wait_cell[0] = False + condition.notify_all() + def half_duplex_request_iterator(): + request = test_pb2.StreamingOutputCallRequest() + request.response_parameters.add(size=1, interval_us=0) + yield request + with condition: + while wait_cell[0]: + condition.wait() + with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server): + with wait(): + responses = stub.HalfDuplexCall( + half_duplex_request_iterator(), SHORT_TIMEOUT) + # half-duplex waits for the client to send all info + with self.assertRaises(exceptions.ExpirationError): + next(responses) + + +if __name__ == '__main__': + os.chdir(os.path.dirname(sys.argv[0])) + unittest.main(verbosity=2) diff --git a/src/python/grpcio_test/grpc_protoc_plugin/test.proto b/src/python/grpcio_test/grpc_protoc_plugin/test.proto new file mode 100644 index 0000000000..ed7c6a7b79 --- /dev/null +++ b/src/python/grpcio_test/grpc_protoc_plugin/test.proto @@ -0,0 +1,139 @@ +// 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. +// This file is duplicated around the code base. See GitHub issue #526. +syntax = "proto2"; + +package grpc.testing; + +enum PayloadType { + // Compressable text format. + COMPRESSABLE= 1; + + // Uncompressable binary format. + UNCOMPRESSABLE = 2; + + // Randomly chosen from all other formats defined in this enum. + RANDOM = 3; +} + +message Payload { + required PayloadType payload_type = 1; + oneof payload_body { + string payload_compressable = 2; + bytes payload_uncompressable = 3; + } +} + +message SimpleRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, server randomly chooses one from other formats. + optional PayloadType response_type = 1 [default=COMPRESSABLE]; + + // Desired payload size in the response from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + optional int32 response_size = 2; + + // Optional input payload sent along with the request. + optional Payload payload = 3; +} + +message SimpleResponse { + optional Payload payload = 1; +} + +message StreamingInputCallRequest { + // Optional input payload sent along with the request. + optional Payload payload = 1; + + // Not expecting any payload from the response. +} + +message StreamingInputCallResponse { + // Aggregated size of payloads received from the client. + optional int32 aggregated_payload_size = 1; +} + +message ResponseParameters { + // Desired payload sizes in responses from the server. + // If response_type is COMPRESSABLE, this denotes the size before compression. + required int32 size = 1; + + // Desired interval between consecutive responses in the response stream in + // microseconds. + required int32 interval_us = 2; +} + +message StreamingOutputCallRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, the payload from each response in the stream + // might be of different types. This is to simulate a mixed type of payload + // stream. + optional PayloadType response_type = 1 [default=COMPRESSABLE]; + + repeated ResponseParameters response_parameters = 2; + + // Optional input payload sent along with the request. + optional Payload payload = 3; +} + +message StreamingOutputCallResponse { + optional Payload payload = 1; +} + +service TestService { + // One request followed by one response. + // The server returns the client payload as-is. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by a sequence of responses (streamed download). + // The server returns the payload with client desired type and sizes. + rpc StreamingOutputCall(StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by one response (streamed upload). + // The server returns the aggregated size of client payload as the result. + rpc StreamingInputCall(stream StreamingInputCallRequest) + returns (StreamingInputCallResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by a sequence of responses. + // The server buffers all the client requests and then serves them in order. A + // stream of responses are returned to the client when the server starts with + // first request. + rpc HalfDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); +} diff --git a/src/python/src/grpc/framework/interfaces/links/__init__.py b/src/python/grpcio_test/grpc_test/__init__.py index 7086519106..7086519106 100644 --- a/src/python/src/grpc/framework/interfaces/links/__init__.py +++ b/src/python/grpcio_test/grpc_test/__init__.py diff --git a/src/python/grpcio_test/grpc_test/_adapter/.gitignore b/src/python/grpcio_test/grpc_test/_adapter/.gitignore new file mode 100644 index 0000000000..a6f96cd6db --- /dev/null +++ b/src/python/grpcio_test/grpc_test/_adapter/.gitignore @@ -0,0 +1,5 @@ +*.a +*.so +*.dll +*.pyc +*.pyd diff --git a/src/python/interop/setup.py b/src/python/grpcio_test/grpc_test/_adapter/__init__.py index 75012b0d8f..7086519106 100644 --- a/src/python/interop/setup.py +++ b/src/python/grpcio_test/grpc_test/_adapter/__init__.py @@ -27,31 +27,4 @@ # (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 setup module for the GRPC Python interop testing package.""" -import setuptools - -_PACKAGES = ( - 'interop', -) - -_PACKAGE_DIRECTORIES = { - 'interop': 'interop', -} - -_PACKAGE_DATA = { - 'interop': [ - 'credentials/ca.pem', 'credentials/server1.key', - 'credentials/server1.pem',] -} - -_INSTALL_REQUIRES = ['oauth2client>=1.4.7', 'grpcio>=0.10.0a0'] - -setuptools.setup( - name='interop', - version='0.0.1', - packages=_PACKAGES, - package_dir=_PACKAGE_DIRECTORIES, - package_data=_PACKAGE_DATA, - install_requires=_INSTALL_REQUIRES -) diff --git a/src/python/src/grpc/_adapter/_blocking_invocation_inline_service_test.py b/src/python/grpcio_test/grpc_test/_adapter/_blocking_invocation_inline_service_test.py index 7a8ff0ad89..a1f776211c 100644 --- a/src/python/src/grpc/_adapter/_blocking_invocation_inline_service_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_blocking_invocation_inline_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc._adapter import _face_test_case -from grpc.framework.face.testing import blocking_invocation_inline_service_test_case as test_case +from grpc_test._adapter import _face_test_case +from grpc_test.framework.face.testing import blocking_invocation_inline_service_test_case as test_case class BlockingInvocationInlineServiceTest( diff --git a/src/python/src/grpc/_adapter/_c_test.py b/src/python/grpcio_test/grpc_test/_adapter/_c_test.py index fe020e2a9c..fe020e2a9c 100644 --- a/src/python/src/grpc/_adapter/_c_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_c_test.py diff --git a/src/python/src/grpc/_adapter/_event_invocation_synchronous_event_service_test.py b/src/python/grpcio_test/grpc_test/_adapter/_event_invocation_synchronous_event_service_test.py index b8ceb75d68..0d01ebc8dc 100644 --- a/src/python/src/grpc/_adapter/_event_invocation_synchronous_event_service_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_event_invocation_synchronous_event_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc._adapter import _face_test_case -from grpc.framework.face.testing import event_invocation_synchronous_event_service_test_case as test_case +from grpc_test._adapter import _face_test_case +from grpc_test.framework.face.testing import event_invocation_synchronous_event_service_test_case as test_case class EventInvocationSynchronousEventServiceTest( diff --git a/src/python/src/grpc/_adapter/_face_test_case.py b/src/python/grpcio_test/grpc_test/_adapter/_face_test_case.py index 5fa974ed06..dfbd0b60af 100644 --- a/src/python/src/grpc/_adapter/_face_test_case.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_face_test_case.py @@ -36,10 +36,10 @@ from grpc._adapter import rear from grpc.framework.base import util from grpc.framework.base import implementations as base_implementations from grpc.framework.face import implementations as face_implementations -from grpc.framework.face.testing import coverage -from grpc.framework.face.testing import serial -from grpc.framework.face.testing import test_case from grpc.framework.foundation import logging_pool +from grpc_test.framework.face.testing import coverage +from grpc_test.framework.face.testing import serial +from grpc_test.framework.face.testing import test_case _TIMEOUT = 3 _MAXIMUM_TIMEOUT = 90 diff --git a/src/python/src/grpc/_adapter/_future_invocation_asynchronous_event_service_test.py b/src/python/grpcio_test/grpc_test/_adapter/_future_invocation_asynchronous_event_service_test.py index 3773e65575..ea4a6a0bae 100644 --- a/src/python/src/grpc/_adapter/_future_invocation_asynchronous_event_service_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_future_invocation_asynchronous_event_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc._adapter import _face_test_case -from grpc.framework.face.testing import future_invocation_asynchronous_event_service_test_case as test_case +from grpc_test._adapter import _face_test_case +from grpc_test.framework.face.testing import future_invocation_asynchronous_event_service_test_case as test_case class FutureInvocationAsynchronousEventServiceTest( diff --git a/src/python/src/grpc/_adapter/_intermediary_low_test.py b/src/python/grpcio_test/grpc_test/_adapter/_intermediary_low_test.py index 27a5b82e9c..27a5b82e9c 100644 --- a/src/python/src/grpc/_adapter/_intermediary_low_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_intermediary_low_test.py diff --git a/src/python/src/grpc/_adapter/_links_test.py b/src/python/grpcio_test/grpc_test/_adapter/_links_test.py index 4729b84f84..c4686b327a 100644 --- a/src/python/src/grpc/_adapter/_links_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_links_test.py @@ -32,12 +32,12 @@ import threading import unittest -from grpc._adapter import _proto_scenarios -from grpc._adapter import _test_links from grpc._adapter import fore from grpc._adapter import rear from grpc.framework.base import interfaces from grpc.framework.foundation import logging_pool +from grpc_test._adapter import _proto_scenarios +from grpc_test._adapter import _test_links _IDENTITY = lambda x: x _TIMEOUT = 32 diff --git a/src/python/src/grpc/_adapter/_lonely_rear_link_test.py b/src/python/grpcio_test/grpc_test/_adapter/_lonely_rear_link_test.py index 7f5021f40e..9b5758f60f 100644 --- a/src/python/src/grpc/_adapter/_lonely_rear_link_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_lonely_rear_link_test.py @@ -31,10 +31,10 @@ import unittest -from grpc._adapter import _test_links from grpc._adapter import rear from grpc.framework.base import interfaces from grpc.framework.foundation import logging_pool +from grpc_test._adapter import _test_links _IDENTITY = lambda x: x _TIMEOUT = 2 diff --git a/src/python/src/grpc/_adapter/_low_test.py b/src/python/grpcio_test/grpc_test/_adapter/_low_test.py index a49cd007bf..44fe760fbc 100644 --- a/src/python/src/grpc/_adapter/_low_test.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_low_test.py @@ -31,11 +31,12 @@ import threading import time import unittest +from grpc import _grpcio_metadata from grpc._adapter import _types from grpc._adapter import _low -def WaitForEvents(completion_queues, deadline): +def wait_for_events(completion_queues, deadline): """ Args: completion_queues: list of completion queues to wait for events on @@ -62,6 +63,7 @@ def WaitForEvents(completion_queues, deadline): thread.join() return results + class InsecureServerInsecureClient(unittest.TestCase): def setUp(self): @@ -97,7 +99,7 @@ class InsecureServerInsecureClient(unittest.TestCase): CLIENT_METADATA_BIN_VALUE = b'\0'*1000 SERVER_INITIAL_METADATA_KEY = 'init_me_me_me' SERVER_INITIAL_METADATA_VALUE = 'whodawha?' - SERVER_TRAILING_METADATA_KEY = 'California_is_in_a_drought' + SERVER_TRAILING_METADATA_KEY = 'california_is_in_a_drought' SERVER_TRAILING_METADATA_VALUE = 'zomg it is' SERVER_STATUS_CODE = _types.StatusCode.OK SERVER_STATUS_DETAILS = 'our work is never over' @@ -115,7 +117,7 @@ class InsecureServerInsecureClient(unittest.TestCase): client_initial_metadata = [(CLIENT_METADATA_ASCII_KEY, CLIENT_METADATA_ASCII_VALUE), (CLIENT_METADATA_BIN_KEY, CLIENT_METADATA_BIN_VALUE)] client_start_batch_result = client_call.start_batch([ _types.OpArgs.send_initial_metadata(client_initial_metadata), - _types.OpArgs.send_message(REQUEST), + _types.OpArgs.send_message(REQUEST, 0), _types.OpArgs.send_close_from_client(), _types.OpArgs.recv_initial_metadata(), _types.OpArgs.recv_message(), @@ -123,20 +125,34 @@ class InsecureServerInsecureClient(unittest.TestCase): ], client_call_tag) self.assertEquals(_types.CallError.OK, client_start_batch_result) - client_no_event, request_event, = WaitForEvents([self.client_completion_queue, self.server_completion_queue], time.time() + 2) + client_no_event, request_event, = wait_for_events([self.client_completion_queue, self.server_completion_queue], time.time() + 2) self.assertEquals(client_no_event, None) self.assertEquals(_types.EventType.OP_COMPLETE, request_event.type) self.assertIsInstance(request_event.call, _low.Call) self.assertIs(server_request_tag, request_event.tag) self.assertEquals(1, len(request_event.results)) - got_initial_metadata = dict(request_event.results[0].initial_metadata) + received_initial_metadata = dict(request_event.results[0].initial_metadata) + # Check that our metadata were transmitted self.assertEquals( dict(client_initial_metadata), - dict((x, got_initial_metadata[x]) for x in zip(*client_initial_metadata)[0])) + dict((x, received_initial_metadata[x]) for x in zip(*client_initial_metadata)[0])) + # Check that Python's user agent string is a part of the full user agent + # string + self.assertIn('Python-gRPC-{}'.format(_grpcio_metadata.__version__), + received_initial_metadata['user-agent']) self.assertEquals(METHOD, request_event.call_details.method) self.assertEquals(HOST, request_event.call_details.host) self.assertLess(abs(DEADLINE - request_event.call_details.deadline), DEADLINE_TOLERANCE) + # Check that the channel is connected, and that both it and the call have + # the proper target and peer; do this after the first flurry of messages to + # avoid the possibility that connection was delayed by the core until the + # first message was sent. + self.assertEqual(_types.ConnectivityState.READY, + self.client_channel.check_connectivity_state(False)) + self.assertIsNotNone(self.client_channel.target()) + self.assertIsNotNone(client_call.peer()) + server_call_tag = object() server_call = request_event.call server_initial_metadata = [(SERVER_INITIAL_METADATA_KEY, SERVER_INITIAL_METADATA_VALUE)] @@ -144,13 +160,13 @@ class InsecureServerInsecureClient(unittest.TestCase): server_start_batch_result = server_call.start_batch([ _types.OpArgs.send_initial_metadata(server_initial_metadata), _types.OpArgs.recv_message(), - _types.OpArgs.send_message(RESPONSE), + _types.OpArgs.send_message(RESPONSE, 0), _types.OpArgs.recv_close_on_server(), _types.OpArgs.send_status_from_server(server_trailing_metadata, SERVER_STATUS_CODE, SERVER_STATUS_DETAILS) ], server_call_tag) self.assertEquals(_types.CallError.OK, server_start_batch_result) - client_event, server_event, = WaitForEvents([self.client_completion_queue, self.server_completion_queue], time.time() + 1) + client_event, server_event, = wait_for_events([self.client_completion_queue, self.server_completion_queue], time.time() + 1) self.assertEquals(6, len(client_event.results)) found_client_op_types = set() diff --git a/src/python/src/grpc/_adapter/_proto_scenarios.py b/src/python/grpcio_test/grpc_test/_adapter/_proto_scenarios.py index 60a622ba8b..b3d6ec8607 100644 --- a/src/python/src/grpc/_adapter/_proto_scenarios.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_proto_scenarios.py @@ -32,7 +32,7 @@ import abc import threading -from grpc._junkdrawer import math_pb2 +from grpc_test._junkdrawer import math_pb2 class ProtoScenario(object): diff --git a/src/python/src/grpc/_adapter/_test_links.py b/src/python/grpcio_test/grpc_test/_adapter/_test_links.py index 86c7e61b17..86c7e61b17 100644 --- a/src/python/src/grpc/_adapter/_test_links.py +++ b/src/python/grpcio_test/grpc_test/_adapter/_test_links.py diff --git a/src/python/grpcio_test/grpc_test/_cython/.gitignore b/src/python/grpcio_test/grpc_test/_cython/.gitignore new file mode 100644 index 0000000000..c315029288 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/_cython/.gitignore @@ -0,0 +1,7 @@ +*.h +*.c +*.a +*.so +*.dll +*.pyc +*.pyd diff --git a/src/python/grpcio_test/grpc_test/_cython/__init__.py b/src/python/grpcio_test/grpc_test/_cython/__init__.py new file mode 100644 index 0000000000..b89398809f --- /dev/null +++ b/src/python/grpcio_test/grpc_test/_cython/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/python/src/grpc/_cython/adapter_low_test.py b/src/python/grpcio_test/grpc_test/_cython/adapter_low_test.py index 9bab930e56..9bab930e56 100644 --- a/src/python/src/grpc/_cython/adapter_low_test.py +++ b/src/python/grpcio_test/grpc_test/_cython/adapter_low_test.py diff --git a/src/python/src/grpc/_cython/cygrpc_test.py b/src/python/grpcio_test/grpc_test/_cython/cygrpc_test.py index 22d210b16b..637506b42e 100644 --- a/src/python/src/grpc/_cython/cygrpc_test.py +++ b/src/python/grpcio_test/grpc_test/_cython/cygrpc_test.py @@ -31,7 +31,7 @@ import time import unittest from grpc._cython import cygrpc -from grpc._cython import test_utilities +from grpc_test._cython import test_utilities class TypeSmokeTest(unittest.TestCase): diff --git a/src/python/src/grpc/_cython/test_utilities.py b/src/python/grpcio_test/grpc_test/_cython/test_utilities.py index 21ea3075b4..21ea3075b4 100644 --- a/src/python/src/grpc/_cython/test_utilities.py +++ b/src/python/grpcio_test/grpc_test/_cython/test_utilities.py diff --git a/src/python/grpcio_test/grpc_test/_junkdrawer/__init__.py b/src/python/grpcio_test/grpc_test/_junkdrawer/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/_junkdrawer/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/_junkdrawer/math_pb2.py b/src/python/grpcio_test/grpc_test/_junkdrawer/math_pb2.py index 20165955b4..20165955b4 100644 --- a/src/python/src/grpc/_junkdrawer/math_pb2.py +++ b/src/python/grpcio_test/grpc_test/_junkdrawer/math_pb2.py diff --git a/src/python/src/grpc/_junkdrawer/stock_pb2.py b/src/python/grpcio_test/grpc_test/_junkdrawer/stock_pb2.py index eef18f82d6..eef18f82d6 100644 --- a/src/python/src/grpc/_junkdrawer/stock_pb2.py +++ b/src/python/grpcio_test/grpc_test/_junkdrawer/stock_pb2.py diff --git a/src/python/grpcio_test/grpc_test/_links/__init__.py b/src/python/grpcio_test/grpc_test/_links/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/_links/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/_links/_lonely_invocation_link_test.py b/src/python/grpcio_test/grpc_test/_links/_lonely_invocation_link_test.py index 3d629f4387..abe240e07a 100644 --- a/src/python/src/grpc/_links/_lonely_invocation_link_test.py +++ b/src/python/grpcio_test/grpc_test/_links/_lonely_invocation_link_test.py @@ -33,10 +33,10 @@ 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 +from grpc_test.framework.common import test_constants +from grpc_test.framework.interfaces.links import test_cases +from grpc_test.framework.interfaces.links import test_utilities _NULL_BEHAVIOR = lambda unused_argument: None diff --git a/src/python/src/grpc/_links/_proto_scenarios.py b/src/python/grpcio_test/grpc_test/_links/_proto_scenarios.py index ccf3c29782..0d74d66297 100644 --- a/src/python/src/grpc/_links/_proto_scenarios.py +++ b/src/python/grpcio_test/grpc_test/_links/_proto_scenarios.py @@ -32,7 +32,8 @@ import abc import threading -from grpc._junkdrawer import math_pb2 +from grpc_test._junkdrawer import math_pb2 +from grpc_test.framework.common import test_constants class ProtoScenario(object): @@ -219,10 +220,9 @@ class BidirectionallyUnaryScenario(ProtoScenario): 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)) + for index in range(test_constants.STREAM_LENGTH)) def __init__(self): self._lock = threading.Lock() diff --git a/src/python/src/grpc/_links/_transmission_test.py b/src/python/grpcio_test/grpc_test/_links/_transmission_test.py index 3eeec03f46..9cdc9620f0 100644 --- a/src/python/src/grpc/_links/_transmission_test.py +++ b/src/python/grpcio_test/grpc_test/_links/_transmission_test.py @@ -32,13 +32,14 @@ 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 +from grpc_test import test_common +from grpc_test._links import _proto_scenarios +from grpc_test.framework.common import test_constants +from grpc_test.framework.interfaces.links import test_cases +from grpc_test.framework.interfaces.links import test_utilities _IDENTITY = lambda x: x @@ -94,12 +95,11 @@ class TransmissionTest(test_cases.TransmissionTest, unittest.TestCase): return _intermediary_low.Code.OK, 'An exuberant test "details" message!' def assertMetadataTransmitted(self, original_metadata, transmitted_metadata): - # we need to filter out any additional metadata added in transmitted_metadata - # since implementations are allowed to add to what is sent (in any position) - keys, _ = zip(*original_metadata) - self.assertSequenceEqual( - original_metadata, - [x for x in transmitted_metadata if x[0] in keys]) + self.assertTrue( + test_common.metadata_transmitted( + original_metadata, transmitted_metadata), + '%s erroneously transmitted as %s' % ( + original_metadata, transmitted_metadata)) class RoundTripTest(unittest.TestCase): diff --git a/src/python/grpcio_test/grpc_test/early_adopter/__init__.py b/src/python/grpcio_test/grpc_test/early_adopter/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/early_adopter/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/early_adopter/implementations_test.py b/src/python/grpcio_test/grpc_test/early_adopter/implementations_test.py index 49f0e949c4..611637e8b8 100644 --- a/src/python/src/grpc/early_adopter/implementations_test.py +++ b/src/python/grpcio_test/grpc_test/early_adopter/implementations_test.py @@ -35,7 +35,7 @@ import unittest from grpc.early_adopter import implementations from grpc.framework.alpha import utilities -from grpc._junkdrawer import math_pb2 +from grpc_test._junkdrawer import math_pb2 SERVICE_NAME = 'math.Math' diff --git a/src/python/grpcio_test/grpc_test/framework/__init__.py b/src/python/grpcio_test/grpc_test/framework/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/grpcio_test/grpc_test/framework/base/__init__.py b/src/python/grpcio_test/grpc_test/framework/base/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/base/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/base/implementations_test.py b/src/python/grpcio_test/grpc_test/framework/base/implementations_test.py index 72087f4456..5a7d1398fd 100644 --- a/src/python/src/grpc/framework/base/implementations_test.py +++ b/src/python/grpcio_test/grpc_test/framework/base/implementations_test.py @@ -32,9 +32,9 @@ import unittest from grpc.framework.base import implementations -from grpc.framework.base import interfaces_test_case from grpc.framework.base import util from grpc.framework.foundation import logging_pool +from grpc_test.framework.base import interfaces_test_case POOL_MAX_WORKERS = 10 DEFAULT_TIMEOUT = 30 diff --git a/src/python/src/grpc/framework/base/interfaces_test_case.py b/src/python/grpcio_test/grpc_test/framework/base/interfaces_test_case.py index dec10c2924..be775ad4e0 100644 --- a/src/python/src/grpc/framework/base/interfaces_test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/base/interfaces_test_case.py @@ -35,8 +35,8 @@ import time from grpc.framework.base import interfaces from grpc.framework.base import util from grpc.framework.foundation import stream -from grpc.framework.foundation import stream_testing from grpc.framework.foundation import stream_util +from grpc_test.framework.foundation import stream_testing TICK = 0.1 SMALL_TIMEOUT = TICK * 50 diff --git a/src/python/grpcio_test/grpc_test/framework/common/__init__.py b/src/python/grpcio_test/grpc_test/framework/common/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/common/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/common/test_constants.py b/src/python/grpcio_test/grpc_test/framework/common/test_constants.py index 237b8754ed..3126d0d82c 100644 --- a/src/python/src/grpc/framework/common/test_constants.py +++ b/src/python/grpcio_test/grpc_test/framework/common/test_constants.py @@ -35,3 +35,9 @@ SHORT_TIMEOUT = 4 # Absurdly large value for maximum duration in seconds for should-not-time-out # RPCs made during tests. LONG_TIMEOUT = 3000 + +# The number of payloads to transmit in streaming tests. +STREAM_LENGTH = 200 + +# The size of thread pools to use in tests. +POOL_SIZE = 10 diff --git a/src/python/src/grpc/framework/common/test_control.py b/src/python/grpcio_test/grpc_test/framework/common/test_control.py index 3960c4e649..3960c4e649 100644 --- a/src/python/src/grpc/framework/common/test_control.py +++ b/src/python/grpcio_test/grpc_test/framework/common/test_control.py diff --git a/src/python/src/grpc/framework/common/test_coverage.py b/src/python/grpcio_test/grpc_test/framework/common/test_coverage.py index a7ed3582c4..a7ed3582c4 100644 --- a/src/python/src/grpc/framework/common/test_coverage.py +++ b/src/python/grpcio_test/grpc_test/framework/common/test_coverage.py diff --git a/src/python/grpcio_test/grpc_test/framework/face/__init__.py b/src/python/grpcio_test/grpc_test/framework/face/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/face/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/face/_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/_test_case.py index 642d500628..486b6e630e 100644 --- a/src/python/src/grpc/framework/face/_test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/face/_test_case.py @@ -30,9 +30,9 @@ """Common lifecycle code for in-memory-ticket-exchange Face-layer tests.""" from grpc.framework.face import implementations -from grpc.framework.face.testing import base_util -from grpc.framework.face.testing import test_case from grpc.framework.foundation import logging_pool +from grpc_test.framework.face.testing import base_util +from grpc_test.framework.face.testing import test_case _TIMEOUT = 3 _MAXIMUM_POOL_SIZE = 10 diff --git a/src/python/src/grpc/framework/face/blocking_invocation_inline_service_test.py b/src/python/grpcio_test/grpc_test/framework/face/blocking_invocation_inline_service_test.py index 763f0f0edc..8674666418 100644 --- a/src/python/src/grpc/framework/face/blocking_invocation_inline_service_test.py +++ b/src/python/grpcio_test/grpc_test/framework/face/blocking_invocation_inline_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc.framework.face import _test_case -from grpc.framework.face.testing import blocking_invocation_inline_service_test_case as test_case +from grpc_test.framework.face import _test_case +from grpc_test.framework.face.testing import blocking_invocation_inline_service_test_case as test_case class BlockingInvocationInlineServiceTest( diff --git a/src/python/src/grpc/framework/face/event_invocation_synchronous_event_service_test.py b/src/python/grpcio_test/grpc_test/framework/face/event_invocation_synchronous_event_service_test.py index e1ab3cf711..dca373ef7c 100644 --- a/src/python/src/grpc/framework/face/event_invocation_synchronous_event_service_test.py +++ b/src/python/grpcio_test/grpc_test/framework/face/event_invocation_synchronous_event_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc.framework.face import _test_case -from grpc.framework.face.testing import event_invocation_synchronous_event_service_test_case as test_case +from grpc_test.framework.face import _test_case +from grpc_test.framework.face.testing import event_invocation_synchronous_event_service_test_case as test_case class EventInvocationSynchronousEventServiceTest( diff --git a/src/python/src/grpc/framework/face/future_invocation_asynchronous_event_service_test.py b/src/python/grpcio_test/grpc_test/framework/face/future_invocation_asynchronous_event_service_test.py index 2d13bb911d..99fdf18123 100644 --- a/src/python/src/grpc/framework/face/future_invocation_asynchronous_event_service_test.py +++ b/src/python/grpcio_test/grpc_test/framework/face/future_invocation_asynchronous_event_service_test.py @@ -31,8 +31,8 @@ import unittest -from grpc.framework.face import _test_case -from grpc.framework.face.testing import future_invocation_asynchronous_event_service_test_case as test_case +from grpc_test.framework.face import _test_case +from grpc_test.framework.face.testing import future_invocation_asynchronous_event_service_test_case as test_case class FutureInvocationAsynchronousEventServiceTest( diff --git a/src/python/grpcio_test/grpc_test/framework/face/testing/__init__.py b/src/python/grpcio_test/grpc_test/framework/face/testing/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/face/testing/base_util.py b/src/python/grpcio_test/grpc_test/framework/face/testing/base_util.py index 1df1529b27..1df1529b27 100644 --- a/src/python/src/grpc/framework/face/testing/base_util.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/base_util.py diff --git a/src/python/src/grpc/framework/face/testing/blocking_invocation_inline_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py index e57ee00104..251e1eb68e 100644 --- a/src/python/src/grpc/framework/face/testing/blocking_invocation_inline_service_test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/blocking_invocation_inline_service_test_case.py @@ -34,14 +34,12 @@ import abc import unittest # pylint: disable=unused-import from grpc.framework.face import exceptions -from grpc.framework.face.testing import control -from grpc.framework.face.testing import coverage -from grpc.framework.face.testing import digest -from grpc.framework.face.testing import stock_service -from grpc.framework.face.testing import test_case - -_TIMEOUT = 3 -_LONG_TIMEOUT = 45 +from grpc_test.framework.common import test_constants +from grpc_test.framework.face.testing import control +from grpc_test.framework.face.testing import coverage +from grpc_test.framework.face.testing import digest +from grpc_test.framework.face.testing import stock_service +from grpc_test.framework.face.testing import test_case class BlockingInvocationInlineServiceTestCase( @@ -79,7 +77,7 @@ class BlockingInvocationInlineServiceTestCase( request = test_messages.request() response = self.stub.blocking_value_in_value_out( - name, request, _LONG_TIMEOUT) + name, request, test_constants.LONG_TIMEOUT) test_messages.verify(request, response, self) @@ -90,7 +88,7 @@ class BlockingInvocationInlineServiceTestCase( request = test_messages.request() response_iterator = self.stub.inline_value_in_stream_out( - name, request, _LONG_TIMEOUT) + name, request, test_constants.LONG_TIMEOUT) responses = list(response_iterator) test_messages.verify(request, responses, self) @@ -102,7 +100,7 @@ class BlockingInvocationInlineServiceTestCase( requests = test_messages.requests() response = self.stub.blocking_stream_in_value_out( - name, iter(requests), _LONG_TIMEOUT) + name, iter(requests), test_constants.LONG_TIMEOUT) test_messages.verify(requests, response, self) @@ -113,7 +111,7 @@ class BlockingInvocationInlineServiceTestCase( requests = test_messages.requests() response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _LONG_TIMEOUT) + name, iter(requests), test_constants.LONG_TIMEOUT) responses = list(response_iterator) test_messages.verify(requests, responses, self) @@ -126,12 +124,12 @@ class BlockingInvocationInlineServiceTestCase( second_request = test_messages.request() first_response = self.stub.blocking_value_in_value_out( - name, first_request, _TIMEOUT) + name, first_request, test_constants.SHORT_TIMEOUT) test_messages.verify(first_request, first_response, self) second_response = self.stub.blocking_value_in_value_out( - name, second_request, _TIMEOUT) + name, second_request, test_constants.SHORT_TIMEOUT) test_messages.verify(second_request, second_response, self) @@ -144,7 +142,7 @@ class BlockingInvocationInlineServiceTestCase( with self.control.pause(), self.assertRaises( exceptions.ExpirationError): multi_callable = self.stub.unary_unary_multi_callable(name) - multi_callable(request, _TIMEOUT) + multi_callable(request, test_constants.SHORT_TIMEOUT) def testExpiredUnaryRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -155,7 +153,7 @@ class BlockingInvocationInlineServiceTestCase( with self.control.pause(), self.assertRaises( exceptions.ExpirationError): response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) list(response_iterator) def testExpiredStreamRequestUnaryResponse(self): @@ -167,7 +165,7 @@ class BlockingInvocationInlineServiceTestCase( with self.control.pause(), self.assertRaises( exceptions.ExpirationError): multi_callable = self.stub.stream_unary_multi_callable(name) - multi_callable(iter(requests), _TIMEOUT) + multi_callable(iter(requests), test_constants.SHORT_TIMEOUT) def testExpiredStreamRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -178,7 +176,7 @@ class BlockingInvocationInlineServiceTestCase( with self.control.pause(), self.assertRaises( exceptions.ExpirationError): response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) list(response_iterator) def testFailedUnaryRequestUnaryResponse(self): @@ -188,7 +186,8 @@ class BlockingInvocationInlineServiceTestCase( request = test_messages.request() with self.control.fail(), self.assertRaises(exceptions.ServicerError): - self.stub.blocking_value_in_value_out(name, request, _TIMEOUT) + self.stub.blocking_value_in_value_out(name, request, + test_constants.SHORT_TIMEOUT) def testFailedUnaryRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -198,7 +197,7 @@ class BlockingInvocationInlineServiceTestCase( with self.control.fail(), self.assertRaises(exceptions.ServicerError): response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) list(response_iterator) def testFailedStreamRequestUnaryResponse(self): @@ -208,7 +207,8 @@ class BlockingInvocationInlineServiceTestCase( requests = test_messages.requests() with self.control.fail(), self.assertRaises(exceptions.ServicerError): - self.stub.blocking_stream_in_value_out(name, iter(requests), _TIMEOUT) + self.stub.blocking_stream_in_value_out(name, iter(requests), + test_constants.SHORT_TIMEOUT) def testFailedStreamRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -218,5 +218,5 @@ class BlockingInvocationInlineServiceTestCase( with self.control.fail(), self.assertRaises(exceptions.ServicerError): response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) list(response_iterator) diff --git a/src/python/src/grpc/framework/face/testing/callback.py b/src/python/grpcio_test/grpc_test/framework/face/testing/callback.py index d0e63c8c56..d0e63c8c56 100644 --- a/src/python/src/grpc/framework/face/testing/callback.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/callback.py diff --git a/src/python/src/grpc/framework/face/testing/control.py b/src/python/grpcio_test/grpc_test/framework/face/testing/control.py index 3960c4e649..3960c4e649 100644 --- a/src/python/src/grpc/framework/face/testing/control.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/control.py diff --git a/src/python/src/grpc/framework/face/testing/coverage.py b/src/python/grpcio_test/grpc_test/framework/face/testing/coverage.py index f3aca113fe..f3aca113fe 100644 --- a/src/python/src/grpc/framework/face/testing/coverage.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/coverage.py diff --git a/src/python/src/grpc/framework/face/testing/digest.py b/src/python/grpcio_test/grpc_test/framework/face/testing/digest.py index db8fcbb018..54ff21779a 100644 --- a/src/python/src/grpc/framework/face/testing/digest.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/digest.py @@ -38,11 +38,11 @@ from grpc.framework.common import cardinality from grpc.framework.common import style from grpc.framework.face import exceptions from grpc.framework.face import interfaces as face_interfaces -from grpc.framework.face.testing import control as testing_control # pylint: disable=unused-import -from grpc.framework.face.testing import interfaces # pylint: disable=unused-import -from grpc.framework.face.testing import service as testing_service # pylint: disable=unused-import from grpc.framework.foundation import stream from grpc.framework.foundation import stream_util +from grpc_test.framework.face.testing import control as testing_control # pylint: disable=unused-import +from grpc_test.framework.face.testing import interfaces # pylint: disable=unused-import +from grpc_test.framework.face.testing import service as testing_service # pylint: disable=unused-import _IDENTITY = lambda x: x diff --git a/src/python/src/grpc/framework/face/testing/event_invocation_synchronous_event_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py index 0f0b0e3d52..9df77678eb 100644 --- a/src/python/src/grpc/framework/face/testing/event_invocation_synchronous_event_service_test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/event_invocation_synchronous_event_service_test_case.py @@ -33,14 +33,13 @@ import abc import unittest from grpc.framework.face import interfaces -from grpc.framework.face.testing import callback as testing_callback -from grpc.framework.face.testing import control -from grpc.framework.face.testing import coverage -from grpc.framework.face.testing import digest -from grpc.framework.face.testing import stock_service -from grpc.framework.face.testing import test_case - -_TIMEOUT = 3 +from grpc_test.framework.common import test_constants +from grpc_test.framework.face.testing import callback as testing_callback +from grpc_test.framework.face.testing import control +from grpc_test.framework.face.testing import coverage +from grpc_test.framework.face.testing import digest +from grpc_test.framework.face.testing import stock_service +from grpc_test.framework.face.testing import test_case class EventInvocationSynchronousEventServiceTestCase( @@ -79,7 +78,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() self.stub.event_value_in_value_out( - name, request, callback.complete, callback.abort, _TIMEOUT) + name, request, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() response = callback.response() @@ -93,7 +93,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() self.stub.event_value_in_stream_out( - name, request, callback, callback.abort, _TIMEOUT) + name, request, callback, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() responses = callback.responses() @@ -107,7 +108,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() unused_call, request_consumer = self.stub.event_stream_in_value_out( - name, callback.complete, callback.abort, _TIMEOUT) + name, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) request_consumer.terminate() @@ -124,7 +126,7 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() unused_call, request_consumer = self.stub.event_stream_in_stream_out( - name, callback, callback.abort, _TIMEOUT) + name, callback, callback.abort, test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) request_consumer.terminate() @@ -147,11 +149,11 @@ class EventInvocationSynchronousEventServiceTestCase( first_callback.complete(first_response) self.stub.event_value_in_value_out( name, second_request, second_callback.complete, - second_callback.abort, _TIMEOUT) + second_callback.abort, test_constants.SHORT_TIMEOUT) self.stub.event_value_in_value_out( name, first_request, make_second_invocation, first_callback.abort, - _TIMEOUT) + test_constants.SHORT_TIMEOUT) second_callback.block_until_terminated() first_response = first_callback.response() @@ -168,7 +170,8 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.pause(): self.stub.event_value_in_value_out( - name, request, callback.complete, callback.abort, _TIMEOUT) + name, request, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion()) @@ -182,7 +185,8 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.pause(): self.stub.event_value_in_stream_out( - name, request, callback, callback.abort, _TIMEOUT) + name, request, callback, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion()) @@ -194,7 +198,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() self.stub.event_stream_in_value_out( - name, callback.complete, callback.abort, _TIMEOUT) + name, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() self.assertEqual(interfaces.Abortion.EXPIRED, callback.abortion()) @@ -207,7 +212,7 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() unused_call, request_consumer = self.stub.event_stream_in_stream_out( - name, callback, callback.abort, _TIMEOUT) + name, callback, callback.abort, test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) callback.block_until_terminated() @@ -223,10 +228,12 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.fail(): self.stub.event_value_in_value_out( - name, request, callback.complete, callback.abort, _TIMEOUT) + name, request, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() - self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion()) + self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, + callback.abortion()) def testFailedUnaryRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -237,10 +244,12 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.fail(): self.stub.event_value_in_stream_out( - name, request, callback, callback.abort, _TIMEOUT) + name, request, callback, callback.abort, + test_constants.SHORT_TIMEOUT) callback.block_until_terminated() - self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion()) + self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, + callback.abortion()) def testFailedStreamRequestUnaryResponse(self): for name, test_messages_sequence in ( @@ -251,13 +260,15 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.fail(): unused_call, request_consumer = self.stub.event_stream_in_value_out( - name, callback.complete, callback.abort, _TIMEOUT) + name, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) request_consumer.terminate() callback.block_until_terminated() - self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, callback.abortion()) + self.assertEqual(interfaces.Abortion.SERVICER_FAILURE, + callback.abortion()) def testFailedStreamRequestStreamResponse(self): for name, test_messages_sequence in ( @@ -268,7 +279,7 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.fail(): unused_call, request_consumer = self.stub.event_stream_in_stream_out( - name, callback, callback.abort, _TIMEOUT) + name, callback, callback.abort, test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) request_consumer.terminate() @@ -287,10 +298,10 @@ class EventInvocationSynchronousEventServiceTestCase( self.stub.event_value_in_value_out( name, first_request, first_callback.complete, first_callback.abort, - _TIMEOUT) + test_constants.SHORT_TIMEOUT) self.stub.event_value_in_value_out( name, second_request, second_callback.complete, - second_callback.abort, _TIMEOUT) + second_callback.abort, test_constants.SHORT_TIMEOUT) first_callback.block_until_terminated() second_callback.block_until_terminated() @@ -312,7 +323,8 @@ class EventInvocationSynchronousEventServiceTestCase( with self.control.pause(): call = self.stub.event_value_in_value_out( - name, request, callback.complete, callback.abort, _TIMEOUT) + name, request, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) call.cancel() callback.block_until_terminated() @@ -326,7 +338,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() call = self.stub.event_value_in_stream_out( - name, request, callback, callback.abort, _TIMEOUT) + name, request, callback, callback.abort, + test_constants.SHORT_TIMEOUT) call.cancel() callback.block_until_terminated() @@ -340,7 +353,8 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() call, request_consumer = self.stub.event_stream_in_value_out( - name, callback.complete, callback.abort, _TIMEOUT) + name, callback.complete, callback.abort, + test_constants.SHORT_TIMEOUT) for request in requests: request_consumer.consume(request) call.cancel() @@ -355,7 +369,7 @@ class EventInvocationSynchronousEventServiceTestCase( callback = testing_callback.Callback() call, unused_request_consumer = self.stub.event_stream_in_stream_out( - name, callback, callback.abort, _TIMEOUT) + name, callback, callback.abort, test_constants.SHORT_TIMEOUT) call.cancel() callback.block_until_terminated() diff --git a/src/python/src/grpc/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py index 21bf9a4248..70d86a0422 100644 --- a/src/python/src/grpc/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/future_invocation_asynchronous_event_service_test_case.py @@ -35,15 +35,15 @@ import threading import unittest from grpc.framework.face import exceptions -from grpc.framework.face.testing import control -from grpc.framework.face.testing import coverage -from grpc.framework.face.testing import digest -from grpc.framework.face.testing import stock_service -from grpc.framework.face.testing import test_case from grpc.framework.foundation import future from grpc.framework.foundation import logging_pool +from grpc_test.framework.common import test_constants +from grpc_test.framework.face.testing import control +from grpc_test.framework.face.testing import coverage +from grpc_test.framework.face.testing import digest +from grpc_test.framework.face.testing import stock_service +from grpc_test.framework.face.testing import test_case -_TIMEOUT = 3 _MAXIMUM_POOL_SIZE = 10 @@ -110,7 +110,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( request = test_messages.request() response_future = self.stub.future_value_in_value_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) response = response_future.result() test_messages.verify(request, response, self) @@ -122,7 +122,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( request = test_messages.request() response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) responses = list(response_iterator) test_messages.verify(request, responses, self) @@ -138,7 +138,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( # returned to calling code before the iterator yields any requests. with request_iterator.pause(): response_future = self.stub.future_stream_in_value_out( - name, request_iterator, _TIMEOUT) + name, request_iterator, test_constants.SHORT_TIMEOUT) response = response_future.result() test_messages.verify(requests, response, self) @@ -154,7 +154,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( # returned to calling code before the iterator yields any requests. with request_iterator.pause(): response_iterator = self.stub.inline_stream_in_stream_out( - name, request_iterator, _TIMEOUT) + name, request_iterator, test_constants.SHORT_TIMEOUT) responses = list(response_iterator) test_messages.verify(requests, responses, self) @@ -167,13 +167,13 @@ class FutureInvocationAsynchronousEventServiceTestCase( second_request = test_messages.request() first_response_future = self.stub.future_value_in_value_out( - name, first_request, _TIMEOUT) + name, first_request, test_constants.SHORT_TIMEOUT) first_response = first_response_future.result() test_messages.verify(first_request, first_response, self) second_response_future = self.stub.future_value_in_value_out( - name, second_request, _TIMEOUT) + name, second_request, test_constants.SHORT_TIMEOUT) second_response = second_response_future.result() test_messages.verify(second_request, second_response, self) @@ -186,7 +186,8 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): multi_callable = self.stub.unary_unary_multi_callable(name) - response_future = multi_callable.future(request, _TIMEOUT) + response_future = multi_callable.future(request, + test_constants.SHORT_TIMEOUT) self.assertIsInstance( response_future.exception(), exceptions.ExpirationError) with self.assertRaises(exceptions.ExpirationError): @@ -200,7 +201,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) with self.assertRaises(exceptions.ExpirationError): list(response_iterator) @@ -212,7 +213,8 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): multi_callable = self.stub.stream_unary_multi_callable(name) - response_future = multi_callable.future(iter(requests), _TIMEOUT) + response_future = multi_callable.future(iter(requests), + test_constants.SHORT_TIMEOUT) self.assertIsInstance( response_future.exception(), exceptions.ExpirationError) with self.assertRaises(exceptions.ExpirationError): @@ -226,7 +228,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) with self.assertRaises(exceptions.ExpirationError): list(response_iterator) @@ -238,7 +240,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.fail(): response_future = self.stub.future_value_in_value_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) # Because the servicer fails outside of the thread from which the # servicer-side runtime called into it its failure is @@ -261,7 +263,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( # expiration of the RPC. with self.control.fail(), self.assertRaises(exceptions.ExpirationError): response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) list(response_iterator) def testFailedStreamRequestUnaryResponse(self): @@ -272,7 +274,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.fail(): response_future = self.stub.future_stream_in_value_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) # Because the servicer fails outside of the thread from which the # servicer-side runtime called into it its failure is @@ -295,7 +297,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( # expiration of the RPC. with self.control.fail(), self.assertRaises(exceptions.ExpirationError): response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) list(response_iterator) def testParallelInvocations(self): @@ -305,10 +307,11 @@ class FutureInvocationAsynchronousEventServiceTestCase( first_request = test_messages.request() second_request = test_messages.request() + # TODO(bug 2039): use LONG_TIMEOUT instead first_response_future = self.stub.future_value_in_value_out( - name, first_request, _TIMEOUT) + name, first_request, test_constants.SHORT_TIMEOUT) second_response_future = self.stub.future_value_in_value_out( - name, second_request, _TIMEOUT) + name, second_request, test_constants.SHORT_TIMEOUT) first_response = first_response_future.result() second_response = second_response_future.result() @@ -327,7 +330,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_future = self.stub.future_value_in_value_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) cancel_method_return_value = response_future.cancel() self.assertFalse(cancel_method_return_value) @@ -341,7 +344,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_iterator = self.stub.inline_value_in_stream_out( - name, request, _TIMEOUT) + name, request, test_constants.SHORT_TIMEOUT) response_iterator.cancel() with self.assertRaises(future.CancelledError): @@ -355,7 +358,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_future = self.stub.future_stream_in_value_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) cancel_method_return_value = response_future.cancel() self.assertFalse(cancel_method_return_value) @@ -369,7 +372,7 @@ class FutureInvocationAsynchronousEventServiceTestCase( with self.control.pause(): response_iterator = self.stub.inline_stream_in_stream_out( - name, iter(requests), _TIMEOUT) + name, iter(requests), test_constants.SHORT_TIMEOUT) response_iterator.cancel() with self.assertRaises(future.CancelledError): diff --git a/src/python/src/grpc/framework/face/testing/interfaces.py b/src/python/grpcio_test/grpc_test/framework/face/testing/interfaces.py index 5932dabf1e..5932dabf1e 100644 --- a/src/python/src/grpc/framework/face/testing/interfaces.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/interfaces.py diff --git a/src/python/src/grpc/framework/face/testing/serial.py b/src/python/grpcio_test/grpc_test/framework/face/testing/serial.py index 47fc5822de..47fc5822de 100644 --- a/src/python/src/grpc/framework/face/testing/serial.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/serial.py diff --git a/src/python/src/grpc/framework/face/testing/service.py b/src/python/grpcio_test/grpc_test/framework/face/testing/service.py index bf54d41d66..ee9d6a3da3 100644 --- a/src/python/src/grpc/framework/face/testing/service.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/service.py @@ -33,7 +33,7 @@ import abc # interfaces is referenced from specification in this module. from grpc.framework.face import interfaces as face_interfaces # pylint: disable=unused-import -from grpc.framework.face.testing import interfaces +from grpc_test.framework.face.testing import interfaces class UnaryUnaryTestMethodImplementation(interfaces.Method): diff --git a/src/python/src/grpc/framework/face/testing/stock_service.py b/src/python/grpcio_test/grpc_test/framework/face/testing/stock_service.py index 61aaf444a0..0f83ca4db1 100644 --- a/src/python/src/grpc/framework/face/testing/stock_service.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/stock_service.py @@ -30,11 +30,11 @@ """Examples of Python implementations of the stock.proto Stock service.""" from grpc.framework.common import cardinality -from grpc.framework.face.testing import service from grpc.framework.foundation import abandonment from grpc.framework.foundation import stream from grpc.framework.foundation import stream_util -from grpc._junkdrawer import stock_pb2 +from grpc_test.framework.face.testing import service +from grpc_test._junkdrawer import stock_pb2 SYMBOL_FORMAT = 'test symbol:%03d' STREAM_LENGTH = 400 diff --git a/src/python/src/grpc/framework/face/testing/test_case.py b/src/python/grpcio_test/grpc_test/framework/face/testing/test_case.py index e60e3d1d40..858d5cf7fd 100644 --- a/src/python/src/grpc/framework/face/testing/test_case.py +++ b/src/python/grpcio_test/grpc_test/framework/face/testing/test_case.py @@ -33,7 +33,7 @@ import abc # face_interfaces and interfaces are referenced in specification in this module. from grpc.framework.face import interfaces as face_interfaces # pylint: disable=unused-import -from grpc.framework.face.testing import interfaces # pylint: disable=unused-import +from grpc_test.framework.face.testing import interfaces # pylint: disable=unused-import class FaceTestCase(object): diff --git a/src/python/grpcio_test/grpc_test/framework/foundation/__init__.py b/src/python/grpcio_test/grpc_test/framework/foundation/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/foundation/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/foundation/_later_test.py b/src/python/grpcio_test/grpc_test/framework/foundation/_later_test.py index 6c2459e185..6c2459e185 100644 --- a/src/python/src/grpc/framework/foundation/_later_test.py +++ b/src/python/grpcio_test/grpc_test/framework/foundation/_later_test.py diff --git a/src/python/src/grpc/framework/foundation/_logging_pool_test.py b/src/python/grpcio_test/grpc_test/framework/foundation/_logging_pool_test.py index 452802da6a..452802da6a 100644 --- a/src/python/src/grpc/framework/foundation/_logging_pool_test.py +++ b/src/python/grpcio_test/grpc_test/framework/foundation/_logging_pool_test.py diff --git a/src/python/src/grpc/framework/foundation/stream_testing.py b/src/python/grpcio_test/grpc_test/framework/foundation/stream_testing.py index 098a53d5e7..098a53d5e7 100644 --- a/src/python/src/grpc/framework/foundation/stream_testing.py +++ b/src/python/grpcio_test/grpc_test/framework/foundation/stream_testing.py diff --git a/src/python/grpcio_test/grpc_test/framework/interfaces/__init__.py b/src/python/grpcio_test/grpc_test/framework/interfaces/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/interfaces/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/grpcio_test/grpc_test/framework/interfaces/links/__init__.py b/src/python/grpcio_test/grpc_test/framework/interfaces/links/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/grpcio_test/grpc_test/framework/interfaces/links/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/interfaces/links/test_cases.py b/src/python/grpcio_test/grpc_test/framework/interfaces/links/test_cases.py index bf1f09d99d..26ca035c44 100644 --- a/src/python/src/grpc/framework/interfaces/links/test_cases.py +++ b/src/python/grpcio_test/grpc_test/framework/interfaces/links/test_cases.py @@ -33,9 +33,9 @@ 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 +from grpc_test.framework.common import test_constants +from grpc_test.framework.interfaces.links import test_utilities def at_least_n_payloads_received_predicate(n): diff --git a/src/python/src/grpc/framework/interfaces/links/test_utilities.py b/src/python/grpcio_test/grpc_test/framework/interfaces/links/test_utilities.py index 6c2e3346aa..6c2e3346aa 100644 --- a/src/python/src/grpc/framework/interfaces/links/test_utilities.py +++ b/src/python/grpcio_test/grpc_test/framework/interfaces/links/test_utilities.py diff --git a/src/python/grpcio_test/grpc_test/test_common.py b/src/python/grpcio_test/grpc_test/test_common.py new file mode 100644 index 0000000000..f8e1f1e43f --- /dev/null +++ b/src/python/grpcio_test/grpc_test/test_common.py @@ -0,0 +1,71 @@ +# 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. + +"""Common code used throughout tests of gRPC.""" + +import collections + + +def metadata_transmitted(original_metadata, transmitted_metadata): + """Judges whether or not metadata was acceptably transmitted. + + gRPC is allowed to insert key-value pairs into the metadata values given by + applications and to reorder key-value pairs with different keys but it is not + allowed to alter existing key-value pairs or to reorder key-value pairs with + the same key. + + Args: + original_metadata: A metadata value used in a test of gRPC. + transmitted_metadata: A metadata value corresponding to original_metadata + after having been transmitted via gRPC. + + Returns: + A boolean indicating whether transmitted_metadata accurately reflects + original_metadata after having been transmitted via gRPC. + """ + original = collections.defaultdict(list) + for key, value in original_metadata: + original[key].append(value) + transmitted = collections.defaultdict(list) + for key, value in transmitted_metadata: + transmitted[key].append(value) + + for key, values in original.iteritems(): + transmitted_values = transmitted[key] + transmitted_iterator = iter(transmitted_values) + try: + for value in values: + while True: + transmitted_value = next(transmitted_iterator) + if value == transmitted_value: + break + except StopIteration: + return False + else: + return True diff --git a/src/python/grpcio_test/requirements.txt b/src/python/grpcio_test/requirements.txt new file mode 100644 index 0000000000..856198def5 --- /dev/null +++ b/src/python/grpcio_test/requirements.txt @@ -0,0 +1,5 @@ +pytest>=2.6 +pytest-cov>=2.0 +pytest-xdist>=1.11 +oauth2client>=1.4.7 +grpcio>=0.10.0a0 diff --git a/src/python/grpcio_test/setup.cfg b/src/python/grpcio_test/setup.cfg new file mode 100644 index 0000000000..b32d3f5972 --- /dev/null +++ b/src/python/grpcio_test/setup.cfg @@ -0,0 +1,3 @@ +[pytest] +norecursedirs = _cython +python_files = *_test.py diff --git a/src/python/grpcio_test/setup.py b/src/python/grpcio_test/setup.py new file mode 100644 index 0000000000..a6203cae2d --- /dev/null +++ b/src/python/grpcio_test/setup.py @@ -0,0 +1,84 @@ +# 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 setup module for the GRPC Python interop testing package.""" + +import os +import os.path + +import setuptools + +# Ensure we're in the proper directory whether or not we're being used by pip. +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +# Break import-style to ensure we can actually find our commands module. +import commands + +_PACKAGES = setuptools.find_packages('.', exclude=['*._cython', '*._cython.*']) + +_PACKAGE_DIRECTORIES = { + '': '.', +} + +_PACKAGE_DATA = { + 'grpc_interop': [ + 'credentials/ca.pem', + 'credentials/server1.key', + 'credentials/server1.pem', + ], + 'grpc_protoc_plugin': [ + 'test.proto', + ], +} + +_SETUP_REQUIRES = ( + 'pytest>=2.6', + 'pytest-cov>=2.0', + 'pytest-xdist>=1.11', +) + +_INSTALL_REQUIRES = ( + 'oauth2client>=1.4.7', + 'grpcio>=0.10.0a0', +) + +_COMMAND_CLASS = { + 'test': commands.RunTests +} + +setuptools.setup( + name='grpcio_test', + version='0.10.0a0', + packages=_PACKAGES, + package_dir=_PACKAGE_DIRECTORIES, + package_data=_PACKAGE_DATA, + install_requires=_INSTALL_REQUIRES + _SETUP_REQUIRES, + setup_requires=_SETUP_REQUIRES, + cmdclass=_COMMAND_CLASS, +) diff --git a/src/python/src/.gitignore b/src/python/src/.gitignore deleted file mode 100644 index 144e501237..0000000000 --- a/src/python/src/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -MANIFEST -grpcio.egg-info/ -build/ -dist/ diff --git a/src/python/src/MANIFEST.in b/src/python/src/MANIFEST.in deleted file mode 100644 index 6f32db0548..0000000000 --- a/src/python/src/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -graft grpc diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c index bfb9f6ff01..88659da535 100644 --- a/src/ruby/ext/grpc/rb_call.c +++ b/src/ruby/ext/grpc/rb_call.c @@ -179,6 +179,19 @@ static VALUE grpc_rb_call_cancel(VALUE self) { return Qnil; } +/* Called to obtain the peer that this call is connected to. */ +static VALUE grpc_rb_call_get_peer(VALUE self) { + VALUE res = Qnil; + grpc_call *call = NULL; + char *peer = NULL; + TypedData_Get_Struct(self, grpc_call, &grpc_call_data_type, call); + peer = grpc_call_get_peer(call); + res = rb_str_new2(peer); + gpr_free(peer); + + return res; +} + /* call-seq: status = call.status @@ -235,8 +248,8 @@ static VALUE grpc_rb_call_set_metadata(VALUE self, VALUE metadata) { */ static int grpc_rb_md_ary_fill_hash_cb(VALUE key, VALUE val, VALUE md_ary_obj) { grpc_metadata_array *md_ary = NULL; - int array_length; - int i; + long array_length; + long i; /* Construct a metadata object from key and value and add it */ TypedData_Get_Struct(md_ary_obj, grpc_metadata_array, @@ -720,6 +733,7 @@ void Init_grpc_call() { /* Add ruby analogues of the Call methods. */ rb_define_method(grpc_rb_cCall, "run_batch", grpc_rb_call_run_batch, 4); rb_define_method(grpc_rb_cCall, "cancel", grpc_rb_call_cancel, 0); + rb_define_method(grpc_rb_cCall, "peer", grpc_rb_call_get_peer, 0); rb_define_method(grpc_rb_cCall, "status", grpc_rb_call_get_status, 0); rb_define_method(grpc_rb_cCall, "status=", grpc_rb_call_set_status, 1); rb_define_method(grpc_rb_cCall, "metadata", grpc_rb_call_get_metadata, 0); diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c index 9bf1a9f945..2129ba3485 100644 --- a/src/ruby/ext/grpc/rb_channel.c +++ b/src/ruby/ext/grpc/rb_channel.c @@ -37,6 +37,7 @@ #include <grpc/grpc.h> #include <grpc/grpc_security.h> +#include <grpc/support/alloc.h> #include "rb_grpc.h" #include "rb_call.h" #include "rb_channel_args.h" @@ -146,7 +147,7 @@ static VALUE grpc_rb_channel_init(int argc, VALUE *argv, VALUE self) { target_chars = StringValueCStr(target); grpc_rb_hash_convert_to_channel_args(channel_args, &args); if (credentials == Qnil) { - ch = grpc_channel_create(target_chars, &args); + ch = grpc_insecure_channel_create(target_chars, &args); } else { creds = grpc_rb_get_wrapped_credentials(credentials); ch = grpc_secure_channel_create(creds, target_chars, &args); @@ -164,6 +165,65 @@ static VALUE grpc_rb_channel_init(int argc, VALUE *argv, VALUE self) { return self; } +/* + call-seq: + insecure_channel = Channel:new("myhost:8080", {'arg1': 'value1'}) + creds = ... + secure_channel = Channel:new("myhost:443", {'arg1': 'value1'}, creds) + + Creates channel instances. */ +static VALUE grpc_rb_channel_get_connectivity_state(int argc, VALUE *argv, + VALUE self) { + VALUE try_to_connect = Qfalse; + grpc_rb_channel *wrapper = NULL; + grpc_channel *ch = NULL; + + /* "01" == 0 mandatory args, 1 (try_to_connect) is optional */ + rb_scan_args(argc, argv, "01", try_to_connect); + + TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper); + ch = wrapper->wrapped; + if (ch == NULL) { + rb_raise(rb_eRuntimeError, "closed!"); + return Qnil; + } + return NUM2LONG( + grpc_channel_check_connectivity_state(ch, (int)try_to_connect)); +} + +/* Watch for a change in connectivity state. + + Once the channel connectivity state is different from the last observed + state, tag will be enqueued on cq with success=1 + + If deadline expires BEFORE the state is changed, tag will be enqueued on + the completion queue with success=0 */ +static VALUE grpc_rb_channel_watch_connectivity_state(VALUE self, + VALUE last_state, + VALUE cqueue, + VALUE deadline, + VALUE tag) { + grpc_rb_channel *wrapper = NULL; + grpc_channel *ch = NULL; + grpc_completion_queue *cq = NULL; + + cq = grpc_rb_get_wrapped_completion_queue(cqueue); + TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper); + ch = wrapper->wrapped; + if (ch == NULL) { + rb_raise(rb_eRuntimeError, "closed!"); + return Qnil; + } + grpc_channel_watch_connectivity_state( + ch, + NUM2LONG(last_state), + grpc_rb_time_timeval(deadline, /* absolute time */ 0), + cq, + ROBJECT(tag)); + + return Qnil; +} + /* Clones Channel instances. Gives Channel a consistent implementation of Ruby's object copy/dup @@ -194,15 +254,28 @@ static VALUE grpc_rb_channel_init_copy(VALUE copy, VALUE orig) { /* Create a call given a grpc_channel, in order to call method. The request is not sent until grpc_call_invoke is called. */ -static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method, - VALUE host, VALUE deadline) { +static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, + VALUE parent, VALUE mask, + VALUE method, VALUE host, + VALUE deadline) { VALUE res = Qnil; grpc_rb_channel *wrapper = NULL; grpc_call *call = NULL; + grpc_call *parent_call = NULL; grpc_channel *ch = NULL; grpc_completion_queue *cq = NULL; + int flags = GRPC_PROPAGATE_DEFAULTS; char *method_chars = StringValueCStr(method); - char *host_chars = StringValueCStr(host); + char *host_chars = NULL; + if (host != Qnil) { + host_chars = StringValueCStr(host); + } + if (mask != Qnil) { + flags = NUM2UINT(mask); + } + if (parent != Qnil) { + parent_call = grpc_rb_get_wrapped_call(parent); + } cq = grpc_rb_get_wrapped_completion_queue(cqueue); TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper); @@ -212,10 +285,10 @@ static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method, return Qnil; } - call = - grpc_channel_create_call(ch, cq, method_chars, host_chars, - grpc_rb_time_timeval(deadline, - /* absolute time */ 0)); + call = grpc_channel_create_call(ch, parent_call, flags, cq, method_chars, + host_chars, grpc_rb_time_timeval( + deadline, + /* absolute time */ 0)); if (call == NULL) { rb_raise(rb_eRuntimeError, "cannot create call with method %s", method_chars); @@ -233,6 +306,7 @@ static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method, return res; } + /* Closes the channel, calling it's destroy method */ static VALUE grpc_rb_channel_destroy(VALUE self) { grpc_rb_channel *wrapper = NULL; @@ -249,6 +323,53 @@ static VALUE grpc_rb_channel_destroy(VALUE self) { return Qnil; } + +/* Called to obtain the target that this channel accesses. */ +static VALUE grpc_rb_channel_get_target(VALUE self) { + grpc_rb_channel *wrapper = NULL; + VALUE res = Qnil; + char* target = NULL; + + TypedData_Get_Struct(self, grpc_rb_channel, &grpc_channel_data_type, wrapper); + target = grpc_channel_get_target(wrapper->wrapped); + res = rb_str_new2(target); + gpr_free(target); + + return res; +} + +static void Init_grpc_propagate_masks() { + /* Constants representing call propagation masks in grpc.h */ + VALUE grpc_rb_mPropagateMasks = rb_define_module_under( + grpc_rb_mGrpcCore, "PropagateMasks"); + rb_define_const(grpc_rb_mPropagateMasks, "DEADLINE", + UINT2NUM(GRPC_PROPAGATE_DEADLINE)); + rb_define_const(grpc_rb_mPropagateMasks, "CENSUS_STATS_CONTEXT", + UINT2NUM(GRPC_PROPAGATE_CENSUS_STATS_CONTEXT)); + rb_define_const(grpc_rb_mPropagateMasks, "CENSUS_TRACING_CONTEXT", + UINT2NUM(GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT)); + rb_define_const(grpc_rb_mPropagateMasks, "CANCELLATION", + UINT2NUM(GRPC_PROPAGATE_CANCELLATION)); + rb_define_const(grpc_rb_mPropagateMasks, "DEFAULTS", + UINT2NUM(GRPC_PROPAGATE_DEFAULTS)); +} + +static void Init_grpc_connectivity_states() { + /* Constants representing call propagation masks in grpc.h */ + VALUE grpc_rb_mConnectivityStates = rb_define_module_under( + grpc_rb_mGrpcCore, "ConnectivityStates"); + rb_define_const(grpc_rb_mConnectivityStates, "IDLE", + LONG2NUM(GRPC_CHANNEL_IDLE)); + rb_define_const(grpc_rb_mConnectivityStates, "CONNECTING", + LONG2NUM(GRPC_CHANNEL_CONNECTING)); + rb_define_const(grpc_rb_mConnectivityStates, "READY", + LONG2NUM(GRPC_CHANNEL_READY)); + rb_define_const(grpc_rb_mConnectivityStates, "TRANSIENT_FAILURE", + LONG2NUM(GRPC_CHANNEL_TRANSIENT_FAILURE)); + rb_define_const(grpc_rb_mConnectivityStates, "FATAL_FAILURE", + LONG2NUM(GRPC_CHANNEL_FATAL_FAILURE)); +} + void Init_grpc_channel() { grpc_rb_cChannelArgs = rb_define_class("TmpChannelArgs", rb_cObject); grpc_rb_cChannel = @@ -263,8 +384,14 @@ void Init_grpc_channel() { grpc_rb_channel_init_copy, 1); /* Add ruby analogues of the Channel methods. */ + rb_define_method(grpc_rb_cChannel, "connectivity_state", + grpc_rb_channel_get_connectivity_state, + -1); + rb_define_method(grpc_rb_cChannel, "watch_connectivity_state", + grpc_rb_channel_watch_connectivity_state, 4); rb_define_method(grpc_rb_cChannel, "create_call", - grpc_rb_channel_create_call, 4); + grpc_rb_channel_create_call, 6); + rb_define_method(grpc_rb_cChannel, "target", grpc_rb_channel_get_target, 0); rb_define_method(grpc_rb_cChannel, "destroy", grpc_rb_channel_destroy, 0); rb_define_alias(grpc_rb_cChannel, "close", "destroy"); @@ -279,6 +406,8 @@ void Init_grpc_channel() { ID2SYM(rb_intern(GRPC_ARG_MAX_CONCURRENT_STREAMS))); rb_define_const(grpc_rb_cChannel, "MAX_MESSAGE_LENGTH", ID2SYM(rb_intern(GRPC_ARG_MAX_MESSAGE_LENGTH))); + Init_grpc_propagate_masks(); + Init_grpc_connectivity_states(); } /* Gets the wrapped channel from the ruby wrapper */ diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c index 65d9c9a237..327fd1a4fc 100644 --- a/src/ruby/ext/grpc/rb_grpc.c +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -139,7 +139,7 @@ gpr_timespec grpc_rb_time_timeval(VALUE time, int interval) { rb_raise(rb_eRangeError, "%f out of Time range", RFLOAT_VALUE(time)); } - t.tv_nsec = (time_t)(d * 1e9 + 0.5); + t.tv_nsec = (int)(d * 1e9 + 0.5); } break; diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c index 375a651d24..79a4ae8757 100644 --- a/src/ruby/ext/grpc/rb_server.c +++ b/src/ruby/ext/grpc/rb_server.c @@ -357,7 +357,8 @@ static VALUE grpc_rb_server_add_http2_port(int argc, VALUE *argv, VALUE self) { rb_raise(rb_eRuntimeError, "destroyed!"); return Qnil; } else if (rb_creds == Qnil) { - recvd_port = grpc_server_add_http2_port(s->wrapped, StringValueCStr(port)); + recvd_port = + grpc_server_add_insecure_http2_port(s->wrapped, StringValueCStr(port)); if (recvd_port == 0) { rb_raise(rb_eRuntimeError, "could not add port %s to server, not sure why", diff --git a/src/ruby/ext/grpc/rb_server_credentials.c b/src/ruby/ext/grpc/rb_server_credentials.c index 5f40935890..62c211d769 100644 --- a/src/ruby/ext/grpc/rb_server_credentials.c +++ b/src/ruby/ext/grpc/rb_server_credentials.c @@ -176,11 +176,12 @@ static VALUE grpc_rb_server_credentials_init(VALUE self, VALUE pem_root_certs, } key_cert_pair.private_key = RSTRING_PTR(pem_private_key); key_cert_pair.cert_chain = RSTRING_PTR(pem_cert_chain); + /* TODO Add a force_client_auth parameter and pass it here. */ if (pem_root_certs == Qnil) { - creds = grpc_ssl_server_credentials_create(NULL, &key_cert_pair, 1); + creds = grpc_ssl_server_credentials_create(NULL, &key_cert_pair, 1, 0); } else { creds = grpc_ssl_server_credentials_create(RSTRING_PTR(pem_root_certs), - &key_cert_pair, 1); + &key_cert_pair, 1, 0); } if (creds == NULL) { rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why"); diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec index dd4e27df51..eb748458b9 100755 --- a/src/ruby/grpc.gemspec +++ b/src/ruby/grpc.gemspec @@ -16,12 +16,15 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.0.0' s.requirements << 'libgrpc ~> 0.10.0 needs to be installed' - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- spec/*`.split("\n") - s.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f| - File.basename(f) + s.files = %w( Rakefile ) + s.files += Dir.glob('lib/**/*') + s.files += Dir.glob('ext/**/*') + s.files += Dir.glob('bin/**/*') + s.test_files = Dir.glob('spec/**/*') + %w(math noproto).each do |b| + s.executables += ["#{b}_client.rb", "#{b}_server.rb"] end - s.require_paths = ['lib'] + s.require_paths = %w( bin lib ) s.platform = Gem::Platform::RUBY s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1' diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb index 7b2c04aa22..cce718537c 100644 --- a/src/ruby/lib/grpc/generic/client_stub.rb +++ b/src/ruby/lib/grpc/generic/client_stub.rb @@ -28,16 +28,19 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'grpc/generic/active_call' +require 'grpc/version' # GRPC contains the General RPC module. module GRPC + # rubocop:disable Metrics/ParameterLists + # ClientStub represents an endpoint used to send requests to GRPC servers. class ClientStub include Core::StatusCodes include Core::TimeConsts - # Default timeout is 5 seconds. - DEFAULT_TIMEOUT = 5 + # Default timeout is infinity. + DEFAULT_TIMEOUT = INFINITE_FUTURE # setup_channel is used by #initialize to constuct a channel from its # arguments. @@ -46,6 +49,7 @@ module GRPC fail(TypeError, '!Channel') unless alt_chan.is_a?(Core::Channel) return alt_chan end + kw['grpc.primary_user_agent'] = "grpc-ruby/#{VERSION}" return Core::Channel.new(host, kw) if creds.nil? fail(TypeError, '!Credentials') unless creds.is_a?(Core::Credentials) Core::Channel.new(host, kw, creds) @@ -66,6 +70,12 @@ module GRPC update_metadata end + # Allows users of the stub to modify the propagate mask. + # + # This is an advanced feature for use when making calls to another gRPC + # server whilst running in the handler of an existing one. + attr_writer :propagate_mask + # Creates a new ClientStub. # # Minimally, a stub is created with the just the host of the gRPC service @@ -89,8 +99,8 @@ module GRPC # # - :update_metadata # when present, this a func that takes a hash and returns a hash - # it can be used to update metadata, i.e, remove, change or update - # amend metadata values. + # it can be used to update metadata, i.e, remove, or amend + # metadata values. # # @param host [String] the host the stub connects to # @param q [Core::CompletionQueue] used to wait for events @@ -103,6 +113,7 @@ module GRPC channel_override: nil, timeout: nil, creds: nil, + propagate_mask: nil, update_metadata: nil, **kw) fail(TypeError, '!CompletionQueue') unless q.is_a?(Core::CompletionQueue) @@ -111,6 +122,7 @@ module GRPC @update_metadata = ClientStub.check_update_metadata(update_metadata) alt_host = kw[Core::Channel::SSL_TARGET] @host = alt_host.nil? ? host : alt_host + @propagate_mask = propagate_mask @timeout = timeout.nil? ? DEFAULT_TIMEOUT : timeout end @@ -149,11 +161,15 @@ module GRPC # @param marshal [Function] f(obj)->string that marshals requests # @param unmarshal [Function] f(string)->obj that unmarshals responses # @param timeout [Numeric] (optional) the max completion time in seconds + # @param parent [Core::Call] a prior call whose reserved metadata + # will be propagated by this one. # @param return_op [true|false] return an Operation if true # @return [Object] the response received from the server def request_response(method, req, marshal, unmarshal, timeout = nil, - return_op: false, **kw) - c = new_active_call(method, marshal, unmarshal, timeout) + return_op: false, + parent: parent, + **kw) + c = new_active_call(method, marshal, unmarshal, timeout, parent: parent) kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method) md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri) return c.request_response(req, **md) unless return_op @@ -208,10 +224,14 @@ module GRPC # @param unmarshal [Function] f(string)->obj that unmarshals responses # @param timeout [Numeric] the max completion time in seconds # @param return_op [true|false] return an Operation if true + # @param parent [Core::Call] a prior call whose reserved metadata + # will be propagated by this one. # @return [Object|Operation] the response received from the server def client_streamer(method, requests, marshal, unmarshal, timeout = nil, - return_op: false, **kw) - c = new_active_call(method, marshal, unmarshal, timeout) + return_op: false, + parent: nil, + **kw) + c = new_active_call(method, marshal, unmarshal, timeout, parent: parent) kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method) md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri) return c.client_streamer(requests, **md) unless return_op @@ -274,11 +294,16 @@ module GRPC # @param unmarshal [Function] f(string)->obj that unmarshals responses # @param timeout [Numeric] the max completion time in seconds # @param return_op [true|false]return an Operation if true + # @param parent [Core::Call] a prior call whose reserved metadata + # will be propagated by this one. # @param blk [Block] when provided, is executed for each response # @return [Enumerator|Operation|nil] as discussed above def server_streamer(method, req, marshal, unmarshal, timeout = nil, - return_op: false, **kw, &blk) - c = new_active_call(method, marshal, unmarshal, timeout) + return_op: false, + parent: nil, + **kw, + &blk) + c = new_active_call(method, marshal, unmarshal, timeout, parent: parent) kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method) md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri) return c.server_streamer(req, **md, &blk) unless return_op @@ -379,12 +404,17 @@ module GRPC # @param marshal [Function] f(obj)->string that marshals requests # @param unmarshal [Function] f(string)->obj that unmarshals responses # @param timeout [Numeric] (optional) the max completion time in seconds - # @param blk [Block] when provided, is executed for each response + # @param parent [Core::Call] a prior call whose reserved metadata + # will be propagated by this one. # @param return_op [true|false] return an Operation if true + # @param blk [Block] when provided, is executed for each response # @return [Enumerator|nil|Operation] as discussed above def bidi_streamer(method, requests, marshal, unmarshal, timeout = nil, - return_op: false, **kw, &blk) - c = new_active_call(method, marshal, unmarshal, timeout) + return_op: false, + parent: nil, + **kw, + &blk) + c = new_active_call(method, marshal, unmarshal, timeout, parent: parent) kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method) md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri) return c.bidi_streamer(requests, **md, &blk) unless return_op @@ -405,10 +435,17 @@ module GRPC # @param method [string] the method being called. # @param marshal [Function] f(obj)->string that marshals requests # @param unmarshal [Function] f(string)->obj that unmarshals responses + # @param parent [Grpc::Call] a parent call, available when calls are + # made from server # @param timeout [TimeConst] - def new_active_call(method, marshal, unmarshal, timeout = nil) + def new_active_call(method, marshal, unmarshal, timeout = nil, parent: nil) deadline = from_relative_time(timeout.nil? ? @timeout : timeout) - call = @ch.create_call(@queue, method, @host, deadline) + call = @ch.create_call(@queue, + parent, # parent call + @propagate_mask, # propagation options + method, + nil, # host use nil, + deadline) ActiveCall.new(call, @queue, marshal, unmarshal, deadline, started: false) end end diff --git a/src/ruby/spec/call_spec.rb b/src/ruby/spec/call_spec.rb index 4977c10a7e..3c5d33ffcd 100644 --- a/src/ruby/spec/call_spec.rb +++ b/src/ruby/spec/call_spec.rb @@ -137,7 +137,7 @@ describe GRPC::Core::Call do end def make_test_call - @ch.create_call(client_queue, 'dummy_method', 'dummy_host', deadline) + @ch.create_call(client_queue, nil, nil, 'dummy_method', nil, deadline) end def deadline diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb index d471ff5db6..25cefcdfb7 100644 --- a/src/ruby/spec/channel_spec.rb +++ b/src/ruby/spec/channel_spec.rb @@ -117,7 +117,7 @@ describe GRPC::Core::Channel do deadline = Time.now + 5 blk = proc do - ch.create_call(cq, 'dummy_method', 'dummy_host', deadline) + ch.create_call(cq, nil, nil, 'dummy_method', nil, deadline) end expect(&blk).to_not raise_error end @@ -128,7 +128,7 @@ describe GRPC::Core::Channel do deadline = Time.now + 5 blk = proc do - ch.create_call(cq, 'dummy_method', 'dummy_host', deadline) + ch.create_call(cq, nil, nil, 'dummy_method', nil, deadline) end expect(&blk).to raise_error(RuntimeError) end diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb index 0e85441209..2e673ff413 100644 --- a/src/ruby/spec/client_server_spec.rb +++ b/src/ruby/spec/client_server_spec.rb @@ -61,7 +61,7 @@ shared_context 'setup: tags' do end def new_client_call - @ch.create_call(@client_queue, '/method', 'foo.test.google.fr', deadline) + @ch.create_call(@client_queue, nil, nil, '/method', nil, deadline) end end @@ -69,6 +69,23 @@ shared_examples 'basic GRPC message delivery is OK' do include GRPC::Core include_context 'setup: tags' + context 'the test channel' do + it 'should have a target' do + expect(@ch.target).to be_a(String) + end + end + + context 'a client call' do + it 'should have a peer' do + expect(new_client_call.peer).to be_a(String) + end + end + + it 'calls have peer info' do + call = new_client_call + expect(call.peer).to be_a(String) + end + it 'servers receive requests from clients and can respond' do call = new_client_call server_call = nil diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb index bc3bee3d44..0bf65ba2e9 100644 --- a/src/ruby/spec/generic/active_call_spec.rb +++ b/src/ruby/spec/generic/active_call_spec.rb @@ -338,7 +338,7 @@ describe GRPC::ActiveCall do end def make_test_call - @ch.create_call(@client_queue, '/method', 'a.dummy.host', deadline) + @ch.create_call(@client_queue, nil, nil, '/method', nil, deadline) end def deadline |