diff --git a/binding.gyp b/binding.gyp index 52e0ea6c394..32e44dd61a0 100644 --- a/binding.gyp +++ b/binding.gyp @@ -346,11 +346,12 @@ "src/node/ext/node_grpc.cc", "src/node/ext/server.cc", "src/node/ext/server_credentials.cc", - "src/node/ext/timeval.cc" + "src/node/ext/timeval.cc", ], "dependencies": [ - "grpc" + "grpc", + "gpr", ] - } + }, ] } diff --git a/build.yaml b/build.yaml index 873606f9806..38dce3ce0d7 100644 --- a/build.yaml +++ b/build.yaml @@ -2232,3 +2232,19 @@ vspackages: props: false redist: false version: 1.7.0.1 +node_modules: +- deps: + - grpc + - gpr + name: grpc_node + src: + - src/node/ext/byte_buffer.cc + - src/node/ext/call.cc + - src/node/ext/call_credentials.cc + - src/node/ext/channel.cc + - src/node/ext/channel_credentials.cc + - src/node/ext/completion_queue_async_worker.cc + - src/node/ext/node_grpc.cc + - src/node/ext/server.cc + - src/node/ext/server_credentials.cc + - src/node/ext/timeval.cc diff --git a/doc/PROTOCOL-HTTP2.md b/doc/PROTOCOL-HTTP2.md index 4c933623503..7a26724c85d 100644 --- a/doc/PROTOCOL-HTTP2.md +++ b/doc/PROTOCOL-HTTP2.md @@ -66,9 +66,9 @@ Base64-encoded values. **ASCII-Value** should not have leading or trailing whitespace. If it contains leading or trailing whitespace, it may be stripped. The **ASCII-Value** character range defined is more strict than HTTP. Implementations must not error -due to receiving an invalid **ASCII-Value** but value valid in HTTP, but the -precise behavior is not strictly defined: they may throw the value away or -accept the value. If accepted, care must be taken to make sure that the +due to receiving an invalid **ASCII-Value** that's a valid **field-value** in +HTTP, but the precise behavior is not strictly defined: they may throw the value +away or accept the value. If accepted, care must be taken to make sure that the application is permitted to echo the value back as metadata. For example, if the metadata is provided to the application as a list in a request, the application should not trigger an error by providing that same list as the metadata in the diff --git a/doc/interop-test-descriptions.md b/doc/interop-test-descriptions.md index 8649213bc6a..53cc47b1f83 100644 --- a/doc/interop-test-descriptions.md +++ b/doc/interop-test-descriptions.md @@ -4,7 +4,7 @@ Interoperability Test Case Descriptions Client and server use [test.proto](https://github.com/grpc/grpc/blob/master/test/proto/test.proto) and the [gRPC over HTTP/2 v2 -protocol](doc/PROTOCOL-HTTP2.md). +protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md). Client ------ diff --git a/examples/node/README.md b/examples/node/README.md index df681e85dd7..09c56f7fa6f 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -4,7 +4,7 @@ gRPC in 3 minutes (Node.js) PREREQUISITES ------------- -- `node`: This requires Node 10.x or greater. +- `node`: This requires Node 0.10.x or greater. - [homebrew][] on Mac OS X. This simplifies the installation of the gRPC C core. INSTALL diff --git a/examples/python/helloworld/greeter_server.py b/examples/python/helloworld/greeter_server.py index 1514d8f270b..2cde5add435 100644 --- a/examples/python/helloworld/greeter_server.py +++ b/examples/python/helloworld/greeter_server.py @@ -50,7 +50,7 @@ def serve(): while True: time.sleep(_ONE_DAY_IN_SECONDS) except KeyboardInterrupt: - server.stop() + server.stop(0) if __name__ == '__main__': serve() diff --git a/include/grpc++/client_context.h b/include/grpc++/client_context.h index 7046f939e5e..82d97bd1ae0 100644 --- a/include/grpc++/client_context.h +++ b/include/grpc++/client_context.h @@ -53,15 +53,16 @@ #include #include -#include -#include -#include -#include +#include #include #include #include #include #include +#include +#include +#include +#include struct census_context; @@ -315,7 +316,9 @@ class ClientContext { bool initial_metadata_received_; std::shared_ptr channel_; + grpc::mutex mu_; grpc_call* call_; + bool call_canceled_; gpr_timespec deadline_; grpc::string authority_; std::shared_ptr creds_; diff --git a/include/grpc++/support/channel_arguments.h b/include/grpc++/support/channel_arguments.h index 9957712a965..4da76a83edc 100644 --- a/include/grpc++/support/channel_arguments.h +++ b/include/grpc++/support/channel_arguments.h @@ -70,7 +70,8 @@ class ChannelArguments { void SetChannelArgs(grpc_channel_args* channel_args) const; // gRPC specific channel argument setters - /// Set target name override for SSL host name checking. + /// Set target name override for SSL host name checking. This option is for + /// testing only and should never be used in production. void SetSslTargetNameOverride(const grpc::string& name); // TODO(yangg) add flow control options /// Set the compression algorithm for the channel. diff --git a/include/grpc++/support/time.h b/include/grpc++/support/time.h index 2d4196b93b7..e00e0d8e917 100644 --- a/include/grpc++/support/time.h +++ b/include/grpc++/support/time.h @@ -35,6 +35,7 @@ #define GRPCXX_SUPPORT_TIME_H #include +#include namespace grpc { diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc index 574656a7e9b..9bb358b2339 100644 --- a/src/cpp/client/client_context.cc +++ b/src/cpp/client/client_context.cc @@ -48,6 +48,7 @@ namespace grpc { ClientContext::ClientContext() : initial_metadata_received_(false), call_(nullptr), + call_canceled_(false), deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)), propagate_from_call_(nullptr) {} @@ -72,6 +73,7 @@ void ClientContext::AddMetadata(const grpc::string& meta_key, void ClientContext::set_call(grpc_call* call, const std::shared_ptr& channel) { + grpc::unique_lock lock(mu_); GPR_ASSERT(call_ == nullptr); call_ = call; channel_ = channel; @@ -79,6 +81,9 @@ void ClientContext::set_call(grpc_call* call, grpc_call_cancel_with_status(call, GRPC_STATUS_CANCELLED, "Failed to set credentials to rpc.", nullptr); } + if (call_canceled_) { + grpc_call_cancel(call_, nullptr); + } } void ClientContext::set_compression_algorithm( @@ -101,8 +106,11 @@ std::shared_ptr ClientContext::auth_context() const { } void ClientContext::TryCancel() { + grpc::unique_lock lock(mu_); if (call_) { grpc_call_cancel(call_, nullptr); + } else { + call_canceled_ = true; } } diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc index 584a0cf8abf..c11734d7374 100644 --- a/src/node/ext/channel.cc +++ b/src/node/ext/channel.cc @@ -82,7 +82,7 @@ bool ParseChannelArgs(Local args_val, return false; } grpc_channel_args *channel_args = reinterpret_cast( - malloc(sizeof(channel_args))); + malloc(sizeof(grpc_channel_args))); *channel_args_ptr = channel_args; Local args_hash = Nan::To(args_val).ToLocalChecked(); Local keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked(); diff --git a/src/objective-c/examples/Sample/Podfile b/src/objective-c/examples/Sample/Podfile index 72308c16192..3b2f4125694 100644 --- a/src/objective-c/examples/Sample/Podfile +++ b/src/objective-c/examples/Sample/Podfile @@ -1,8 +1,9 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' +pod 'Protobuf', :path => "../../../../third_party/protobuf" pod 'gRPC', :path => "../../../.." -pod 'RemoteTest', :path => "../../generated_libraries/RemoteTestClient" +pod 'RemoteTest', :path => "../RemoteTestClient" target 'Sample' do end diff --git a/src/objective-c/examples/Sample/Sample/ViewController.m b/src/objective-c/examples/Sample/Sample/ViewController.m index 05bd6fa2dbc..3d634a340df 100644 --- a/src/objective-c/examples/Sample/Sample/ViewController.m +++ b/src/objective-c/examples/Sample/Sample/ViewController.m @@ -34,7 +34,7 @@ #import "ViewController.h" #import -#import +#import #import #import #import @@ -66,14 +66,14 @@ // Same example call using the generic gRPC client library: - GRPCMethodName *method = [[GRPCMethodName alloc] initWithPackage:@"grpc.testing" - interface:@"TestService" - method:@"UnaryCall"]; + ProtoMethod *method = [[ProtoMethod alloc] initWithPackage:@"grpc.testing" + service:@"TestService" + method:@"UnaryCall"]; - id requestsWriter = [GRXWriter writerWithValue:[request data]]; + GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteHost - method:method + path:method.HTTPPath requestsWriter:requestsWriter]; id responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { diff --git a/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec b/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec deleted file mode 100644 index 23ccffe69da..00000000000 --- a/src/objective-c/generated_libraries/RouteGuideClient/RouteGuide.podspec +++ /dev/null @@ -1,31 +0,0 @@ -Pod::Spec.new do |s| - s.name = "RouteGuide" - s.version = "0.0.1" - s.license = "New BSD" - - s.ios.deployment_target = "6.0" - s.osx.deployment_target = "10.8" - - # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients. - s.prepare_command = <<-CMD - 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| - ms.source_files = "*.pbobjc.{h,m}" - ms.header_mappings_dir = "." - ms.requires_arc = false - ms.dependency "Protobuf", "~> 3.0.0-alpha-3" - end - - s.subspec "Services" do |ss| - ss.source_files = "*.pbrpc.{h,m}" - ss.header_mappings_dir = "." - ss.requires_arc = true - ss.dependency "gRPC", "~> 0.5" - ss.dependency "#{s.name}/Messages" - end -end diff --git a/src/objective-c/generated_libraries/RouteGuideClient/route_guide.proto b/src/objective-c/generated_libraries/RouteGuideClient/route_guide.proto deleted file mode 100644 index 19592e2ebdd..00000000000 --- a/src/objective-c/generated_libraries/RouteGuideClient/route_guide.proto +++ /dev/null @@ -1,120 +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. - -syntax = "proto3"; - -package routeguide; - -option objc_class_prefix = "RGD"; - -// Interface exported by the server. -service RouteGuide { - // A simple RPC. - // - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} - - // A server-to-client streaming RPC. - // - // Obtains the Features available within the given Rectangle. Results are - // streamed rather than returned at once (e.g. in a response message with a - // repeated field), as the rectangle may cover a large area and contain a - // huge number of features. - rpc ListFeatures(Rectangle) returns (stream Feature) {} - - // A client-to-server streaming RPC. - // - // Accepts a stream of Points on a route being traversed, returning a - // RouteSummary when traversal is completed. - rpc RecordRoute(stream Point) returns (RouteSummary) {} - - // A Bidirectional streaming RPC. - // - // Accepts a stream of RouteNotes sent while a route is being traversed, - // while receiving other RouteNotes (e.g. from other users). - rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} -} - -// Points are represented as latitude-longitude pairs in the E7 representation -// (degrees multiplied by 10**7 and rounded to the nearest integer). -// Latitudes should be in the range +/- 90 degrees and longitude should be in -// the range +/- 180 degrees (inclusive). -message Point { - int32 latitude = 1; - int32 longitude = 2; -} - -// A latitude-longitude rectangle, represented as two diagonally opposite -// points "lo" and "hi". -message Rectangle { - // One corner of the rectangle. - Point lo = 1; - - // The other corner of the rectangle. - Point hi = 2; -} - -// A feature names something at a given point. -// -// If a feature could not be named, the name is empty. -message Feature { - // The name of the feature. - string name = 1; - - // The point where the feature is detected. - Point location = 2; -} - -// A RouteNote is a message sent while at a given point. -message RouteNote { - // The location from which the message is sent. - Point location = 1; - - // The message to be sent. - string message = 2; -} - -// A RouteSummary is received in response to a RecordRoute rpc. -// -// It contains the number of individual points received, the number of -// detected features, and the total distance covered as the cumulative sum of -// the distance between each point. -message RouteSummary { - // The number of points received. - int32 point_count = 1; - - // The number of known features passed while traversing the route. - int32 feature_count = 2; - - // The distance covered in metres. - int32 distance = 3; - - // The duration of the traversal in seconds. - int32 elapsed_time = 4; -} diff --git a/src/objective-c/tests/Podfile b/src/objective-c/tests/Podfile index 2a9b894cf63..cab608d37fe 100644 --- a/src/objective-c/tests/Podfile +++ b/src/objective-c/tests/Podfile @@ -3,8 +3,7 @@ platform :ios, '8.0' pod 'Protobuf', :path => "../../../third_party/protobuf" pod 'gRPC', :path => "../../.." -pod 'RemoteTest', :path => "../generated_libraries/RemoteTestClient" -pod 'RouteGuide', :path => "../generated_libraries/RouteGuideClient" +pod 'RemoteTest', :path => "RemoteTestClient" link_with 'AllTests', 'RxLibraryUnitTests', diff --git a/src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec b/src/objective-c/tests/RemoteTestClient/RemoteTest.podspec similarity index 100% rename from src/objective-c/generated_libraries/RemoteTestClient/RemoteTest.podspec rename to src/objective-c/tests/RemoteTestClient/RemoteTest.podspec diff --git a/src/objective-c/generated_libraries/RemoteTestClient/empty.proto b/src/objective-c/tests/RemoteTestClient/empty.proto similarity index 100% rename from src/objective-c/generated_libraries/RemoteTestClient/empty.proto rename to src/objective-c/tests/RemoteTestClient/empty.proto diff --git a/src/objective-c/generated_libraries/RemoteTestClient/messages.proto b/src/objective-c/tests/RemoteTestClient/messages.proto similarity index 100% rename from src/objective-c/generated_libraries/RemoteTestClient/messages.proto rename to src/objective-c/tests/RemoteTestClient/messages.proto diff --git a/src/objective-c/generated_libraries/RemoteTestClient/test.proto b/src/objective-c/tests/RemoteTestClient/test.proto similarity index 100% rename from src/objective-c/generated_libraries/RemoteTestClient/test.proto rename to src/objective-c/tests/RemoteTestClient/test.proto diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php index c26be607ffc..aa4de349ea7 100755 --- a/src/php/lib/Grpc/BaseStub.php +++ b/src/php/lib/Grpc/BaseStub.php @@ -51,6 +51,7 @@ class BaseStub * @param $opts array * - 'update_metadata': (optional) a callback function which takes in a * metadata array, and returns an updated metadata array + * - 'grpc.primary_user_agent': (optional) a user-agent string */ public function __construct($hostname, $opts) { @@ -64,7 +65,12 @@ class BaseStub } $package_config = json_decode( file_get_contents(dirname(__FILE__).'/../../composer.json'), true); - $opts['grpc.primary_user_agent'] = + if (!empty($opts['grpc.primary_user_agent'])) { + $opts['grpc.primary_user_agent'] .= ' '; + } else { + $opts['grpc.primary_user_agent'] = ''; + } + $opts['grpc.primary_user_agent'] .= 'grpc-php/'.$package_config['version']; $this->channel = new Channel($hostname, $opts); } diff --git a/src/python/grpcio/grpc/framework/interfaces/face/face.py b/src/python/grpcio/grpc/framework/interfaces/face/face.py index bc9a434a760..3b402356d2d 100644 --- a/src/python/grpcio/grpc/framework/interfaces/face/face.py +++ b/src/python/grpcio/grpc/framework/interfaces/face/face.py @@ -117,6 +117,10 @@ class AbortionError(Exception): self.code = code self.details = details + def __str__(self): + return '%s(code=%s, details="%s")' % ( + self.__class__.__name__, self.code, self.details) + class CancellationError(AbortionError): """Indicates that an RPC has been cancelled.""" diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c index 6b5beb6f5d7..40364328ee8 100644 --- a/src/ruby/ext/grpc/rb_call.c +++ b/src/ruby/ext/grpc/rb_call.c @@ -139,7 +139,15 @@ static const rb_data_type_t grpc_rb_md_ary_data_type = { {NULL, NULL}}, NULL, NULL, - 0}; +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + /* it is unsafe to specify RUBY_TYPED_FREE_IMMEDIATELY because + * grpc_rb_call_destroy + * touches a hash object. + * TODO(yugui) Directly use st_table and call the free function earlier? + */ + 0, +#endif +}; /* Describes grpc_call struct for RTypedData */ static const rb_data_type_t grpc_call_data_type = { @@ -148,12 +156,15 @@ static const rb_data_type_t grpc_call_data_type = { {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY /* it is unsafe to specify RUBY_TYPED_FREE_IMMEDIATELY because * grpc_rb_call_destroy * touches a hash object. * TODO(yugui) Directly use st_table and call the free function earlier? */ - 0}; + 0, +#endif +}; /* Error code details is a hash containing text strings describing errors */ VALUE rb_error_code_details; diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c index 90afdc3fe11..cd0b966e726 100644 --- a/src/ruby/ext/grpc/rb_channel.c +++ b/src/ruby/ext/grpc/rb_channel.c @@ -111,7 +111,9 @@ static rb_data_type_t grpc_channel_data_type = { {grpc_rb_channel_mark, grpc_rb_channel_free, GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY RUBY_TYPED_FREE_IMMEDIATELY +#endif }; /* Allocates grpc_rb_channel instances. */ diff --git a/src/ruby/ext/grpc/rb_channel_args.c b/src/ruby/ext/grpc/rb_channel_args.c index 1ba30b69aa2..37dd981925a 100644 --- a/src/ruby/ext/grpc/rb_channel_args.c +++ b/src/ruby/ext/grpc/rb_channel_args.c @@ -44,7 +44,9 @@ static rb_data_type_t grpc_rb_channel_args_data_type = { {GRPC_RB_GC_NOT_MARKED, GRPC_RB_GC_DONT_FREE, GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY RUBY_TYPED_FREE_IMMEDIATELY +#endif }; /* A callback the processes the hash key values in channel_args hash */ diff --git a/src/ruby/ext/grpc/rb_completion_queue.c b/src/ruby/ext/grpc/rb_completion_queue.c index 0bc9eb2a97a..a7de96d7189 100644 --- a/src/ruby/ext/grpc/rb_completion_queue.c +++ b/src/ruby/ext/grpc/rb_completion_queue.c @@ -121,9 +121,11 @@ static rb_data_type_t grpc_rb_completion_queue_data_type = { {GRPC_RB_GC_NOT_MARKED, grpc_rb_completion_queue_destroy, GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY /* cannot immediately free because grpc_rb_completion_queue_shutdown_drain * calls rb_thread_call_without_gvl. */ - 0 + 0, +#endif }; /* Allocates a completion queue. */ diff --git a/src/ruby/ext/grpc/rb_credentials.c b/src/ruby/ext/grpc/rb_credentials.c index ae757f69866..486ff79f910 100644 --- a/src/ruby/ext/grpc/rb_credentials.c +++ b/src/ruby/ext/grpc/rb_credentials.c @@ -92,7 +92,10 @@ static rb_data_type_t grpc_rb_credentials_data_type = { GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, - RUBY_TYPED_FREE_IMMEDIATELY}; +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY +#endif +}; /* Allocates Credential instances. Provides safe initial defaults for the instance fields. */ diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c index 327fd1a4fc1..33f48779d84 100644 --- a/src/ruby/ext/grpc/rb_grpc.c +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -55,7 +55,10 @@ static rb_data_type_t grpc_rb_timespec_data_type = { {NULL, NULL}}, NULL, NULL, - RUBY_TYPED_FREE_IMMEDIATELY}; +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY +#endif +}; /* Alloc func that blocks allocation of a given object by raising an * exception. */ @@ -262,10 +265,20 @@ static void Init_grpc_time_consts() { id_tv_nsec = rb_intern("tv_nsec"); } +/* + TODO: find an alternative to ruby_vm_at_exit that is ok in Ruby 2.0 where + RUBY_TYPED_FREE_IMMEDIATELY is not defined. + + At the moment, registering a function using ruby_vm_at_exit segfaults in Ruby + 2.0. This is not an issue with the gRPC handler. More likely, this was an + in issue with 2.0 that got resolved in 2.1 and has not been backported. +*/ +#ifdef RUBY_TYPED_FREE_IMMEDIATELY static void grpc_rb_shutdown(ruby_vm_t *vm) { (void)vm; grpc_shutdown(); } +#endif /* Initialize the GRPC module structs */ @@ -285,7 +298,12 @@ VALUE sym_metadata = Qundef; void Init_grpc() { grpc_init(); + +/* TODO: find alternative to ruby_vm_at_exit that is ok in Ruby 2.0 */ +#ifdef RUBY_TYPED_FREE_IMMEDIATELY ruby_vm_at_exit(grpc_rb_shutdown); +#endif + grpc_rb_mGRPC = rb_define_module("GRPC"); grpc_rb_mGrpcCore = rb_define_module_under(grpc_rb_mGRPC, "Core"); grpc_rb_sNewServerRpc = diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c index 44696588696..ebdd7e1a346 100644 --- a/src/ruby/ext/grpc/rb_server.c +++ b/src/ruby/ext/grpc/rb_server.c @@ -101,11 +101,14 @@ static const rb_data_type_t grpc_rb_server_data_type = { {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY /* It is unsafe to specify RUBY_TYPED_FREE_IMMEDIATELY because the free function would block * and we might want to unlock GVL * TODO(yugui) Unlock GVL? */ - 0}; + 0, +#endif +}; /* Allocates grpc_rb_server instances. */ static VALUE grpc_rb_server_alloc(VALUE cls) { diff --git a/src/ruby/ext/grpc/rb_server_credentials.c b/src/ruby/ext/grpc/rb_server_credentials.c index ea4d0d864ec..de57585e0ba 100644 --- a/src/ruby/ext/grpc/rb_server_credentials.c +++ b/src/ruby/ext/grpc/rb_server_credentials.c @@ -91,7 +91,9 @@ static const rb_data_type_t grpc_rb_server_credentials_data_type = { {grpc_rb_server_credentials_mark, grpc_rb_server_credentials_free, GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY RUBY_TYPED_FREE_IMMEDIATELY +#endif }; /* Allocates ServerCredential instances. diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb index d9cb9247356..e80d24edc9b 100644 --- a/src/ruby/lib/grpc/generic/active_call.rb +++ b/src/ruby/lib/grpc/generic/active_call.rb @@ -199,11 +199,7 @@ module GRPC # marshalled. def remote_send(req, marshalled = false) GRPC.logger.debug("sending #{req}, marshalled? #{marshalled}") - if marshalled - payload = req - else - payload = @marshal.call(req) - end + payload = marshalled ? req : @marshal.call(req) @call.run_batch(@cq, self, INFINITE_FUTURE, SEND_MESSAGE => payload) end @@ -417,7 +413,9 @@ module GRPC # @return [Enumerator, nil] a response Enumerator def bidi_streamer(requests, **kw, &blk) start_call(**kw) unless @started - bd = BidiCall.new(@call, @cq, @marshal, @unmarshal) + bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, + metadata_tag: @metadata_tag) + @metadata_tag = nil # run_on_client ensures metadata is read bd.run_on_client(requests, @op_notifier, &blk) end diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb index 9dbbb74caff..6b9b7856933 100644 --- a/src/ruby/lib/grpc/generic/bidi_call.rb +++ b/src/ruby/lib/grpc/generic/bidi_call.rb @@ -56,7 +56,8 @@ module GRPC # the call # @param marshal [Function] f(obj)->string that marshal requests # @param unmarshal [Function] f(string)->obj that unmarshals responses - def initialize(call, q, marshal, unmarshal) + # @param metadata_tag [Object] tag object used to collect metadata + def initialize(call, q, marshal, unmarshal, metadata_tag: nil) fail(ArgumentError, 'not a call') unless call.is_a? Core::Call unless q.is_a? Core::CompletionQueue fail(ArgumentError, 'not a CompletionQueue') @@ -67,6 +68,7 @@ module GRPC @op_notifier = nil # signals completion on clients @readq = Queue.new @unmarshal = unmarshal + @metadata_tag = metadata_tag end # Begins orchestration of the Bidi stream for a client sending requests. @@ -113,6 +115,18 @@ module GRPC @op_notifier.notify(self) end + # performs a read using @call.run_batch, ensures metadata is set up + def read_using_run_batch + ops = { RECV_MESSAGE => nil } + ops[RECV_INITIAL_METADATA] = nil unless @metadata_tag.nil? + batch_result = @call.run_batch(@cq, self, INFINITE_FUTURE, ops) + unless @metadata_tag.nil? + @call.metadata = batch_result.metadata + @metadata_tag = nil + end + batch_result + end + # each_queued_msg yields each message on this instances readq # # - messages are added to the readq by #read_loop @@ -169,9 +183,7 @@ module GRPC loop do GRPC.logger.debug("bidi-read-loop: #{count}") count += 1 - # TODO: ensure metadata is read if available, currently it's not - batch_result = @call.run_batch(@cq, read_tag, INFINITE_FUTURE, - RECV_MESSAGE => nil) + batch_result = read_using_run_batch # handle the next message if batch_result.message.nil? diff --git a/src/ruby/lib/grpc/generic/rpc_server.rb b/src/ruby/lib/grpc/generic/rpc_server.rb index 228c500672b..0e318bd53b8 100644 --- a/src/ruby/lib/grpc/generic/rpc_server.rb +++ b/src/ruby/lib/grpc/generic/rpc_server.rb @@ -418,11 +418,11 @@ module GRPC an_rpc = @server.request_call(@cq, loop_tag, INFINITE_FUTURE) break if (!an_rpc.nil?) && an_rpc.call.nil? - c = new_active_server_call(an_rpc) - unless c.nil? - mth = an_rpc.method.to_sym - @pool.schedule(c) do |call| - rpc_descs[mth].run_server_method(call, rpc_handlers[mth]) + active_call = new_active_server_call(an_rpc) + unless active_call.nil? + @pool.schedule(active_call) do |ac| + c, mth = ac + rpc_descs[mth].run_server_method(c, rpc_handlers[mth]) end end rescue Core::CallError, RuntimeError => e @@ -442,6 +442,7 @@ module GRPC # allow the metadata to be accessed from the call handle_call_tag = Object.new an_rpc.call.metadata = an_rpc.metadata # attaches md to call for handlers + GRPC.logger.debug("call md is #{an_rpc.metadata}") connect_md = nil unless @connect_md_proc.nil? connect_md = @connect_md_proc.call(an_rpc.method, an_rpc.metadata) @@ -454,9 +455,11 @@ module GRPC # Create the ActiveCall GRPC.logger.info("deadline is #{an_rpc.deadline}; (now=#{Time.now})") rpc_desc = rpc_descs[an_rpc.method.to_sym] - ActiveCall.new(an_rpc.call, @cq, - rpc_desc.marshal_proc, rpc_desc.unmarshal_proc(:input), - an_rpc.deadline) + c = ActiveCall.new(an_rpc.call, @cq, + rpc_desc.marshal_proc, rpc_desc.unmarshal_proc(:input), + an_rpc.deadline) + mth = an_rpc.method.to_sym + [c, mth] end protected diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb index 1388685734a..b84cd430906 100755 --- a/src/ruby/pb/test/client.rb +++ b/src/ruby/pb/test/client.rb @@ -46,6 +46,7 @@ $LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir) $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) require 'optparse' +require 'logger' require 'grpc' require 'googleauth' @@ -59,6 +60,22 @@ require 'signet/ssl_config' AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR +# RubyLogger defines a logger for gRPC based on the standard ruby logger. +module RubyLogger + def logger + LOGGER + end + + LOGGER = Logger.new(STDOUT) + LOGGER.level = Logger::INFO +end + +# GRPC is the general RPC module +module GRPC + # Inject the noop #logger if no module-level logger method has been injected. + extend RubyLogger +end + # AssertionError is use to indicate interop test failures. class AssertionError < RuntimeError; end diff --git a/src/ruby/pb/test/server.rb b/src/ruby/pb/test/server.rb index 25c1b1e9e64..67877a191fc 100755 --- a/src/ruby/pb/test/server.rb +++ b/src/ruby/pb/test/server.rb @@ -45,6 +45,7 @@ $LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir) $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) require 'forwardable' +require 'logger' require 'optparse' require 'grpc' @@ -53,6 +54,60 @@ require 'test/proto/empty' require 'test/proto/messages' require 'test/proto/test_services' +# DebugIsTruncated extends the default Logger to truncate debug messages +class DebugIsTruncated < Logger + def debug(s) + super(truncate(s, 1024)) + end + + # Truncates a given +text+ after a given length if +text+ is longer than length: + # + # 'Once upon a time in a world far far away'.truncate(27) + # # => "Once upon a time in a wo..." + # + # Pass a string or regexp :separator to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) + # # => "Once upon a time in a..." + # + # The last characters will be replaced with the :omission string (defaults to "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(s, truncate_at, options = {}) + return s unless s.length > truncate_at + omission = options[:omission] || '...' + with_extra_room = truncate_at - omission.length + stop = \ + if options[:separator] + rindex(options[:separator], with_extra_room) || with_extra_room + else + with_extra_room + end + "#{s[0, stop]}#{omission}" + end +end + +# RubyLogger defines a logger for gRPC based on the standard ruby logger. +module RubyLogger + def logger + LOGGER + end + + LOGGER = DebugIsTruncated.new(STDOUT) + LOGGER.level = Logger::WARN +end + +# GRPC is the general RPC module +module GRPC + # Inject the noop #logger if no module-level logger method has been injected. + extend RubyLogger +end + # loads the certificates by the test server. def load_test_certs this_dir = File.expand_path(File.dirname(__FILE__)) @@ -113,7 +168,7 @@ class TestTarget < Grpc::Testing::TestService::Service def streaming_input_call(call) sizes = call.each_remote_read.map { |x| x.payload.body.length } - sum = sizes.inject { |s, x| s + x } + sum = sizes.inject(0) { |s, x| s + x } StreamingInputCallResponse.new(aggregated_payload_size: sum) end diff --git a/src/ruby/spec/pb/health/checker_spec.rb b/src/ruby/spec/pb/health/checker_spec.rb index 9bc82638c75..322566b7842 100644 --- a/src/ruby/spec/pb/health/checker_spec.rb +++ b/src/ruby/spec/pb/health/checker_spec.rb @@ -31,6 +31,7 @@ require 'grpc' require 'grpc/health/v1alpha/health' require 'grpc/health/checker' require 'open3' +require 'tmpdir' def can_run_codegen_check system('which grpc_ruby_plugin') && system('which protoc') diff --git a/templates/binding.gyp.template b/templates/binding.gyp.template index 52070cf6e9e..be80750eb7f 100644 --- a/templates/binding.gyp.template +++ b/templates/binding.gyp.template @@ -89,8 +89,9 @@ ] }, 'targets': [ + % for module in node_modules: % for lib in libs: - % if lib.name == 'gpr' or lib.name == 'grpc': + % if lib.name in module.transitive_deps: { 'target_name': '${lib.name}', 'product_prefix': 'lib', @@ -142,22 +143,18 @@ } }] ], - "target_name": "grpc_node", + "target_name": "${module.name}", "sources": [ - "src/node/ext/byte_buffer.cc", - "src/node/ext/call.cc", - "src/node/ext/call_credentials.cc", - "src/node/ext/channel.cc", - "src/node/ext/channel_credentials.cc", - "src/node/ext/completion_queue_async_worker.cc", - "src/node/ext/node_grpc.cc", - "src/node/ext/server.cc", - "src/node/ext/server_credentials.cc", - "src/node/ext/timeval.cc" + % for source in module.src: + "${source}", + % endfor ], "dependencies": [ - "grpc" + % for dep in getattr(module, 'deps', []): + "${dep}", + % endfor ] - } + }, + % endfor ] } diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc index 89a556c5877..7343b61fcab 100644 --- a/test/cpp/end2end/end2end_test.cc +++ b/test/cpp/end2end/end2end_test.cc @@ -575,6 +575,18 @@ void CancelRpc(ClientContext* context, int delay_us, TestServiceImpl* service) { context->TryCancel(); } +TEST_P(End2endTest, CancelRpcBeforeStart) { + ResetStub(); + EchoRequest request; + EchoResponse response; + ClientContext context; + request.set_message("hello"); + context.TryCancel(); + Status s = stub_->Echo(&context, request, &response); + EXPECT_EQ("", response.message()); + EXPECT_EQ(grpc::StatusCode::CANCELLED, s.error_code()); +} + // Client cancels request stream after sending two messages TEST_P(End2endTest, ClientCancelsRequestStream) { ResetStub(); diff --git a/test/cpp/interop/stress_test.cc b/test/cpp/interop/stress_test.cc index 5d1419728e4..018f4ab4f71 100644 --- a/test/cpp/interop/stress_test.cc +++ b/test/cpp/interop/stress_test.cc @@ -41,6 +41,7 @@ #include #include #include +#include #include "test/cpp/interop/interop_client.h" #include "test/cpp/interop/stress_interop_client.h" @@ -80,7 +81,6 @@ DEFINE_string(test_cases, "", using std::make_pair; using std::pair; -using std::thread; using std::vector; using grpc::testing::kTestCaseList; @@ -202,7 +202,7 @@ int main(int argc, char** argv) { gpr_log(GPR_INFO, "Starting test(s).."); - vector test_threads; + vector test_threads; int thread_idx = 0; for (auto it = server_addresses.begin(); it != server_addresses.end(); it++) { StressTestInteropClient* client = new StressTestInteropClient( @@ -210,7 +210,7 @@ int main(int argc, char** argv) { FLAGS_sleep_duration_ms); test_threads.emplace_back( - thread(&StressTestInteropClient::MainLoop, client)); + grpc::thread(&StressTestInteropClient::MainLoop, client)); } for (auto it = test_threads.begin(); it != test_threads.end(); it++) { diff --git a/tools/buildgen/plugins/transitive_dependencies.py b/tools/buildgen/plugins/transitive_dependencies.py new file mode 100644 index 00000000000..c2d3da3a3b2 --- /dev/null +++ b/tools/buildgen/plugins/transitive_dependencies.py @@ -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. + +"""Buildgen transitive dependencies + +This takes the list of libs, node_modules, and targets from our +yaml dictionary, and adds to each the transitive closure +of the list of dependencies. + +""" + +def get_lib(libs, name): + return next(lib for lib in libs if lib['name']==name) + +def transitive_deps(lib, libs): + if 'deps' in lib: + # Recursively call transitive_deps on each dependency, and take the union + return set.union(set(lib['deps']), + *[set(transitive_deps(get_lib(libs, dep), libs)) + for dep in lib['deps']]) + else: + return set() + +def mako_plugin(dictionary): + """The exported plugin code for transitive_dependencies. + + Each item in libs, node_modules, and targets can have a deps list. + We add a transitive_deps property to each with the transitive closure + of those dependency lists. + """ + libs = dictionary.get('libs') + node_modules = dictionary.get('node_modules') + targets = dictionary.get('targets') + + for target_list in (libs, node_modules, targets): + for target in target_list: + target['transitive_deps'] = transitive_deps(target, libs)