From 097ef9b7d9a8e000a5654432cb2fd35816777068 Mon Sep 17 00:00:00 2001 From: nnoble Date: Mon, 1 Dec 2014 17:06:10 -0800 Subject: [PATCH] Incorporating ruby into the master grpc repository. Change on 2014/12/01 by nnoble ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=81111468 --- src/ruby/.gitignore | 15 + src/ruby/.rspec | 1 + src/ruby/Gemfile | 13 + src/ruby/README.md | 93 +++ src/ruby/Rakefile | 38 ++ src/ruby/bin/math.pb.rb | 65 +++ src/ruby/bin/math.proto | 50 ++ src/ruby/bin/math_client.rb | 110 ++++ src/ruby/bin/math_server.rb | 166 ++++++ src/ruby/bin/noproto_client.rb | 75 +++ src/ruby/bin/noproto_server.rb | 75 +++ src/ruby/ext/grpc/extconf.rb | 92 +++ src/ruby/ext/grpc/rb_byte_buffer.c | 243 ++++++++ src/ruby/ext/grpc/rb_byte_buffer.h | 54 ++ src/ruby/ext/grpc/rb_call.c | 542 ++++++++++++++++++ src/ruby/ext/grpc/rb_call.h | 59 ++ src/ruby/ext/grpc/rb_channel.c | 235 ++++++++ src/ruby/ext/grpc/rb_channel.h | 49 ++ src/ruby/ext/grpc/rb_channel_args.c | 157 +++++ src/ruby/ext/grpc/rb_channel_args.h | 53 ++ src/ruby/ext/grpc/rb_completion_queue.c | 194 +++++++ src/ruby/ext/grpc/rb_completion_queue.h | 50 ++ src/ruby/ext/grpc/rb_event.c | 284 +++++++++ src/ruby/ext/grpc/rb_event.h | 55 ++ src/ruby/ext/grpc/rb_grpc.c | 230 ++++++++ src/ruby/ext/grpc/rb_grpc.h | 71 +++ src/ruby/ext/grpc/rb_metadata.c | 215 +++++++ src/ruby/ext/grpc/rb_metadata.h | 53 ++ src/ruby/ext/grpc/rb_server.c | 226 ++++++++ src/ruby/ext/grpc/rb_server.h | 50 ++ src/ruby/ext/grpc/rb_status.c | 243 ++++++++ src/ruby/ext/grpc/rb_status.h | 53 ++ src/ruby/grpc.gemspec | 30 + src/ruby/lib/grpc.rb | 38 ++ src/ruby/lib/grpc/errors.rb | 68 +++ src/ruby/lib/grpc/event.rb | 38 ++ src/ruby/lib/grpc/generic/active_call.rb | 485 ++++++++++++++++ src/ruby/lib/grpc/generic/bidi_call.rb | 320 +++++++++++ src/ruby/lib/grpc/generic/client_stub.rb | 358 ++++++++++++ src/ruby/lib/grpc/generic/rpc_desc.rb | 157 +++++ src/ruby/lib/grpc/generic/rpc_server.rb | 408 +++++++++++++ src/ruby/lib/grpc/generic/service.rb | 247 ++++++++ src/ruby/lib/grpc/logconfig.rb | 40 ++ src/ruby/lib/grpc/time_consts.rb | 69 +++ src/ruby/lib/grpc/version.rb | 34 ++ src/ruby/spec/alloc_spec.rb | 46 ++ src/ruby/spec/byte_buffer_spec.rb | 71 +++ src/ruby/spec/call_spec.rb | 200 +++++++ src/ruby/spec/channel_spec.rb | 164 ++++++ src/ruby/spec/client_server_spec.rb | 349 +++++++++++ src/ruby/spec/completion_queue_spec.rb | 82 +++ src/ruby/spec/event_spec.rb | 54 ++ src/ruby/spec/generic/active_call_spec.rb | 321 +++++++++++ src/ruby/spec/generic/client_stub_spec.rb | 484 ++++++++++++++++ src/ruby/spec/generic/rpc_desc_spec.rb | 380 ++++++++++++ src/ruby/spec/generic/rpc_server_pool_spec.rb | 153 +++++ src/ruby/spec/generic/rpc_server_spec.rb | 391 +++++++++++++ src/ruby/spec/generic/service_spec.rb | 324 +++++++++++ src/ruby/spec/metadata_spec.rb | 67 +++ src/ruby/spec/port_picker.rb | 45 ++ src/ruby/spec/server_spec.rb | 185 ++++++ src/ruby/spec/spec_helper.rb | 39 ++ src/ruby/spec/status_spec.rb | 161 ++++++ src/ruby/spec/time_consts_spec.rb | 95 +++ 64 files changed, 9812 insertions(+) create mode 100755 src/ruby/.gitignore create mode 100755 src/ruby/.rspec create mode 100755 src/ruby/Gemfile create mode 100755 src/ruby/README.md create mode 100755 src/ruby/Rakefile create mode 100755 src/ruby/bin/math.pb.rb create mode 100755 src/ruby/bin/math.proto create mode 100644 src/ruby/bin/math_client.rb create mode 100644 src/ruby/bin/math_server.rb create mode 100644 src/ruby/bin/noproto_client.rb create mode 100644 src/ruby/bin/noproto_server.rb create mode 100644 src/ruby/ext/grpc/extconf.rb create mode 100644 src/ruby/ext/grpc/rb_byte_buffer.c create mode 100644 src/ruby/ext/grpc/rb_byte_buffer.h create mode 100644 src/ruby/ext/grpc/rb_call.c create mode 100644 src/ruby/ext/grpc/rb_call.h create mode 100644 src/ruby/ext/grpc/rb_channel.c create mode 100644 src/ruby/ext/grpc/rb_channel.h create mode 100644 src/ruby/ext/grpc/rb_channel_args.c create mode 100644 src/ruby/ext/grpc/rb_channel_args.h create mode 100644 src/ruby/ext/grpc/rb_completion_queue.c create mode 100644 src/ruby/ext/grpc/rb_completion_queue.h create mode 100644 src/ruby/ext/grpc/rb_event.c create mode 100644 src/ruby/ext/grpc/rb_event.h create mode 100644 src/ruby/ext/grpc/rb_grpc.c create mode 100644 src/ruby/ext/grpc/rb_grpc.h create mode 100644 src/ruby/ext/grpc/rb_metadata.c create mode 100644 src/ruby/ext/grpc/rb_metadata.h create mode 100644 src/ruby/ext/grpc/rb_server.c create mode 100644 src/ruby/ext/grpc/rb_server.h create mode 100644 src/ruby/ext/grpc/rb_status.c create mode 100644 src/ruby/ext/grpc/rb_status.h create mode 100755 src/ruby/grpc.gemspec create mode 100644 src/ruby/lib/grpc.rb create mode 100644 src/ruby/lib/grpc/errors.rb create mode 100644 src/ruby/lib/grpc/event.rb create mode 100644 src/ruby/lib/grpc/generic/active_call.rb create mode 100644 src/ruby/lib/grpc/generic/bidi_call.rb create mode 100644 src/ruby/lib/grpc/generic/client_stub.rb create mode 100644 src/ruby/lib/grpc/generic/rpc_desc.rb create mode 100644 src/ruby/lib/grpc/generic/rpc_server.rb create mode 100644 src/ruby/lib/grpc/generic/service.rb create mode 100644 src/ruby/lib/grpc/logconfig.rb create mode 100644 src/ruby/lib/grpc/time_consts.rb create mode 100644 src/ruby/lib/grpc/version.rb create mode 100644 src/ruby/spec/alloc_spec.rb create mode 100644 src/ruby/spec/byte_buffer_spec.rb create mode 100644 src/ruby/spec/call_spec.rb create mode 100644 src/ruby/spec/channel_spec.rb create mode 100644 src/ruby/spec/client_server_spec.rb create mode 100644 src/ruby/spec/completion_queue_spec.rb create mode 100644 src/ruby/spec/event_spec.rb create mode 100644 src/ruby/spec/generic/active_call_spec.rb create mode 100644 src/ruby/spec/generic/client_stub_spec.rb create mode 100644 src/ruby/spec/generic/rpc_desc_spec.rb create mode 100644 src/ruby/spec/generic/rpc_server_pool_spec.rb create mode 100644 src/ruby/spec/generic/rpc_server_spec.rb create mode 100644 src/ruby/spec/generic/service_spec.rb create mode 100644 src/ruby/spec/metadata_spec.rb create mode 100644 src/ruby/spec/port_picker.rb create mode 100644 src/ruby/spec/server_spec.rb create mode 100644 src/ruby/spec/spec_helper.rb create mode 100644 src/ruby/spec/status_spec.rb create mode 100644 src/ruby/spec/time_consts_spec.rb diff --git a/src/ruby/.gitignore b/src/ruby/.gitignore new file mode 100755 index 0000000000000..62fcb4fa9430d --- /dev/null +++ b/src/ruby/.gitignore @@ -0,0 +1,15 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.bundle +*.so +*.o +*.a +mkmf.log +vendor diff --git a/src/ruby/.rspec b/src/ruby/.rspec new file mode 100755 index 0000000000000..60a4aad5a25f3 --- /dev/null +++ b/src/ruby/.rspec @@ -0,0 +1 @@ +-I. diff --git a/src/ruby/Gemfile b/src/ruby/Gemfile new file mode 100755 index 0000000000000..4d41544ce9272 --- /dev/null +++ b/src/ruby/Gemfile @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +# Modify this when working locally, see README.md +# e.g, +# gem 'beefcake', path: "/usr/local/google/repos/beefcake" +# +# The default value is what's used for gRPC ruby's GCE configuration +# +# gem 'beefcake', path: "/var/local/git/beefcake" +gem 'beefcake', path: "/usr/local/google/repos/beefcake" + +# Specify your gem's dependencies in grpc.gemspec +gemspec diff --git a/src/ruby/README.md b/src/ruby/README.md new file mode 100755 index 0000000000000..837786634429e --- /dev/null +++ b/src/ruby/README.md @@ -0,0 +1,93 @@ +Ruby for GRPC +============= + +LAYOUT +------ + +Directory structure is the recommended layout for [ruby extensions](http://guides.rubygems.org/gems-with-extensions/) + + * ext: the extension code + * lib: the entrypoint grpc ruby library to be used in a 'require' statement + * test: tests + + +DEPENDENCIES +------------ + + +* Extension + +The extension can be built and tested using +[rake](https://rubygems.org/gems/rake). However, the rake-extensiontask rule +is not supported on older versions of rubygems, and the necessary version of +rubygems is not available on the latest version of Goobuntu. + +This is resolved by using [RVM](https://rvm.io/) instead; install a single-user +ruby environment, and develop on the latest stable version of ruby (2.1.2). + + +* Proto code generation + +To build generate service stubs and skeletons, it's currently necessary to use +a patched version of a beefcake, a simple third-party proto2 library. This is +feature compatible with proto3 and will be replaced by official proto3 support +in protoc. + +* Patched protoc + +The patched version of beefcake in turn depends on a patched version of protoc. +This is an update of the latest open source release of protoc with some forward +looking proto3 patches. + + +INSTALLATION PREREQUISITES +-------------------------- + +Install the patched protoc + +$ cd +$ git clone sso://team/one-platform-grpc-team/protobuf +$ cd protobuf +$ ./configure --prefix=/usr +$ make +$ sudo make install + +Install RVM + +$ \curl -sSL https://get.rvm.io | bash -s stable --ruby +$ # follow the instructions to ensure that your're using the latest stable version of Ruby +$ +$ gem install bundler # install bundler, the standard ruby package manager + +Install the patched beefcake, and update the Gemfile to reference + +$ cd +$ git clone sso://team/one-platform-grpc-team/grpc-ruby-beefcake beefcake +$ cd beefcake +$ bundle install +$ + +HACKING +------- + +The extension can be built and tested using the Rakefile. + +$ # create a workspace +$ git5 start net/grpc +$ +$ # build the C library and install it in $HOME/grpc_dev +$ /net/grpc/c/build_gyp/build_grpc_dev.sh +$ +$ # build the ruby extension and test it. +$ cd google3_dir/net/grpc/ruby +$ rake + +Finally, install grpc ruby locally. + +$ cd +$ +$ # update the Gemfile, modify the line beginning # gem 'beefcake' to refer to +$ # the patched beefcake dir +$ +$ bundle install + diff --git a/src/ruby/Rakefile b/src/ruby/Rakefile new file mode 100755 index 0000000000000..11b3d04f3fd0b --- /dev/null +++ b/src/ruby/Rakefile @@ -0,0 +1,38 @@ +# -*- ruby -*- +require 'rake/extensiontask' +require 'rspec/core/rake_task' + + +Rake::ExtensionTask.new 'grpc' do |ext| + ext.lib_dir = File.join('lib', 'grpc') +end + +SPEC_SUITES = [ + { :id => :wrapper, :title => 'wrapper layer', :files => %w(spec/*.rb) }, + { :id => :idiomatic, :title => 'idiomatic layer', :dir => %w(spec/generic) } +] + +desc "Run all RSpec tests" +namespace :spec do + namespace :suite do + SPEC_SUITES.each do |suite| + desc "Run all specs in #{suite[:title]} spec suite" + RSpec::Core::RakeTask.new(suite[:id]) do |t| + spec_files = [] + if suite[:files] + suite[:files].each { |f| spec_files += Dir[f] } + end + + if suite[:dirs] + suite[:dirs].each { |f| spec_files += Dir["#{f}/**/*_spec.rb"] } + end + + t.pattern = spec_files + end + end + end +end + +desc "Run tests" +task :default => [ "spec:suite:wrapper", "spec:suite:idiomatic"] +task :spec => :compile diff --git a/src/ruby/bin/math.pb.rb b/src/ruby/bin/math.pb.rb new file mode 100755 index 0000000000000..9278a8438279e --- /dev/null +++ b/src/ruby/bin/math.pb.rb @@ -0,0 +1,65 @@ +## Generated from bin/math.proto for math +require "beefcake" +require "grpc" + +module Math + + class DivArgs + include Beefcake::Message + end + + class DivReply + include Beefcake::Message + end + + class FibArgs + include Beefcake::Message + end + + class Num + include Beefcake::Message + end + + class FibReply + include Beefcake::Message + end + + class DivArgs + required :dividend, :int64, 1 + required :divisor, :int64, 2 + end + + class DivReply + required :quotient, :int64, 1 + required :remainder, :int64, 2 + end + + class FibArgs + optional :limit, :int64, 1 + end + + class Num + required :num, :int64, 1 + end + + class FibReply + required :count, :int64, 1 + end + + module Math + + class Service + include GRPC::GenericService + + self.marshal_instance_method = :encode + self.unmarshal_class_method = :decode + + rpc :Div, DivArgs, DivReply + rpc :DivMany, stream(DivArgs), stream(DivReply) + rpc :Fib, FibArgs, stream(Num) + rpc :Sum, stream(Num), Num + end + Stub = Service.rpc_stub_class + + end +end diff --git a/src/ruby/bin/math.proto b/src/ruby/bin/math.proto new file mode 100755 index 0000000000000..de18a5026024d --- /dev/null +++ b/src/ruby/bin/math.proto @@ -0,0 +1,50 @@ +syntax = "proto2"; + +package math; + +message DivArgs { + required int64 dividend = 1; + required int64 divisor = 2; +} + +message DivReply { + required int64 quotient = 1; + required int64 remainder = 2; +} + +message FibArgs { + optional int64 limit = 1; +} + +message Num { + required int64 num = 1; +} + +message FibReply { + required int64 count = 1; +} + +service Math { + // Div divides args.dividend by args.divisor and returns the quotient and + // remainder. + rpc Div (DivArgs) returns (DivReply) { + } + + // DivMany accepts an arbitrary number of division args from the client stream + // and sends back the results in the reply stream. The stream continues until + // the client closes its end; the server does the same after sending all the + // replies. The stream ends immediately if either end aborts. + rpc DivMany (stream DivArgs) returns (stream DivReply) { + } + + // Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib + // generates up to limit numbers; otherwise it continues until the call is + // canceled. Unlike Fib above, Fib has no final FibReply. + rpc Fib (FibArgs) returns (stream Num) { + } + + // Sum sums a stream of numbers, returning the final result once the stream + // is closed. + rpc Sum (stream Num) returns (Num) { + } +} diff --git a/src/ruby/bin/math_client.rb b/src/ruby/bin/math_client.rb new file mode 100644 index 0000000000000..f8cf8580e8b5a --- /dev/null +++ b/src/ruby/bin/math_client.rb @@ -0,0 +1,110 @@ +# Copyright 2014, 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. + +#!/usr/bin/env ruby +# +# Sample app that accesses a Calc service running on a Ruby gRPC server and +# helps validate RpcServer as a gRPC server using proto2 serialization. +# +# Usage: $ path/to/math_client.rb + +this_dir = File.expand_path(File.dirname(__FILE__)) +lib_dir = File.join(File.dirname(this_dir), 'lib') +$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) +$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) + +require 'grpc' +require 'grpc/generic/client_stub' +require 'grpc/generic/service' +require 'math.pb' + +def do_div(stub) + logger.info('request_response') + logger.info('----------------') + req = Math::DivArgs.new(:dividend => 7, :divisor => 3) + logger.info("div(7/3): req=#{req.inspect}") + resp = stub.div(req, deadline=GRPC::TimeConsts::INFINITE_FUTURE) + logger.info("Answer: #{resp.inspect}") + logger.info('----------------') +end + +def do_sum(stub) + # to make client streaming requests, pass an enumerable of the inputs + logger.info('client_streamer') + logger.info('---------------') + reqs = [1, 2, 3, 4, 5].map { |x| Math::Num.new(:num => x) } + logger.info("sum(1, 2, 3, 4, 5): reqs=#{reqs.inspect}") + resp = stub.sum(reqs) # reqs.is_a?(Enumerable) + logger.info("Answer: #{resp.inspect}") + logger.info('---------------') +end + +def do_fib(stub) + logger.info('server_streamer') + logger.info('----------------') + req = Math::FibArgs.new(:limit => 11) + logger.info("fib(11): req=#{req.inspect}") + resp = stub.fib(req, deadline=GRPC::TimeConsts::INFINITE_FUTURE) + resp.each do |r| + logger.info("Answer: #{r.inspect}") + end + logger.info('----------------') +end + +def do_div_many(stub) + logger.info('bidi_streamer') + logger.info('-------------') + reqs = [] + reqs << Math::DivArgs.new(:dividend => 7, :divisor => 3) + reqs << Math::DivArgs.new(:dividend => 5, :divisor => 2) + reqs << Math::DivArgs.new(:dividend => 7, :divisor => 2) + logger.info("div(7/3), div(5/2), div(7/2): reqs=#{reqs.inspect}") + resp = stub.div_many(reqs, deadline=10) + resp.each do |r| + logger.info("Answer: #{r.inspect}") + end + logger.info('----------------') +end + + +def main + host_port = 'localhost:7070' + if ARGV.size > 0 + host_port = ARGV[0] + end + # The Math::Math:: module occurs because the service has the same name as its + # package. That practice should be avoided by defining real services. + stub = Math::Math::Stub.new(host_port) + do_div(stub) + do_sum(stub) + do_fib(stub) + do_div_many(stub) +end + +main diff --git a/src/ruby/bin/math_server.rb b/src/ruby/bin/math_server.rb new file mode 100644 index 0000000000000..72a1f6b398787 --- /dev/null +++ b/src/ruby/bin/math_server.rb @@ -0,0 +1,166 @@ +# Copyright 2014, 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. + +#!/usr/bin/env ruby +# +# Sample gRPC Ruby server that implements the Math::Calc service and helps +# validate GRPC::RpcServer as GRPC implementation using proto2 serialization. +# +# Usage: $ path/to/math_server.rb + +this_dir = File.expand_path(File.dirname(__FILE__)) +lib_dir = File.join(File.dirname(this_dir), 'lib') +$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) +$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) + +require 'forwardable' +require 'grpc' +require 'grpc/generic/service' +require 'grpc/generic/rpc_server' +require 'math.pb' + +# Holds state for a fibonacci series +class Fibber + + def initialize(limit) + raise "bad limit: got #{limit}, want limit > 0" if limit < 1 + @limit = limit + end + + def generator + return enum_for(:generator) unless block_given? + idx, current, previous = 0, 1, 1 + until idx == @limit + if idx == 0 || idx == 1 + yield Math::Num.new(:num => 1) + idx += 1 + next + end + tmp = current + current = previous + current + previous = tmp + yield Math::Num.new(:num => current) + idx += 1 + end + end +end + +# A EnumeratorQueue wraps a Queue to yield the items added to it. +class EnumeratorQueue + extend Forwardable + def_delegators :@q, :push + + def initialize(sentinel) + @q = Queue.new + @sentinel = sentinel + end + + def each_item + return enum_for(:each_item) unless block_given? + loop do + r = @q.pop + break if r.equal?(@sentinel) + raise r if r.is_a?Exception + yield r + end + end + +end + +# The Math::Math:: module occurs because the service has the same name as its +# package. That practice should be avoided by defining real services. +class Calculator < Math::Math::Service + + def div(div_args, call) + if div_args.divisor == 0 + # To send non-OK status handlers raise a StatusError with the code and + # and detail they want sent as a Status. + raise GRPC::StatusError.new(GRPC::Status::INVALID_ARGUMENT, + 'divisor cannot be 0') + end + + Math::DivReply.new(:quotient => div_args.dividend/div_args.divisor, + :remainder => div_args.dividend % div_args.divisor) + end + + def sum(call) + # the requests are accesible as the Enumerator call#each_request + nums = call.each_remote_read.collect { |x| x.num } + sum = nums.inject { |sum,x| sum + x } + Math::Num.new(:num => sum) + end + + def fib(fib_args, call) + if fib_args.limit < 1 + raise StatusError.new(Status::INVALID_ARGUMENT, 'limit must be >= 0') + end + + # return an Enumerator of Nums + Fibber.new(fib_args.limit).generator() + # just return the generator, GRPC::GenericServer sends each actual response + end + + def div_many(requests) + # requests is an lazy Enumerator of the requests sent by the client. + q = EnumeratorQueue.new(self) + t = Thread.new do + begin + requests.each do |req| + logger.info("read #{req.inspect}") + resp = Math::DivReply.new(:quotient => req.dividend/req.divisor, + :remainder => req.dividend % req.divisor) + q.push(resp) + Thread::pass # let the internal Bidi threads run + end + logger.info('finished reads') + q.push(self) + rescue StandardError => e + q.push(e) # share the exception with the enumerator + raise e + end + end + t.priority = -2 # hint that the div_many thread should not be favoured + q.each_item + end + +end + +def main + host_port = 'localhost:7070' + if ARGV.size > 0 + host_port = ARGV[0] + end + + s = GRPC::RpcServer.new() + s.add_http2_port(host_port) + s.handle(Calculator) + s.run +end + +main diff --git a/src/ruby/bin/noproto_client.rb b/src/ruby/bin/noproto_client.rb new file mode 100644 index 0000000000000..fbd10a06b5398 --- /dev/null +++ b/src/ruby/bin/noproto_client.rb @@ -0,0 +1,75 @@ +# Copyright 2014, 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. + +#!/usr/bin/env ruby +# Sample app that helps validate RpcServer without protobuf serialization. +# +# Usage: $ ruby -S path/to/noproto_client.rb + +this_dir = File.expand_path(File.dirname(__FILE__)) +lib_dir = File.join(File.dirname(this_dir), 'lib') +$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) + +require 'grpc' +require 'grpc/generic/client_stub' +require 'grpc/generic/service' + +class EchoMsg + def marshal + '' + end + + def self.unmarshal(o) + EchoMsg.new + end +end + +class EchoService + include GRPC::GenericService + rpc :AnRPC, EchoMsg, EchoMsg + + def initialize(default_var='ignored') + end + + def an_rpc(req, call) + logger.info('echo service received a request') + req + end +end + +EchoStub = EchoService.rpc_stub_class + +def main + stub = EchoStub.new('localhost:9090') + logger.info('sending an rpc') + resp = stub.an_rpc(EchoMsg.new) + logger.info("got a response: #{resp}") +end + +main diff --git a/src/ruby/bin/noproto_server.rb b/src/ruby/bin/noproto_server.rb new file mode 100644 index 0000000000000..c5b7c192eb41a --- /dev/null +++ b/src/ruby/bin/noproto_server.rb @@ -0,0 +1,75 @@ +# Copyright 2014, 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. + +#!/usr/bin/env ruby +# Sample app that helps validate RpcServer without protobuf serialization. +# +# Usage: $ path/to/noproto_server.rb + +this_dir = File.expand_path(File.dirname(__FILE__)) +lib_dir = File.join(File.dirname(this_dir), 'lib') +$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) + +require 'grpc' +require 'grpc/generic/rpc_server' +require 'grpc/generic/service' + +class EchoMsg + def marshal + '' + end + + def self.unmarshal(o) + EchoMsg.new + end +end + +class EchoService + include GRPC::GenericService + rpc :AnRPC, EchoMsg, EchoMsg +end + +class Echo < EchoService + def initialize(default_var='ignored') + end + + def an_rpc(req, call) + logger.info('echo service received a request') + req + end +end + +def main + s = GRPC::RpcServer.new() + s.add_http2_port('localhost:9090') + s.handle(Echo) + s.run +end + +main diff --git a/src/ruby/ext/grpc/extconf.rb b/src/ruby/ext/grpc/extconf.rb new file mode 100644 index 0000000000000..06bfad9e6ced7 --- /dev/null +++ b/src/ruby/ext/grpc/extconf.rb @@ -0,0 +1,92 @@ +# Copyright 2014, 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. + +require 'mkmf' + +LIBDIR = RbConfig::CONFIG['libdir'] +INCLUDEDIR = RbConfig::CONFIG['includedir'] + +HEADER_DIRS = [ + # First search the local development dir + ENV['HOME'] + '/grpc_dev/include', + + # Then search /opt/local (Mac) + '/opt/local/include', + + # Then search /usr/local (Source install) + '/usr/local/include', + + # Check the ruby install locations + INCLUDEDIR, + + # Finally fall back to /usr + '/usr/include' +] + +LIB_DIRS = [ + # First search the local development dir + ENV['HOME'] + '/grpc_dev/lib', + + # Then search /opt/local for (Mac) + '/opt/local/lib', + + # Then search /usr/local (Source install) + '/usr/local/lib', + + # Check the ruby install locations + LIBDIR, + + # Finally fall back to /usr + '/usr/lib' +] + +def crash(msg) + print(" extconf failure: %s\n" % msg) + exit 1 +end + +dir_config('grpc', HEADER_DIRS, LIB_DIRS) + +$CFLAGS << ' -std=c89 ' +$CFLAGS << ' -Wno-implicit-function-declaration ' +$CFLAGS << ' -Wno-pointer-sign ' +$CFLAGS << ' -Wno-return-type ' +$CFLAGS << ' -Wall ' +$CFLAGS << ' -pedantic ' + +$LDFLAGS << ' -lgrpc -lgpr -levent -levent_pthreads -levent_core' + +# crash('need grpc lib') unless have_library('grpc', 'grpc_channel_destroy') +# +# TODO(temiola): figure out why this stopped working, but the so is built OK +# and the tests pass + +have_library('grpc', 'grpc_channel_destroy') +crash('need gpr lib') unless have_library('gpr', 'gpr_now') +create_makefile('grpc/grpc') diff --git a/src/ruby/ext/grpc/rb_byte_buffer.c b/src/ruby/ext/grpc/rb_byte_buffer.c new file mode 100644 index 0000000000000..a520ca44dd7d6 --- /dev/null +++ b/src/ruby/ext/grpc/rb_byte_buffer.c @@ -0,0 +1,243 @@ +/* + * + * Copyright 2014, 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 "rb_byte_buffer.h" + +#include + +#include +#include +#include "rb_grpc.h" + +/* grpc_rb_byte_buffer wraps a grpc_byte_buffer. It provides a peer ruby + * object, 'mark' to minimize copying when a byte_buffer is created from + * ruby. */ +typedef struct grpc_rb_byte_buffer { + /* Holder of ruby objects involved in constructing the status */ + VALUE mark; + /* The actual status */ + grpc_byte_buffer *wrapped; +} grpc_rb_byte_buffer; + + +/* Destroys ByteBuffer instances. */ +static void grpc_rb_byte_buffer_free(void *p) { + grpc_rb_byte_buffer *bb = NULL; + if (p == NULL) { + return; + }; + bb = (grpc_rb_byte_buffer *)p; + + /* Deletes the wrapped object if the mark object is Qnil, which indicates + * that no other object is the actual owner. */ + if (bb->wrapped != NULL && bb->mark == Qnil) { + grpc_byte_buffer_destroy(bb->wrapped); + } + + xfree(p); +} + +/* Protects the mark object from GC */ +static void grpc_rb_byte_buffer_mark(void *p) { + grpc_rb_byte_buffer *bb = NULL; + if (p == NULL) { + return; + } + bb = (grpc_rb_byte_buffer *)p; + + /* If it's not already cleaned up, mark the mark object */ + if (bb->mark != Qnil && BUILTIN_TYPE(bb->mark) != T_NONE) { + rb_gc_mark(bb->mark); + } +} + +/* id_source is the name of the hidden ivar the preserves the original + * byte_buffer source string */ +static ID id_source; + +/* Allocates ByteBuffer instances. + + Provides safe default values for the byte_buffer fields. */ +static VALUE grpc_rb_byte_buffer_alloc(VALUE cls) { + grpc_rb_byte_buffer *wrapper = ALLOC(grpc_rb_byte_buffer); + wrapper->wrapped = NULL; + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_byte_buffer_mark, + grpc_rb_byte_buffer_free, wrapper); +} + +/* Clones ByteBuffer instances. + + Gives ByteBuffer a consistent implementation of Ruby's object copy/dup + protocol. */ +static VALUE grpc_rb_byte_buffer_init_copy(VALUE copy, VALUE orig) { + grpc_rb_byte_buffer *orig_bb = NULL; + grpc_rb_byte_buffer *copy_bb = NULL; + + if (copy == orig) { + return copy; + } + + /* Raise an error if orig is not a metadata object or a subclass. */ + if (TYPE(orig) != T_DATA || + RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_byte_buffer_free) { + rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cByteBuffer)); + } + + Data_Get_Struct(orig, grpc_rb_byte_buffer, orig_bb); + Data_Get_Struct(copy, grpc_rb_byte_buffer, copy_bb); + + /* use ruby's MEMCPY to make a byte-for-byte copy of the metadata wrapper + * object. */ + MEMCPY(copy_bb, orig_bb, grpc_rb_byte_buffer, 1); + return copy; +} + +/* id_empty is used to return the empty string from to_s when necessary. */ +static ID id_empty; + +static VALUE grpc_rb_byte_buffer_to_s(VALUE self) { + grpc_rb_byte_buffer *wrapper = NULL; + grpc_byte_buffer *bb = NULL; + grpc_byte_buffer_reader *reader = NULL; + char *output = NULL; + size_t length = 0; + size_t offset = 0; + VALUE output_obj = Qnil; + gpr_slice next; + + Data_Get_Struct(self, grpc_rb_byte_buffer, wrapper); + output_obj = rb_ivar_get(wrapper->mark, id_source); + if (output_obj != Qnil) { + /* From ruby, ByteBuffers are immutable so if a source is set, return that + * as the to_s value */ + return output_obj; + } + + /* Read the bytes. */ + bb = wrapper->wrapped; + if (bb == NULL) { + return rb_id2str(id_empty); + } + length = grpc_byte_buffer_length(bb); + if (length == 0) { + return rb_id2str(id_empty); + } + reader = grpc_byte_buffer_reader_create(bb); + output = xmalloc(length); + while (grpc_byte_buffer_reader_next(reader, &next) != 0) { + memcpy(output + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next)); + offset += GPR_SLICE_LENGTH(next); + } + output_obj = rb_str_new(output, length); + + /* Save a references to the computed string in the mark object so that the + * calling to_s does not do any allocations. */ + wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject); + rb_ivar_set(wrapper->mark, id_source, output_obj); + + return output_obj; +} + + +/* Initializes ByteBuffer instances. */ +static VALUE grpc_rb_byte_buffer_init(VALUE self, VALUE src) { + gpr_slice a_slice; + grpc_rb_byte_buffer *wrapper = NULL; + grpc_byte_buffer *byte_buffer = NULL; + + if (TYPE(src) != T_STRING) { + rb_raise(rb_eTypeError, "bad byte_buffer arg: got <%s>, want ", + rb_obj_classname(src)); + return Qnil; + } + Data_Get_Struct(self, grpc_rb_byte_buffer, wrapper); + a_slice = gpr_slice_malloc(RSTRING_LEN(src)); + memcpy(GPR_SLICE_START_PTR(a_slice), RSTRING_PTR(src), RSTRING_LEN(src)); + byte_buffer = grpc_byte_buffer_create(&a_slice, 1); + gpr_slice_unref(a_slice); + + if (byte_buffer == NULL) { + rb_raise(rb_eArgError, "could not create a byte_buffer, not sure why"); + return Qnil; + } + wrapper->wrapped = byte_buffer; + + /* Save a references to the original string in the mark object so that the + * pointers used there is valid for the lifetime of the object. */ + wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject); + rb_ivar_set(wrapper->mark, id_source, src); + + return self; +} + +/* rb_cByteBuffer is the ruby class that proxies grpc_byte_buffer. */ +VALUE rb_cByteBuffer = Qnil; + +void Init_google_rpc_byte_buffer() { + rb_cByteBuffer = rb_define_class_under(rb_mGoogleRPC, "ByteBuffer", + rb_cObject); + + /* Allocates an object managed by the ruby runtime */ + rb_define_alloc_func(rb_cByteBuffer, grpc_rb_byte_buffer_alloc); + + /* Provides a ruby constructor and support for dup/clone. */ + rb_define_method(rb_cByteBuffer, "initialize", grpc_rb_byte_buffer_init, 1); + rb_define_method(rb_cByteBuffer, "initialize_copy", + grpc_rb_byte_buffer_init_copy, 1); + + /* Provides a to_s method that returns the buffer value */ + rb_define_method(rb_cByteBuffer, "to_s", grpc_rb_byte_buffer_to_s, 0); + + id_source = rb_intern("__source"); + id_empty = rb_intern(""); +} + +VALUE grpc_rb_byte_buffer_create_with_mark(VALUE mark, grpc_byte_buffer* bb) { + grpc_rb_byte_buffer *byte_buffer = NULL; + if (bb == NULL) { + return Qnil; + } + byte_buffer = ALLOC(grpc_rb_byte_buffer); + byte_buffer->wrapped = bb; + byte_buffer->mark = mark; + return Data_Wrap_Struct(rb_cByteBuffer, grpc_rb_byte_buffer_mark, + grpc_rb_byte_buffer_free, byte_buffer); +} + +/* Gets the wrapped byte_buffer from the ruby wrapper */ +grpc_byte_buffer* grpc_rb_get_wrapped_byte_buffer(VALUE v) { + grpc_rb_byte_buffer *wrapper = NULL; + Data_Get_Struct(v, grpc_rb_byte_buffer, wrapper); + return wrapper->wrapped; +} diff --git a/src/ruby/ext/grpc/rb_byte_buffer.h b/src/ruby/ext/grpc/rb_byte_buffer.h new file mode 100644 index 0000000000000..1bdcfe401992b --- /dev/null +++ b/src/ruby/ext/grpc/rb_byte_buffer.h @@ -0,0 +1,54 @@ +/* + * + * Copyright 2014, 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_RB_BYTE_BUFFER_H_ +#define GRPC_RB_BYTE_BUFFER_H_ + +#include +#include + +/* rb_cByteBuffer is the ByteBuffer class whose instances proxy + grpc_byte_buffer. */ +extern VALUE rb_cByteBuffer; + +/* Initializes the ByteBuffer class. */ +void Init_google_rpc_byte_buffer(); + +/* grpc_rb_byte_buffer_create_with_mark creates a grpc_rb_byte_buffer with a + * ruby mark object that will be kept alive while the byte_buffer is alive. */ +VALUE grpc_rb_byte_buffer_create_with_mark(VALUE mark, grpc_byte_buffer* bb); + +/* Gets the wrapped byte_buffer from its ruby object. */ +grpc_byte_buffer* grpc_rb_get_wrapped_byte_buffer(VALUE v); + +#endif /* GRPC_RB_BYTE_BUFFER_H_ */ diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c new file mode 100644 index 0000000000000..07f70e041ac5c --- /dev/null +++ b/src/ruby/ext/grpc/rb_call.c @@ -0,0 +1,542 @@ +/* + * + * Copyright 2014, 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 "rb_call.h" + +#include + +#include +#include "rb_byte_buffer.h" +#include "rb_completion_queue.h" +#include "rb_metadata.h" +#include "rb_status.h" +#include "rb_grpc.h" + +/* id_cq is the name of the hidden ivar that preserves a reference to a + * completion queue */ +static ID id_cq; + +/* id_flags is the name of the hidden ivar that preserves the value of + * the flags used to create metadata from a Hash */ +static ID id_flags; + +/* id_input_md is the name of the hidden ivar that preserves the hash used to + * create metadata, so that references to the strings it contains last as long + * as the call the metadata is added to. */ +static ID id_input_md; + +/* id_metadata is name of the attribute used to access the metadata hash + * received by the call and subsequently saved on it. */ +static ID id_metadata; + +/* id_status is name of the attribute used to access the status object + * received by the call and subsequently saved on it. */ +static ID id_status; + +/* hash_all_calls is a hash of Call address -> reference count that is used to + * track the creation and destruction of rb_call instances. + */ +static VALUE hash_all_calls; + +/* Destroys a Call. */ +void grpc_rb_call_destroy(void *p) { + grpc_call *call = NULL; + VALUE ref_count = Qnil; + if (p == NULL) { + return; + }; + call = (grpc_call *)p; + + ref_count = rb_hash_aref(hash_all_calls, OFFT2NUM((VALUE)call)); + if (ref_count == Qnil) { + return; /* No longer in the hash, so already deleted */ + } else if (NUM2UINT(ref_count) == 1) { + rb_hash_delete(hash_all_calls, OFFT2NUM((VALUE)call)); + grpc_call_destroy(call); + } else { + rb_hash_aset(hash_all_calls, OFFT2NUM((VALUE)call), + UINT2NUM(NUM2UINT(ref_count) - 1)); + } +} + +/* Error code details is a hash containing text strings describing errors */ +VALUE rb_error_code_details; + +/* Obtains the error detail string for given error code */ +const char* grpc_call_error_detail_of(grpc_call_error err) { + VALUE detail_ref = rb_hash_aref(rb_error_code_details, UINT2NUM(err)); + const char* detail = "unknown error code!"; + if (detail_ref != Qnil) { + detail = StringValueCStr(detail_ref); + } + return detail; +} + +/* grpc_rb_call_add_metadata_hash_cb is the hash iteration callback used by + grpc_rb_call_add_metadata. +*/ +int grpc_rb_call_add_metadata_hash_cb(VALUE key, VALUE val, VALUE call_obj) { + grpc_call *call = NULL; + grpc_metadata *md = NULL; + VALUE md_obj = Qnil; + VALUE md_obj_args[2]; + VALUE flags = rb_ivar_get(call_obj, id_flags); + grpc_call_error err; + int array_length; + int i; + + /* Construct a metadata object from key and value and add it */ + Data_Get_Struct(call_obj, grpc_call, call); + md_obj_args[0] = key; + + if (TYPE(val) == T_ARRAY) { + /* If the value is an array, add each value in the array separately */ + array_length = RARRAY_LEN(val); + for (i = 0; i < array_length; i++) { + md_obj_args[1] = rb_ary_entry(val, i); + md_obj = rb_class_new_instance(2, md_obj_args, rb_cMetadata); + md = grpc_rb_get_wrapped_metadata(md_obj); + err = grpc_call_add_metadata(call, md, NUM2UINT(flags)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "add metadata failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + return ST_STOP; + } + } + } else { + md_obj_args[1] = val; + md_obj = rb_class_new_instance(2, md_obj_args, rb_cMetadata); + md = grpc_rb_get_wrapped_metadata(md_obj); + err = grpc_call_add_metadata(call, md, NUM2UINT(flags)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "add metadata failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + return ST_STOP; + } + } + + return ST_CONTINUE; +} + +/* + call-seq: + call.add_metadata(completion_queue, hash_elements, flags=nil) + + Add metadata elements to the call from a ruby hash, to be sent upon + invocation. flags is a bit-field combination of the write flags defined + above. REQUIRES: grpc_call_start_invoke/grpc_call_accept have not been + called on this call. Produces no events. */ + +static VALUE grpc_rb_call_add_metadata(int argc, VALUE *argv, VALUE self) { + VALUE metadata; + VALUE flags = Qnil; + ID id_size = rb_intern("size"); + + /* "11" == 1 mandatory args, 1 (flags) is optional */ + rb_scan_args(argc, argv, "11", &metadata, &flags); + if (NIL_P(flags)) { + flags = UINT2NUM(0); /* Default to no flags */ + } + if (TYPE(metadata) != T_HASH) { + rb_raise(rb_eTypeError, "add metadata failed: metadata should be a hash"); + return Qnil; + } + if (NUM2UINT(rb_funcall(metadata, id_size, 0)) == 0) { + return Qnil; + } + rb_ivar_set(self, id_flags, flags); + rb_ivar_set(self, id_input_md, metadata); + rb_hash_foreach(metadata, grpc_rb_call_add_metadata_hash_cb, self); + return Qnil; +} + +/* Called by clients to cancel an RPC on the server. + Can be called multiple times, from any thread. */ +static VALUE grpc_rb_call_cancel(VALUE self) { + grpc_call *call = NULL; + grpc_call_error err; + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_cancel(call); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "cancel failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + return Qnil; +} + +/* + call-seq: + call.start_invoke(completion_queue, tag, flags=nil) + + Invoke the RPC. Starts sending metadata and request headers on the wire. + flags is a bit-field combination of the write flags defined above. + REQUIRES: Can be called at most once per call. + Can only be called on the client. + Produces a GRPC_INVOKE_ACCEPTED event on completion. */ +static VALUE grpc_rb_call_start_invoke(int argc, VALUE *argv, VALUE self) { + VALUE cqueue = Qnil; + VALUE invoke_accepted_tag = Qnil; + VALUE metadata_read_tag = Qnil; + VALUE finished_tag = Qnil; + VALUE flags = Qnil; + grpc_call *call = NULL; + grpc_completion_queue *cq = NULL; + grpc_call_error err; + + /* "41" == 4 mandatory args, 1 (flags) is optional */ + rb_scan_args(argc, argv, "41", &cqueue, &invoke_accepted_tag, + &metadata_read_tag, &finished_tag, &flags); + if (NIL_P(flags)) { + flags = UINT2NUM(0); /* Default to no flags */ + } + cq = grpc_rb_get_wrapped_completion_queue(cqueue); + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_start_invoke(call, cq, ROBJECT(invoke_accepted_tag), + ROBJECT(metadata_read_tag), + ROBJECT(finished_tag), + NUM2UINT(flags)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "invoke failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + /* Add the completion queue as an instance attribute, prevents it from being + * GCed until this call object is GCed */ + rb_ivar_set(self, id_cq, cqueue); + + return Qnil; +} + +/* Initiate a read on a call. Output event contains a byte buffer with the + result of the read. + REQUIRES: No other reads are pending on the call. It is only safe to start + the next read after the corresponding read event is received. */ +static VALUE grpc_rb_call_start_read(VALUE self, VALUE tag) { + grpc_call *call = NULL; + grpc_call_error err; + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_start_read(call, ROBJECT(tag)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "start read failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + return Qnil; +} + +/* + call-seq: + status = call.status + + Gets the status object saved the call. */ +static VALUE grpc_rb_call_get_status(VALUE self) { + return rb_ivar_get(self, id_status); +} + +/* + call-seq: + call.status = status + + Saves a status object on the call. */ +static VALUE grpc_rb_call_set_status(VALUE self, VALUE status) { + if (!NIL_P(status) && rb_obj_class(status) != rb_cStatus) { + rb_raise(rb_eTypeError, "bad status: got:<%s> want: ", + rb_obj_classname(status)); + return Qnil; + } + + return rb_ivar_set(self, id_status, status); +} + +/* + call-seq: + metadata = call.metadata + + Gets the metadata object saved the call. */ +static VALUE grpc_rb_call_get_metadata(VALUE self) { + return rb_ivar_get(self, id_metadata); +} + +/* + call-seq: + call.metadata = metadata + + Saves the metadata hash on the call. */ +static VALUE grpc_rb_call_set_metadata(VALUE self, VALUE metadata) { + if (!NIL_P(metadata) && TYPE(metadata) != T_HASH) { + rb_raise(rb_eTypeError, "bad metadata: got:<%s> want: ", + rb_obj_classname(metadata)); + return Qnil; + } + + return rb_ivar_set(self, id_metadata, metadata); +} + +/* + call-seq: + call.start_write(byte_buffer, tag, flags=nil) + + Queue a byte buffer for writing. + flags is a bit-field combination of the write flags defined above. + A write with byte_buffer null is allowed, and will not send any bytes on the + wire. If this is performed without GRPC_WRITE_BUFFER_HINT flag it provides + a mechanism to flush any previously buffered writes to outgoing flow control. + REQUIRES: No other writes are pending on the call. It is only safe to + start the next write after the corresponding write_accepted event + is received. + GRPC_INVOKE_ACCEPTED must have been received by the application + prior to calling this on the client. On the server, + grpc_call_accept must have been called successfully. + Produces a GRPC_WRITE_ACCEPTED event. */ +static VALUE grpc_rb_call_start_write(int argc, VALUE *argv, VALUE self) { + VALUE byte_buffer = Qnil; + VALUE tag = Qnil; + VALUE flags = Qnil; + grpc_call *call = NULL; + grpc_byte_buffer *bfr = NULL; + grpc_call_error err; + + /* "21" == 2 mandatory args, 1 (flags) is optional */ + rb_scan_args(argc, argv, "21", &byte_buffer, &tag, &flags); + if (NIL_P(flags)) { + flags = UINT2NUM(0); /* Default to no flags */ + } + bfr = grpc_rb_get_wrapped_byte_buffer(byte_buffer); + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_start_write(call, bfr, ROBJECT(tag), NUM2UINT(flags)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "start write failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + return Qnil; +} + +/* Queue a status for writing. + REQUIRES: No other writes are pending on the call. It is only safe to + start the next write after the corresponding write_accepted event + is received. + GRPC_INVOKE_ACCEPTED must have been received by the application + prior to calling this. + Only callable on the server. + Produces a GRPC_FINISHED event when the status is sent and the stream is + fully closed */ +static VALUE grpc_rb_call_start_write_status(VALUE self, VALUE status, + VALUE tag) { + grpc_call *call = NULL; + grpc_status *sts = grpc_rb_get_wrapped_status(status); + grpc_call_error err; + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_start_write_status(call, *sts, ROBJECT(tag)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "start write status: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + return Qnil; +} + +/* No more messages to send. + REQUIRES: No other writes are pending on the call. */ +static VALUE grpc_rb_call_writes_done(VALUE self, VALUE tag) { + grpc_call *call = NULL; + grpc_call_error err; + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_writes_done(call, ROBJECT(tag)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "writes done: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + return Qnil; +} + +/* call-seq: + call.accept(completion_queue, flags=nil) + + Accept an incoming RPC, binding a completion queue to it. + To be called after adding metadata to the call, but before sending + messages. + flags is a bit-field combination of the write flags defined above. + REQUIRES: Can be called at most once per call. + Can only be called on the server. + Produces no events. */ +static VALUE grpc_rb_call_accept(int argc, VALUE *argv, VALUE self) { + VALUE cqueue = Qnil; + VALUE finished_tag = Qnil; + VALUE flags = Qnil; + grpc_call *call = NULL; + grpc_completion_queue *cq = NULL; + grpc_call_error err; + + /* "21" == 2 mandatory args, 1 (flags) is optional */ + rb_scan_args(argc, argv, "21", &cqueue, &finished_tag, &flags); + if (NIL_P(flags)) { + flags = UINT2NUM(0); /* Default to no flags */ + } + cq = grpc_rb_get_wrapped_completion_queue(cqueue); + Data_Get_Struct(self, grpc_call, call); + err = grpc_call_accept(call, cq, ROBJECT(finished_tag), NUM2UINT(flags)); + if (err != GRPC_CALL_OK) { + rb_raise(rb_eCallError, "accept failed: %s (code=%d)", + grpc_call_error_detail_of(err), err); + } + + /* Add the completion queue as an instance attribute, prevents it from being + * GCed until this call object is GCed */ + rb_ivar_set(self, id_cq, cqueue); + + return Qnil; +} + +/* rb_cCall is the ruby class that proxies grpc_call. */ +VALUE rb_cCall = Qnil; + +/* rb_eCallError is the ruby class of the exception thrown during call + operations; */ +VALUE rb_eCallError = Qnil; + +void Init_google_rpc_error_codes() { + /* Constants representing the error codes of grpc_call_error in grpc.h */ + VALUE rb_RpcErrors = rb_define_module_under(rb_mGoogleRPC, "RpcErrors"); + rb_define_const(rb_RpcErrors, "OK", UINT2NUM(GRPC_CALL_OK)); + rb_define_const(rb_RpcErrors, "ERROR", UINT2NUM(GRPC_CALL_ERROR)); + rb_define_const(rb_RpcErrors, "NOT_ON_SERVER", + UINT2NUM(GRPC_CALL_ERROR_NOT_ON_SERVER)); + rb_define_const(rb_RpcErrors, "NOT_ON_CLIENT", + UINT2NUM(GRPC_CALL_ERROR_NOT_ON_CLIENT)); + rb_define_const(rb_RpcErrors, "ALREADY_INVOKED", + UINT2NUM(GRPC_CALL_ERROR_ALREADY_INVOKED)); + rb_define_const(rb_RpcErrors, "NOT_INVOKED", + UINT2NUM(GRPC_CALL_ERROR_NOT_INVOKED)); + rb_define_const(rb_RpcErrors, "ALREADY_FINISHED", + UINT2NUM(GRPC_CALL_ERROR_ALREADY_FINISHED)); + rb_define_const(rb_RpcErrors, "TOO_MANY_OPERATIONS", + UINT2NUM(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS)); + rb_define_const(rb_RpcErrors, "INVALID_FLAGS", + UINT2NUM(GRPC_CALL_ERROR_INVALID_FLAGS)); + + /* Add the detail strings to a Hash */ + rb_error_code_details = rb_hash_new(); + rb_hash_aset(rb_error_code_details, + UINT2NUM(GRPC_CALL_OK), rb_str_new2("ok")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR), + rb_str_new2("unknown error")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR_NOT_ON_SERVER), + rb_str_new2("not available on a server")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR_NOT_ON_CLIENT), + rb_str_new2("not available on a client")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR_ALREADY_INVOKED), + rb_str_new2("call is already invoked")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR_NOT_INVOKED), + rb_str_new2("call is not yet invoked")); + rb_hash_aset(rb_error_code_details, + UINT2NUM(GRPC_CALL_ERROR_ALREADY_FINISHED), + rb_str_new2("call is already finished")); + rb_hash_aset(rb_error_code_details, + UINT2NUM(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS), + rb_str_new2("outstanding read or write present")); + rb_hash_aset(rb_error_code_details, UINT2NUM(GRPC_CALL_ERROR_INVALID_FLAGS), + rb_str_new2("a bad flag was given")); + rb_define_const(rb_RpcErrors, "ErrorMessages", rb_error_code_details); + rb_obj_freeze(rb_error_code_details); +} + +void Init_google_rpc_call() { + /* CallError inherits from Exception to signal that it is non-recoverable */ + rb_eCallError = rb_define_class_under(rb_mGoogleRPC, "CallError", + rb_eException); + rb_cCall = rb_define_class_under(rb_mGoogleRPC, "Call", rb_cObject); + + /* Prevent allocation or inialization of the Call class */ + rb_define_alloc_func(rb_cCall, grpc_rb_cannot_alloc); + rb_define_method(rb_cCall, "initialize", grpc_rb_cannot_init, 0); + rb_define_method(rb_cCall, "initialize_copy", grpc_rb_cannot_init_copy, 1); + + /* Add ruby analogues of the Call methods. */ + rb_define_method(rb_cCall, "accept", grpc_rb_call_accept, -1); + rb_define_method(rb_cCall, "add_metadata", grpc_rb_call_add_metadata, + -1); + rb_define_method(rb_cCall, "cancel", grpc_rb_call_cancel, 0); + rb_define_method(rb_cCall, "start_invoke", grpc_rb_call_start_invoke, -1); + rb_define_method(rb_cCall, "start_read", grpc_rb_call_start_read, 1); + rb_define_method(rb_cCall, "start_write", grpc_rb_call_start_write, -1); + rb_define_method(rb_cCall, "start_write_status", + grpc_rb_call_start_write_status, 2); + rb_define_method(rb_cCall, "writes_done", grpc_rb_call_writes_done, 1); + rb_define_method(rb_cCall, "status", grpc_rb_call_get_status, 0); + rb_define_method(rb_cCall, "status=", grpc_rb_call_set_status, 1); + rb_define_method(rb_cCall, "metadata", grpc_rb_call_get_metadata, 0); + rb_define_method(rb_cCall, "metadata=", grpc_rb_call_set_metadata, 1); + + /* Ids used to support call attributes */ + id_metadata = rb_intern("metadata"); + id_status = rb_intern("status"); + + /* Ids used by the c wrapping internals. */ + id_cq = rb_intern("__cq"); + id_flags = rb_intern("__flags"); + id_input_md = rb_intern("__input_md"); + + /* The hash for reference counting calls, to ensure they can't be destroyed + * more than once */ + hash_all_calls = rb_hash_new(); + rb_define_const(rb_cCall, "INTERNAL_ALL_CALLs", hash_all_calls); + + Init_google_rpc_error_codes(); +} + +/* Gets the call from the ruby object */ +grpc_call* grpc_rb_get_wrapped_call(VALUE v) { + grpc_call *c = NULL; + Data_Get_Struct(v, grpc_call, c); + return c; +} + +/* Obtains the wrapped object for a given call */ +VALUE grpc_rb_wrap_call(grpc_call* c) { + VALUE obj = Qnil; + if (c == NULL) { + return Qnil; + } + obj = rb_hash_aref(hash_all_calls, OFFT2NUM((VALUE)c)); + if (obj == Qnil) { /* Not in the hash add it */ + rb_hash_aset(hash_all_calls, OFFT2NUM((VALUE)c), UINT2NUM(1)); + } else { + rb_hash_aset(hash_all_calls, OFFT2NUM((VALUE)c), + UINT2NUM(NUM2UINT(obj) + 1)); + } + return Data_Wrap_Struct(rb_cCall, GC_NOT_MARKED, grpc_rb_call_destroy, + c); +} diff --git a/src/ruby/ext/grpc/rb_call.h b/src/ruby/ext/grpc/rb_call.h new file mode 100644 index 0000000000000..422e7e7a6cc4b --- /dev/null +++ b/src/ruby/ext/grpc/rb_call.h @@ -0,0 +1,59 @@ +/* + * + * Copyright 2014, 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_RB_CALL_H_ +#define GRPC_RB_CALL_H_ + +#include +#include + +/* Gets the wrapped call from a VALUE. */ +grpc_call* grpc_rb_get_wrapped_call(VALUE v); + +/* Gets the VALUE corresponding to given grpc_call. */ +VALUE grpc_rb_wrap_call(grpc_call* c); + +/* Provides the details of an call error */ +const char* grpc_call_error_detail_of(grpc_call_error err); + +/* rb_cCall is the Call class whose instances proxy grpc_call. */ +extern VALUE rb_cCall; + +/* rb_cCallError is the ruby class of the exception thrown during call + operations. */ +extern VALUE rb_eCallError; + +/* Initializes the Call class. */ +void Init_google_rpc_call(); + +#endif /* GRPC_RB_CALL_H_ */ diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c new file mode 100644 index 0000000000000..f4c09a392a195 --- /dev/null +++ b/src/ruby/ext/grpc/rb_channel.c @@ -0,0 +1,235 @@ +/* + * + * Copyright 2014, 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 "rb_channel.h" + +#include + +#include +#include "rb_grpc.h" +#include "rb_call.h" +#include "rb_channel_args.h" +#include "rb_completion_queue.h" +#include "rb_server.h" + +/* id_channel is the name of the hidden ivar that preserves a reference to the + * channel on a call, so that calls are not GCed before their channel. */ +static ID id_channel; + +/* id_target is the name of the hidden ivar that preserves a reference to the + * target string used to create the call, preserved so that is does not get + * GCed before the channel */ +static ID id_target; + +/* Used during the conversion of a hash to channel args during channel setup */ +static VALUE rb_cChannelArgs; + +/* grpc_rb_channel wraps a grpc_channel. It provides a peer ruby object, + * 'mark' to minimize copying when a channel is created from ruby. */ +typedef struct grpc_rb_channel { + /* Holder of ruby objects involved in constructing the channel */ + VALUE mark; + /* The actual channel */ + grpc_channel *wrapped; +} grpc_rb_channel; + +/* Destroys Channel instances. */ +static void grpc_rb_channel_free(void *p) { + grpc_rb_channel *ch = NULL; + if (p == NULL) { + return; + }; + ch = (grpc_rb_channel *)p; + + /* Deletes the wrapped object if the mark object is Qnil, which indicates + * that no other object is the actual owner. */ + if (ch->wrapped != NULL && ch->mark == Qnil) { + grpc_channel_destroy(ch->wrapped); + rb_warning("channel gc: destroyed the c channel"); + } else { + rb_warning("channel gc: did not destroy the c channel"); + } + + xfree(p); +} + +/* Protects the mark object from GC */ +static void grpc_rb_channel_mark(void *p) { + grpc_rb_channel *channel = NULL; + if (p == NULL) { + return; + } + channel = (grpc_rb_channel *)p; + if (channel->mark != Qnil) { + rb_gc_mark(channel->mark); + } +} + +/* Allocates grpc_rb_channel instances. */ +static VALUE grpc_rb_channel_alloc(VALUE cls) { + grpc_rb_channel *wrapper = ALLOC(grpc_rb_channel); + wrapper->wrapped = NULL; + wrapper->mark = Qnil; + return Data_Wrap_Struct(cls, grpc_rb_channel_mark, grpc_rb_channel_free, + wrapper); +} + +/* Initializes channel instances */ +static VALUE grpc_rb_channel_init(VALUE self, VALUE target, + VALUE channel_args) { + grpc_rb_channel *wrapper = NULL; + grpc_channel *ch = NULL; + char *target_chars = StringValueCStr(target); + grpc_channel_args args; + MEMZERO(&args, grpc_channel_args, 1); + + Data_Get_Struct(self, grpc_rb_channel, wrapper); + grpc_rb_hash_convert_to_channel_args(channel_args, &args); + ch = grpc_channel_create(target_chars, &args); + if (args.args != NULL) { + xfree(args.args); /* Allocated by grpc_rb_hash_convert_to_channel_args */ + } + if (ch == NULL) { + rb_raise(rb_eRuntimeError, "could not create an rpc channel to target:%s", + target_chars); + } + rb_ivar_set(self, id_target, target); + wrapper->wrapped = ch; + return self; +} + +/* Clones Channel instances. + + Gives Channel a consistent implementation of Ruby's object copy/dup + protocol. */ +static VALUE grpc_rb_channel_init_copy(VALUE copy, VALUE orig) { + grpc_rb_channel *orig_ch = NULL; + grpc_rb_channel *copy_ch = NULL; + + if (copy == orig) { + return copy; + } + + /* Raise an error if orig is not a channel object or a subclass. */ + if (TYPE(orig) != T_DATA || + RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_channel_free) { + rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cChannel)); + } + + Data_Get_Struct(orig, grpc_rb_channel, orig_ch); + Data_Get_Struct(copy, grpc_rb_channel, copy_ch); + + /* use ruby's MEMCPY to make a byte-for-byte copy of the channel wrapper + * object. */ + MEMCPY(copy_ch, orig_ch, grpc_rb_channel, 1); + return copy; +} + +/* 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 method, VALUE host, + VALUE deadline) { + VALUE res = Qnil; + grpc_rb_channel *wrapper = NULL; + grpc_channel *ch = NULL; + grpc_call *call = NULL; + char *method_chars = StringValueCStr(method); + char *host_chars = StringValueCStr(host); + + Data_Get_Struct(self, grpc_rb_channel, wrapper); + ch = wrapper->wrapped; + if (ch == NULL) { + rb_raise(rb_eRuntimeError, "closed!"); + } + + call = grpc_channel_create_call(ch, 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); + } + res = grpc_rb_wrap_call(call); + + /* Make this channel an instance attribute of the call so that is is not GCed + * before the call. */ + rb_ivar_set(res, id_channel, self); + return res; +} + +/* Closes the channel, calling it's destroy method */ +static VALUE grpc_rb_channel_destroy(VALUE self) { + grpc_rb_channel *wrapper = NULL; + grpc_channel *ch = NULL; + + Data_Get_Struct(self, grpc_rb_channel, wrapper); + ch = wrapper->wrapped; + if (ch != NULL) { + grpc_channel_destroy(ch); + wrapper->wrapped = NULL; + wrapper->mark = Qnil; + } + + return Qnil; +} + +/* rb_cChannel is the ruby class that proxies grpc_channel. */ +VALUE rb_cChannel = Qnil; + +void Init_google_rpc_channel() { + rb_cChannelArgs = rb_define_class("TmpChannelArgs", rb_cObject); + rb_cChannel = rb_define_class_under(rb_mGoogleRPC, "Channel", rb_cObject); + + /* Allocates an object managed by the ruby runtime */ + rb_define_alloc_func(rb_cChannel, grpc_rb_channel_alloc); + + /* Provides a ruby constructor and support for dup/clone. */ + rb_define_method(rb_cChannel, "initialize", grpc_rb_channel_init, 2); + rb_define_method(rb_cChannel, "initialize_copy", grpc_rb_channel_init_copy, + 1); + + /* Add ruby analogues of the Channel methods. */ + rb_define_method(rb_cChannel, "create_call", grpc_rb_channel_create_call, 3); + rb_define_method(rb_cChannel, "destroy", grpc_rb_channel_destroy, 0); + rb_define_alias(rb_cChannel, "close", "destroy"); + + id_channel = rb_intern("__channel"); + id_target = rb_intern("__target"); +} + +/* Gets the wrapped channel from the ruby wrapper */ +grpc_channel* grpc_rb_get_wrapped_channel(VALUE v) { + grpc_rb_channel *wrapper = NULL; + Data_Get_Struct(v, grpc_rb_channel, wrapper); + return wrapper->wrapped; +} diff --git a/src/ruby/ext/grpc/rb_channel.h b/src/ruby/ext/grpc/rb_channel.h new file mode 100644 index 0000000000000..b0a3634474660 --- /dev/null +++ b/src/ruby/ext/grpc/rb_channel.h @@ -0,0 +1,49 @@ +/* + * + * Copyright 2014, 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_RB_CHANNEL_H_ +#define GRPC_RB_CHANNEL_H_ + +#include +#include + +/* rb_cChannel is the Channel class whose instances proxy grpc_channel. */ +extern VALUE rb_cChannel; + +/* Initializes the Channel class. */ +void Init_google_rpc_channel(); + +/* Gets the wrapped channel from the ruby wrapper */ +grpc_channel* grpc_rb_get_wrapped_channel(VALUE v); + +#endif /* GRPC_RB_CHANNEL_H_ */ diff --git a/src/ruby/ext/grpc/rb_channel_args.c b/src/ruby/ext/grpc/rb_channel_args.c new file mode 100644 index 0000000000000..eebced0bd8067 --- /dev/null +++ b/src/ruby/ext/grpc/rb_channel_args.c @@ -0,0 +1,157 @@ +/* + * + * Copyright 2014, 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 "rb_channel_args.h" + +#include +#include + +#include "rb_grpc.h" + +/* A callback the processes the hash key values in channel_args hash */ +static int grpc_rb_channel_create_in_process_add_args_hash_cb(VALUE key, + VALUE val, + VALUE args_obj) { + const char* the_key; + grpc_channel_args* args; + + switch (TYPE(key)) { + + case T_STRING: + the_key = StringValuePtr(key); + break; + + case T_SYMBOL: + the_key = rb_id2name(SYM2ID(key)); + break; + + default: + rb_raise(rb_eTypeError, "bad chan arg: got <%s>, want ", + rb_obj_classname(key)); + return ST_STOP; + } + + Data_Get_Struct(args_obj, grpc_channel_args, args); + if (args->num_args <= 0) { + rb_raise(rb_eRuntimeError, "hash_cb bug: num_args is %lu for key:%s", + args->num_args, StringValueCStr(key)); + return ST_STOP; + } + + args->args[args->num_args - 1].key = (char *)the_key; + switch (TYPE(val)) { + + case T_SYMBOL: + args->args[args->num_args - 1].type = GRPC_ARG_STRING; + args->args[args->num_args - 1].value.string = + (char *)rb_id2name(SYM2ID(val)); + --args->num_args; + return ST_CONTINUE; + + case T_STRING: + args->args[args->num_args - 1].type = GRPC_ARG_STRING; + args->args[args->num_args - 1].value.string = StringValueCStr(val); + --args->num_args; + return ST_CONTINUE; + + case T_FIXNUM: + args->args[args->num_args - 1].type = GRPC_ARG_INTEGER; + args->args[args->num_args - 1].value.integer = NUM2INT(val); + --args->num_args; + return ST_CONTINUE; + + default: + rb_raise(rb_eTypeError, "%s: bad value: got <%s>, want ", + StringValueCStr(key), rb_obj_classname(val)); + return ST_STOP; + } + rb_raise(rb_eRuntimeError, "impl bug: hash_cb reached to far while on key:%s", + StringValueCStr(key)); + return ST_STOP; +} + +/* channel_convert_params allows the call to + grpc_rb_hash_convert_to_channel_args to be made within an rb_protect + exception-handler. This allows any allocated memory to be freed before + propagating any exception that occurs */ +typedef struct channel_convert_params { + VALUE src_hash; + grpc_channel_args* dst; +} channel_convert_params; + + +static VALUE grpc_rb_hash_convert_to_channel_args0(VALUE as_value) { + ID id_size = rb_intern("size"); + VALUE rb_cChannelArgs = rb_define_class("TmpChannelArgs", rb_cObject); + channel_convert_params* params = (channel_convert_params *)as_value; + size_t num_args = 0; + + if (!NIL_P(params->src_hash) && TYPE(params->src_hash) != T_HASH) { + rb_raise(rb_eTypeError, "bad channel args: got:<%s> want: a hash or nil", + rb_obj_classname(params->src_hash)); + return Qnil; + } + + if (TYPE(params->src_hash) == T_HASH) { + num_args = NUM2INT(rb_funcall(params->src_hash, id_size, 0)); + params->dst->num_args = num_args; + params->dst->args = ALLOC_N(grpc_arg, num_args); + MEMZERO(params->dst->args, grpc_arg, num_args); + rb_hash_foreach(params->src_hash, + grpc_rb_channel_create_in_process_add_args_hash_cb, + Data_Wrap_Struct(rb_cChannelArgs, GC_NOT_MARKED, + GC_DONT_FREE, params->dst)); + /* reset num_args as grpc_rb_channel_create_in_process_add_args_hash_cb + * decrements it during has processing */ + params->dst->num_args = num_args; + } + return Qnil; +} + +void grpc_rb_hash_convert_to_channel_args(VALUE src_hash, + grpc_channel_args* dst) { + channel_convert_params params; + int status = 0; + + /* Make a protected call to grpc_rb_hash_convert_channel_args */ + params.src_hash = src_hash; + params.dst = dst; + rb_protect(grpc_rb_hash_convert_to_channel_args0, (VALUE) ¶ms, &status); + if (status != 0) { + if (dst->args != NULL) { + /* Free any allocated memory before propagating the error */ + xfree(dst->args); + } + rb_jump_tag(status); + } +} diff --git a/src/ruby/ext/grpc/rb_channel_args.h b/src/ruby/ext/grpc/rb_channel_args.h new file mode 100644 index 0000000000000..bbff017c1ec09 --- /dev/null +++ b/src/ruby/ext/grpc/rb_channel_args.h @@ -0,0 +1,53 @@ +/* + * + * Copyright 2014, 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_RB_CHANNEL_ARGS_H_ +#define GRPC_RB_CHANNEL_ARGS_H_ + +#include +#include + +/* Converts a hash object containing channel args to a channel args instance. + * + * This func ALLOCs args->args. The caller is responsible for freeing it. If + * a ruby error is raised during processing of the hash values, the func takes + * care to deallocate any memory allocated so far, and propagate the error. + * + * @param src_hash A ruby hash + * @param dst the grpc_channel_args that the hash entries will be added to. + */ +void grpc_rb_hash_convert_to_channel_args(VALUE src_hash, + grpc_channel_args* dst); + + +#endif /* GRPC_RB_CHANNEL_ARGS_H_ */ diff --git a/src/ruby/ext/grpc/rb_completion_queue.c b/src/ruby/ext/grpc/rb_completion_queue.c new file mode 100644 index 0000000000000..62d045e971219 --- /dev/null +++ b/src/ruby/ext/grpc/rb_completion_queue.c @@ -0,0 +1,194 @@ +/* + * + * Copyright 2014, 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 "rb_completion_queue.h" + +#include + +#include +#include +#include "rb_grpc.h" +#include "rb_event.h" + +/* Used to allow grpc_completion_queue_next call to release the GIL */ +typedef struct next_call_stack { + grpc_completion_queue *cq; + grpc_event *event; + gpr_timespec timeout; + void* tag; +} next_call_stack; + +/* Calls grpc_completion_queue_next without holding the ruby GIL */ +static void *grpc_rb_completion_queue_next_no_gil( + next_call_stack *next_call) { + next_call->event = grpc_completion_queue_next(next_call->cq, + next_call->timeout); + return NULL; +} + +/* Calls grpc_completion_queue_pluck without holding the ruby GIL */ +static void *grpc_rb_completion_queue_pluck_no_gil( + next_call_stack *next_call) { + next_call->event = grpc_completion_queue_pluck(next_call->cq, + next_call->tag, + next_call->timeout); + return NULL; +} + + +/* Shuts down and drains the completion queue if necessary. + * + * This is done when the ruby completion queue object is about to be GCed. + */ +static void grpc_rb_completion_queue_shutdown_drain( + grpc_completion_queue* cq) { + next_call_stack next_call; + grpc_completion_type type; + int drained = 0; + MEMZERO(&next_call, next_call_stack, 1); + + grpc_completion_queue_shutdown(cq); + next_call.cq = cq; + next_call.event = NULL; + /* TODO(temiola): the timeout should be a module level constant that defaults + * to gpr_inf_future. + * + * - at the moment this does not work, it stalls. Using a small timeout like + * this one works, and leads to fast test run times; a longer timeout was + * causing unnecessary delays in the test runs. + * + * - investigate further, this is probably another example of C-level cleanup + * not working consistently in all cases. + */ + next_call.timeout = gpr_time_add(gpr_now(), gpr_time_from_micros(5e3)); + do { + rb_thread_call_without_gvl(grpc_rb_completion_queue_next_no_gil, + (void *)&next_call, NULL, NULL); + if (next_call.event == NULL) { + break; + } + type = next_call.event->type; + if (type != GRPC_QUEUE_SHUTDOWN) { + ++drained; + rb_warning("completion queue shutdown: %d undrained events", drained); + } + grpc_event_finish(next_call.event); + next_call.event = NULL; + } while (type != GRPC_QUEUE_SHUTDOWN); +} + +/* Helper function to free a completion queue. */ +static void grpc_rb_completion_queue_destroy(void *p) { + grpc_completion_queue *cq = NULL; + if (p == NULL) { + return; + } + cq = (grpc_completion_queue *)p; + grpc_rb_completion_queue_shutdown_drain(cq); + grpc_completion_queue_destroy(cq); +} + +/* Allocates a completion queue. */ +static VALUE grpc_rb_completion_queue_alloc(VALUE cls) { + grpc_completion_queue* cq = grpc_completion_queue_create(); + if (cq == NULL) { + rb_raise(rb_eArgError, + "could not create a completion queue: not sure why"); + } + return Data_Wrap_Struct(cls, GC_NOT_MARKED, + grpc_rb_completion_queue_destroy, cq); +} + +/* Blocks until the next event is available, and returns the event. */ +static VALUE grpc_rb_completion_queue_next(VALUE self, VALUE timeout) { + next_call_stack next_call; + MEMZERO(&next_call, next_call_stack, 1); + Data_Get_Struct(self, grpc_completion_queue, next_call.cq); + next_call.timeout = grpc_rb_time_timeval(timeout, /* absolute time*/ 0); + next_call.event = NULL; + rb_thread_call_without_gvl(grpc_rb_completion_queue_next_no_gil, + (void *)&next_call, NULL, NULL); + if (next_call.event == NULL) { + return Qnil; + } + return Data_Wrap_Struct(rb_cEvent, GC_NOT_MARKED, grpc_rb_event_finish, + next_call.event); +} + +/* Blocks until the next event for given tag is available, and returns the + * event. */ +static VALUE grpc_rb_completion_queue_pluck(VALUE self, VALUE tag, + VALUE timeout) { + next_call_stack next_call; + MEMZERO(&next_call, next_call_stack, 1); + Data_Get_Struct(self, grpc_completion_queue, next_call.cq); + next_call.timeout = grpc_rb_time_timeval(timeout, /* absolute time*/ 0); + next_call.tag = ROBJECT(tag); + next_call.event = NULL; + rb_thread_call_without_gvl(grpc_rb_completion_queue_pluck_no_gil, + (void *)&next_call, NULL, NULL); + if (next_call.event == NULL) { + return Qnil; + } + return Data_Wrap_Struct(rb_cEvent, GC_NOT_MARKED, grpc_rb_event_finish, + next_call.event); +} + +/* rb_cCompletionQueue is the ruby class that proxies grpc_completion_queue. */ +VALUE rb_cCompletionQueue = Qnil; + +void Init_google_rpc_completion_queue() { + rb_cCompletionQueue = rb_define_class_under(rb_mGoogleRPC, + "CompletionQueue", + rb_cObject); + + /* constructor: uses an alloc func without an initializer. Using a simple + alloc func works here as the grpc header does not specify any args for + this func, so no separate initialization step is necessary. */ + rb_define_alloc_func(rb_cCompletionQueue, grpc_rb_completion_queue_alloc); + + /* Add the next method that waits for the next event. */ + rb_define_method(rb_cCompletionQueue, "next", + grpc_rb_completion_queue_next, 1); + + /* Add the pluck method that waits for the next event of given tag */ + rb_define_method(rb_cCompletionQueue, "pluck", + grpc_rb_completion_queue_pluck, 2); +} + +/* Gets the wrapped completion queue from the ruby wrapper */ +grpc_completion_queue* grpc_rb_get_wrapped_completion_queue(VALUE v) { + grpc_completion_queue *cq = NULL; + Data_Get_Struct(v, grpc_completion_queue, cq); + return cq; +} diff --git a/src/ruby/ext/grpc/rb_completion_queue.h b/src/ruby/ext/grpc/rb_completion_queue.h new file mode 100644 index 0000000000000..1ec2718ed4ea8 --- /dev/null +++ b/src/ruby/ext/grpc/rb_completion_queue.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2014, 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_RB_COMPLETION_QUEUE_H_ +#define GRPC_RB_COMPLETION_QUEUE_H_ + +#include +#include + +/* Gets the wrapped completion queue from the ruby wrapper */ +grpc_completion_queue *grpc_rb_get_wrapped_completion_queue(VALUE v); + +/* rb_cCompletionQueue is the CompletionQueue class whose instances proxy + grpc_completion_queue. */ +extern VALUE rb_cCompletionQueue; + +/* Initializes the CompletionQueue class. */ +void Init_google_rpc_completion_queue(); + +#endif /* GRPC_RB_COMPLETION_QUEUE_H_ */ diff --git a/src/ruby/ext/grpc/rb_event.c b/src/ruby/ext/grpc/rb_event.c new file mode 100644 index 0000000000000..6f542f9eba89c --- /dev/null +++ b/src/ruby/ext/grpc/rb_event.c @@ -0,0 +1,284 @@ +/* + * + * Copyright 2014, 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 "rb_event.h" + +#include + +#include +#include "rb_grpc.h" +#include "rb_byte_buffer.h" +#include "rb_call.h" +#include "rb_metadata.h" +#include "rb_status.h" + +/* rb_mCompletionType is a ruby module that holds the completion type values */ +VALUE rb_mCompletionType = Qnil; + +/* Helper function to free an event. */ +void grpc_rb_event_finish(void *p) { + grpc_event_finish(p); +} + +static VALUE grpc_rb_event_result(VALUE self); + +/* Obtains the type of an event. */ +static VALUE grpc_rb_event_type(VALUE self) { + grpc_event *event = NULL; + Data_Get_Struct(self, grpc_event, event); + switch (event->type) { + case GRPC_QUEUE_SHUTDOWN: + return rb_const_get(rb_mCompletionType, rb_intern("QUEUE_SHUTDOWN")); + + case GRPC_READ: + return rb_const_get(rb_mCompletionType, rb_intern("READ")); + + case GRPC_INVOKE_ACCEPTED: + grpc_rb_event_result(self); /* validates the result */ + return rb_const_get(rb_mCompletionType, rb_intern("INVOKE_ACCEPTED")); + + case GRPC_WRITE_ACCEPTED: + grpc_rb_event_result(self); /* validates the result */ + return rb_const_get(rb_mCompletionType, rb_intern("WRITE_ACCEPTED")); + + case GRPC_FINISH_ACCEPTED: + grpc_rb_event_result(self); /* validates the result */ + return rb_const_get(rb_mCompletionType, rb_intern("FINISH_ACCEPTED")); + + case GRPC_CLIENT_METADATA_READ: + return rb_const_get(rb_mCompletionType, + rb_intern("CLIENT_METADATA_READ")); + + case GRPC_FINISHED: + return rb_const_get(rb_mCompletionType, rb_intern("FINISHED")); + + case GRPC_SERVER_RPC_NEW: + return rb_const_get(rb_mCompletionType, rb_intern("SERVER_RPC_NEW")); + + default: + rb_raise(rb_eRuntimeError, + "unrecognized event code for an rpc event:%d", event->type); + } + return Qnil; /* should not be reached */ +} + +/* Obtains the tag associated with an event. */ +static VALUE grpc_rb_event_tag(VALUE self) { + grpc_event *event = NULL; + Data_Get_Struct(self, grpc_event, event); + if (event->tag == NULL) { + return Qnil; + } + return (VALUE)event->tag; +} + +/* Obtains the call associated with an event. */ +static VALUE grpc_rb_event_call(VALUE self) { + grpc_event *ev = NULL; + Data_Get_Struct(self, grpc_event, ev); + if (ev->call != NULL) { + return grpc_rb_wrap_call(ev->call); + } + return Qnil; +} + +/* Obtains the metadata associated with an event. */ +static VALUE grpc_rb_event_metadata(VALUE self) { + grpc_event *event = NULL; + grpc_metadata *metadata = NULL; + VALUE key = Qnil; + VALUE new_ary = Qnil; + VALUE result = Qnil; + VALUE value = Qnil; + size_t count = 0; + size_t i = 0; + + /* Figure out which metadata to read. */ + Data_Get_Struct(self, grpc_event, event); + switch (event->type) { + + case GRPC_CLIENT_METADATA_READ: + count = event->data.client_metadata_read.count; + metadata = event->data.client_metadata_read.elements; + break; + + case GRPC_SERVER_RPC_NEW: + count = event->data.server_rpc_new.metadata_count; + metadata = event->data.server_rpc_new.metadata_elements; + break; + + default: + rb_raise(rb_eRuntimeError, + "bug: bad event type reading server metadata. got %d; want %d", + event->type, GRPC_SERVER_RPC_NEW); + return Qnil; + } + + result = rb_hash_new(); + for (i = 0; i < count; i++) { + key = rb_str_new2(metadata[i].key); + value = rb_hash_aref(result, key); + if (value == Qnil) { + value = rb_str_new( + metadata[i].value, + metadata[i].value_length); + rb_hash_aset(result, key, value); + } else if (TYPE(value) == T_ARRAY) { + /* Add the string to the returned array */ + rb_ary_push(value, rb_str_new( + metadata[i].value, + metadata[i].value_length)); + } else { + /* Add the current value with this key and the new one to an array */ + new_ary = rb_ary_new(); + rb_ary_push(new_ary, value); + rb_ary_push(new_ary, rb_str_new( + metadata[i].value, + metadata[i].value_length)); + rb_hash_aset(result, key, new_ary); + } + } + return result; +} + +/* Obtains the data associated with an event. */ +static VALUE grpc_rb_event_result(VALUE self) { + grpc_event *event = NULL; + Data_Get_Struct(self, grpc_event, event); + + switch (event->type) { + + case GRPC_QUEUE_SHUTDOWN: + return Qnil; + + case GRPC_READ: + return grpc_rb_byte_buffer_create_with_mark(self, event->data.read); + + case GRPC_FINISH_ACCEPTED: + if (event->data.finish_accepted == GRPC_OP_OK) { + return Qnil; + } + rb_raise(rb_eEventError, "finish failed, not sure why (code=%d)", + event->data.finish_accepted); + break; + + case GRPC_INVOKE_ACCEPTED: + if (event->data.invoke_accepted == GRPC_OP_OK) { + return Qnil; + } + rb_raise(rb_eEventError, "invoke failed, not sure why (code=%d)", + event->data.invoke_accepted); + break; + + case GRPC_WRITE_ACCEPTED: + if (event->data.write_accepted == GRPC_OP_OK) { + return Qnil; + } + rb_raise(rb_eEventError, "write failed, not sure why (code=%d)", + event->data.invoke_accepted); + break; + + case GRPC_CLIENT_METADATA_READ: + return grpc_rb_event_metadata(self); + + case GRPC_FINISHED: + return grpc_rb_status_create_with_mark(self, &event->data.finished); + break; + + case GRPC_SERVER_RPC_NEW: + return rb_struct_new( + rb_sNewServerRpc, + rb_str_new2(event->data.server_rpc_new.method), + rb_str_new2(event->data.server_rpc_new.host), + Data_Wrap_Struct( + rb_cTimeVal, GC_NOT_MARKED, GC_DONT_FREE, + (void *)&event->data.server_rpc_new.deadline), + grpc_rb_event_metadata(self), + NULL); + + default: + rb_raise(rb_eRuntimeError, + "unrecognized event code for an rpc event:%d", event->type); + } + + return Qfalse; +} + +/* rb_sNewServerRpc is the struct that holds new server rpc details. */ +VALUE rb_sNewServerRpc = Qnil; + +/* rb_cEvent is the Event class whose instances proxy grpc_event */ +VALUE rb_cEvent = Qnil; + +/* rb_eEventError is the ruby class of the exception thrown on failures during + rpc event processing. */ +VALUE rb_eEventError = Qnil; + +void Init_google_rpc_event() { + rb_eEventError = rb_define_class_under(rb_mGoogleRPC, "EventError", + rb_eStandardError); + rb_cEvent = rb_define_class_under(rb_mGoogleRPC, "Event", rb_cObject); + rb_sNewServerRpc = rb_struct_define("NewServerRpc", "method", "host", + "deadline", "metadata", NULL); + + /* Prevent allocation or inialization from ruby. */ + rb_define_alloc_func(rb_cEvent, grpc_rb_cannot_alloc); + rb_define_method(rb_cEvent, "initialize", grpc_rb_cannot_init, 0); + rb_define_method(rb_cEvent, "initialize_copy", grpc_rb_cannot_init_copy, 1); + + /* Accessors for the data available in an event. */ + rb_define_method(rb_cEvent, "call", grpc_rb_event_call, 0); + rb_define_method(rb_cEvent, "result", grpc_rb_event_result, 0); + rb_define_method(rb_cEvent, "tag", grpc_rb_event_tag, 0); + rb_define_method(rb_cEvent, "type", grpc_rb_event_type, 0); + + /* Constants representing the completion types */ + rb_mCompletionType = rb_define_module_under(rb_mGoogleRPC, "CompletionType"); + rb_define_const(rb_mCompletionType, "QUEUE_SHUTDOWN", + INT2NUM(GRPC_QUEUE_SHUTDOWN)); + rb_define_const(rb_mCompletionType, "READ", INT2NUM(GRPC_READ)); + rb_define_const(rb_mCompletionType, "INVOKE_ACCEPTED", + INT2NUM(GRPC_INVOKE_ACCEPTED)); + rb_define_const(rb_mCompletionType, "WRITE_ACCEPTED", + INT2NUM(GRPC_WRITE_ACCEPTED)); + rb_define_const(rb_mCompletionType, "FINISH_ACCEPTED", + INT2NUM(GRPC_FINISH_ACCEPTED)); + rb_define_const(rb_mCompletionType, "CLIENT_METADATA_READ", + INT2NUM(GRPC_CLIENT_METADATA_READ)); + rb_define_const(rb_mCompletionType, "FINISHED", + INT2NUM(GRPC_FINISHED)); + rb_define_const(rb_mCompletionType, "SERVER_RPC_NEW", + INT2NUM(GRPC_SERVER_RPC_NEW)); + rb_define_const(rb_mCompletionType, "RESERVED", + INT2NUM(GRPC_COMPLETION_DO_NOT_USE)); +} diff --git a/src/ruby/ext/grpc/rb_event.h b/src/ruby/ext/grpc/rb_event.h new file mode 100644 index 0000000000000..c398b6c6c81fc --- /dev/null +++ b/src/ruby/ext/grpc/rb_event.h @@ -0,0 +1,55 @@ +/* + * + * Copyright 2014, 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_RB_EVENT_H_ +#define GRPC_RB_EVENT_H_ + +#include + +/* rb_sNewServerRpc is the struct that holds new server rpc details. */ +extern VALUE rb_sNewServerRpc; + +/* rb_cEvent is the Event class whose instances proxy grpc_event. */ +extern VALUE rb_cEvent; + +/* rb_cEventError is the ruby class that acts the exception thrown during rpc + event processing. */ +extern VALUE rb_eEventError; + +/* Helper function to free an event. */ +void grpc_rb_event_finish(void *p); + +/* Initializes the Event and EventError classes. */ +void Init_google_rpc_event(); + +#endif /* GRPC_RB_EVENT_H_ */ diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c new file mode 100644 index 0000000000000..5cc45cf743af7 --- /dev/null +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -0,0 +1,230 @@ +/* + * + * Copyright 2014, 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 "rb_grpc.h" + +#include +#include +#include + +#include +#include +#include "rb_byte_buffer.h" +#include "rb_call.h" +#include "rb_channel.h" +#include "rb_completion_queue.h" +#include "rb_event.h" +#include "rb_metadata.h" +#include "rb_server.h" +#include "rb_status.h" + +/* Define common vars and funcs declared in rb.h */ +const RUBY_DATA_FUNC GC_NOT_MARKED = NULL; +const RUBY_DATA_FUNC GC_DONT_FREE = NULL; + +VALUE rb_cTimeVal = Qnil; + +/* Alloc func that blocks allocation of a given object by raising an + * exception. */ +VALUE grpc_rb_cannot_alloc(VALUE cls) { + rb_raise(rb_eTypeError, + "allocation of %s only allowed from the gRPC native layer", + rb_class2name(cls)); + return Qnil; +} + +/* Init func that fails by raising an exception. */ +VALUE grpc_rb_cannot_init(VALUE self) { + rb_raise(rb_eTypeError, + "initialization of %s only allowed from the gRPC native layer", + rb_obj_classname(self)); + return Qnil; +} + +/* Init/Clone func that fails by raising an exception. */ +VALUE grpc_rb_cannot_init_copy(VALUE copy, VALUE self) { + rb_raise(rb_eTypeError, + "initialization of %s only allowed from the gRPC native layer", + rb_obj_classname(copy)); + return Qnil; +} + +/* id_tv_{,u}sec are accessor methods on Ruby Time instances. */ +static ID id_tv_sec; +static ID id_tv_nsec; + +/** + * grpc_rb_time_timeval creates a time_eval from a ruby time object. + * + * This func is copied from ruby source, MRI/source/time.c, which is published + * under the same license as the ruby.h, on which the entire extensions is + * based. + */ +gpr_timespec grpc_rb_time_timeval(VALUE time, int interval) { + gpr_timespec t; + gpr_timespec *time_const; + const char *tstr = interval ? "time interval" : "time"; + const char *want = " want |