diff --git a/Makefile b/Makefile index b90ced4a2fc..60fbc5a05f5 100644 --- a/Makefile +++ b/Makefile @@ -262,6 +262,8 @@ LDFLAGS += -fPIC endif INCLUDES = . include $(GENDIR) +LDFLAGS += -Llibs/$(CONFIG) + ifeq ($(SYSTEM),Darwin) ifneq ($(wildcard /usr/local/ssl/include),) INCLUDES += /usr/local/ssl/include @@ -3075,15 +3077,15 @@ ifeq ($(SYSTEM),MINGW32) $(LIBDIR)/$(CONFIG)/grpc.$(SHARED_EXT): $(LIBGRPC_OBJS) $(ZLIB_DEP) $(LIBDIR)/$(CONFIG)/gpr.$(SHARED_EXT) $(OPENSSL_DEP) $(E) "[LD] Linking $@" $(Q) mkdir -p `dirname $@` - $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared -Wl,--output-def=$(LIBDIR)/$(CONFIG)/grpc.def -Wl,--out-implib=$(LIBDIR)/$(CONFIG)/libgrpc-imp.a -o $(LIBDIR)/$(CONFIG)/grpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr-imp + $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared -Wl,--output-def=$(LIBDIR)/$(CONFIG)/grpc.def -Wl,--out-implib=$(LIBDIR)/$(CONFIG)/libgrpc-imp.a -o $(LIBDIR)/$(CONFIG)/grpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) -lgpr-imp else $(LIBDIR)/$(CONFIG)/libgrpc.$(SHARED_EXT): $(LIBGRPC_OBJS) $(ZLIB_DEP) $(LIBDIR)/$(CONFIG)/libgpr.$(SHARED_EXT) $(OPENSSL_DEP) $(E) "[LD] Linking $@" $(Q) mkdir -p `dirname $@` ifeq ($(SYSTEM),Darwin) - $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -install_name libgrpc.$(SHARED_EXT) -dynamiclib -o $(LIBDIR)/$(CONFIG)/libgrpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr + $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -install_name libgrpc.$(SHARED_EXT) -dynamiclib -o $(LIBDIR)/$(CONFIG)/libgrpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) -lgpr else - $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared -Wl,-soname,libgrpc.so.0 -o $(LIBDIR)/$(CONFIG)/libgrpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS) -lgpr + $(Q) $(LD) $(LDFLAGS) -L$(LIBDIR)/$(CONFIG) -shared -Wl,-soname,libgrpc.so.0 -o $(LIBDIR)/$(CONFIG)/libgrpc.$(SHARED_EXT) $(LIBGRPC_OBJS) $(LDLIBS) $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE) -lgpr $(Q) ln -sf libgrpc.$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/libgrpc.so.0 $(Q) ln -sf libgrpc.$(SHARED_EXT) $(LIBDIR)/$(CONFIG)/libgrpc.so endif diff --git a/gRPC.podspec b/gRPC.podspec index 8429357a9e1..217d5ca81e4 100644 --- a/gRPC.podspec +++ b/gRPC.podspec @@ -1,13 +1,13 @@ Pod::Spec.new do |s| s.name = 'gRPC' - s.version = '0.0.1' - s.summary = 'Generic gRPC client library for iOS/OSX' - s.homepage = 'https://www.grpc.io' + s.version = '0.5.1' + s.summary = 'gRPC client library for iOS/OSX' + s.homepage = 'http://www.grpc.io' s.license = 'New BSD' - s.authors = { 'Jorge Canizales' => 'jcanizales@google.com', - 'Michael Lumish' => 'mlumish@google.com' } + s.authors = { 'The gRPC contributors' => 'grpc-packages@google.com' } - # s.source = { :git => 'https://github.com/grpc/grpc.git', :tag => 'release-0_5_0' } + # s.source = { :git => 'https://github.com/grpc/grpc.git', + # :tag => 'release-0_9_1-objectivec-0.5.1' } s.ios.deployment_target = '6.0' s.osx.deployment_target = '10.8' @@ -15,7 +15,6 @@ Pod::Spec.new do |s| s.subspec 'RxLibrary' do |rs| rs.summary = 'Reactive Extensions library for iOS.' - rs.authors = { 'Jorge Canizales' => 'jcanizales@google.com' } rs.source_files = 'src/objective-c/RxLibrary/*.{h,m}', 'src/objective-c/RxLibrary/transformations/*.{h,m}', @@ -25,16 +24,13 @@ Pod::Spec.new do |s| s.subspec 'C-Core' do |cs| cs.summary = 'Core cross-platform gRPC library, written in C.' - cs.authors = { 'Craig Tiller' => 'ctiller@google.com', - 'David Klempner' => 'klempner@google.com', - 'Nicolas Noble' => 'nnoble@google.com', - 'Vijay Pai' => 'vpai@google.com', - 'Yang Gao' => 'yangg@google.com' } cs.source_files = 'src/core/**/*.{h,c}', 'include/grpc/*.h', 'include/grpc/**/*.h' cs.private_header_files = 'src/core/**/*.h' cs.header_mappings_dir = '.' - cs.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Build/gRPC" "$(PODS_ROOT)/Headers/Build/gRPC/include"' } + cs.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Build/gRPC" ' + '"$(PODS_ROOT)/Headers/Build/gRPC/include"' } + cs.compiler_flags = '-GCC_WARN_INHIBIT_ALL_WARNINGS', '-w' cs.requires_arc = false cs.libraries = 'z' @@ -67,8 +63,6 @@ Pod::Spec.new do |s| s.subspec 'GRPCClient' do |gs| gs.summary = 'Objective-C wrapper around the core gRPC library.' - gs.authors = { 'Jorge Canizales' => 'jcanizales@google.com', - 'Michael Lumish' => 'mlumish@google.com' } gs.source_files = 'src/objective-c/GRPCClient/*.{h,m}', 'src/objective-c/GRPCClient/private/*.{h,m}' @@ -85,7 +79,6 @@ Pod::Spec.new do |s| s.subspec 'ProtoRPC' do |ps| ps.summary = 'RPC library for ProtocolBuffers, based on gRPC' - ps.authors = { 'Jorge Canizales' => 'jcanizales@google.com' } ps.source_files = 'src/objective-c/ProtoRPC/*.{h,m}' diff --git a/include/grpc++/server_context.h b/include/grpc++/server_context.h index a62babd9316..d88a3ae2627 100644 --- a/include/grpc++/server_context.h +++ b/include/grpc++/server_context.h @@ -106,6 +106,10 @@ class ServerContext { template friend class ::grpc::ServerReaderWriter; + // Prevent copying. + ServerContext(const ServerContext&); + ServerContext& operator=(const ServerContext&); + class CompletionOp; void BeginCompletionOp(Call* call); diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m index c350f32f2ac..e14e503ae0a 100644 --- a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m +++ b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m @@ -35,21 +35,90 @@ #include +#pragma mark Category for binary metadata elements + +@interface NSData (GRPCMetadata) ++ (instancetype)grpc_dataFromMetadataValue:(grpc_metadata *)metadata; + +// Fill a metadata object with the binary value in this NSData and the given key. +- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key; +@end + +@implementation NSData (GRPCMetadata) ++ (instancetype)grpc_dataFromMetadataValue:(grpc_metadata *)metadata { + // TODO(jcanizales): Should we use a non-copy constructor? + return [self dataWithBytes:metadata->value length:metadata->value_length]; +} + +- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key { + // TODO(jcanizales): Encode Unicode chars as ASCII. + metadata->key = [key stringByAppendingString:@"-bin"].UTF8String; + metadata->value = self.bytes; + metadata->value_length = self.length; +} +@end + +#pragma mark Category for textual metadata elements + +@interface NSString (GRPCMetadata) ++ (instancetype)grpc_stringFromMetadataValue:(grpc_metadata *)metadata; + +// Fill a metadata object with the textual value in this NSString and the given key. +- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key; +@end + +@implementation NSString (GRPCMetadata) ++ (instancetype)grpc_stringFromMetadataValue:(grpc_metadata *)metadata { + return [[self alloc] initWithBytes:metadata->value + length:metadata->value_length + encoding:NSASCIIStringEncoding]; +} + +- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key { + if ([key hasSuffix:@"-bin"]) { + // Disallow this, as at best it will confuse the server. If the app really needs to send a + // textual header with a name ending in "-bin", it can be done by removing the suffix and + // encoding the NSString as a NSData object. + // + // Why raise an exception: In the most common case, the developer knows this won't happen in + // their code, so the exception isn't triggered. In the rare cases when the developer can't + // tell, it's easy enough to add a sanitizing filter before the header is set. There, the + // developer can choose whether to drop such a header, or trim its name. Doing either ourselves, + // silently, would be very unintuitive for the user. + [NSException raise:NSInvalidArgumentException + format:@"Metadata keys ending in '-bin' are reserved for NSData values."]; + } + // TODO(jcanizales): Encode Unicode chars as ASCII. + metadata->key = key.UTF8String; + metadata->value = self.UTF8String; + metadata->value_length = self.length; +} +@end + +#pragma mark Category for metadata arrays + @implementation NSDictionary (GRPC) + (instancetype)grpc_dictionaryFromMetadata:(grpc_metadata *)entries count:(size_t)count { NSMutableDictionary *metadata = [NSMutableDictionary dictionaryWithCapacity:count]; for (grpc_metadata *entry = entries; entry < entries + count; entry++) { - // TODO(jcanizales): Verify in a C library test that it's converting header names to lower case automatically. - NSString *name = [NSString stringWithUTF8String:entry->key]; + // TODO(jcanizales): Verify in a C library test that it's converting header names to lower case + // automatically. + NSString *name = [NSString stringWithCString:entry->key encoding:NSASCIIStringEncoding]; if (!name) { + // log? continue; } + id value; + if ([name hasSuffix:@"-bin"]) { + name = [name substringToIndex:name.length - 4]; + value = [NSData grpc_dataFromMetadataValue:entry]; + } else { + value = [NSString grpc_stringFromMetadataValue:entry]; + } if (!metadata[name]) { metadata[name] = [NSMutableArray array]; } - // TODO(jcanizales): Should we use a non-copy constructor? - [metadata[name] addObject:[NSData dataWithBytes:entry->value - length:entry->value_length]]; + [metadata[name] addObject:value]; } return metadata; } @@ -60,11 +129,8 @@ for (id key in self) { id value = self[key]; grpc_metadata *current = &metadata[i]; - current->key = [key UTF8String]; - if ([value isKindOfClass:[NSData class]]) { - current->value = [value bytes]; - } else if ([value isKindOfClass:[NSString class]]) { - current->value = [value UTF8String]; + if ([value respondsToSelector:@selector(grpc_initMetadata:withKey:)]) { + [value grpc_initMetadata:current withKey:key]; } else { [NSException raise:NSInvalidArgumentException format:@"Metadata values must be NSString or NSData."]; diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb index 8df03ffb3cd..16fb1b199dd 100755 --- a/src/ruby/bin/interop/interop_client.rb +++ b/src/ruby/bin/interop/interop_client.rb @@ -274,6 +274,7 @@ class NamedTests op = @stub.streaming_input_call(reqs, return_op: true) op.cancel assert_raises(GRPC::Cancelled) { op.execute } + assert(op.cancelled, 'call operation should be CANCELLED') p 'OK: cancel_after_begin' end @@ -282,7 +283,8 @@ class NamedTests ppp = PingPongPlayer.new(msg_sizes) op = @stub.full_duplex_call(ppp.each_item, return_op: true) ppp.canceller_op = op # causes ppp to cancel after the 1st message - assert_raises(GRPC::Cancelled) { op.execute.each { |r| ppp.queue.push(r) } } + op.execute.each { |r| ppp.queue.push(r) } + assert(op.cancelled, 'call operation should be CANCELLED') p 'OK: cancel_after_first_response' end diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb index 04abab8ac37..3814ef34b41 100644 --- a/src/ruby/lib/grpc/generic/active_call.rb +++ b/src/ruby/lib/grpc/generic/active_call.rb @@ -55,7 +55,6 @@ module GRPC # The ActiveCall class provides simple methods for sending marshallable # data to a call class ActiveCall - include Core::StatusCodes include Core::TimeConsts include Core::CallOps extend Forwardable @@ -129,6 +128,11 @@ module GRPC @output_metadata ||= {} end + # cancelled indicates if the call was cancelled + def cancelled + !@call.status.nil? && @call.status.code == Core::StatusCodes::CANCELLED + end + # multi_req_view provides a restricted view of this ActiveCall for use # in a server client-streaming handler. def multi_req_view @@ -162,6 +166,7 @@ module GRPC ops[RECV_STATUS_ON_CLIENT] = nil if assert_finished batch_result = @call.run_batch(@cq, self, INFINITE_FUTURE, ops) return unless assert_finished + @call.status = batch_result.status batch_result.check_status end @@ -178,6 +183,7 @@ module GRPC @call.metadata.merge!(batch_result.status.metadata) end end + @call.status = batch_result.status batch_result.check_status end @@ -410,9 +416,6 @@ module GRPC start_call(**kw) unless @started bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, @deadline) bd.run_on_client(requests, &blk) - rescue GRPC::Core::CallError => e - finished # checks for Cancelled - raise e end # run_server_bidi orchestrates a BiDi stream processing on a server. diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb index f1b9f6b00da..489dd5162a0 100644 --- a/src/ruby/lib/grpc/generic/bidi_call.rb +++ b/src/ruby/lib/grpc/generic/bidi_call.rb @@ -78,11 +78,9 @@ module GRPC # @param requests the Enumerable of requests to send # @return an Enumerator of requests to yield def run_on_client(requests, &blk) - @enq_th = start_write_loop(requests) + @enq_th = Thread.new { write_loop(requests) } @loop_th = start_read_loop - replies = each_queued_msg - return replies if blk.nil? - replies.each { |r| blk.call(r) } + each_queued_msg(&blk) end # Begins orchestration of the Bidi stream for a server generating replies. @@ -98,9 +96,8 @@ module GRPC # @param gen_each_reply [Proc] generates the BiDi stream replies. def run_on_server(gen_each_reply) replys = gen_each_reply.call(each_queued_msg) - @enq_th = start_write_loop(replys, is_client: false) @loop_th = start_read_loop - @enq_th.join if @enq_th.alive? + write_loop(replys, is_client: false) end private @@ -126,37 +123,32 @@ module GRPC end end - # during bidi-streaming, read the requests to send from a separate thread - # read so that read_loop does not block waiting for requests to read. - def start_write_loop(requests, is_client: true) - Thread.new do # TODO: run on a thread pool - GRPC.logger.debug('bidi-write-loop: starting') - begin - write_tag = Object.new - count = 0 - requests.each do |req| - GRPC.logger.debug("bidi-write-loop: #{count}") - count += 1 - payload = @marshal.call(req) - @call.run_batch(@cq, write_tag, INFINITE_FUTURE, - SEND_MESSAGE => payload) - end - GRPC.logger.debug("bidi-write-loop: #{count} writes done") - if is_client - GRPC.logger.debug("bidi-write-loop: client sent #{count}, waiting") - @call.run_batch(@cq, write_tag, INFINITE_FUTURE, - SEND_CLOSE_FROM_CLIENT => nil) - batch_result = @call.run_batch(@cq, write_tag, INFINITE_FUTURE, - RECV_STATUS_ON_CLIENT => nil) - batch_result.check_status - end - rescue StandardError => e - GRPC.logger.warn('bidi-write-loop: failed') - GRPC.logger.warn(e) - raise e - end - GRPC.logger.debug('bidi-write-loop: finished') + def write_loop(requests, is_client: true) + GRPC.logger.debug('bidi-write-loop: starting') + write_tag = Object.new + count = 0 + requests.each do |req| + GRPC.logger.debug("bidi-write-loop: #{count}") + count += 1 + payload = @marshal.call(req) + @call.run_batch(@cq, write_tag, INFINITE_FUTURE, + SEND_MESSAGE => payload) + end + GRPC.logger.debug("bidi-write-loop: #{count} writes done") + if is_client + GRPC.logger.debug("bidi-write-loop: client sent #{count}, waiting") + batch_result = @call.run_batch(@cq, write_tag, INFINITE_FUTURE, + SEND_CLOSE_FROM_CLIENT => nil, + RECV_STATUS_ON_CLIENT => nil) + @call.status = batch_result.status + batch_result.check_status + GRPC.logger.debug("bidi-write-loop: done status #{@call.status}") end + GRPC.logger.debug('bidi-write-loop: finished') + rescue StandardError => e + GRPC.logger.warn('bidi-write-loop: failed') + GRPC.logger.warn(e) + raise e end # starts the read loop diff --git a/templates/Makefile.template b/templates/Makefile.template index 099a722873c..c155b20a722 100644 --- a/templates/Makefile.template +++ b/templates/Makefile.template @@ -276,6 +276,8 @@ LDFLAGS += -fPIC endif INCLUDES = . include $(GENDIR) +LDFLAGS += -Llibs/$(CONFIG) + ifeq ($(SYSTEM),Darwin) ifneq ($(wildcard /usr/local/ssl/include),) INCLUDES += /usr/local/ssl/include @@ -1220,7 +1222,7 @@ endif mingw_lib_deps = mingw_lib_deps + ' $(LIBDIR)/$(CONFIG)/' + dep + '.$(SHARED_EXT)' if lib.get('secure', 'check') == 'yes': - common = common + ' $(LDLIBS_SECURE) $(OPENSSL_MERGE_LIBS)' + common = common + ' $(OPENSSL_MERGE_LIBS) $(LDLIBS_SECURE)' for src in lib.src: sources_that_need_openssl.add(src) else: diff --git a/test/cpp/qps/server_async.cc b/test/cpp/qps/server_async.cc index b9998405f69..977dfc2372d 100644 --- a/test/cpp/qps/server_async.cc +++ b/test/cpp/qps/server_async.cc @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -158,11 +159,12 @@ class AsyncQpsServerTest : public Server { void *)> request_method, std::function invoke_method) - : next_state_(&ServerRpcContextUnaryImpl::invoker), + : srv_ctx_(new ServerContext), + next_state_(&ServerRpcContextUnaryImpl::invoker), request_method_(request_method), invoke_method_(invoke_method), - response_writer_(&srv_ctx_) { - request_method_(&srv_ctx_, &req_, &response_writer_, + response_writer_(srv_ctx_.get()) { + request_method_(srv_ctx_.get(), &req_, &response_writer_, AsyncQpsServerTest::tag(this)); } ~ServerRpcContextUnaryImpl() GRPC_OVERRIDE {} @@ -170,14 +172,14 @@ class AsyncQpsServerTest : public Server { return (this->*next_state_)(ok); } void Reset() GRPC_OVERRIDE { - srv_ctx_ = ServerContext(); + srv_ctx_.reset(new ServerContext); req_ = RequestType(); response_writer_ = - grpc::ServerAsyncResponseWriter(&srv_ctx_); + grpc::ServerAsyncResponseWriter(srv_ctx_.get()); // Then request the method next_state_ = &ServerRpcContextUnaryImpl::invoker; - request_method_(&srv_ctx_, &req_, &response_writer_, + request_method_(srv_ctx_.get(), &req_, &response_writer_, AsyncQpsServerTest::tag(this)); } @@ -198,7 +200,7 @@ class AsyncQpsServerTest : public Server { response_writer_.Finish(response, status, AsyncQpsServerTest::tag(this)); return true; } - ServerContext srv_ctx_; + std::unique_ptr srv_ctx_; RequestType req_; bool (ServerRpcContextUnaryImpl::*next_state_)(bool); std::function request_method, std::function invoke_method) - : next_state_(&ServerRpcContextStreamingImpl::request_done), + : srv_ctx_(new ServerContext), + next_state_(&ServerRpcContextStreamingImpl::request_done), request_method_(request_method), invoke_method_(invoke_method), - stream_(&srv_ctx_) { - request_method_(&srv_ctx_, &stream_, AsyncQpsServerTest::tag(this)); + stream_(srv_ctx_.get()) { + request_method_(srv_ctx_.get(), &stream_, AsyncQpsServerTest::tag(this)); } ~ServerRpcContextStreamingImpl() GRPC_OVERRIDE {} bool RunNextState(bool ok) GRPC_OVERRIDE { return (this->*next_state_)(ok); } void Reset() GRPC_OVERRIDE { - srv_ctx_ = ServerContext(); + srv_ctx_.reset(new ServerContext); req_ = RequestType(); - stream_ = - grpc::ServerAsyncReaderWriter(&srv_ctx_); + stream_ = grpc::ServerAsyncReaderWriter( + srv_ctx_.get()); // Then request the method next_state_ = &ServerRpcContextStreamingImpl::request_done; - request_method_(&srv_ctx_, &stream_, AsyncQpsServerTest::tag(this)); + request_method_(srv_ctx_.get(), &stream_, AsyncQpsServerTest::tag(this)); } private: @@ -278,7 +281,7 @@ class AsyncQpsServerTest : public Server { } bool finish_done(bool ok) { return false; /* reset the context */ } - ServerContext srv_ctx_; + std::unique_ptr srv_ctx_; RequestType req_; bool (ServerRpcContextStreamingImpl::*next_state_)(bool); std::function