Incorporating ruby into the master grpc repository.
	Change on 2014/12/01 by nnoble <nnoble@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=81111468
diff --git a/src/ruby/.gitignore b/src/ruby/.gitignore
new file mode 100755
index 0000000..62fcb4f
--- /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 0000000..60a4aad
--- /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 0000000..4d41544
--- /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 0000000..8377866
--- /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_repo_dir>
+$ 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_repo_dir>
+$ 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 <your-git5-branch> net/grpc
+$
+$ # build the C library and install it in $HOME/grpc_dev
+$ <google3>/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 <this_dir>
+$
+$ # 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 0000000..11b3d04
--- /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 0000000..9278a84
--- /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 0000000..de18a50
--- /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 0000000..f8cf858
--- /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 0000000..72a1f6b
--- /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 0000000..fbd10a0
--- /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 0000000..c5b7c19
--- /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 0000000..06bfad9
--- /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 0000000..a520ca4
--- /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 <ruby.h>
+
+#include <grpc/grpc.h>
+#include <grpc/support/slice.h>
+#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 <String>",
+             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 0000000..1bdcfe4
--- /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 <grpc/grpc.h>
+#include <ruby.h>
+
+/* 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 0000000..07f70e0
--- /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 <ruby.h>
+
+#include <grpc/grpc.h>
+#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: <Status>",
+             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: <Hash>",
+             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 0000000..422e7e7
--- /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 <grpc/grpc.h>
+#include <ruby.h>
+
+/* 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 0000000..f4c09a3
--- /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 <ruby.h>
+
+#include <grpc/grpc.h>
+#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 0000000..b0a3634
--- /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 <ruby.h>
+#include <grpc/grpc.h>
+
+/* 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 0000000..eebced0
--- /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 <ruby.h>
+#include <grpc/grpc.h>
+
+#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 <String|Symbol>",
+               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 <String|Fixnum>",
+               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) &params, &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 0000000..bbff017
--- /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 <ruby.h>
+#include <grpc/grpc.h>
+
+/* 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 0000000..62d045e
--- /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 <ruby.h>
+
+#include <grpc/grpc.h>
+#include <grpc/support/time.h>
+#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 0000000..1ec2718
--- /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 <grpc/grpc.h>
+#include <ruby.h>
+
+/* 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 0000000..6f542f9
--- /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 <ruby.h>
+
+#include <grpc/grpc.h>
+#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 0000000..c398b6c
--- /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 <ruby.h>
+
+/* 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 0000000..5cc45cf
--- /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 <math.h>
+#include <ruby.h>
+#include <sys/time.h>
+
+#include <grpc/grpc.h>
+#include <grpc/support/time.h>
+#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 <secs from epoch>|<Time>|<GRPC::TimeConst.*>";
+
+  switch (TYPE(time)) {
+
+    case T_DATA:
+      if (CLASS_OF(time) == rb_cTimeVal) {
+        Data_Get_Struct(time, gpr_timespec, time_const);
+        t = *time_const;
+      } else if (CLASS_OF(time) == rb_cTime) {
+        t.tv_sec =  NUM2INT(rb_funcall(time, id_tv_sec, 0));
+        t.tv_nsec = NUM2INT(rb_funcall(time, id_tv_nsec, 0));
+      } else {
+        rb_raise(rb_eTypeError,
+                 "bad input: (%s)->c_timeval, got <%s>,%s",
+                 tstr, rb_obj_classname(time), want);
+      }
+      break;
+
+    case T_FIXNUM:
+      t.tv_sec = FIX2LONG(time);
+      if (interval && t.tv_sec < 0)
+        rb_raise(rb_eArgError, "%s must be positive", tstr);
+      t.tv_nsec = 0;
+      break;
+
+    case T_FLOAT:
+      if (interval && RFLOAT(time)->float_value < 0.0)
+        rb_raise(rb_eArgError, "%s must be positive", tstr);
+      else {
+        double f, d;
+
+        d = modf(RFLOAT(time)->float_value, &f);
+        if (d < 0) {
+          d += 1;
+          f -= 1;
+        }
+        t.tv_sec = (time_t)f;
+        if (f != t.tv_sec) {
+          rb_raise(rb_eRangeError, "%f out of Time range",
+                   RFLOAT(time)->float_value);
+        }
+        t.tv_nsec = (time_t)(d*1e9+0.5);
+      }
+      break;
+
+    case T_BIGNUM:
+      t.tv_sec = NUM2LONG(time);
+      if (interval && t.tv_sec < 0)
+        rb_raise(rb_eArgError, "%s must be positive", tstr);
+      t.tv_nsec = 0;
+      break;
+
+    default:
+      rb_raise(rb_eTypeError,
+               "bad input: (%s)->c_timeval, got <%s>,%s",
+               tstr, rb_obj_classname(time), want);
+      break;
+  }
+  return t;
+}
+
+/* id_at is the constructor method of the ruby standard Time class. */
+static ID id_at;
+
+/* id_inspect is the inspect method found on various ruby objects. */
+static ID id_inspect;
+
+/* id_to_s is the to_s method found on various ruby objects. */
+static ID id_to_s;
+
+/* Converts `a wrapped time constant to a standard time. */
+VALUE grpc_rb_time_val_to_time(VALUE self) {
+  gpr_timespec *time_const = NULL;
+  Data_Get_Struct(self, gpr_timespec, time_const);
+  return rb_funcall(rb_cTime, id_at, 2, INT2NUM(time_const->tv_sec),
+                    INT2NUM(time_const->tv_nsec));
+}
+
+/* Invokes inspect on the ctime version of the time val. */
+VALUE grpc_rb_time_val_inspect(VALUE self) {
+  return rb_funcall(grpc_rb_time_val_to_time(self), id_inspect, 0);
+}
+
+/* Invokes to_s on the ctime version of the time val. */
+VALUE grpc_rb_time_val_to_s(VALUE self) {
+  return rb_funcall(grpc_rb_time_val_to_time(self), id_to_s, 0);
+}
+
+/* Adds a module with constants that map to gpr's static timeval structs. */
+void Init_google_time_consts() {
+  VALUE rb_mTimeConsts = rb_define_module_under(rb_mGoogleRPC, "TimeConsts");
+  rb_cTimeVal = rb_define_class_under(rb_mGoogleRPC, "TimeSpec", rb_cObject);
+  rb_define_const(rb_mTimeConsts, "ZERO",
+                  Data_Wrap_Struct(rb_cTimeVal, GC_NOT_MARKED,
+                                   GC_DONT_FREE, (void *)&gpr_time_0));
+  rb_define_const(rb_mTimeConsts, "INFINITE_FUTURE",
+                  Data_Wrap_Struct(rb_cTimeVal, GC_NOT_MARKED,
+                                   GC_DONT_FREE, (void *)&gpr_inf_future));
+  rb_define_const(rb_mTimeConsts, "INFINITE_PAST",
+                  Data_Wrap_Struct(rb_cTimeVal, GC_NOT_MARKED,
+                                   GC_DONT_FREE, (void *)&gpr_inf_past));
+  rb_define_method(rb_cTimeVal, "to_time", grpc_rb_time_val_to_time, 0);
+  rb_define_method(rb_cTimeVal, "inspect", grpc_rb_time_val_inspect, 0);
+  rb_define_method(rb_cTimeVal, "to_s", grpc_rb_time_val_to_s, 0);
+  id_at = rb_intern("at");
+  id_inspect = rb_intern("inspect");
+  id_to_s = rb_intern("to_s");
+  id_tv_sec = rb_intern("tv_sec");
+  id_tv_nsec = rb_intern("tv_nsec");
+}
+
+void grpc_rb_shutdown(void *vm) {
+  grpc_shutdown();
+}
+
+/* Initialize the Google RPC module. */
+VALUE rb_mGoogle = Qnil;
+VALUE rb_mGoogleRPC = Qnil;
+void Init_grpc() {
+  grpc_init();
+  ruby_vm_at_exit(grpc_rb_shutdown);
+  rb_mGoogle = rb_define_module("Google");
+  rb_mGoogleRPC = rb_define_module_under(rb_mGoogle, "RPC");
+
+  Init_google_rpc_byte_buffer();
+  Init_google_rpc_event();
+  Init_google_rpc_channel();
+  Init_google_rpc_completion_queue();
+  Init_google_rpc_call();
+  Init_google_rpc_metadata();
+  Init_google_rpc_server();
+  Init_google_rpc_status();
+  Init_google_time_consts();
+}
diff --git a/src/ruby/ext/grpc/rb_grpc.h b/src/ruby/ext/grpc/rb_grpc.h
new file mode 100644
index 0000000..fd43c37
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_grpc.h
@@ -0,0 +1,71 @@
+/*
+ *
+ * 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_H_
+#define GRPC_RB_H_
+
+#include <sys/time.h>
+#include <ruby.h>
+#include <grpc/support/time.h>
+
+/* rb_mGoogle is the top-level Google module. */
+extern VALUE rb_mGoogle;
+
+/* rb_mGoogleRPC is the module containing all the ruby wrapper GRPC classes. */
+extern VALUE rb_mGoogleRPC;
+
+/* Class used to wrap timeval structs. */
+extern VALUE rb_cTimeVal;
+
+/* GC_NOT_MARKED is used in calls to Data_Wrap_Struct to indicate that the
+   wrapped struct does not need to participate in ruby gc. */
+extern const RUBY_DATA_FUNC GC_NOT_MARKED;
+
+/* GC_DONT_FREED is used in calls to Data_Wrap_Struct to indicate that the
+   wrapped struct should not be freed the wrapped ruby object is released by
+   the garbage collector. */
+extern const RUBY_DATA_FUNC GC_DONT_FREE;
+
+/* A ruby object alloc func that fails by raising an exception. */
+VALUE grpc_rb_cannot_alloc(VALUE cls);
+
+/* A ruby object init func that fails by raising an exception. */
+VALUE grpc_rb_cannot_init(VALUE self);
+
+/* A ruby object clone init func that fails by raising an exception. */
+VALUE grpc_rb_cannot_init_copy(VALUE copy, VALUE self);
+
+/* grpc_rb_time_timeval creates a gpr_timespec from a ruby time object. */
+gpr_timespec grpc_rb_time_timeval(VALUE time, int interval);
+
+#endif  /* GRPC_RB_H_ */
diff --git a/src/ruby/ext/grpc/rb_metadata.c b/src/ruby/ext/grpc/rb_metadata.c
new file mode 100644
index 0000000..13d515a
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_metadata.c
@@ -0,0 +1,215 @@
+/*
+ *
+ * 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_metadata.h"
+
+#include <ruby.h>
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include "rb_grpc.h"
+
+/* grpc_rb_metadata wraps a grpc_metadata.  It provides a peer ruby object,
+ * 'mark' to minimize copying when a metadata is created from ruby. */
+typedef struct grpc_rb_metadata {
+  /* Holder of ruby objects involved in constructing the metadata */
+  VALUE mark;
+  /* The actual metadata */
+  grpc_metadata *wrapped;
+} grpc_rb_metadata;
+
+
+/* Destroys Metadata instances. */
+static void grpc_rb_metadata_free(void *p) {
+  if (p == NULL) {
+    return;
+  };
+
+  /* Because metadata is only created during a call to grpc_call_add_metadata,
+   * and the call takes ownership of the metadata, this does not free the
+   * wrapped struct, only the wrapper */
+  xfree(p);
+}
+
+/* Protects the mark object from GC */
+static void grpc_rb_metadata_mark(void *p) {
+  grpc_rb_metadata *md = NULL;
+  if (p == NULL) {
+    return;
+  }
+
+  md = (grpc_rb_metadata *)p;
+  /* If it's not already cleaned up, mark the mark object */
+  if (md->mark != Qnil && BUILTIN_TYPE(md->mark) != T_NONE) {
+    rb_gc_mark(md->mark);
+  }
+}
+
+/* Allocates Metadata instances.
+
+   Provides safe default values for the Metadata fields. */
+static VALUE grpc_rb_metadata_alloc(VALUE cls) {
+  grpc_rb_metadata *wrapper = ALLOC(grpc_rb_metadata);
+  wrapper->wrapped = NULL;
+  wrapper->mark = Qnil;
+  return Data_Wrap_Struct(cls, grpc_rb_metadata_mark, grpc_rb_metadata_free,
+                          wrapper);
+}
+
+/* id_key and id_value are the names of the hidden ivars that preserve the
+ * original byte_buffer source string */
+static ID id_key;
+static ID id_value;
+
+/* Initializes Metadata instances. */
+static VALUE grpc_rb_metadata_init(VALUE self, VALUE key, VALUE value) {
+  grpc_rb_metadata *wrapper = NULL;
+  grpc_metadata *md = ALLOC(grpc_metadata);
+
+  /* Use direct pointers to the strings wrapped by the ruby object to avoid
+   * copying */
+  Data_Get_Struct(self, grpc_rb_metadata, wrapper);
+  wrapper->wrapped = md;
+  if (TYPE(key) == T_SYMBOL) {
+    md->key = (char *)rb_id2name(SYM2ID(key));
+  } else {  /* StringValueCStr does all other type exclusions for us */
+    md->key = StringValueCStr(key);
+  }
+  md->value = RSTRING_PTR(value);
+  md->value_length = RSTRING_LEN(value);
+
+  /* Save references to the original values on the mark object so that the
+   * pointers used there are valid for the lifetime of the object. */
+  wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject);
+  rb_ivar_set(wrapper->mark, id_key, key);
+  rb_ivar_set(wrapper->mark, id_value, value);
+
+  return self;
+}
+
+/* Clones Metadata instances.
+
+   Gives Metadata a consistent implementation of Ruby's object copy/dup
+   protocol. */
+static VALUE grpc_rb_metadata_init_copy(VALUE copy, VALUE orig) {
+  grpc_rb_metadata *orig_md = NULL;
+  grpc_rb_metadata *copy_md = 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_metadata_free) {
+    rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cMetadata));
+  }
+
+  Data_Get_Struct(orig, grpc_rb_metadata, orig_md);
+  Data_Get_Struct(copy, grpc_rb_metadata, copy_md);
+
+  /* use ruby's MEMCPY to make a byte-for-byte copy of the metadata wrapper
+   * object. */
+  MEMCPY(copy_md, orig_md, grpc_rb_metadata, 1);
+  return copy;
+}
+
+/* Gets the key from a metadata instance. */
+static VALUE grpc_rb_metadata_key(VALUE self) {
+  VALUE key = Qnil;
+  grpc_rb_metadata *wrapper = NULL;
+  grpc_metadata *md = NULL;
+
+  Data_Get_Struct(self, grpc_rb_metadata, wrapper);
+  if (wrapper->mark != Qnil) {
+    key = rb_ivar_get(wrapper->mark, id_key);
+    if (key != Qnil) {
+      return key;
+    }
+  }
+
+  md = wrapper->wrapped;
+  if (md == NULL || md->key == NULL) {
+    return Qnil;
+  }
+  return rb_str_new2(md->key);
+}
+
+/* Gets the value from a metadata instance. */
+static VALUE grpc_rb_metadata_value(VALUE self) {
+  VALUE val = Qnil;
+  grpc_rb_metadata *wrapper = NULL;
+  grpc_metadata *md = NULL;
+
+  Data_Get_Struct(self, grpc_rb_metadata, wrapper);
+  if (wrapper->mark != Qnil) {
+    val = rb_ivar_get(wrapper->mark, id_value);
+    if (val != Qnil) {
+      return val;
+    }
+  }
+
+  md = wrapper->wrapped;
+  if (md == NULL || md->value == NULL) {
+    return Qnil;
+  }
+  return rb_str_new2(md->value);
+}
+
+/* rb_cMetadata is the Metadata class whose instances proxy grpc_metadata. */
+VALUE rb_cMetadata = Qnil;
+void Init_google_rpc_metadata() {
+  rb_cMetadata = rb_define_class_under(rb_mGoogleRPC, "Metadata", rb_cObject);
+
+  /* Allocates an object managed by the ruby runtime */
+  rb_define_alloc_func(rb_cMetadata, grpc_rb_metadata_alloc);
+
+  /* Provides a ruby constructor and support for dup/clone. */
+  rb_define_method(rb_cMetadata, "initialize", grpc_rb_metadata_init, 2);
+  rb_define_method(rb_cMetadata, "initialize_copy", grpc_rb_metadata_init_copy,
+                   1);
+
+  /* Provides accessors for the code and details. */
+  rb_define_method(rb_cMetadata, "key", grpc_rb_metadata_key, 0);
+  rb_define_method(rb_cMetadata, "value", grpc_rb_metadata_value, 0);
+
+  id_key = rb_intern("__key");
+  id_value = rb_intern("__value");
+}
+
+/* Gets the wrapped metadata from the ruby wrapper */
+grpc_metadata* grpc_rb_get_wrapped_metadata(VALUE v) {
+  grpc_rb_metadata *wrapper = NULL;
+  Data_Get_Struct(v, grpc_rb_metadata, wrapper);
+  return wrapper->wrapped;
+}
diff --git a/src/ruby/ext/grpc/rb_metadata.h b/src/ruby/ext/grpc/rb_metadata.h
new file mode 100644
index 0000000..6b70591
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_metadata.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_METADATA_H_
+#define GRPC_RB_METADATA_H_
+
+#include <grpc/grpc.h>
+#include <ruby.h>
+
+/* rb_cMetadata is the Metadata class whose instances proxy grpc_metadata. */
+extern VALUE rb_cMetadata;
+
+/* grpc_rb_metadata_create_with_mark creates a grpc_rb_metadata with a ruby mark
+ * object that will be kept alive while the metadata is alive. */
+extern VALUE grpc_rb_metadata_create_with_mark(VALUE mark, grpc_metadata *md);
+
+/* Gets the wrapped metadata from the ruby wrapper */
+grpc_metadata* grpc_rb_get_wrapped_metadata(VALUE v);
+
+/* Initializes the Metadata class. */
+void Init_google_rpc_metadata();
+
+#endif  /* GRPC_RB_METADATA_H_ */
diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c
new file mode 100644
index 0000000..f4230bd
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_server.c
@@ -0,0 +1,226 @@
+/*
+ *
+ * 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_server.h"
+
+#include <ruby.h>
+
+#include <grpc/grpc.h>
+#include "rb_call.h"
+#include "rb_channel_args.h"
+#include "rb_completion_queue.h"
+#include "rb_grpc.h"
+
+/* rb_cServer is the ruby class that proxies grpc_server. */
+VALUE rb_cServer = Qnil;
+
+/* grpc_rb_server wraps a grpc_server.  It provides a peer ruby object,
+ * 'mark' to minimize copying when a server is created from ruby. */
+typedef struct grpc_rb_server {
+  /* Holder of ruby objects involved in constructing the server */
+  VALUE mark;
+  /* The actual server */
+  grpc_server *wrapped;
+} grpc_rb_server;
+
+/* Destroys server instances. */
+static void grpc_rb_server_free(void *p) {
+  grpc_rb_server *svr = NULL;
+  if (p == NULL) {
+    return;
+  };
+  svr = (grpc_rb_server *)p;
+
+  /* Deletes the wrapped object if the mark object is Qnil, which indicates
+   * that no other object is the actual owner. */
+  if (svr->wrapped != NULL && svr->mark == Qnil) {
+    grpc_server_shutdown(svr->wrapped);
+    grpc_server_destroy(svr->wrapped);
+  }
+
+  xfree(p);
+}
+
+/* Protects the mark object from GC */
+static void grpc_rb_server_mark(void *p) {
+  grpc_rb_server *server = NULL;
+  if (p == NULL) {
+    return;
+  }
+  server = (grpc_rb_server *)p;
+  if (server->mark != Qnil) {
+    rb_gc_mark(server->mark);
+  }
+}
+
+/* Allocates grpc_rb_server instances. */
+static VALUE grpc_rb_server_alloc(VALUE cls) {
+  grpc_rb_server *wrapper = ALLOC(grpc_rb_server);
+  wrapper->wrapped = NULL;
+  wrapper->mark = Qnil;
+  return Data_Wrap_Struct(cls, grpc_rb_server_mark, grpc_rb_server_free,
+                          wrapper);
+}
+
+/* Initializes Server instances. */
+static VALUE grpc_rb_server_init(VALUE self, VALUE cqueue, VALUE channel_args) {
+  grpc_completion_queue *cq = grpc_rb_get_wrapped_completion_queue(cqueue);
+  grpc_rb_server *wrapper = NULL;
+  grpc_server *srv = NULL;
+  grpc_channel_args args;
+  MEMZERO(&args, grpc_channel_args, 1);
+
+  Data_Get_Struct(self, grpc_rb_server, wrapper);
+  grpc_rb_hash_convert_to_channel_args(channel_args, &args);
+  srv = grpc_server_create(cq, &args);
+  if (args.args != NULL) {
+    xfree(args.args);  /* Allocated by grpc_rb_hash_convert_to_channel_args */
+  }
+  if (srv == NULL) {
+    rb_raise(rb_eRuntimeError, "could not create a gRPC server, not sure why");
+  }
+  wrapper->wrapped = srv;
+
+  /* Add the cq as the server's mark object. This ensures the ruby cq can't be
+   * GCed before the server */
+  wrapper->mark = cqueue;
+  return self;
+}
+
+/* Clones Server instances.
+
+   Gives Server a consistent implementation of Ruby's object copy/dup
+   protocol. */
+static VALUE grpc_rb_server_init_copy(VALUE copy, VALUE orig) {
+  grpc_rb_server *orig_srv = NULL;
+  grpc_rb_server *copy_srv = NULL;
+
+  if (copy == orig) {
+    return copy;
+  }
+
+  /* Raise an error if orig is not a server object or a subclass. */
+  if (TYPE(orig) != T_DATA ||
+      RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_server_free) {
+    rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cServer));
+  }
+
+  Data_Get_Struct(orig, grpc_rb_server, orig_srv);
+  Data_Get_Struct(copy, grpc_rb_server, copy_srv);
+
+  /* use ruby's MEMCPY to make a byte-for-byte copy of the server wrapper
+   * object. */
+  MEMCPY(copy_srv, orig_srv, grpc_rb_server, 1);
+  return copy;
+}
+
+static VALUE grpc_rb_server_request_call(VALUE self, VALUE tag_new) {
+  grpc_call_error err;
+  grpc_rb_server *s = NULL;
+  Data_Get_Struct(self, grpc_rb_server, s);
+  if (s->wrapped == NULL) {
+    rb_raise(rb_eRuntimeError, "closed!");
+  } else {
+    err = grpc_server_request_call(s->wrapped, ROBJECT(tag_new));
+    if (err != GRPC_CALL_OK) {
+      rb_raise(rb_eCallError, "server request failed: %s (code=%d)",
+               grpc_call_error_detail_of(err), err);
+    }
+  }
+  return Qnil;
+}
+
+static VALUE grpc_rb_server_start(VALUE self) {
+  grpc_rb_server *s = NULL;
+  Data_Get_Struct(self, grpc_rb_server, s);
+  if (s->wrapped == NULL) {
+    rb_raise(rb_eRuntimeError, "closed!");
+  } else {
+    grpc_server_start(s->wrapped);
+  }
+  return Qnil;
+}
+
+static VALUE grpc_rb_server_destroy(VALUE self) {
+  grpc_rb_server *s = NULL;
+  Data_Get_Struct(self, grpc_rb_server, s);
+  if (s->wrapped != NULL) {
+    grpc_server_shutdown(s->wrapped);
+    grpc_server_destroy(s->wrapped);
+    s->wrapped = NULL;
+    s->mark = Qnil;
+  }
+  return Qnil;
+}
+
+static VALUE grpc_rb_server_add_http2_port(VALUE self, VALUE port) {
+  grpc_rb_server *s = NULL;
+  int added_ok = 0;
+  Data_Get_Struct(self, grpc_rb_server, s);
+  if (s->wrapped == NULL) {
+    rb_raise(rb_eRuntimeError, "closed!");
+  } else {
+    added_ok = grpc_server_add_http2_port(s->wrapped, StringValueCStr(port));
+    if (added_ok == 0) {
+      rb_raise(rb_eRuntimeError, "could not add port %s to server, not sure why",
+               StringValueCStr(port));
+    }
+  }
+  return Qnil;
+}
+
+void Init_google_rpc_server() {
+  rb_cServer = rb_define_class_under(rb_mGoogleRPC, "Server", rb_cObject);
+
+  /* Allocates an object managed by the ruby runtime */
+  rb_define_alloc_func(rb_cServer, grpc_rb_server_alloc);
+
+  /* Provides a ruby constructor and support for dup/clone. */
+  rb_define_method(rb_cServer, "initialize", grpc_rb_server_init, 2);
+  rb_define_method(rb_cServer, "initialize_copy", grpc_rb_server_init_copy, 1);
+
+  /* Add the server methods. */
+  rb_define_method(rb_cServer, "request_call", grpc_rb_server_request_call, 1);
+  rb_define_method(rb_cServer, "start", grpc_rb_server_start, 0);
+  rb_define_method(rb_cServer, "destroy", grpc_rb_server_destroy, 0);
+  rb_define_alias(rb_cServer, "close", "destroy");
+  rb_define_method(rb_cServer, "add_http2_port", grpc_rb_server_add_http2_port,
+                   1);
+}
+
+/* Gets the wrapped server from the ruby wrapper */
+grpc_server* grpc_rb_get_wrapped_server(VALUE v) {
+  grpc_rb_server *wrapper = NULL;
+  Data_Get_Struct(v, grpc_rb_server, wrapper);
+  return wrapper->wrapped;
+}
diff --git a/src/ruby/ext/grpc/rb_server.h b/src/ruby/ext/grpc/rb_server.h
new file mode 100644
index 0000000..4619203
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_server.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_BYTE_BUFFER_H_
+#define GRPC_RB_SERVER_H_
+
+#include <ruby.h>
+#include <grpc/grpc.h>
+
+/* rb_cServer is the Server class whose instances proxy
+   grpc_byte_buffer. */
+extern VALUE rb_cServer;
+
+/* Initializes the Server class. */
+void Init_google_rpc_server();
+
+/* Gets the wrapped server from the ruby wrapper */
+grpc_server* grpc_rb_get_wrapped_server(VALUE v);
+
+#endif  /* GRPC_RB_SERVER_H_ */
diff --git a/src/ruby/ext/grpc/rb_status.c b/src/ruby/ext/grpc/rb_status.c
new file mode 100644
index 0000000..747c47c
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_status.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_status.h"
+
+#include <ruby.h>
+#include <string.h>
+
+#include <grpc/grpc.h>
+#include <grpc/status.h>
+#include "rb_grpc.h"
+
+/* grpc_rb_status wraps a grpc_status.  It provides a peer ruby object, 'mark'
+ * to minimize copying when a status is created from ruby. */
+typedef struct grpc_rb_status {
+  /* Holder of ruby objects involved in constructing the status */
+  VALUE mark;
+  /* The actual status */
+  grpc_status *wrapped;
+} grpc_rb_status;
+
+/* Destroys Status instances. */
+static void grpc_rb_status_free(void *p) {
+  grpc_rb_status *status = NULL;
+  if (p == NULL) {
+    return;
+  };
+  status = (grpc_rb_status *)p;
+
+  /* Delete the wrapped object if the mark object is Qnil, which indicates that
+   * no other object is the actual owner. */
+  if (status->wrapped != NULL && status->mark == Qnil) {
+    status->mark = Qnil;
+    if (status->wrapped->details) {
+      xfree(status->wrapped->details);
+    }
+    xfree(status->wrapped);
+  }
+
+  xfree(p);
+}
+
+/* Protects the mark object from GC */
+static void grpc_rb_status_mark(void *p) {
+  grpc_rb_status *status = NULL;
+  if (p == NULL) {
+    return;
+  }
+  status = (grpc_rb_status *)p;
+
+  /* If it's not already cleaned up, mark the mark object */
+  if (status->mark != Qnil) {
+    rb_gc_mark(status->mark);
+  }
+}
+
+/* Allocates Status instances.
+
+   Provides safe initial defaults for the instance fields. */
+static VALUE grpc_rb_status_alloc(VALUE cls) {
+  grpc_rb_status *wrapper = ALLOC(grpc_rb_status);
+  wrapper->wrapped = NULL;
+  wrapper->mark = Qnil;
+  return Data_Wrap_Struct(cls, grpc_rb_status_mark, grpc_rb_status_free,
+                          wrapper);
+}
+
+/* The name of the attribute used on the mark object to hold the details. */
+static ID id_details;
+
+/* Initializes Status instances. */
+static VALUE grpc_rb_status_init(VALUE self, VALUE code, VALUE details) {
+  grpc_rb_status *wrapper = NULL;
+  grpc_status *status = NULL;
+  Data_Get_Struct(self, grpc_rb_status, wrapper);
+
+  /* Use a direct pointer to the original detail value to avoid copying. Assume
+   * that details is null-terminated. */
+  status = ALLOC(grpc_status);
+  status->details = StringValueCStr(details);
+  status->code = NUM2INT(code);
+  wrapper->wrapped = status;
+
+  /* Create the mark and add the original details object to it. */
+  wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject);
+  rb_ivar_set(wrapper->mark, id_details, details);
+  return self;
+}
+
+/* Clones Status instances.
+
+   Gives Status a consistent implementation of Ruby's object copy/dup
+   protocol. */
+static VALUE grpc_rb_status_init_copy(VALUE copy, VALUE orig) {
+  grpc_rb_status *orig_status = NULL;
+  grpc_rb_status *copy_status = NULL;
+
+  if (copy == orig) {
+    return copy;
+  }
+
+  /* Raise an error if orig is not a Status object or a subclass. */
+  if (TYPE(orig) != T_DATA ||
+      RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_status_free) {
+    rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cStatus));
+  }
+
+  Data_Get_Struct(orig, grpc_rb_status, orig_status);
+  Data_Get_Struct(copy, grpc_rb_status, copy_status);
+  MEMCPY(copy_status, orig_status, grpc_rb_status, 1);
+  return copy;
+}
+
+/* Gets the Status code. */
+static VALUE grpc_rb_status_code(VALUE self) {
+  grpc_rb_status *status = NULL;
+  Data_Get_Struct(self, grpc_rb_status, status);
+  return INT2NUM(status->wrapped->code);
+}
+
+/* Gets the Status details. */
+static VALUE grpc_rb_status_details(VALUE self) {
+  VALUE from_ruby;
+  grpc_rb_status *wrapper = NULL;
+  grpc_status *status;
+
+  Data_Get_Struct(self, grpc_rb_status, wrapper);
+  if (wrapper->mark != Qnil) {
+    from_ruby = rb_ivar_get(wrapper->mark, id_details);
+    if (from_ruby != Qnil) {
+      return from_ruby;
+    }
+  }
+
+  status = wrapper->wrapped;
+  if (status == NULL || status->details == NULL) {
+    return Qnil;
+  }
+
+  return rb_str_new2(status->details);
+}
+
+void Init_google_status_codes() {
+  /* Constants representing the status codes or grpc_status_code in status.h */
+  VALUE rb_mStatusCodes = rb_define_module_under(rb_mGoogleRPC, "StatusCodes");
+  rb_define_const(rb_mStatusCodes, "OK", INT2NUM(GRPC_STATUS_OK));
+  rb_define_const(rb_mStatusCodes, "CANCELLED", INT2NUM(GRPC_STATUS_CANCELLED));
+  rb_define_const(rb_mStatusCodes, "UNKNOWN", INT2NUM(GRPC_STATUS_UNKNOWN));
+  rb_define_const(rb_mStatusCodes, "INVALID_ARGUMENT",
+                  INT2NUM(GRPC_STATUS_INVALID_ARGUMENT));
+  rb_define_const(rb_mStatusCodes, "DEADLINE_EXCEEDED",
+                  INT2NUM(GRPC_STATUS_DEADLINE_EXCEEDED));
+  rb_define_const(rb_mStatusCodes, "NOT_FOUND", INT2NUM(GRPC_STATUS_NOT_FOUND));
+  rb_define_const(rb_mStatusCodes, "ALREADY_EXISTS",
+                  INT2NUM(GRPC_STATUS_ALREADY_EXISTS));
+  rb_define_const(rb_mStatusCodes, "PERMISSION_DENIED",
+                  INT2NUM(GRPC_STATUS_PERMISSION_DENIED));
+  rb_define_const(rb_mStatusCodes, "UNAUTHENTICATED",
+                  INT2NUM(GRPC_STATUS_UNAUTHENTICATED));
+  rb_define_const(rb_mStatusCodes, "RESOURCE_EXHAUSTED",
+                  INT2NUM(GRPC_STATUS_RESOURCE_EXHAUSTED));
+  rb_define_const(rb_mStatusCodes, "FAILED_PRECONDITION",
+                  INT2NUM(GRPC_STATUS_FAILED_PRECONDITION));
+  rb_define_const(rb_mStatusCodes, "ABORTED", INT2NUM(GRPC_STATUS_ABORTED));
+  rb_define_const(rb_mStatusCodes, "OUT_OF_RANGE",
+                  INT2NUM(GRPC_STATUS_OUT_OF_RANGE));
+  rb_define_const(rb_mStatusCodes, "UNIMPLEMENTED",
+                  INT2NUM(GRPC_STATUS_UNIMPLEMENTED));
+  rb_define_const(rb_mStatusCodes, "INTERNAL", INT2NUM(GRPC_STATUS_INTERNAL));
+  rb_define_const(rb_mStatusCodes, "UNAVAILABLE",
+                  INT2NUM(GRPC_STATUS_UNAVAILABLE));
+  rb_define_const(rb_mStatusCodes, "DATA_LOSS", INT2NUM(GRPC_STATUS_DATA_LOSS));
+}
+
+/* rb_cStatus is the Status class whose instances proxy grpc_status. */
+VALUE rb_cStatus = Qnil;
+
+/* Initializes the Status class. */
+void Init_google_rpc_status() {
+  rb_cStatus = rb_define_class_under(rb_mGoogleRPC, "Status", rb_cObject);
+
+  /* Allocates an object whose memory is managed by the Ruby. */
+  rb_define_alloc_func(rb_cStatus, grpc_rb_status_alloc);
+
+  /* Provides a ruby constructor and support for dup/clone. */
+  rb_define_method(rb_cStatus, "initialize", grpc_rb_status_init, 2);
+  rb_define_method(rb_cStatus, "initialize_copy", grpc_rb_status_init_copy, 1);
+
+  /* Provides accessors for the code and details. */
+  rb_define_method(rb_cStatus, "code", grpc_rb_status_code, 0);
+  rb_define_method(rb_cStatus, "details", grpc_rb_status_details, 0);
+  id_details = rb_intern("__details");
+  Init_google_status_codes();
+}
+
+VALUE grpc_rb_status_create_with_mark(VALUE mark, grpc_status* s) {
+  grpc_rb_status *status = NULL;
+  if (s == NULL) {
+    return Qnil;
+  }
+  status = ALLOC(grpc_rb_status);
+  status->wrapped = s;
+  status->mark = mark;
+  return Data_Wrap_Struct(rb_cStatus, grpc_rb_status_mark, grpc_rb_status_free,
+                          status);
+}
+
+/* Gets the wrapped status from the ruby wrapper */
+grpc_status* grpc_rb_get_wrapped_status(VALUE v) {
+  grpc_rb_status *wrapper = NULL;
+  Data_Get_Struct(v, grpc_rb_status, wrapper);
+  return wrapper->wrapped;
+}
diff --git a/src/ruby/ext/grpc/rb_status.h b/src/ruby/ext/grpc/rb_status.h
new file mode 100644
index 0000000..ceb6f9f
--- /dev/null
+++ b/src/ruby/ext/grpc/rb_status.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_STATUS_H_
+#define GRPC_RB_STATUS_H_
+
+#include <grpc/grpc.h>
+#include <ruby.h>
+
+/* rb_cStatus is the Status class whose instances proxy grpc_status. */
+extern VALUE rb_cStatus;
+
+/* grpc_rb_status_create_with_mark creates a grpc_rb_status with a ruby mark
+ * object that will be kept alive while the status is alive. */
+extern VALUE grpc_rb_status_create_with_mark(VALUE mark, grpc_status *s);
+
+/* Gets the wrapped status from the ruby wrapper object */
+grpc_status* grpc_rb_get_wrapped_status(VALUE v);
+
+/* Initializes the Status class. */
+void Init_google_rpc_status();
+
+#endif  /* GRPC_RB_STATUS_H_ */
diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec
new file mode 100755
index 0000000..c63c80d
--- /dev/null
+++ b/src/ruby/grpc.gemspec
@@ -0,0 +1,30 @@
+# encoding: utf-8
+$:.push File.expand_path("../lib", __FILE__)
+require 'grpc/version'
+
+Gem::Specification.new do |s|
+  s.name          = "grpc"
+  s.version       = Google::RPC::VERSION
+  s.authors       = ["One Platform Team"]
+  s.email         = "stubby-team@google.com"
+  s.homepage      = "http://go/grpc"
+  s.summary       = 'Google RPC system in Ruby'
+  s.description   = 'Send RPCs from Ruby'
+
+  s.files         = `git ls-files`.split("\n")
+  s.test_files    = `git ls-files -- spec/*`.split("\n")
+  s.executables   = `git ls-files -- examples/*.rb`.split("\n").map{ |f| File.basename(f) }
+  s.require_paths = ['lib' ]
+  s.platform      = Gem::Platform::RUBY
+
+  s.add_dependency 'xray'
+  s.add_dependency 'logging', '~> 1.8'
+  s.add_dependency 'beefcake', '~> 1.1'
+
+  s.add_development_dependency "bundler", "~> 1.7"
+  s.add_development_dependency "rake", "~> 10.0"
+  s.add_development_dependency 'rake-compiler', '~> 0'
+  s.add_development_dependency 'rspec', "~> 3.0"
+
+  s.extensions = %w[ext/grpc/extconf.rb]
+end
diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb
new file mode 100644
index 0000000..60a3b96
--- /dev/null
+++ b/src/ruby/lib/grpc.rb
@@ -0,0 +1,38 @@
+# 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 'grpc/event'
+require 'grpc/errors'
+require 'grpc/grpc'
+require 'grpc/logconfig'
+require 'grpc/time_consts'
+require 'grpc/version'
+
+# alias GRPC
+GRPC = Google::RPC
diff --git a/src/ruby/lib/grpc/errors.rb b/src/ruby/lib/grpc/errors.rb
new file mode 100644
index 0000000..d14e69c
--- /dev/null
+++ b/src/ruby/lib/grpc/errors.rb
@@ -0,0 +1,68 @@
+# 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 'grpc'
+
+module Google
+
+  module RPC
+
+    # OutOfTime is an exception class that indicates that an RPC exceeded its
+    # deadline.
+    OutOfTime = Class.new(StandardError)
+
+    # BadStatus is an exception class that indicates that an error occurred at
+    # either end of a GRPC connection.  When raised, it indicates that a status
+    # error should be returned to the other end of a GRPC connection; when
+    # caught it means that this end received a status error.
+    class BadStatus < StandardError
+
+      attr_reader :code, :details
+
+      # @param code [Numeric] the status code
+      # @param details [String] the details of the exception
+      def initialize(code, details='unknown cause')
+        super("#{code}:#{details}")
+        @code = code
+        @details = details
+      end
+
+      # Converts the exception to a GRPC::Status for use in the networking
+      # wrapper layer.
+      #
+      # @return [Status] with the same code and details
+      def to_status
+        Status.new(code, details)
+      end
+
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/event.rb b/src/ruby/lib/grpc/event.rb
new file mode 100644
index 0000000..c108cd4
--- /dev/null
+++ b/src/ruby/lib/grpc/event.rb
@@ -0,0 +1,38 @@
+# 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.
+
+module Google
+  module RPC
+    class Event  # Add an inspect method to C-defined Event class.
+      def inspect
+        '<%s: type:%s, tag:%s result:%s>' % [self.class, type, tag, result]
+      end
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb
new file mode 100644
index 0000000..d987b39
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/active_call.rb
@@ -0,0 +1,485 @@
+# 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 'forwardable'
+require 'grpc'
+require 'grpc/generic/bidi_call'
+
+def assert_event_type(got, want)
+  raise 'Unexpected rpc event: got %s, want %s' % [got, want] unless got == want
+end
+
+module GRPC
+
+  # The ActiveCall class provides simple methods for sending marshallable
+  # data to a call
+  class ActiveCall
+    include CompletionType
+    include StatusCodes
+    attr_reader(:deadline)
+
+    # client_start_invoke begins a client invocation.
+    #
+    # Flow Control note: this blocks until flow control accepts that client
+    # request can go ahead.
+    #
+    # deadline is the absolute deadline for the call.
+    #
+    # @param call [Call] a call on which to start and invocation
+    # @param q [CompletionQueue] used to wait for INVOKE_ACCEPTED
+    # @param deadline [Fixnum,TimeSpec] the deadline for INVOKE_ACCEPTED
+    def self.client_start_invoke(call, q, deadline)
+      raise ArgumentError.new('not a call') unless call.is_a?Call
+      if !q.is_a?CompletionQueue
+        raise ArgumentError.new('not a CompletionQueue')
+      end
+      invoke_accepted, client_metadata_read = Object.new, Object.new
+      finished_tag = Object.new
+      call.start_invoke(q, invoke_accepted, client_metadata_read, finished_tag)
+      # wait for the invocation to be accepted
+      ev = q.pluck(invoke_accepted, TimeConsts::INFINITE_FUTURE)
+      raise OutOfTime if ev.nil?
+      finished_tag
+    end
+
+    # Creates an ActiveCall.
+    #
+    # ActiveCall should only be created after a call is accepted.  That means
+    # different things on a client and a server.  On the client, the call is
+    # accepted after call.start_invoke followed by receipt of the
+    # corresponding INVOKE_ACCEPTED.  on the server, this is after
+    # call.accept.
+    #
+    # #initialize cannot determine if the call is accepted or not; so if a
+    # call that's not accepted is used here, the error won't be visible until
+    # the ActiveCall methods are called.
+    #
+    # deadline is the absolute deadline for the call.
+    #
+    # @param call [Call] the call used by the ActiveCall
+    # @param q [CompletionQueue] the completion queue used to accept
+    #          the call
+    # @param marshal [Function] f(obj)->string that marshal requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Fixnum] the deadline for the call to complete
+    # @param finished_tag [Object] the object used as the call's finish tag,
+    #                              if the call has begun
+    # @param started [true|false] (default true) indicates if the call has begun
+    def initialize(call, q, marshal, unmarshal, deadline, finished_tag: nil,
+                   started: true)
+      raise ArgumentError.new('not a call') unless call.is_a?Call
+      if !q.is_a?CompletionQueue
+        raise ArgumentError.new('not a CompletionQueue')
+      end
+      @call = call
+      @cq = q
+      @deadline = deadline
+      @finished_tag = finished_tag
+      @marshal = marshal
+      @started = started
+      @unmarshal = unmarshal
+    end
+
+    # Obtains the status of the call.
+    #
+    # this value is nil until the call completes
+    # @return this call's status
+    def status
+      @call.status
+    end
+
+    # Obtains the metadata of the call.
+    #
+    # At the start of the call this will be nil.  During the call this gets
+    # some values as soon as the other end of the connection acknowledges the
+    # request.
+    #
+    # @return this calls's metadata
+    def metadata
+      @call.metadata
+    end
+
+    # Cancels the call.
+    #
+    # Cancels the call.  The call does not return any result, but once this it
+    # has been called, the call should eventually terminate.  Due to potential
+    # races between the execution of the cancel and the in-flight request, the
+    # result of the call after calling #cancel is indeterminate:
+    #
+    # - the call may terminate with a BadStatus exception, with code=CANCELLED
+    # - the call may terminate with OK Status, and return a response
+    # - the call may terminate with a different BadStatus exception if that was
+    #   happening
+    def cancel
+      @call.cancel
+    end
+
+    # indicates if the call is shutdown
+    def shutdown
+      @shutdown ||= false
+    end
+
+    # indicates if the call is cancelled.
+    def cancelled
+      @cancelled ||= false
+    end
+
+    # multi_req_view provides a restricted view of this ActiveCall for use
+    # in a server client-streaming handler.
+    def multi_req_view
+      MultiReqView.new(self)
+    end
+
+    # single_req_view provides a restricted view of this ActiveCall for use in
+    # a server request-response handler.
+    def single_req_view
+      SingleReqView.new(self)
+    end
+
+    # operation provides a restricted view of this ActiveCall for use as
+    # a Operation.
+    def operation
+      Operation.new(self)
+    end
+
+    # writes_done indicates that all writes are completed.
+    #
+    # It blocks until the remote endpoint acknowledges by sending a FINISHED
+    # event, unless assert_finished is set to false.  Any calls to
+    # #remote_send after this call will fail.
+    #
+    # @param assert_finished [true, false] when true(default), waits for
+    # FINISHED.
+    def writes_done(assert_finished=true)
+      @call.writes_done(self)
+      ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+      assert_event_type(ev.type, FINISH_ACCEPTED)
+      logger.debug("Writes done: waiting for finish? #{assert_finished}")
+      if assert_finished
+        ev = @cq.pluck(@finished_tag, TimeConsts::INFINITE_FUTURE)
+        raise "unexpected event: #{ev.inspect}" if ev.nil?
+        return @call.status
+      end
+    end
+
+    # finished waits until the call is completed.
+    #
+    # It blocks until the remote endpoint acknowledges by sending a FINISHED
+    # event.
+    def finished
+      ev = @cq.pluck(@finished_tag, TimeConsts::INFINITE_FUTURE)
+      raise "unexpected event: #{ev.inspect}" unless ev.type == FINISHED
+      if ev.result.code != StatusCodes::OK
+        raise BadStatus.new(ev.result.code, ev.result.details)
+      end
+      res = ev.result
+
+      # NOTE(temiola): This is necessary to allow the C call struct wrapped
+      # within the active_call to be GCed; this is necessary so that other
+      # C-level destructors get called in the required order.
+      ev = nil  # allow the event to be GCed
+      res
+    end
+
+    # remote_send sends a request to the remote endpoint.
+    #
+    # It blocks until the remote endpoint acknowledges by sending a
+    # WRITE_ACCEPTED.  req can be marshalled already.
+    #
+    # @param req [Object, String] the object to send or it's marshal form.
+    # @param marshalled [false, true] indicates if the object is already
+    # marshalled.
+    def remote_send(req, marshalled=false)
+      assert_queue_is_ready
+      logger.debug("sending payload #{req.inspect}, marshalled? #{marshalled}")
+      if marshalled
+        payload = req
+      else
+        payload = @marshal.call(req)
+      end
+      @call.start_write(ByteBuffer.new(payload), self)
+
+      # call queue#pluck, and wait for WRITE_ACCEPTED, so as not to return
+      # until the flow control allows another send on this call.
+      ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+      assert_event_type(ev.type, WRITE_ACCEPTED)
+      ev = nil
+    end
+
+    # send_status sends a status to the remote endpoint
+    #
+    # @param code [int] the status code to send
+    # @param details [String] details
+    # @param assert_finished [true, false] when true(default), waits for
+    # FINISHED.
+    def send_status(code=OK, details='', assert_finished=false)
+      assert_queue_is_ready
+      @call.start_write_status(Status.new(code, details), self)
+      ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+      assert_event_type(ev.type, FINISH_ACCEPTED)
+      logger.debug("Status sent: #{code}:'#{details}'")
+      if assert_finished
+        return finished
+      end
+      nil
+    end
+
+    # remote_read reads a response from the remote endpoint.
+    #
+    # It blocks until the remote endpoint sends a READ or FINISHED event.  On
+    # a READ, it returns the response after unmarshalling it. On
+    # FINISHED, it returns nil if the status is OK, otherwise raising BadStatus
+    def remote_read
+      @call.start_read(self)
+      ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+      assert_event_type(ev.type, READ)
+      logger.debug("received req: #{ev.result.inspect}")
+      if !ev.result.nil?
+        logger.debug("received req.to_s: #{ev.result.to_s}")
+        res = @unmarshal.call(ev.result.to_s)
+        logger.debug("received_req (unmarshalled): #{res.inspect}")
+        return res
+      end
+      logger.debug('found nil; the final response has been sent')
+      nil
+    end
+
+    # each_remote_read passes each response to the given block or returns an
+    # enumerator the responses if no block is given.
+    #
+    # == Enumerator ==
+    #
+    # * #next blocks until the remote endpoint sends a READ or FINISHED
+    # * for each read, enumerator#next yields the response
+    # * on status
+    #    * if it's is OK, enumerator#next raises StopException
+    #    * if is not OK, enumerator#next raises RuntimeException
+    #
+    # == Block ==
+    #
+    # * if provided it is executed for each response
+    # * the call blocks until no more responses are provided
+    #
+    # @return [Enumerator] if no block was given
+    def each_remote_read
+      return enum_for(:each_remote_read) if !block_given?
+      loop do
+        resp = remote_read()
+        break if resp.is_a?Status  # this will be an OK status, bad statii raise
+        break if resp.nil?  # the last response was received
+        yield resp
+      end
+    end
+
+    # each_remote_read_then_finish passes each response to the given block or
+    # returns an enumerator of the responses if no block is given.
+    #
+    # It is like each_remote_read, but it blocks on finishing on detecting
+    # the final message.
+    #
+    # == Enumerator ==
+    #
+    # * #next blocks until the remote endpoint sends a READ or FINISHED
+    # * for each read, enumerator#next yields the response
+    # * on status
+    #    * if it's is OK, enumerator#next raises StopException
+    #    * if is not OK, enumerator#next raises RuntimeException
+    #
+    # == Block ==
+    #
+    # * if provided it is executed for each response
+    # * the call blocks until no more responses are provided
+    #
+    # @return [Enumerator] if no block was given
+    def each_remote_read_then_finish
+      return enum_for(:each_remote_read_then_finish) if !block_given?
+      loop do
+        resp = remote_read
+        break if resp.is_a?Status  # this will be an OK status, bad statii raise
+        if resp.nil?  # the last response was received, but not finished yet
+          finished
+          break
+        end
+        yield resp
+      end
+    end
+
+    # request_response sends a request to a GRPC server, and returns the
+    # response.
+    # @param req [Object] the request sent to the server
+    # @return [Object] the response received from the server
+    def request_response(req)
+      start_call unless @started
+      remote_send(req)
+      writes_done(false)
+      response = remote_read
+      if !response.is_a?(Status)  # finish if status not yet received
+        finished
+      end
+      response
+    end
+
+    # client_streamer sends a stream of requests to a GRPC server, and
+    # returns a single response.
+    #
+    # requests provides an 'iterable' of Requests. I.e. it follows Ruby's
+    # #each enumeration protocol. In the simplest case, requests will be an
+    # array of marshallable objects; in typical case it will be an Enumerable
+    # that allows dynamic construction of the marshallable objects.
+    #
+    # @param requests [Object] an Enumerable of requests to send
+    # @return [Object] the response received from the server
+    def client_streamer(requests)
+      start_call unless @started
+      requests.each { |r| remote_send(r) }
+      writes_done(false)
+      response = remote_read
+      if !response.is_a?(Status)  # finish if status not yet received
+        finished
+      end
+      response
+    end
+
+    # server_streamer sends one request to the GRPC server, which yields a
+    # stream of responses.
+    #
+    # responses provides an enumerator over the streamed responses, i.e. it
+    # follows Ruby's #each iteration protocol.  The enumerator blocks while
+    # waiting for each response, stops when the server signals that no
+    # further responses will be supplied.  If the implicit block is provided,
+    # it is executed with each response as the argument and no result is
+    # returned.
+    #
+    # @param req [Object] the request sent to the server
+    # @return [Enumerator|nil] a response Enumerator
+    def server_streamer(req)
+      start_call unless @started
+      remote_send(req)
+      writes_done(false)
+      replies = enum_for(:each_remote_read_then_finish)
+      return replies if !block_given?
+      replies.each { |r| yield r }
+    end
+
+    # bidi_streamer sends a stream of requests to the GRPC server, and yields
+    # a stream of responses.
+    #
+    # This method takes an Enumerable of requests, and returns and enumerable
+    # of responses.
+    #
+    # == requests ==
+    #
+    # requests provides an 'iterable' of Requests. I.e. it follows Ruby's #each
+    # enumeration protocol. In the simplest case, requests will be an array of
+    # marshallable objects; in typical case it will be an Enumerable that
+    # allows dynamic construction of the marshallable objects.
+    #
+    # == responses ==
+    #
+    # This is an enumerator of responses.  I.e, its #next method blocks
+    # waiting for the next response.  Also, if at any point the block needs
+    # to consume all the remaining responses, this can be done using #each or
+    # #collect.  Calling #each or #collect should only be done if
+    # the_call#writes_done has been called, otherwise the block will loop
+    # forever.
+    #
+    # @param requests [Object] an Enumerable of requests to send
+    # @return [Enumerator, nil] a response Enumerator
+    def bidi_streamer(requests, &blk)
+      start_call unless @started
+      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, @deadline,
+                        @finished_tag)
+      bd.run_on_client(requests, &blk)
+    end
+
+    # run_server_bidi orchestrates a BiDi stream processing on a server.
+    #
+    # N.B. gen_each_reply is a func(Enumerable<Requests>)
+    #
+    # It takes an enumerable of requests as an arg, in case there is a
+    # relationship between the stream of requests and the stream of replies.
+    #
+    # This does not mean that must necessarily be one.  E.g, the replies
+    # produced by gen_each_reply could ignore the received_msgs
+    #
+    # @param gen_each_reply [Proc] generates the BiDi stream replies
+    def run_server_bidi(gen_each_reply)
+      bd = BidiCall.new(@call, @cq, @marshal, @unmarshal, @deadline,
+                        @finished_tag)
+      bd.run_on_server(gen_each_reply)
+    end
+
+    private
+
+    def start_call
+      @finished_tag = ActiveCall.client_start_invoke(@call, @cq, @deadline)
+      @started = true
+    end
+
+    def self.view_class(*visible_methods)
+      Class.new do
+        extend ::Forwardable
+        def_delegators :@wrapped, *visible_methods
+
+        # @param wrapped [ActiveCall] the call whose methods are shielded
+        def initialize(wrapped)
+          @wrapped = wrapped
+        end
+      end
+    end
+
+    # SingleReqView limits access to an ActiveCall's methods for use in server
+    # handlers that receive just one request.
+    SingleReqView = view_class(:cancelled, :deadline)
+
+    # MultiReqView limits access to an ActiveCall's methods for use in
+    # server client_streamer handlers.
+    MultiReqView = view_class(:cancelled, :deadline, :each_queued_msg,
+                              :each_remote_read)
+
+    # Operation limits access to an ActiveCall's methods for use as
+    # a Operation on the client.
+    Operation = view_class(:cancel, :cancelled, :deadline, :execute, :metadata,
+                           :status)
+
+    # confirms that no events are enqueued, and that the queue is not
+    # shutdown.
+    def assert_queue_is_ready
+      begin
+        ev = @cq.pluck(self, TimeConsts::ZERO)
+        raise "unexpected event #{ev.inspect}" unless ev.nil?
+      rescue OutOfTime
+        # expected, nothing should be on the queue and the deadline was ZERO,
+        # except things using another tag
+      end
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb
new file mode 100644
index 0000000..a3566e1
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/bidi_call.rb
@@ -0,0 +1,320 @@
+# 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 'forwardable'
+require 'grpc'
+
+module GRPC
+
+  # The BiDiCall class orchestrates exection of a BiDi stream on a client or
+  # server.
+  class BidiCall
+    include CompletionType
+    include StatusCodes
+
+    # Creates a BidiCall.
+    #
+    # BidiCall should only be created after a call is accepted.  That means
+    # different things on a client and a server.  On the client, the call is
+    # accepted after call.start_invoke followed by receipt of the corresponding
+    # INVOKE_ACCEPTED.  On the server, this is after call.accept.
+    #
+    # #initialize cannot determine if the call is accepted or not; so if a
+    # call that's not accepted is used here, the error won't be visible until
+    # the BidiCall#run is called.
+    #
+    # deadline is the absolute deadline for the call.
+    #
+    # @param call [Call] the call used by the ActiveCall
+    # @param q [CompletionQueue] the completion queue used to accept
+    #          the call
+    # @param marshal [Function] f(obj)->string that marshal requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Fixnum] the deadline for the call to complete
+    # @param finished_tag [Object] the object used as the call's finish tag,
+    def initialize(call, q, marshal, unmarshal, deadline, finished_tag)
+      raise ArgumentError.new('not a call') unless call.is_a?Call
+      if !q.is_a?CompletionQueue
+        raise ArgumentError.new('not a CompletionQueue')
+      end
+      @call = call
+      @cq = q
+      @deadline = deadline
+      @finished_tag = finished_tag
+      @marshal = marshal
+      @readq = Queue.new
+      @unmarshal = unmarshal
+      @writeq = Queue.new
+    end
+
+    # Begins orchestration of the Bidi stream for a client sending requests.
+    #
+    # The method either returns an Enumerator of the responses, or accepts a
+    # block that can be invoked with each response.
+    #
+    # @param requests the Enumerable of requests to send
+    # @return an Enumerator of requests to yield
+    def run_on_client(requests, &blk)
+      enq_th = enqueue_for_sending(requests)
+      loop_th = start_read_write_loop
+      replies = each_queued_msg
+      return replies if blk.nil?
+      replies.each { |r| blk.call(r) }
+    end
+
+    # Begins orchestration of the Bidi stream for a server generating replies.
+    #
+    # N.B. gen_each_reply is a func(Enumerable<Requests>)
+    #
+    # It takes an enumerable of requests as an arg, in case there is a
+    # relationship between the stream of requests and the stream of replies.
+    #
+    # This does not mean that must necessarily be one.  E.g, the replies
+    # produced by gen_each_reply could ignore the received_msgs
+    #
+    # @param gen_each_reply [Proc] generates the BiDi stream replies.
+    def run_on_server(gen_each_reply)
+      replys = gen_each_reply.call(each_queued_msg)
+      enq_th = enqueue_for_sending(replys)
+      loop_th = start_read_write_loop(is_client:false)
+      loop_th.join
+      enq_th.join
+    end
+
+    private
+
+    END_OF_READS = :end_of_reads
+    END_OF_WRITES = :end_of_writes
+
+    # each_queued_msg yields each message on this instances readq
+    #
+    # - messages are added to the readq by #read_write_loop
+    # - iteration ends when the instance itself is added
+    def each_queued_msg
+      return enum_for(:each_queued_msg) if !block_given?
+      count = 0
+      loop do
+        logger.debug("each_queued_msg: msg##{count}")
+        count += 1
+        req = @readq.pop
+        throw req if req.is_a?StandardError
+        break if req.equal?(END_OF_READS)
+        yield req
+      end
+    end
+
+    # during bidi-streaming, read the requests to send from a separate thread
+    # read so that read_write_loop does not block waiting for requests to read.
+    def enqueue_for_sending(requests)
+      Thread.new do  # TODO(temiola) run on a thread pool
+        begin
+          requests.each { |req| @writeq.push(req)}
+          @writeq.push(END_OF_WRITES)
+        rescue StandardError => e
+          logger.warn('enqueue_for_sending failed')
+          logger.warn(e)
+          @writeq.push(e)
+        end
+      end
+    end
+
+    # starts the read_write loop
+    def start_read_write_loop(is_client: true)
+      t = Thread.new do
+        begin
+          read_write_loop(is_client: is_client)
+        rescue StandardError => e
+          logger.warn('start_read_write_loop failed')
+          logger.warn(e)
+          @readq.push(e)  # let each_queued_msg terminate with the error
+        end
+      end
+      t.priority = 3  # hint that read_write_loop threads should be favoured
+      t
+    end
+
+    # drain_writeq removes any outstanding message on the writeq
+    def drain_writeq
+      while @writeq.size != 0 do
+        discarded = @writeq.pop
+        logger.warn("discarding: queued write: #{discarded}")
+      end
+    end
+
+    # sends the next queued write
+    #
+    # The return value is an array with three values
+    # - the first indicates if a writes was started
+    # - the second that all writes are done
+    # - the third indicates that are still writes to perform but they are lates
+    #
+    # If value pulled from writeq is a StandardError, the producer hit an error
+    # that should be raised.
+    #
+    # @param is_client [Boolean] when true, writes_done will be called when the
+    # last entry is read from the writeq
+    #
+    # @return [in_write, done_writing]
+    def next_queued_write(is_client: true)
+      in_write, done_writing = false, false
+
+      # send the next item on the queue if there is any
+      return [in_write, done_writing] if @writeq.size == 0
+
+      # TODO(temiola): provide a queue class that returns nil after a timeout
+      req = @writeq.pop
+      if req.equal?(END_OF_WRITES)
+        logger.debug('done writing after last req')
+        if is_client
+          logger.debug('sent writes_done after last req')
+          @call.writes_done(self)
+        end
+        done_writing = true
+        return [in_write, done_writing]
+      elsif req.is_a?(StandardError)  # used to signal an error in the producer
+        logger.debug('done writing due to a failure')
+        if is_client
+          logger.debug('sent writes_done after a failure')
+          @call.writes_done(self)
+        end
+        logger.warn(req)
+        done_writing = true
+        return [in_write, done_writing]
+      end
+
+      # send the payload
+      payload = @marshal.call(req)
+      @call.start_write(ByteBuffer.new(payload), self)
+      logger.debug("rwloop: sent payload #{req.inspect}")
+      in_write = true
+      return [in_write, done_writing]
+    end
+
+    # read_write_loop takes items off the write_queue and sends them, reads
+    # msgs and adds them to the read queue.
+    def read_write_loop(is_client: true)
+      done_reading, done_writing = false, false
+      finished, pre_finished = false, false
+      in_write, writes_late = false, false
+      count = 0
+
+      # queue the initial read before beginning the loop
+      @call.start_read(self)
+
+      loop do
+        # whether or not there are outstanding writes is independent of the
+        # next event from the completion queue.  The producer may queue the
+        # first msg at any time, e.g, after the loop is started running. So,
+        # it's essential for the loop to check for possible writes here, in
+        # order to correctly begin writing.
+        if !in_write and !done_writing
+          in_write, done_writing = next_queued_write(is_client: is_client)
+        end
+        logger.debug("rwloop is_client? #{is_client}")
+        logger.debug("rwloop count: #{count}")
+        count += 1
+
+        # Loop control:
+        #
+        # - Break when no further events need to read. On clients, this means
+        # waiting for a FINISHED, servers just need to wait for all reads and
+        # writes to be done.
+        #
+        # - Also, don't read an event unless there's one expected.  This can
+        # happen, e.g, when all the reads are done, there are no writes
+        # available, but writing is not complete.
+        logger.debug("done_reading? #{done_reading}")
+        logger.debug("done_writing? #{done_writing}")
+        logger.debug("finish accepted? #{pre_finished}")
+        logger.debug("finished? #{finished}")
+        logger.debug("in write? #{in_write}")
+        if is_client
+          break if done_writing and done_reading and pre_finished and finished
+          logger.debug('waiting for another event')
+          if in_write or !done_reading or !pre_finished
+            logger.debug('waiting for another event')
+            ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+          elsif !finished
+            logger.debug('waiting for another event')
+            ev = @cq.pluck(@finish_tag, TimeConsts::INFINITE_FUTURE)
+          else
+            next  # no events to wait on, but not done writing
+          end
+        else
+          break if done_writing and done_reading
+          if in_write or !done_reading
+            logger.debug('waiting for another event')
+            ev = @cq.pluck(self, TimeConsts::INFINITE_FUTURE)
+          else
+            next  # no events to wait on, but not done writing
+          end
+        end
+
+        # handle the next event.
+        if ev.nil?
+          drain_writeq
+          raise OutOfTime
+        elsif ev.type == WRITE_ACCEPTED
+          logger.debug('write accepted!')
+          in_write = false
+          next
+        elsif ev.type == FINISH_ACCEPTED
+          logger.debug('finish accepted!')
+          pre_finished = true
+          next
+        elsif ev.type == READ
+          logger.debug("received req: #{ev.result.inspect}")
+          if ev.result.nil?
+            logger.debug('done reading!')
+            done_reading = true
+            @readq.push(END_OF_READS)
+          else
+            # push the latest read onto the queue and continue reading
+            logger.debug("received req.to_s: #{ev.result.to_s}")
+            res = @unmarshal.call(ev.result.to_s)
+            logger.debug("req (unmarshalled): #{res.inspect}")
+            @readq.push(res)
+            if !done_reading
+              @call.start_read(self)
+            end
+          end
+        elsif ev.type == FINISHED
+          logger.debug("finished! with status:#{ev.result.inspect}")
+          finished = true
+          ev.call.status = ev.result
+          if ev.result.code != OK
+            raise BadStatus.new(ev.result.code, ev.result.details)
+          end
+        end
+      end
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
new file mode 100644
index 0000000..fee31e3
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -0,0 +1,358 @@
+# 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 'grpc'
+require 'grpc/generic/active_call'
+require 'xray/thread_dump_signal_handler'
+
+module GRPC
+
+  # ClientStub represents an endpoint used to send requests to GRPC servers.
+  class ClientStub
+    include StatusCodes
+
+    # Default deadline is 5 seconds.
+    DEFAULT_DEADLINE = 5
+
+    # Creates a new ClientStub.
+    #
+    # Minimally, a stub is created with the just the host of the gRPC service
+    # it wishes to access, e.g.,
+    #
+    # my_stub = ClientStub.new(example.host.com:50505)
+    #
+    # Any arbitrary keyword arguments are treated as channel arguments used to
+    # configure the RPC connection to the host.
+    #
+    # There are two specific keywords are that not used to configure the
+    # channel:
+    #
+    # - :channel_override
+    # when present, this must be a pre-created GRPC::Channel.  If it's present
+    # the host and arbitrary keyword arg areignored, and the RPC connection uses
+    # this channel.
+    #
+    # - :deadline
+    # when present, this is the default deadline used for calls
+    #
+    # @param host [String] the host the stub connects to
+    # @param q [TaggedCompletionQueue] used to wait for events
+    # @param channel_override [Channel] a pre-created channel
+    # @param deadline [Number] the default deadline to use in requests
+    # @param kw [KeywordArgs] the channel arguments
+    def initialize(host, q,
+                   channel_override:nil,
+                   deadline:DEFAULT_DEADLINE,
+                   **kw)
+      if !q.is_a?CompletionQueue
+        raise ArgumentError.new('not a CompletionQueue')
+      end
+      @host = host
+      if !channel_override.nil?
+        ch = channel_override
+        raise ArgumentError.new('not a Channel') unless ch.is_a?(Channel)
+      else
+        ch = Channel.new(host, **kw)
+      end
+
+      @deadline = deadline
+      @ch = ch
+      @queue = q
+    end
+
+    # request_response sends a request to a GRPC server, and returns the
+    # response.
+    #
+    # == Flow Control ==
+    # This is a blocking call.
+    #
+    # * it does not return until a response is received.
+    #
+    # * the requests is sent only when GRPC core's flow control allows it to
+    #   be sent.
+    #
+    # == Errors ==
+    # An RuntimeError is raised if
+    #
+    # * the server responds with a non-OK status
+    #
+    # * the deadline is exceeded
+    #
+    # == Return Value ==
+    #
+    # If return_op is false, the call returns the response
+    #
+    # If return_op is true, the call returns an Operation, calling execute
+    # on the Operation returns the response.
+    #
+    # @param method [String] the RPC method to call on the GRPC server
+    # @param req [Object] the request sent to the server
+    # @param marshal [Function] f(obj)->string that marshals requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Numeric] (optional) the max completion time in seconds
+    # @param return_op [true|false] (default false) return an Operation if true
+    # @return [Object] the response received from the server
+    def request_response(method, req, marshal, unmarshal, deadline=nil,
+                         return_op:false)
+      c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
+      return c.request_response(req) unless return_op
+
+      # return the operation view of the active_call; define #execute as a
+      # new method for this instance that invokes #request_response.
+      op = c.operation
+      op.define_singleton_method(:execute) do
+        c.request_response(req)
+      end
+      op
+    end
+
+    # client_streamer sends a stream of requests to a GRPC server, and
+    # returns a single response.
+    #
+    # requests provides an 'iterable' of Requests. I.e. it follows Ruby's
+    # #each enumeration protocol. In the simplest case, requests will be an
+    # array of marshallable objects; in typical case it will be an Enumerable
+    # that allows dynamic construction of the marshallable objects.
+    #
+    # == Flow Control ==
+    # This is a blocking call.
+    #
+    # * it does not return until a response is received.
+    #
+    # * each requests is sent only when GRPC core's flow control allows it to
+    #   be sent.
+    #
+    # == Errors ==
+    # An RuntimeError is raised if
+    #
+    # * the server responds with a non-OK status
+    #
+    # * the deadline is exceeded
+    #
+    # == Return Value ==
+    #
+    # If return_op is false, the call consumes the requests and returns
+    # the response.
+    #
+    # If return_op is true, the call returns the response.
+    #
+    # @param method [String] the RPC method to call on the GRPC server
+    # @param requests [Object] an Enumerable of requests to send
+    # @param marshal [Function] f(obj)->string that marshals requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Numeric] the max completion time in seconds
+    # @param return_op [true|false] (default false) return an Operation if true
+    # @return [Object|Operation] the response received from the server
+    def client_streamer(method, requests, marshal, unmarshal, deadline=nil,
+                        return_op:false)
+      c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
+      return c.client_streamer(requests) unless return_op
+
+      # return the operation view of the active_call; define #execute as a
+      # new method for this instance that invokes #client_streamer.
+      op = c.operation
+      op.define_singleton_method(:execute) do
+        c.client_streamer(requests)
+      end
+      op
+    end
+
+    # server_streamer sends one request to the GRPC server, which yields a
+    # stream of responses.
+    #
+    # responses provides an enumerator over the streamed responses, i.e. it
+    # follows Ruby's #each iteration protocol.  The enumerator blocks while
+    # waiting for each response, stops when the server signals that no
+    # further responses will be supplied.  If the implicit block is provided,
+    # it is executed with each response as the argument and no result is
+    # returned.
+    #
+    # == Flow Control ==
+    # This is a blocking call.
+    #
+    # * the request is sent only when GRPC core's flow control allows it to
+    #   be sent.
+    #
+    # * the request will not complete until the server sends the final response
+    #   followed by a status message.
+    #
+    # == Errors ==
+    # An RuntimeError is raised if
+    #
+    # * the server responds with a non-OK status when any response is
+    # * retrieved
+    #
+    # * the deadline is exceeded
+    #
+    # == Return Value ==
+    #
+    # if the return_op is false, the return value is an Enumerator of the
+    # results, unless a block is provided, in which case the block is
+    # executed with each response.
+    #
+    # if return_op is true, the function returns an Operation whose #execute
+    # method runs server streamer call. Again, Operation#execute either
+    # calls the given block with each response or returns an Enumerator of the
+    # responses.
+    #
+    # @param method [String] the RPC method to call on the GRPC server
+    # @param req [Object] the request sent to the server
+    # @param marshal [Function] f(obj)->string that marshals requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Numeric] the max completion time in seconds
+    # @param return_op [true|false] (default false) return an Operation if true
+    # @param blk [Block] when provided, is executed for each response
+    # @return [Enumerator|Operation|nil] as discussed above
+    def server_streamer(method, req, marshal, unmarshal, deadline=nil,
+                        return_op:false, &blk)
+      c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
+      return c.server_streamer(req, &blk) unless return_op
+
+      # return the operation view of the active_call; define #execute
+      # as a new method for this instance that invokes #server_streamer
+      op = c.operation
+      op.define_singleton_method(:execute) do
+        c.server_streamer(req, &blk)
+      end
+      op
+    end
+
+    # bidi_streamer sends a stream of requests to the GRPC server, and yields
+    # a stream of responses.
+    #
+    # This method takes an Enumerable of requests, and returns and enumerable
+    # of responses.
+    #
+    # == requests ==
+    #
+    # requests provides an 'iterable' of Requests. I.e. it follows Ruby's #each
+    # enumeration protocol. In the simplest case, requests will be an array of
+    # marshallable objects; in typical case it will be an Enumerable that
+    # allows dynamic construction of the marshallable objects.
+    #
+    # == responses ==
+    #
+    # This is an enumerator of responses.  I.e, its #next method blocks
+    # waiting for the next response.  Also, if at any point the block needs
+    # to consume all the remaining responses, this can be done using #each or
+    # #collect.  Calling #each or #collect should only be done if
+    # the_call#writes_done has been called, otherwise the block will loop
+    # forever.
+    #
+    # == Flow Control ==
+    # This is a blocking call.
+    #
+    # * the call completes when the next call to provided block returns
+    # * [False]
+    #
+    # * the execution block parameters are two objects for sending and
+    #   receiving responses, each of which blocks waiting for flow control.
+    #   E.g, calles to bidi_call#remote_send will wait until flow control
+    #   allows another write before returning; and obviously calls to
+    #   responses#next block until the next response is available.
+    #
+    # == Termination ==
+    #
+    # As well as sending and receiving messages, the block passed to the
+    # function is also responsible for:
+    #
+    # * calling bidi_call#writes_done to indicate no further reqs will be
+    #   sent.
+    #
+    # * returning false if once the bidi stream is functionally completed.
+    #
+    # Note that response#next will indicate that there are no further
+    # responses by throwing StopIteration, but can only happen either
+    # if bidi_call#writes_done is called.
+    #
+    # To terminate the RPC correctly the block:
+    #
+    # * must call bidi#writes_done and then
+    #
+    #    * either return false as soon as there is no need for other responses
+    #
+    #    * loop on responses#next until no further responses are available
+    #
+    # == Errors ==
+    # An RuntimeError is raised if
+    #
+    # * the server responds with a non-OK status when any response is
+    # * retrieved
+    #
+    # * the deadline is exceeded
+    #
+    # == Return Value ==
+    #
+    # if the return_op is false, the return value is an Enumerator of the
+    # results, unless a block is provided, in which case the block is
+    # executed with each response.
+    #
+    # if return_op is true, the function returns an Operation whose #execute
+    # method runs the Bidi call. Again, Operation#execute either calls a
+    # given block with each response or returns an Enumerator of the responses.
+    #
+    # @param method [String] the RPC method to call on the GRPC server
+    # @param requests [Object] an Enumerable of requests to send
+    # @param marshal [Function] f(obj)->string that marshals requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [Numeric] (optional) the max completion time in seconds
+    # @param blk [Block] when provided, is executed for each response
+    # @param return_op [true|false] (default false) return an Operation if true
+    # @return [Enumerator|nil|Operation] as discussed above
+    def bidi_streamer(method, requests, marshal, unmarshal, deadline=nil,
+                      return_op:false, &blk)
+      c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
+      return c.bidi_streamer(requests, &blk) unless return_op
+
+      # return the operation view of the active_call; define #execute
+      # as a new method for this instance that invokes #bidi_streamer
+      op = c.operation
+      op.define_singleton_method(:execute) do
+        c.bidi_streamer(requests, &blk)
+      end
+      op
+    end
+
+    private
+    # Creates a new active stub
+    #
+    # @param ch [GRPC::Channel] the channel used to create the stub.
+    # @param marshal [Function] f(obj)->string that marshals requests
+    # @param unmarshal [Function] f(string)->obj that unmarshals responses
+    # @param deadline [TimeConst]
+    def new_active_call(ch, marshal, unmarshal, deadline=nil)
+      absolute_deadline = TimeConsts.from_relative_time(deadline)
+      call = @ch.create_call(ch, @host, absolute_deadline)
+      ActiveCall.new(call, @queue, marshal, unmarshal, absolute_deadline,
+                     started:false)
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/generic/rpc_desc.rb b/src/ruby/lib/grpc/generic/rpc_desc.rb
new file mode 100644
index 0000000..43b4d4f
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/rpc_desc.rb
@@ -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.
+
+require 'grpc'
+
+module GRPC
+
+  # RpcDesc is a Descriptor of an RPC method.
+  class RpcDesc < Struct.new(:name, :input, :output, :marshal_method,
+                             :unmarshal_method)
+
+    # Used to wrap a message class to indicate that it needs to be streamed.
+    class Stream
+      attr_accessor :type
+
+      def initialize(type)
+        @type = type
+      end
+    end
+
+    # @return [Proc] { |instance| marshalled(instance) }
+    def marshal_proc
+      Proc.new { |o| o.method(marshal_method).call.to_s }
+    end
+
+    # @param [:input, :output] target determines whether to produce the an
+    #                          unmarshal Proc for the rpc input parameter or
+    #                          its output parameter
+    #
+    # @return [Proc] An unmarshal proc { |marshalled(instance)| instance }
+    def unmarshal_proc(target)
+      raise ArgumentError if not [:input, :output].include?(target)
+      unmarshal_class = method(target).call
+      if unmarshal_class.is_a?Stream
+        unmarshal_class = unmarshal_class.type
+      end
+      Proc.new { |o| unmarshal_class.method(unmarshal_method).call(o) }
+    end
+
+    def run_server_method(active_call, mth)
+      # While a server method is running, it might be cancelled, its deadline
+      # might be reached, the handler could throw an unknown error, or a
+      # well-behaved handler could throw a StatusError.
+      begin
+        if is_request_response?
+          req = active_call.remote_read
+          resp = mth.call(req, active_call.single_req_view)
+          active_call.remote_send(resp)
+        elsif is_client_streamer?
+          resp = mth.call(active_call.multi_req_view)
+          active_call.remote_send(resp)
+        elsif is_server_streamer?
+          req = active_call.remote_read
+          replys = mth.call(req, active_call.single_req_view)
+          replys.each { |r| active_call.remote_send(r) }
+        else  # is a bidi_stream
+          active_call.run_server_bidi(mth)
+        end
+        send_status(active_call, StatusCodes::OK, 'OK')
+        active_call.finished
+      rescue BadStatus => e
+        # this is raised by handlers that want GRPC to send an application
+        # error code and detail message.
+        logger.debug("app error: #{active_call}, status:#{e.code}:#{e.details}")
+        send_status(active_call, e.code, e.details)
+      rescue CallError => e
+        # This is raised by GRPC internals but should rarely, if ever happen.
+        # Log it, but don't notify the other endpoint..
+        logger.warn("failed call: #{active_call}\n#{e}")
+      rescue OutOfTime
+        # This is raised when active_call#method.call exceeeds the deadline
+        # event.  Send a status of deadline exceeded
+        logger.warn("late call: #{active_call}")
+        send_status(active_call, StatusCodes::DEADLINE_EXCEEDED, 'late')
+      rescue EventError => e
+        # This is raised by GRPC internals but should rarely, if ever happen.
+        # Log it, but don't notify the other endpoint..
+        logger.warn("failed call: #{active_call}\n#{e}")
+      rescue StandardError => e
+        # This will usuaally be an unhandled error in the handling code.
+        # Send back a UNKNOWN status to the client
+        logger.warn("failed handler: #{active_call}; sending status:UNKNOWN")
+        logger.warn(e)
+        send_status(active_call, StatusCodes::UNKNOWN, 'no reason given')
+      end
+    end
+
+    def assert_arity_matches(mth)
+      if (is_request_response? || is_server_streamer?)
+        if mth.arity != 2
+          raise arity_error(mth, 2, "should be #{mth.name}(req, call)")
+        end
+      else
+        if mth.arity != 1
+          raise arity_error(mth, 1, "should be #{mth.name}(call)")
+        end
+      end
+    end
+
+    def is_request_response?
+      !input.is_a?(Stream) && !output.is_a?(Stream)
+    end
+
+    def is_client_streamer?
+      input.is_a?(Stream) && !output.is_a?(Stream)
+    end
+
+    def is_server_streamer?
+      !input.is_a?(Stream) && output.is_a?(Stream)
+    end
+
+    def is_bidi_streamer?
+      input.is_a?(Stream) && output.is_a?(Stream)
+    end
+
+    def arity_error(mth, want, msg)
+      "##{mth.name}: bad arg count; got:#{mth.arity}, want:#{want}, #{msg}"
+    end
+
+    def send_status(active_client, code, details)
+      begin
+        active_client.send_status(code, details)
+      rescue StandardError => e
+        logger.warn('Could not send status %d:%s' % [code, details])
+        logger.warn(e)
+      end
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/generic/rpc_server.rb b/src/ruby/lib/grpc/generic/rpc_server.rb
new file mode 100644
index 0000000..e6efdc3
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/rpc_server.rb
@@ -0,0 +1,408 @@
+# 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 'grpc'
+require 'grpc/generic/active_call'
+require 'grpc/generic/service'
+require 'thread'
+require 'xray/thread_dump_signal_handler'
+
+module GRPC
+
+  # RpcServer hosts a number of services and makes them available on the
+  # network.
+  class RpcServer
+    include CompletionType
+    extend ::Forwardable
+
+    def_delegators :@server, :add_http2_port
+
+    # Default thread pool size is 3
+    DEFAULT_POOL_SIZE = 3
+
+    # Default max_waiting_requests size is 20
+    DEFAULT_MAX_WAITING_REQUESTS = 20
+
+    # Creates a new RpcServer.
+    #
+    # The RPC server is configured using keyword arguments.
+    #
+    # There are some specific keyword args used to configure the RpcServer
+    # instance, however other arbitrary are allowed and when present are used
+    # to configure the listeninng connection set up by the RpcServer.
+    #
+    # * server_override: which if passed must be a [GRPC::Server].  When
+    # present.
+    #
+    # * poll_period: when present, the server polls for new events with this
+    # period
+    #
+    # * pool_size: the size of the thread pool the server uses to run its
+    # threads
+    #
+    # * completion_queue_override: when supplied, this will be used as the
+    # completion_queue that the server uses to receive network events,
+    # otherwise its creates a new instance itself
+    #
+    # * max_waiting_requests: the maximum number of requests that are not
+    # being handled to allow. When this limit is exceeded, the server responds
+    # with not available to new requests
+    def initialize(pool_size:DEFAULT_POOL_SIZE,
+                   max_waiting_requests:DEFAULT_MAX_WAITING_REQUESTS,
+                   poll_period:TimeConsts::INFINITE_FUTURE,
+                   completion_queue_override:nil,
+                   server_override:nil,
+                   **kw)
+      if !completion_queue_override.nil?
+        cq = completion_queue_override
+        if !cq.is_a?(CompletionQueue)
+          raise ArgumentError.new('not a CompletionQueue')
+        end
+      else
+        cq = CompletionQueue.new
+      end
+      @cq = cq
+
+      if !server_override.nil?
+        srv = server_override
+        raise ArgumentError.new('not a Server') unless srv.is_a?(Server)
+      else
+        srv = Server.new(@cq, **kw)
+      end
+      @server = srv
+
+      @pool_size = pool_size
+      @max_waiting_requests = max_waiting_requests
+      @poll_period = poll_period
+      @run_mutex = Mutex.new
+      @run_cond = ConditionVariable.new
+      @pool = Pool.new(@pool_size)
+    end
+
+    # stops a running server
+    #
+    # the call has no impact if the server is already stopped, otherwise
+    # server's current call loop is it's last.
+    def stop
+      if @running
+        @stopped = true
+        @pool.stop
+      end
+    end
+
+    # determines if the server is currently running
+    def running?
+      @running ||= false
+    end
+
+    # Is called from other threads to wait for #run to start up the server.
+    #
+    # If run has not been called, this returns immediately.
+    #
+    # @param timeout [Numeric] number of seconds to wait
+    # @result [true, false] true if the server is running, false otherwise
+    def wait_till_running(timeout=0.1)
+      end_time, sleep_period = Time.now + timeout, (1.0 * timeout)/100
+      while Time.now < end_time
+        if !running?
+          @run_mutex.synchronize { @run_cond.wait(@run_mutex) }
+        end
+        sleep(sleep_period)
+      end
+      return running?
+    end
+
+    # determines if the server is currently stopped
+    def stopped?
+      @stopped ||= false
+    end
+
+    # handle registration of classes
+    #
+    # service is either a class that includes GRPC::GenericService and whose
+    # #new function can be called without argument or any instance of such a
+    # class.
+    #
+    # E.g, after
+    #
+    # class Divider
+    #   include GRPC::GenericService
+    #   rpc :div DivArgs, DivReply    # single request, single response
+    #   def initialize(optional_arg='default option') # no args
+    #     ...
+    #   end
+    #
+    # srv = GRPC::RpcServer.new(...)
+    #
+    # # Either of these works
+    #
+    # srv.handle(Divider)
+    #
+    # # or
+    #
+    # srv.handle(Divider.new('replace optional arg'))
+    #
+    # It raises RuntimeError:
+    # - if service is not valid service class or object
+    # - if it is a valid service, but the handler methods are already registered
+    # - if the server is already running
+    #
+    # @param service [Object|Class] a service class or object as described
+    #        above
+    def handle(service)
+      raise 'cannot add services if the server is running' if running?
+      raise 'cannot add services if the server is stopped' if stopped?
+      cls = service.is_a?(Class) ? service : service.class
+      assert_valid_service_class(cls)
+      add_rpc_descs_for(service)
+    end
+
+    # runs the server
+    #
+    # - if no rpc_descs are registered, this exits immediately, otherwise it
+    #   continues running permanently and does not return until program exit.
+    #
+    # - #running? returns true after this is called, until #stop cause the
+    #   the server to stop.
+    def run
+      if rpc_descs.size == 0
+        logger.warn('did not run as no services were present')
+        return
+      end
+      @run_mutex.synchronize do
+        @running = true
+        @run_cond.signal
+      end
+      @pool.start
+      @server.start
+      server_tag = Object.new
+      while !stopped?
+        @server.request_call(server_tag)
+        ev = @cq.pluck(server_tag, @poll_period)
+        next if ev.nil?
+        if ev.type != SERVER_RPC_NEW
+          logger.warn("bad evt: got:#{ev.type}, want:#{SERVER_RPC_NEW}")
+          next
+        end
+        c = new_active_server_call(ev.call, ev.result)
+        if !c.nil?
+          mth = ev.result.method.to_sym
+
+          # NOTE(temiola): This is necessary to allow the C call struct wrapped
+          # within the active_call created by the event to be GCed; this is
+          # necessary so that other C-level destructors get called in the
+          # required order.
+          ev = nil
+
+          @pool.schedule(c) do |call|
+            rpc_descs[mth].run_server_method(call, rpc_handlers[mth])
+          end
+        end
+      end
+      @running = false
+    end
+
+    def new_active_server_call(call, new_server_rpc)
+      # TODO(temiola): perhaps reuse the main server completion queue here, but
+      # for now, create a new completion queue per call, pending best practice
+      # usage advice from the c core.
+
+      # Accept the call.  This is necessary even if a status is to be sent back
+      # immediately
+      finished_tag = Object.new
+      call_queue = CompletionQueue.new
+      call.accept(call_queue, finished_tag)
+
+      # Send UNAVAILABLE if there are too many unprocessed jobs
+      jobs_count, max = @pool.jobs_waiting, @max_waiting_requests
+      logger.info("waiting: #{jobs_count}, max: #{max}")
+      if @pool.jobs_waiting > @max_waiting_requests
+        logger.warn("NOT AVAILABLE: too many jobs_waiting: #{new_server_rpc}")
+        noop = Proc.new { |x| x }
+        c = ActiveCall.new(call, call_queue, noop, noop,
+                           new_server_rpc.deadline, finished_tag: finished_tag)
+        c.send_status(StatusCodes::UNAVAILABLE, '')
+        return nil
+      end
+
+      # Send NOT_FOUND if the method does not exist
+      mth = new_server_rpc.method.to_sym
+      if !rpc_descs.has_key?(mth)
+        logger.warn("NOT_FOUND: #{new_server_rpc}")
+        noop = Proc.new { |x| x }
+        c = ActiveCall.new(call, call_queue, noop, noop,
+                           new_server_rpc.deadline, finished_tag: finished_tag)
+        c.send_status(StatusCodes::NOT_FOUND, '')
+        return nil
+      end
+
+      # Create the ActiveCall
+      rpc_desc = rpc_descs[mth]
+      logger.info("deadline is #{new_server_rpc.deadline}; (now=#{Time.now})")
+      ActiveCall.new(call, call_queue,
+                     rpc_desc.marshal_proc, rpc_desc.unmarshal_proc(:input),
+                     new_server_rpc.deadline, finished_tag: finished_tag)
+    end
+
+    # Pool is a simple thread pool for running server requests.
+    class Pool
+
+      def initialize(size)
+        raise 'pool size must be positive' unless size > 0
+        @jobs = Queue.new
+        @size = size
+        @stopped = false
+        @stop_mutex = Mutex.new
+        @stop_cond = ConditionVariable.new
+        @workers = []
+      end
+
+      # Returns the number of jobs waiting
+      def jobs_waiting
+        @jobs.size
+      end
+
+      # Runs the given block on the queue with the provided args.
+      #
+      # @param args the args passed blk when it is called
+      # @param blk the block to call
+      def schedule(*args, &blk)
+        raise 'already stopped' if @stopped
+        return if blk.nil?
+        logger.info('schedule another job')
+        @jobs << [blk, args]
+      end
+
+      # Starts running the jobs in the thread pool.
+      def start
+        raise 'already stopped' if @stopped
+        until @workers.size == @size.to_i
+          next_thread = Thread.new do
+            catch(:exit) do  # allows { throw :exit } to kill a thread
+              loop do
+                begin
+                  blk, args = @jobs.pop
+                  blk.call(*args)
+                rescue StandardError => e
+                  logger.warn('Error in worker thread')
+                  logger.warn(e)
+                end
+              end
+            end
+
+            # removes the threads from workers, and signal when all the threads
+            # are complete.
+            @stop_mutex.synchronize do
+              @workers.delete(Thread.current)
+              if @workers.size == 0
+                @stop_cond.signal
+              end
+            end
+          end
+          @workers << next_thread
+        end
+      end
+
+      # Stops the jobs in the pool
+      def stop
+        logger.info('stopping, will wait for all the workers to exit')
+        @workers.size.times { schedule { throw :exit } }
+        @stopped = true
+
+        # TODO(temiola): allow configuration of the keepalive period
+        keep_alive = 5
+        @stop_mutex.synchronize do
+          if @workers.size > 0
+            @stop_cond.wait(@stop_mutex, keep_alive)
+          end
+        end
+
+        # Forcibly shutdown any threads that are still alive.
+        if @workers.size > 0
+          logger.warn("forcibly terminating #{@workers.size} worker(s)")
+          @workers.each do |t|
+            next unless t.alive?
+            begin
+              t.exit
+            rescue StandardError => e
+              logger.warn('error while terminating a worker')
+              logger.warn(e)
+            end
+          end
+        end
+
+        logger.info('stopped, all workers are shutdown')
+      end
+
+    end
+
+    protected
+
+    def rpc_descs
+      @rpc_descs ||= {}
+    end
+
+    def rpc_handlers
+      @rpc_handlers ||= {}
+    end
+
+    private
+
+    def assert_valid_service_class(cls)
+      if !cls.include?(GenericService)
+        raise "#{cls} should 'include GenericService'"
+      end
+      if cls.rpc_descs.size == 0
+        raise "#{cls} should specify some rpc descriptions"
+      end
+      cls.assert_rpc_descs_have_methods
+    end
+
+    def add_rpc_descs_for(service)
+      cls = service.is_a?(Class) ? service : service.class
+      specs = rpc_descs
+      handlers = rpc_handlers
+      cls.rpc_descs.each_pair do |name,spec|
+        route = "/#{cls.service_name}/#{name}".to_sym
+        if specs.has_key?(route)
+          raise "Cannot add rpc #{route} from #{spec}, already registered"
+        else
+          specs[route] = spec
+          if service.is_a?(Class)
+            handlers[route] = cls.new.method(name.to_s.underscore.to_sym)
+          else
+            handlers[route] = service.method(name.to_s.underscore.to_sym)
+          end
+          logger.info("handling #{route} with #{handlers[route]}")
+        end
+      end
+    end
+  end
+
+end
diff --git a/src/ruby/lib/grpc/generic/service.rb b/src/ruby/lib/grpc/generic/service.rb
new file mode 100644
index 0000000..1a3d0dc
--- /dev/null
+++ b/src/ruby/lib/grpc/generic/service.rb
@@ -0,0 +1,247 @@
+# 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 'grpc'
+require 'grpc/generic/client_stub'
+require 'grpc/generic/rpc_desc'
+
+# Extend String to add a method underscore
+class String
+
+  # creates a new string that is the underscore separate version of this one.
+  #
+  # E.g,
+  # PrintHTML -> print_html
+  # AMethod -> a_method
+  # AnRpc -> an_rpc
+  def underscore
+    word = self.dup
+    word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
+    word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
+    word.tr!('-', '_')
+    word.downcase!
+    word
+  end
+
+end
+
+module GRPC
+
+  # Provides behaviour used to implement schema-derived service classes.
+  #
+  # Is intended to be used to support both client and server IDL-schema-derived
+  # servers.
+  module GenericService
+
+    # Used to indicate that a name has already been specified
+    class DuplicateRpcName < StandardError
+      def initialize(name)
+        super("rpc (#{name}) is already defined")
+      end
+    end
+
+    # Provides a simple DSL to describe RPC services.
+    #
+    # E.g, a Maths service that uses the serializable messages DivArgs,
+    # DivReply and Num might define its endpoint uses the following way:
+    #
+    # rpc :div DivArgs, DivReply    # single request, single response
+    # rpc :sum stream(Num), Num     # streamed input, single response
+    # rpc :fib FibArgs, stream(Num) # single request, streamed response
+    # rpc :div_many stream(DivArgs), stream(DivReply)
+    #                               # streamed req and resp
+    #
+    # Each 'rpc' adds an RpcDesc to classes including this module, and
+    # #assert_rpc_descs_have_methods is used to ensure the including class
+    # provides methods with signatures that support all the descriptors.
+    module Dsl
+
+      # This configures the method names that the serializable message
+      # implementation uses to marshal and unmarshal messages.
+      #
+      # - unmarshal_class method must be a class method on the serializable
+      # message type that takes a string (byte stream) and produces and object
+      #
+      # - marshal_instance_method is called on a serializable message instance
+      # and produces a serialized string.
+      #
+      # The Dsl verifies that the types in the descriptor have both the
+      # unmarshal and marshal methods.
+      attr_writer(:marshal_instance_method, :unmarshal_class_method)
+      attr_accessor(:service_name)
+
+      # Adds an RPC spec.
+      #
+      # Takes the RPC name and the classes representing the types to be
+      # serialized, and adds them to the including classes rpc_desc hash.
+      #
+      # input and output should both have the methods #marshal and #unmarshal
+      # that are responsible for writing and reading an object instance from a
+      # byte buffer respectively.
+      #
+      # @param name [String] the name of the rpc
+      # @param input [Object] the input parameter's class
+      # @param output [Object] the output parameter's class
+      def rpc(name, input, output)
+        raise DuplicateRpcName, name if rpc_descs.has_key?(name)
+        assert_can_marshal(input)
+        assert_can_marshal(output)
+        rpc_descs[name] = RpcDesc.new(name, input, output,
+                                      marshal_instance_method,
+                                      unmarshal_class_method)
+      end
+
+      def inherited(subclass)
+        # Each subclass should have distinct class variable with its own
+        # rpc_descs.
+        subclass.rpc_descs.merge!(rpc_descs)
+        subclass.service_name = service_name
+      end
+
+      # the name of the instance method used to marshal events to a byte stream.
+      def marshal_instance_method
+        @marshal_instance_method ||= :marshal
+      end
+
+      # the name of the class method used to unmarshal from a byte stream.
+      def unmarshal_class_method
+        @unmarshal_class_method ||= :unmarshal
+      end
+
+      def assert_can_marshal(cls)
+        if cls.is_a?RpcDesc::Stream
+          cls = cls.type
+        end
+
+        mth = unmarshal_class_method
+        if !cls.methods.include?(mth)
+          raise ArgumentError, "#{cls} needs #{cls}.#{mth}"
+        end
+
+        mth = marshal_instance_method
+        if !cls.instance_methods.include?(mth)
+          raise ArgumentError, "#{cls} needs #{cls}.new.#{mth}"
+        end
+      end
+
+      # @param cls [Class] the class of a serializable type
+      # @return cls wrapped in a RpcDesc::Stream
+      def stream(cls)
+        assert_can_marshal(cls)
+        RpcDesc::Stream.new(cls)
+      end
+
+      # the RpcDescs defined for this GenericService, keyed by name.
+      def rpc_descs
+        @rpc_descs ||= {}
+      end
+
+      # Creates a rpc client class with methods for accessing the methods
+      # currently in rpc_descs.
+      def rpc_stub_class
+        descs = rpc_descs
+        route_prefix = service_name
+        Class.new(ClientStub) do
+
+          # @param host [String] the host the stub connects to
+          # @param kw [KeywordArgs] the channel arguments, plus any optional
+          #                         args for configuring the client's channel
+          def initialize(host, **kw)
+            super(host, CompletionQueue.new, **kw)
+          end
+
+          # Used define_method to add a method for each rpc_desc.  Each method
+          # calls the base class method for the given descriptor.
+          descs.each_pair do |name,desc|
+            mth_name = name.to_s.underscore.to_sym
+            marshal = desc.marshal_proc
+            unmarshal = desc.unmarshal_proc(:output)
+            route = "/#{route_prefix}/#{name}"
+            if desc.is_request_response?
+              define_method(mth_name) do |req,deadline=nil|
+                logger.debug("calling #{@host}:#{route}")
+                request_response(route, req, marshal, unmarshal, deadline)
+              end
+            elsif desc.is_client_streamer?
+              define_method(mth_name) do |reqs,deadline=nil|
+                logger.debug("calling #{@host}:#{route}")
+                client_streamer(route, reqs, marshal, unmarshal, deadline)
+              end
+            elsif desc.is_server_streamer?
+              define_method(mth_name) do |req,deadline=nil,&blk|
+                logger.debug("calling #{@host}:#{route}")
+                server_streamer(route, req, marshal, unmarshal, deadline, &blk)
+              end
+            else  # is a bidi_stream
+              define_method(mth_name) do |reqs, deadline=nil,&blk|
+                logger.debug("calling #{@host}:#{route}")
+                bidi_streamer(route, reqs, marshal, unmarshal, deadline, &blk)
+              end
+            end
+          end
+
+        end
+
+      end
+
+      # Asserts that the appropriate methods are defined for each added rpc
+      # spec. Is intended to aid verifying that server classes are correctly
+      # implemented.
+      def assert_rpc_descs_have_methods
+        rpc_descs.each_pair do |m,spec|
+          mth_name = m.to_s.underscore.to_sym
+          if !self.instance_methods.include?(mth_name)
+            raise "#{self} does not provide instance method '#{mth_name}'"
+          end
+          spec.assert_arity_matches(self.instance_method(mth_name))
+        end
+      end
+
+    end
+
+    def self.included(o)
+      o.extend(Dsl)
+
+      # Update to the use the name including module.  This can be nil e,g. when
+      # modules are declared dynamically.
+      if o.name.nil?
+        o.service_name = 'GenericService'
+      else
+        modules = o.name.split('::')
+        if modules.length > 2
+          o.service_name = modules[modules.length - 2]
+        else
+          o.service_name = modules.first
+        end
+      end
+    end
+
+  end
+
+end
diff --git a/src/ruby/lib/grpc/logconfig.rb b/src/ruby/lib/grpc/logconfig.rb
new file mode 100644
index 0000000..6d8e189
--- /dev/null
+++ b/src/ruby/lib/grpc/logconfig.rb
@@ -0,0 +1,40 @@
+# 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 'logging'
+
+include Logging.globally  # logger is accessible everywhere
+
+Logging.logger.root.appenders = Logging.appenders.stdout
+Logging.logger.root.level = :info
+
+# TODO(temiola): provide command-line configuration for logging
+Logging.logger['Google::RPC'].level = :debug
+Logging.logger['Google::RPC::ActiveCall'].level = :info
+Logging.logger['Google::RPC::BidiCall'].level = :info
diff --git a/src/ruby/lib/grpc/time_consts.rb b/src/ruby/lib/grpc/time_consts.rb
new file mode 100644
index 0000000..2cbab5d
--- /dev/null
+++ b/src/ruby/lib/grpc/time_consts.rb
@@ -0,0 +1,69 @@
+# 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 'grpc'
+
+module Google
+  module RPC
+    module TimeConsts  # re-opens a module in the C extension.
+
+      # Converts a time delta to an absolute deadline.
+      #
+      # Assumes timeish is a relative time, and converts its to an absolute,
+      # with following exceptions:
+      #
+      # * if timish is one of the TimeConsts.TimeSpec constants the value is
+      # preserved.
+      # * timish < 0 => TimeConsts.INFINITE_FUTURE
+      # * timish == 0 => TimeConsts.ZERO
+      #
+      # @param timeish [Number|TimeSpec]
+      # @return timeish [Number|TimeSpec]
+      def from_relative_time(timeish)
+        if timeish.is_a?TimeSpec
+          timeish
+        elsif timeish.nil?
+          TimeConsts::ZERO
+        elsif !timeish.is_a?Numeric
+          raise TypeError('Cannot make an absolute deadline from %s',
+                          timeish.inspect)
+        elsif timeish < 0
+          TimeConsts::INFINITE_FUTURE
+        elsif timeish == 0
+          TimeConsts::ZERO
+        else !timeish.nil?
+          Time.now + timeish
+        end
+      end
+
+      module_function :from_relative_time
+
+    end
+  end
+end
diff --git a/src/ruby/lib/grpc/version.rb b/src/ruby/lib/grpc/version.rb
new file mode 100644
index 0000000..0a84f4c
--- /dev/null
+++ b/src/ruby/lib/grpc/version.rb
@@ -0,0 +1,34 @@
+# 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.
+
+module Google
+  module RPC
+    VERSION = '0.0.1'
+  end
+end
diff --git a/src/ruby/spec/alloc_spec.rb b/src/ruby/spec/alloc_spec.rb
new file mode 100644
index 0000000..99cc39d
--- /dev/null
+++ b/src/ruby/spec/alloc_spec.rb
@@ -0,0 +1,46 @@
+# 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 'grpc'
+
+describe 'Wrapped classes where .new cannot create an instance' do
+
+  describe GRPC::Event do
+    it 'should fail .new fail with a runtime error' do
+      expect { GRPC::Event.new }.to raise_error(TypeError)
+    end
+  end
+
+  describe GRPC::Call do
+    it 'should fail .new fail with a runtime error' do
+      expect { GRPC::Event.new }.to raise_error(TypeError)
+    end
+  end
+
+end
diff --git a/src/ruby/spec/byte_buffer_spec.rb b/src/ruby/spec/byte_buffer_spec.rb
new file mode 100644
index 0000000..d4d3a69
--- /dev/null
+++ b/src/ruby/spec/byte_buffer_spec.rb
@@ -0,0 +1,71 @@
+# 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 'grpc'
+
+describe GRPC::ByteBuffer do
+
+  describe '#new' do
+
+    it 'is constructed from a string' do
+      expect { GRPC::ByteBuffer.new('#new') }.not_to raise_error
+    end
+
+    it 'can be constructed from the empty string' do
+      expect { GRPC::ByteBuffer.new('') }.not_to raise_error
+    end
+
+    it 'cannot be constructed from nil' do
+      expect { GRPC::ByteBuffer.new(nil) }.to raise_error TypeError
+    end
+
+    it 'cannot be constructed from non-strings' do
+      [1, Object.new, :a_symbol].each do |x|
+        expect { GRPC::ByteBuffer.new(x) }.to raise_error TypeError
+      end
+    end
+
+  end
+
+  describe '#to_s' do
+    it 'is the string value the ByteBuffer was constructed with' do
+      expect(GRPC::ByteBuffer.new('#to_s').to_s).to eq('#to_s')
+    end
+  end
+
+  describe '#dup' do
+    it 'makes an instance whose #to_s is the original string value' do
+      bb = GRPC::ByteBuffer.new('#dup')
+      a_copy = bb.dup
+      expect(a_copy.to_s).to eq('#dup')
+      expect(a_copy.dup.to_s).to eq('#dup')
+    end
+  end
+
+end
diff --git a/src/ruby/spec/call_spec.rb b/src/ruby/spec/call_spec.rb
new file mode 100644
index 0000000..339f2c1
--- /dev/null
+++ b/src/ruby/spec/call_spec.rb
@@ -0,0 +1,200 @@
+# 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 'grpc'
+require 'port_picker'
+
+include GRPC::StatusCodes
+
+describe GRPC::RpcErrors do
+
+  before(:each) do
+    @known_types = {
+      :OK => 0,
+      :ERROR => 1,
+      :NOT_ON_SERVER => 2,
+      :NOT_ON_CLIENT => 3,
+      :ALREADY_INVOKED => 4,
+      :NOT_INVOKED => 5,
+      :ALREADY_FINISHED => 6,
+      :TOO_MANY_OPERATIONS => 7,
+      :INVALID_FLAGS => 8,
+      :ErrorMessages => {
+        0=>'ok',
+        1=>'unknown error',
+        2=>'not available on a server',
+        3=>'not available on a client',
+        4=>'call is already invoked',
+        5=>'call is not yet invoked',
+        6=>'call is already finished',
+        7=>'outstanding read or write present',
+        8=>'a bad flag was given',
+      }
+    }
+  end
+
+  it 'should have symbols for all the known error codes' do
+    m = GRPC::RpcErrors
+    syms_and_codes = m.constants.collect { |c| [c, m.const_get(c)] }
+    expect(Hash[syms_and_codes]).to eq(@known_types)
+  end
+
+end
+
+describe GRPC::Call do
+
+  before(:each) do
+    @tag = Object.new
+    @client_queue = GRPC::CompletionQueue.new
+    @server_queue = GRPC::CompletionQueue.new
+    port = find_unused_tcp_port
+    host = "localhost:#{port}"
+    @server = GRPC::Server.new(@server_queue, nil)
+    @server.add_http2_port(host)
+    @ch = GRPC::Channel.new(host, nil)
+  end
+
+  after(:each) do
+    @server.close
+  end
+
+  describe '#start_read' do
+    it 'should fail if called immediately' do
+      expect { make_test_call.start_read(@tag) }.to raise_error GRPC::CallError
+    end
+  end
+
+  describe '#start_write' do
+    it 'should fail if called immediately' do
+      bytes = GRPC::ByteBuffer.new('test string')
+      expect { make_test_call.start_write(bytes, @tag) }
+          .to raise_error GRPC::CallError
+    end
+  end
+
+  describe '#start_write_status' do
+    it 'should fail if called immediately' do
+      sts = GRPC::Status.new(153, 'test detail')
+      expect { make_test_call.start_write_status(sts, @tag) }
+          .to raise_error GRPC::CallError
+    end
+  end
+
+  describe '#writes_done' do
+    it 'should fail if called immediately' do
+      expect { make_test_call.writes_done(@tag) }.to raise_error GRPC::CallError
+    end
+  end
+
+  describe '#add_metadata' do
+    it 'adds metadata to a call without fail' do
+      call = make_test_call
+      n = 37
+      metadata = Hash[n.times.collect { |i| ["key%d" % i, "value%d" %i] } ]
+      expect { call.add_metadata(metadata) }.to_not raise_error
+    end
+  end
+
+  describe '#start_invoke' do
+    it 'should cause the INVOKE_ACCEPTED event' do
+      call = make_test_call
+      expect(call.start_invoke(@client_queue, @tag, @tag, @tag)).to be_nil
+      ev = @client_queue.next(deadline)
+      expect(ev.call).to be_a(GRPC::Call)
+      expect(ev.tag).to be(@tag)
+      expect(ev.type).to be(GRPC::CompletionType::INVOKE_ACCEPTED)
+      expect(ev.call).to_not be(call)
+    end
+  end
+
+  describe '#start_write' do
+    it 'should cause the WRITE_ACCEPTED event' do
+      call = make_test_call
+      call.start_invoke(@client_queue, @tag, @tag, @tag)
+      ev = @client_queue.next(deadline)
+      expect(ev.type).to be(GRPC::CompletionType::INVOKE_ACCEPTED)
+      expect(call.start_write(GRPC::ByteBuffer.new('test_start_write'),
+                              @tag)).to be_nil
+      ev = @client_queue.next(deadline)
+      expect(ev.call).to be_a(GRPC::Call)
+      expect(ev.type).to be(GRPC::CompletionType::WRITE_ACCEPTED)
+      expect(ev.tag).to be(@tag)
+    end
+  end
+
+  describe '#status' do
+    it 'can save the status and read it back' do
+      call = make_test_call
+      sts = GRPC::Status.new(OK, 'OK')
+      expect { call.status = sts }.not_to raise_error
+      expect(call.status).to be(sts)
+    end
+
+    it 'must be set to a status' do
+      call = make_test_call
+      bad_sts = Object.new
+      expect { call.status = bad_sts }.to raise_error(TypeError)
+    end
+
+    it 'can be set to nil' do
+      call = make_test_call
+      expect { call.status = nil }.not_to raise_error
+    end
+  end
+
+  describe '#metadata' do
+    it 'can save the metadata hash and read it back' do
+      call = make_test_call
+      md = {'k1' => 'v1',  'k2' => 'v2'}
+      expect { call.metadata = md }.not_to raise_error
+      expect(call.metadata).to be(md)
+    end
+
+    it 'must be set with a hash' do
+      call = make_test_call
+      bad_md = Object.new
+      expect { call.metadata = bad_md }.to raise_error(TypeError)
+    end
+
+    it 'can be set to nil' do
+      call = make_test_call
+      expect { call.metadata = nil }.not_to raise_error
+    end
+  end
+
+
+  def make_test_call
+    @ch.create_call('dummy_method', 'dummy_host', deadline)
+  end
+
+  def deadline
+    Time.now + 2  # in 2 seconds; arbitrary
+  end
+
+end
diff --git a/src/ruby/spec/channel_spec.rb b/src/ruby/spec/channel_spec.rb
new file mode 100644
index 0000000..bd46bff
--- /dev/null
+++ b/src/ruby/spec/channel_spec.rb
@@ -0,0 +1,164 @@
+# 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 'grpc'
+require 'port_picker'
+
+module GRPC
+
+  describe Channel do
+
+    before(:each) do
+      @cq = CompletionQueue.new
+    end
+
+    describe '#new' do
+
+      it 'take a host name without channel args' do
+        expect { Channel.new('dummy_host', nil) }.not_to raise_error
+      end
+
+      it 'does not take a hash with bad keys as channel args' do
+        blk = construct_with_args(Object.new => 1)
+        expect(&blk).to raise_error TypeError
+        blk = construct_with_args(1 => 1)
+        expect(&blk).to raise_error TypeError
+      end
+
+      it 'does not take a hash with bad values as channel args' do
+        blk = construct_with_args(:symbol => Object.new)
+        expect(&blk).to raise_error TypeError
+        blk = construct_with_args('1' => Hash.new)
+        expect(&blk).to raise_error TypeError
+      end
+
+      it 'can take a hash with a symbol key as channel args' do
+        blk = construct_with_args(:a_symbol => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a string key as channel args' do
+        blk = construct_with_args('a_symbol' => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a string value as channel args' do
+        blk = construct_with_args(:a_symbol => '1')
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a symbol value as channel args' do
+        blk = construct_with_args(:a_symbol => :another_symbol)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a numeric value as channel args' do
+        blk = construct_with_args(:a_symbol => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with many args as channel args' do
+        args = Hash[127.times.collect { |x| [x.to_s, x] } ]
+        blk = construct_with_args(args)
+        expect(&blk).to_not raise_error
+      end
+
+    end
+
+    describe '#create_call' do
+      it 'creates a call OK' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+
+        deadline = Time.now + 5
+        expect(ch.create_call('dummy_method', 'dummy_host', deadline))
+          .not_to be(nil)
+      end
+
+      it 'raises an error if called on a closed channel' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+        ch.close
+
+        deadline = Time.now + 5
+        blk = Proc.new do
+          ch.create_call('dummy_method', 'dummy_host', deadline)
+        end
+        expect(&blk).to raise_error(RuntimeError)
+      end
+
+    end
+
+    describe '#destroy' do
+      it 'destroys a channel ok' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+        blk = Proc.new { ch.destroy }
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can be called more than once without error' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+        blk = Proc.new { ch.destroy }
+        blk.call
+        expect(&blk).to_not raise_error
+      end
+    end
+
+    describe '#close' do
+      it 'closes a channel ok' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+        blk = Proc.new { ch.close }
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can be called more than once without error' do
+        port = find_unused_tcp_port
+        host = "localhost:#{port}"
+        ch = Channel.new(host, nil)
+        blk = Proc.new { ch.close }
+        blk.call
+        expect(&blk).to_not raise_error
+      end
+    end
+
+    def construct_with_args(a)
+      Proc.new {Channel.new('dummy_host', a)}
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/client_server_spec.rb b/src/ruby/spec/client_server_spec.rb
new file mode 100644
index 0000000..64068ab
--- /dev/null
+++ b/src/ruby/spec/client_server_spec.rb
@@ -0,0 +1,349 @@
+# 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 'grpc'
+require 'port_picker'
+require 'spec_helper'
+
+include GRPC::CompletionType
+include GRPC
+
+shared_context 'setup: tags' do
+
+  before(:example) do
+    @server_finished_tag = Object.new
+    @client_finished_tag = Object.new
+    @server_tag = Object.new
+    @tag = Object.new
+  end
+
+  def deadline
+    Time.now + 0.05
+  end
+
+  def expect_next_event_on(queue, type, tag)
+    ev = queue.pluck(tag, deadline)
+    if type.nil?
+      expect(ev).to be_nil
+    else
+      expect(ev).to_not be_nil
+      expect(ev.type).to be(type)
+    end
+    ev
+  end
+
+  def server_receives_and_responds_with(reply_text)
+    reply = ByteBuffer.new(reply_text)
+    @server.request_call(@server_tag)
+    ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
+    expect(ev).not_to be_nil
+    expect(ev.type).to be(SERVER_RPC_NEW)
+    ev.call.accept(@server_queue, @server_finished_tag)
+    ev.call.start_read(@server_tag)
+    ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
+    expect(ev.type).to be(READ)
+    ev.call.start_write(reply, @server_tag)
+    ev = @server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
+    expect(ev).not_to be_nil
+    expect(ev.type).to be(WRITE_ACCEPTED)
+    return ev.call
+  end
+
+  def client_sends(call, sent='a message')
+    req = ByteBuffer.new(sent)
+    call.start_invoke(@client_queue, @tag, @tag, @client_finished_tag)
+    ev = @client_queue.pluck(@tag, TimeConsts::INFINITE_FUTURE)
+    expect(ev).not_to be_nil
+    expect(ev.type).to be(INVOKE_ACCEPTED)
+    call.start_write(req, @tag)
+    ev = @client_queue.pluck(@tag, TimeConsts::INFINITE_FUTURE)
+    expect(ev).not_to be_nil
+    expect(ev.type).to be(WRITE_ACCEPTED)
+    return sent
+  end
+
+  def new_client_call
+    @ch.create_call('/method', 'localhost', deadline)
+  end
+
+end
+
+shared_examples 'basic GRPC message delivery is OK' do
+
+  include_context 'setup: tags'
+
+  it 'servers receive requests from clients and start responding' do
+    reply = ByteBuffer.new('the server payload')
+    call = new_client_call
+    msg = client_sends(call)
+
+    # check the server rpc new was received
+    @server.request_call(@server_tag)
+    ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+
+    # accept the call
+    server_call = ev.call
+    server_call.accept(@server_queue, @server_finished_tag)
+
+    # confirm the server can read the inbound message
+    server_call.start_read(@server_tag)
+    ev = expect_next_event_on(@server_queue, READ, @server_tag)
+    expect(ev.result.to_s).to eq(msg)
+
+    #  the server response
+    server_call.start_write(reply, @server_tag)
+    ev = expect_next_event_on(@server_queue, WRITE_ACCEPTED, @server_tag)
+  end
+
+  it 'responses written by servers are received by the client' do
+    call = new_client_call
+    client_sends(call)
+    server_receives_and_responds_with('server_response')
+
+    call.start_read(@tag)
+    ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+    ev = expect_next_event_on(@client_queue, READ, @tag)
+    expect(ev.result.to_s).to eq('server_response')
+  end
+
+  it 'servers can ignore a client write and send a status' do
+    reply = ByteBuffer.new('the server payload')
+    call = new_client_call
+    msg = client_sends(call)
+
+    # check the server rpc new was received
+    @server.request_call(@server_tag)
+    ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+    expect(ev.tag).to be(@server_tag)
+
+    # accept the call - need to do this to sent status.
+    server_call = ev.call
+    server_call.accept(@server_queue, @server_finished_tag)
+    sts = Status.new(StatusCodes::NOT_FOUND, 'not found')
+    server_call.start_write_status(sts, @server_tag)
+
+    # client gets an empty response for the read, preceeded by some metadata.
+    call.start_read(@tag)
+    ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+    ev = expect_next_event_on(@client_queue, READ, @tag)
+    expect(ev.tag).to be(@tag)
+    expect(ev.result.to_s).to eq('')
+
+    # finally, after client sends writes_done, they get the finished.
+    call.writes_done(@tag)
+    ev = expect_next_event_on(@client_queue, FINISH_ACCEPTED, @tag)
+    ev = expect_next_event_on(@client_queue, FINISHED, @client_finished_tag)
+    expect(ev.result.code).to eq(StatusCodes::NOT_FOUND)
+  end
+
+  it 'completes calls by sending status to client and server' do
+    call = new_client_call
+    client_sends(call)
+    server_call = server_receives_and_responds_with('server_response')
+    sts = Status.new(10101, 'status code is 10101')
+    server_call.start_write_status(sts, @server_tag)
+
+    # first the client says writes are done
+    call.start_read(@tag)
+    ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+    ev = expect_next_event_on(@client_queue, READ, @tag)
+    call.writes_done(@tag)
+
+    # but nothing happens until the server sends a status
+    expect_next_event_on(@server_queue, FINISH_ACCEPTED, @server_tag)
+    ev = expect_next_event_on(@server_queue, FINISHED, @server_finished_tag)
+    expect(ev.result).to be_a(Status)
+
+    # client gets FINISHED
+    expect_next_event_on(@client_queue, FINISH_ACCEPTED, @tag)
+    ev = expect_next_event_on(@client_queue, FINISHED, @client_finished_tag)
+    expect(ev.result.details).to eq('status code is 10101')
+    expect(ev.result.code).to eq(10101)
+  end
+
+end
+
+
+shared_examples 'GRPC metadata delivery works OK' do
+
+  include_context 'setup: tags'
+
+  describe 'from client => server' do
+
+    before(:example) do
+      n = 7  # arbitrary number of metadata
+      diff_keys = Hash[n.times.collect { |i| ['k%d' % i, 'v%d' % i] }]
+      null_vals = Hash[n.times.collect { |i| ['k%d' % i, 'v\0%d' % i] }]
+      same_keys = Hash[n.times.collect { |i| ['k%d' % i, ['v%d' % i] * n] }]
+      symbol_key = {:a_key => 'a val'}
+      @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key]
+      @bad_keys = []
+      @bad_keys << { Object.new => 'a value' }
+      @bad_keys << { 1 => 'a value' }
+    end
+
+    it 'raises an exception if a metadata key is invalid' do
+      @bad_keys.each do |md|
+        call = new_client_call
+        expect { call.add_metadata(md) }.to raise_error
+      end
+    end
+
+    it 'sends an empty hash when no metadata is added' do
+      call = new_client_call
+      client_sends(call)
+
+      # Server gets a response
+      @server.request_call(@server_tag)
+      expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+    end
+
+    it 'sends all the metadata pairs when keys and values are valid' do
+      @valid_metadata.each do |md|
+        call = new_client_call
+        call.add_metadata(md)
+
+        # Client begins a call OK
+        call.start_invoke(@client_queue, @tag, @tag, @client_finished_tag)
+        ev = expect_next_event_on(@client_queue, INVOKE_ACCEPTED, @tag)
+
+        # ... server has all metadata available even though the client did not
+        # send a write
+        @server.request_call(@server_tag)
+        ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+        replace_symbols = Hash[md.each_pair.collect { |x,y| [x.to_s, y] }]
+        result = ev.result.metadata
+        expect(result.merge(replace_symbols)).to eq(result)
+      end
+    end
+
+  end
+
+  describe 'from server => client' do
+
+    before(:example) do
+      n = 7  # arbitrary number of metadata
+      diff_keys = Hash[n.times.collect { |i| ['k%d' % i, 'v%d' % i] }]
+      null_vals = Hash[n.times.collect { |i| ['k%d' % i, 'v\0%d' % i] }]
+      same_keys = Hash[n.times.collect { |i| ['k%d' % i, ['v%d' % i] * n] }]
+      symbol_key = {:a_key => 'a val'}
+      @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key]
+      @bad_keys = []
+      @bad_keys << { Object.new => 'a value' }
+      @bad_keys << { 1 => 'a value' }
+    end
+
+    it 'raises an exception if a metadata key is invalid' do
+      @bad_keys.each do |md|
+        call = new_client_call
+        client_sends(call)
+
+        # server gets the invocation
+        @server.request_call(@server_tag)
+        ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+        expect { ev.call.add_metadata(md) }.to raise_error
+      end
+    end
+
+    it 'sends a hash that contains the status when no metadata is added' do
+      call = new_client_call
+      client_sends(call)
+
+      # server gets the invocation
+      @server.request_call(@server_tag)
+      ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+      server_call = ev.call
+
+      # ... server accepts the call without adding metadata
+      server_call.accept(@server_queue, @server_finished_tag)
+
+      # ... these server sends some data, allowing the metadata read
+      server_call.start_write(ByteBuffer.new('reply with metadata'),
+                              @server_tag)
+      expect_next_event_on(@server_queue, WRITE_ACCEPTED, @server_tag)
+
+      # there is the HTTP status metadata, though there should not be any
+      # TODO(temiola): update this with the bug number to be resolved
+      ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+      expect(ev.result).to eq({':status' => '200'})
+    end
+
+    it 'sends all the pairs and status:200 when keys and values are valid' do
+      @valid_metadata.each do |md|
+        call = new_client_call
+        client_sends(call)
+
+        # server gets the invocation
+        @server.request_call(@server_tag)
+        ev = expect_next_event_on(@server_queue, SERVER_RPC_NEW, @server_tag)
+        server_call = ev.call
+
+        # ... server adds metadata and accepts the call
+        server_call.add_metadata(md)
+        server_call.accept(@server_queue, @server_finished_tag)
+
+        # Now the client can read the metadata
+        ev = expect_next_event_on(@client_queue, CLIENT_METADATA_READ, @tag)
+        replace_symbols = Hash[md.each_pair.collect { |x,y| [x.to_s, y] }]
+        replace_symbols[':status'] = '200'
+        expect(ev.result).to eq(replace_symbols)
+      end
+
+    end
+
+  end
+
+end
+
+
+describe 'the http client/server' do
+
+  before(:example) do
+    port = find_unused_tcp_port
+    host = "localhost:#{port}"
+    @client_queue = GRPC::CompletionQueue.new
+    @server_queue = GRPC::CompletionQueue.new
+    @server = GRPC::Server.new(@server_queue, nil)
+    @server.add_http2_port(host)
+    @server.start
+    @ch = Channel.new(host, nil)
+  end
+
+  after(:example) do
+    @server.close
+  end
+
+
+  it_behaves_like 'basic GRPC message delivery is OK' do
+  end
+
+  it_behaves_like 'GRPC metadata delivery works OK' do
+  end
+
+end
diff --git a/src/ruby/spec/completion_queue_spec.rb b/src/ruby/spec/completion_queue_spec.rb
new file mode 100644
index 0000000..3743244
--- /dev/null
+++ b/src/ruby/spec/completion_queue_spec.rb
@@ -0,0 +1,82 @@
+# 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 'grpc'
+
+describe GRPC::CompletionQueue do
+
+  describe '#new' do
+    it 'is constructed successufully' do
+      expect { GRPC::CompletionQueue.new }.not_to raise_error
+      expect(GRPC::CompletionQueue.new).to be_a(GRPC::CompletionQueue)
+    end
+  end
+
+  describe '#next' do
+    it 'can be called without failing' do
+      ch = GRPC::CompletionQueue.new
+      expect { ch.next(3) }.not_to raise_error
+    end
+
+    it 'can be called with the time constants' do
+      ch = GRPC::CompletionQueue.new
+      # don't use INFINITE_FUTURE, as there we have no events.
+      non_blocking_consts = [:ZERO, :INFINITE_PAST]
+      m = GRPC::TimeConsts
+      non_blocking_consts.each do |c|
+        a_time = m.const_get(c)
+        expect { ch.next(a_time) }.not_to raise_error
+      end
+    end
+
+  end
+
+  describe '#pluck' do
+    it 'can be called without failing' do
+      ch = GRPC::CompletionQueue.new
+      tag = Object.new
+      expect { ch.pluck(tag, 3) }.not_to raise_error
+    end
+
+    it 'can be called with the time constants' do
+      ch = GRPC::CompletionQueue.new
+      # don't use INFINITE_FUTURE, as there we have no events.
+      non_blocking_consts = [:ZERO, :INFINITE_PAST]
+      m = GRPC::TimeConsts
+      tag = Object.new
+      non_blocking_consts.each do |c|
+        a_time = m.const_get(c)
+        expect { ch.pluck(tag, a_time) }.not_to raise_error
+      end
+    end
+
+  end
+
+
+end
diff --git a/src/ruby/spec/event_spec.rb b/src/ruby/spec/event_spec.rb
new file mode 100644
index 0000000..19b9754
--- /dev/null
+++ b/src/ruby/spec/event_spec.rb
@@ -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.
+
+require 'grpc'
+
+describe GRPC::CompletionType do
+
+  before(:each) do
+    @known_types = {
+      :QUEUE_SHUTDOWN => 0,
+      :READ => 1,
+      :INVOKE_ACCEPTED => 2,
+      :WRITE_ACCEPTED => 3,
+      :FINISH_ACCEPTED => 4,
+      :CLIENT_METADATA_READ => 5,
+      :FINISHED => 6,
+      :SERVER_RPC_NEW => 7,
+      :RESERVED => 8
+    }
+  end
+
+  it 'should have all the known types' do
+    mod = GRPC::CompletionType
+    expect(Hash[mod.constants.collect { |c| [c, mod.const_get(c)] }])
+        .to eq(@known_types)
+  end
+
+end
diff --git a/src/ruby/spec/generic/active_call_spec.rb b/src/ruby/spec/generic/active_call_spec.rb
new file mode 100644
index 0000000..872625c
--- /dev/null
+++ b/src/ruby/spec/generic/active_call_spec.rb
@@ -0,0 +1,321 @@
+# 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 'grpc'
+require 'grpc/generic/active_call'
+require_relative '../port_picker'
+
+module GRPC
+
+  describe ActiveCall do
+
+    before(:each) do
+      @pass_through = Proc.new { |x| x }
+      @server_tag = Object.new
+      @server_finished_tag = Object.new
+      @tag = Object.new
+
+      @client_queue = CompletionQueue.new
+      @server_queue = CompletionQueue.new
+      port = find_unused_tcp_port
+      host = "localhost:#{port}"
+      @server = GRPC::Server.new(@server_queue, nil)
+      @server.add_http2_port(host)
+      @server.start
+      @ch = GRPC::Channel.new(host, nil)
+    end
+
+    after(:each) do
+      @server.close
+    end
+
+    describe 'restricted view methods' do
+      before(:each) do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        @client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                      @pass_through, deadline,
+                                      finished_tag: finished_tag)
+      end
+
+      describe '#multi_req_view' do
+        it 'exposes a fixed subset of the ActiveCall methods' do
+          want = ['cancelled', 'deadline', 'each_remote_read', 'shutdown']
+          v = @client_call.multi_req_view
+          want.each do |w|
+            expect(v.methods.include?(w))
+          end
+        end
+      end
+
+      describe '#single_req_view' do
+        it 'exposes a fixed subset of the ActiveCall methods' do
+          want = ['cancelled', 'deadline', 'shutdown']
+          v = @client_call.single_req_view
+          want.each do |w|
+            expect(v.methods.include?(w))
+          end
+        end
+      end
+    end
+
+    describe '#remote_send' do
+      it 'allows a client to send a payload to the server' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        @client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                      @pass_through, deadline,
+                                      finished_tag: finished_tag)
+        msg = 'message is a string'
+        @client_call.remote_send(msg)
+
+        # check that server rpc new was received
+        @server.request_call(@server_tag)
+        ev = @server_queue.next(deadline)
+        expect(ev.type).to be(CompletionType::SERVER_RPC_NEW)
+        expect(ev.call).to be_a(Call)
+        expect(ev.tag).to be(@server_tag)
+
+        # Accept the call, and verify that the server reads the response ok.
+        ev.call.accept(@client_queue, @server_tag)
+        server_call = ActiveCall.new(ev.call, @client_queue, @pass_through,
+                                     @pass_through, deadline)
+        expect(server_call.remote_read).to eq(msg)
+      end
+
+      it 'marshals the payload using the marshal func' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        marshal = Proc.new { |x| 'marshalled:' + x }
+        client_call = ActiveCall.new(call, @client_queue, marshal,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+
+        # confirm that the message was marshalled
+        @server.request_call(@server_tag)
+        ev = @server_queue.next(deadline)
+        ev.call.accept(@client_queue, @server_tag)
+        server_call = ActiveCall.new(ev.call, @client_queue, @pass_through,
+                                     @pass_through, deadline)
+        expect(server_call.remote_read).to eq('marshalled:' + msg)
+      end
+
+    end
+
+    describe '#remote_read' do
+      it 'reads the response sent by a server' do
+        call, pass_through = make_test_call, Proc.new { |x| x }
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        expect(client_call.remote_read).to eq('server_response')
+      end
+
+      it 'get a nil msg before a status when an OK status is sent' do
+        call, pass_through = make_test_call, Proc.new { |x| x }
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        client_call.writes_done(false)
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        server_call.send_status(StatusCodes::OK, 'OK')
+        expect(client_call.remote_read).to eq('server_response')
+        res = client_call.remote_read
+        expect(res).to be_nil
+      end
+
+
+      it 'unmarshals the response using the unmarshal func' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        unmarshal = Proc.new { |x| 'unmarshalled:' + x }
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     unmarshal, deadline,
+                                     finished_tag: finished_tag)
+
+        # confirm the client receives the unmarshalled message
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        expect(client_call.remote_read).to eq('unmarshalled:server_response')
+      end
+
+    end
+
+    describe '#each_remote_read' do
+      it 'creates an Enumerator' do
+        call = make_test_call
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline)
+        expect(client_call.each_remote_read).to be_a(Enumerator)
+      end
+
+      it 'the returns an enumerator that can read n responses' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is 4a string'
+        reply = 'server_response'
+        client_call.remote_send(msg)
+        server_call = expect_server_to_receive(msg)
+        e = client_call.each_remote_read
+        n = 3  # arbitrary value > 1
+        n.times do
+          server_call.remote_send(reply)
+          expect(e.next).to eq(reply)
+        end
+      end
+
+      it 'the returns an enumerator that stops after an OK Status' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        reply = 'server_response'
+        client_call.remote_send(msg)
+        client_call.writes_done(false)
+        server_call = expect_server_to_receive(msg)
+        e = client_call.each_remote_read
+        n = 3  # arbitrary value > 1
+        n.times do
+          server_call.remote_send(reply)
+          expect(e.next).to eq(reply)
+        end
+        server_call.send_status(StatusCodes::OK, 'OK')
+        expect { e.next }.to raise_error(StopIteration)
+      end
+
+    end
+
+    describe '#writes_done' do
+      it 'finishes ok if the server sends a status response' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        expect { client_call.writes_done(false) }.to_not raise_error
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        expect(client_call.remote_read).to eq('server_response')
+        server_call.send_status(StatusCodes::OK, 'status code is OK')
+        expect { server_call.finished }.to_not raise_error
+        expect { client_call.finished }.to_not raise_error
+      end
+
+      it 'finishes ok if the server sends an early status response' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        server_call.send_status(StatusCodes::OK, 'status code is OK')
+        expect(client_call.remote_read).to eq('server_response')
+        expect { client_call.writes_done(false) }.to_not raise_error
+        expect { server_call.finished }.to_not raise_error
+        expect { client_call.finished }.to_not raise_error
+      end
+
+      it 'finishes ok if writes_done is true' do
+        call = make_test_call
+        finished_tag = ActiveCall.client_start_invoke(call, @client_queue,
+                                                      deadline)
+        client_call = ActiveCall.new(call, @client_queue, @pass_through,
+                                     @pass_through, deadline,
+                                     finished_tag: finished_tag)
+        msg = 'message is a string'
+        client_call.remote_send(msg)
+        server_call = expect_server_to_receive(msg)
+        server_call.remote_send('server_response')
+        server_call.send_status(StatusCodes::OK, 'status code is OK')
+        expect(client_call.remote_read).to eq('server_response')
+        expect { client_call.writes_done(true) }.to_not raise_error
+        expect { server_call.finished }.to_not raise_error
+      end
+
+    end
+
+    def expect_server_to_receive(sent_text)
+      c = expect_server_to_be_invoked
+      expect(c.remote_read).to eq(sent_text)
+      c
+    end
+
+    def expect_server_to_be_invoked()
+      @server.request_call(@server_tag)
+      ev = @server_queue.next(deadline)
+      ev.call.accept(@client_queue, @server_finished_tag)
+      ActiveCall.new(ev.call, @client_queue, @pass_through,
+                     @pass_through, deadline,
+                     finished_tag: @server_finished_tag)
+    end
+
+    def make_test_call
+      @ch.create_call('dummy_method', 'dummy_host', deadline)
+    end
+
+    def deadline
+      Time.now + 0.25  # in 0.25 seconds; arbitrary
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb
new file mode 100644
index 0000000..c8dee74
--- /dev/null
+++ b/src/ruby/spec/generic/client_stub_spec.rb
@@ -0,0 +1,484 @@
+# 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 'grpc'
+require 'grpc/generic/active_call'
+require 'grpc/generic/client_stub'
+require 'xray/thread_dump_signal_handler'
+require_relative '../port_picker'
+
+NOOP = Proc.new { |x| x }
+
+def wakey_thread(&blk)
+  awake_mutex, awake_cond = Mutex.new, ConditionVariable.new
+  t = Thread.new do
+    blk.call(awake_mutex, awake_cond)
+  end
+  awake_mutex.synchronize { awake_cond.wait(awake_mutex) }
+  t
+end
+
+
+include GRPC::StatusCodes
+
+describe 'ClientStub' do
+  BadStatus = GRPC::BadStatus
+  TimeConsts = GRPC::TimeConsts
+
+  before(:each) do
+    Thread.abort_on_exception = true
+    @server = nil
+    @method = 'an_rpc_method'
+    @pass = OK
+    @fail = INTERNAL
+    @cq = GRPC::CompletionQueue.new
+  end
+
+  after(:each) do
+    @server.close unless @server.nil?
+  end
+
+  describe '#new' do
+
+    it 'can be created from a host and args' do
+      host = new_test_host
+      opts = {:a_channel_arg => 'an_arg'}
+      blk = Proc.new do
+        GRPC::ClientStub.new(host, @cq, **opts)
+      end
+      expect(&blk).not_to raise_error
+    end
+
+    it 'can be created with a default deadline' do
+      host = new_test_host
+      opts = {:a_channel_arg => 'an_arg', :deadline => 5}
+      blk = Proc.new do
+        GRPC::ClientStub.new(host, @cq, **opts)
+      end
+      expect(&blk).not_to raise_error
+    end
+
+    it 'can be created with an channel override' do
+      host = new_test_host
+      opts = {:a_channel_arg => 'an_arg', :channel_override => @ch}
+      blk = Proc.new do
+        GRPC::ClientStub.new(host, @cq, **opts)
+      end
+      expect(&blk).not_to raise_error
+    end
+
+    it 'cannot be created with a bad channel override' do
+      host = new_test_host
+      blk = Proc.new do
+        opts = {:a_channel_arg => 'an_arg', :channel_override => Object.new}
+        GRPC::ClientStub.new(host, @cq, **opts)
+      end
+      expect(&blk).to raise_error
+    end
+
+  end
+
+  describe '#request_response' do
+    before(:each) do
+      @sent_msg, @resp = 'a_msg', 'a_reply'
+    end
+
+    describe 'without a call operation' do
+
+      it 'should send a request to/receive a_reply from a server' do
+        host = new_test_host
+        th = run_request_response(host, @sent_msg, @resp, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        resp = stub.request_response(@method, @sent_msg, NOOP, NOOP)
+        expect(resp).to eq(@resp)
+        th.join
+      end
+
+      it 'should send a request when configured using an override channel' do
+        alt_host = new_test_host
+        th = run_request_response(alt_host, @sent_msg, @resp, @pass)
+        ch = GRPC::Channel.new(alt_host, nil)
+        stub = GRPC::ClientStub.new('ignored-host', @cq,
+                                    channel_override:ch)
+        resp = stub.request_response(@method, @sent_msg, NOOP, NOOP)
+        expect(resp).to eq(@resp)
+        th.join
+      end
+
+      it 'should raise an error if the status is not OK' do
+        host = new_test_host
+        th = run_request_response(host, @sent_msg, @resp, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        blk = Proc.new do
+          stub.request_response(@method, @sent_msg, NOOP, NOOP)
+        end
+        expect(&blk).to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+    describe 'via a call operation' do
+
+      it 'should send a request to/receive a_reply from a server' do
+        host = new_test_host
+        th = run_request_response(host, @sent_msg, @resp, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.request_response(@method, @sent_msg, NOOP, NOOP,
+                                   return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        resp = op.execute()
+        expect(resp).to eq(@resp)
+        th.join
+      end
+
+      it 'should raise an error if the status is not OK' do
+        host = new_test_host
+        th = run_request_response(host, @sent_msg, @resp, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.request_response(@method, @sent_msg, NOOP, NOOP,
+                                   return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        blk = Proc.new do
+          op.execute()
+        end
+        expect(&blk).to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+  end
+
+  describe '#client_streamer' do
+
+    before(:each) do
+      @sent_msgs = Array.new(3) { |i| 'msg_' + (i+1).to_s }
+      @resp = 'a_reply'
+    end
+
+    describe 'without a call operation' do
+
+      it 'should send requests to/receive a reply from a server' do
+        host = new_test_host
+        th = run_client_streamer(host, @sent_msgs, @resp, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        resp = stub.client_streamer(@method, @sent_msgs, NOOP, NOOP)
+        expect(resp).to eq(@resp)
+        th.join
+      end
+
+      it 'should raise an error if the status is not ok' do
+        host = new_test_host
+        th = run_client_streamer(host, @sent_msgs, @resp, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        blk = Proc.new do
+          stub.client_streamer(@method, @sent_msgs, NOOP, NOOP)
+        end
+        expect(&blk).to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+    describe 'via a call operation' do
+
+      it 'should send requests to/receive a reply from a server' do
+        host = new_test_host
+        th = run_client_streamer(host, @sent_msgs, @resp, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.client_streamer(@method, @sent_msgs, NOOP, NOOP,
+                                  return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        resp = op.execute()
+        expect(resp).to eq(@resp)
+        th.join
+      end
+
+      it 'should raise an error if the status is not ok' do
+        host = new_test_host
+        th = run_client_streamer(host, @sent_msgs, @resp, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.client_streamer(@method, @sent_msgs, NOOP, NOOP,
+                                  return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        blk = Proc.new do
+          op.execute()
+        end
+        expect(&blk).to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+  end
+
+  describe '#server_streamer' do
+
+    before(:each) do
+      @sent_msg = 'a_msg'
+      @replys = Array.new(3) { |i| 'reply_' + (i+1).to_s }
+    end
+
+    describe 'without a call operation' do
+
+      it 'should send a request to/receive replies from a server' do
+        host = new_test_host
+        th = run_server_streamer(host, @sent_msg, @replys, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        e = stub.server_streamer(@method, @sent_msg, NOOP, NOOP)
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@replys)
+        th.join
+      end
+
+      it 'should raise an error if the status is not ok' do
+        host = new_test_host
+        th = run_server_streamer(host, @sent_msg, @replys, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        e = stub.server_streamer(@method, @sent_msg, NOOP, NOOP)
+        expect(e).to be_a(Enumerator)
+        expect { e.collect { |r| r } }.to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+    describe 'via a call operation' do
+
+      it 'should send a request to/receive replies from a server' do
+        host = new_test_host
+        th = run_server_streamer(host, @sent_msg, @replys, @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.server_streamer(@method, @sent_msg, NOOP, NOOP,
+                                  return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        e = op.execute()
+        expect(e).to be_a(Enumerator)
+        th.join
+      end
+
+      it 'should raise an error if the status is not ok' do
+        host = new_test_host
+        th = run_server_streamer(host, @sent_msg, @replys, @fail)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.server_streamer(@method, @sent_msg, NOOP, NOOP,
+                                  return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        e = op.execute()
+        expect(e).to be_a(Enumerator)
+        expect { e.collect { |r| r } }.to raise_error(BadStatus)
+        th.join
+      end
+
+    end
+
+  end
+
+  describe '#bidi_streamer' do
+    before(:each) do
+      @sent_msgs = Array.new(3) { |i| 'msg_' + (i+1).to_s }
+      @replys = Array.new(3) { |i| 'reply_' + (i+1).to_s }
+    end
+
+    describe 'without a call operation' do
+
+      it 'supports a simple scenario with all requests sent first' do
+        host = new_test_host
+        th = run_bidi_streamer_handle_inputs_first(host, @sent_msgs, @replys,
+                                                   @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        e = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP)
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@replys)
+        th.join
+      end
+
+      it 'supports a simple scenario with a client-initiated ping pong' do
+        host = new_test_host
+        th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, true)
+        stub = GRPC::ClientStub.new(host, @cq)
+        e = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP)
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@sent_msgs)
+        th.join
+      end
+
+      # disabled because an unresolved wire-protocol implementation feature
+      #
+      # - servers should be able initiate messaging, however, as it stand
+      # servers don't know if all the client metadata has been sent until
+      # they receive a message from the client.  Without receiving all the
+      # metadata, the server does not accept the call, so this test hangs.
+      xit 'supports a simple scenario with a server-initiated ping pong' do
+        host = new_test_host
+        th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, false)
+        stub = GRPC::ClientStub.new(host, @cq)
+        e = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP)
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@sent_msgs)
+        th.join
+      end
+
+    end
+
+    describe 'via a call operation' do
+
+      it 'supports a simple scenario with all requests sent first' do
+        host = new_test_host
+        th = run_bidi_streamer_handle_inputs_first(host, @sent_msgs, @replys,
+                                                   @pass)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP,
+                                return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        e = op.execute
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@replys)
+        th.join
+      end
+
+      it 'supports a simple scenario with a client-initiated ping pong' do
+        host = new_test_host
+        th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, true)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP,
+                                return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        e = op.execute
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@sent_msgs)
+        th.join
+      end
+
+      # disabled because an unresolved wire-protocol implementation feature
+      #
+      # - servers should be able initiate messaging, however, as it stand
+      # servers don't know if all the client metadata has been sent until
+      # they receive a message from the client.  Without receiving all the
+      # metadata, the server does not accept the call, so this test hangs.
+      xit 'supports a simple scenario with a server-initiated ping pong' do
+        th = run_bidi_streamer_echo_ping_pong(host, @sent_msgs, @pass, false)
+        stub = GRPC::ClientStub.new(host, @cq)
+        op = stub.bidi_streamer(@method, @sent_msgs, NOOP, NOOP,
+                                return_op:true)
+        expect(op).to be_a(GRPC::ActiveCall::Operation)
+        e = op.execute
+        expect(e).to be_a(Enumerator)
+        expect(e.collect { |r| r }).to eq(@sent_msgs)
+        th.join
+      end
+
+    end
+
+  end
+
+  def run_server_streamer(hostname, expected_input, replys, status)
+    wakey_thread do |mtx, cnd|
+      c = expect_server_to_be_invoked(hostname, mtx, cnd)
+      expect(c.remote_read).to eq(expected_input)
+      replys.each { |r| c.remote_send(r) }
+      c.send_status(status, status == @pass ? 'OK' : 'NOK', true)
+    end
+  end
+
+  def run_bidi_streamer_handle_inputs_first(hostname, expected_inputs, replys,
+                                            status)
+    wakey_thread do |mtx, cnd|
+      c = expect_server_to_be_invoked(hostname, mtx, cnd)
+      expected_inputs.each { |i| expect(c.remote_read).to eq(i) }
+      replys.each { |r| c.remote_send(r) }
+      c.send_status(status, status == @pass ? 'OK' : 'NOK', true)
+    end
+  end
+
+  def run_bidi_streamer_echo_ping_pong(hostname, expected_inputs, status,
+                                       client_starts)
+    wakey_thread do |mtx, cnd|
+      c = expect_server_to_be_invoked(hostname, mtx, cnd)
+      expected_inputs.each do |i|
+        if client_starts
+          expect(c.remote_read).to eq(i)
+          c.remote_send(i)
+        else
+          c.remote_send(i)
+          expect(c.remote_read).to eq(i)
+        end
+      end
+      c.send_status(status, status == @pass ? 'OK' : 'NOK', true)
+    end
+  end
+
+  def run_client_streamer(hostname, expected_inputs, resp, status)
+    wakey_thread do |mtx, cnd|
+      c = expect_server_to_be_invoked(hostname, mtx, cnd)
+      expected_inputs.each { |i| expect(c.remote_read).to eq(i) }
+      c.remote_send(resp)
+      c.send_status(status, status == @pass ? 'OK' : 'NOK', true)
+    end
+  end
+
+  def run_request_response(hostname, expected_input, resp, status)
+    wakey_thread do |mtx, cnd|
+      c = expect_server_to_be_invoked(hostname, mtx, cnd)
+      expect(c.remote_read).to eq(expected_input)
+      c.remote_send(resp)
+      c.send_status(status, status == @pass ? 'OK' : 'NOK', true)
+    end
+  end
+
+  def start_test_server(hostname, awake_mutex, awake_cond)
+    server_queue = GRPC::CompletionQueue.new
+    @server = GRPC::Server.new(server_queue, nil)
+    @server.add_http2_port(hostname)
+    @server.start
+    @server_tag = Object.new
+    @server.request_call(@server_tag)
+    awake_mutex.synchronize { awake_cond.signal }
+    server_queue
+  end
+
+  def expect_server_to_be_invoked(hostname, awake_mutex, awake_cond)
+    server_queue = start_test_server(hostname, awake_mutex, awake_cond)
+    test_deadline = Time.now + 10  # fail tests after 10 seconds
+    ev = server_queue.pluck(@server_tag, TimeConsts::INFINITE_FUTURE)
+    raise OutOfTime if ev.nil?
+    finished_tag = Object.new
+    ev.call.accept(server_queue, finished_tag)
+    GRPC::ActiveCall.new(ev.call, server_queue, NOOP,
+                         NOOP, TimeConsts::INFINITE_FUTURE,
+                         finished_tag: finished_tag)
+  end
+
+  def new_test_host
+    port = find_unused_tcp_port
+    "localhost:#{port}"
+  end
+
+end
diff --git a/src/ruby/spec/generic/rpc_desc_spec.rb b/src/ruby/spec/generic/rpc_desc_spec.rb
new file mode 100644
index 0000000..141fb11
--- /dev/null
+++ b/src/ruby/spec/generic/rpc_desc_spec.rb
@@ -0,0 +1,380 @@
+# 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 'grpc'
+require 'grpc/generic/rpc_desc'
+
+
+describe GRPC::RpcDesc do
+
+  RpcDesc = GRPC::RpcDesc
+  Stream = RpcDesc::Stream
+  OK = GRPC::StatusCodes::OK
+  UNKNOWN = GRPC::StatusCodes::UNKNOWN
+
+  before(:each) do
+    @request_response = RpcDesc.new('rr', Object.new, Object.new, 'encode',
+                                    'decode')
+    @client_streamer = RpcDesc.new('cs', Stream.new(Object.new), Object.new,
+                                   'encode', 'decode')
+    @server_streamer = RpcDesc.new('ss', Object.new, Stream.new(Object.new),
+                                   'encode', 'decode')
+    @bidi_streamer = RpcDesc.new('ss', Stream.new(Object.new),
+                                 Stream.new(Object.new), 'encode', 'decode')
+    @bs_code = GRPC::StatusCodes::INTERNAL
+    @no_reason = 'no reason given'
+    @ok_response = Object.new
+  end
+
+  describe '#run_server_method' do
+
+    describe 'for request responses' do
+      before(:each) do
+        @call = double('active_call')
+        allow(@call).to receive(:single_req_view).and_return(@call)
+        allow(@call).to receive(:gc)
+      end
+
+      it 'sends the specified status if BadStatus is raised' do
+        expect(@call).to receive(:remote_read).once.and_return(Object.new)
+        expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK')
+        @request_response.run_server_method(@call, method(:bad_status))
+      end
+
+      it 'sends status UNKNOWN if other StandardErrors are raised' do
+        expect(@call).to receive(:remote_read).once.and_return(Object.new)
+        expect(@call).to receive(:send_status) .once.with(UNKNOWN, @no_reason)
+        @request_response.run_server_method(@call, method(:other_error))
+      end
+
+      it 'absorbs EventError  with no further action' do
+        expect(@call).to receive(:remote_read).once.and_raise(GRPC::EventError)
+        blk = Proc.new do
+          @request_response.run_server_method(@call, method(:fake_reqresp))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'absorbs CallError with no further action' do
+        expect(@call).to receive(:remote_read).once.and_raise(GRPC::CallError)
+        blk = Proc.new do
+          @request_response.run_server_method(@call, method(:fake_reqresp))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'sends a response and closes the stream if there no errors' do
+        req = Object.new
+        expect(@call).to receive(:remote_read).once.and_return(req)
+        expect(@call).to receive(:remote_send).once.with(@ok_response)
+        expect(@call).to receive(:send_status).once.with(OK, 'OK')
+        expect(@call).to receive(:finished).once
+        @request_response.run_server_method(@call, method(:fake_reqresp))
+      end
+
+    end
+
+    describe 'for client streamers' do
+      before(:each) do
+        @call = double('active_call')
+        allow(@call).to receive(:multi_req_view).and_return(@call)
+        allow(@call).to receive(:gc)
+      end
+
+      it 'sends the specified status if BadStatus is raised' do
+        expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK')
+        @client_streamer.run_server_method(@call, method(:bad_status_alt))
+      end
+
+      it 'sends status UNKNOWN if other StandardErrors are raised' do
+        expect(@call).to receive(:send_status) .once.with(UNKNOWN, @no_reason)
+        @client_streamer.run_server_method(@call, method(:other_error_alt))
+      end
+
+      it 'absorbs EventError  with no further action' do
+        expect(@call).to receive(:remote_send).once.and_raise(GRPC::EventError)
+        blk = Proc.new do
+          @client_streamer.run_server_method(@call, method(:fake_clstream))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'absorbs CallError with no further action' do
+        expect(@call).to receive(:remote_send).once.and_raise(GRPC::CallError)
+        blk = Proc.new do
+          @client_streamer.run_server_method(@call, method(:fake_clstream))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'sends a response and closes the stream if there no errors' do
+        req = Object.new
+        expect(@call).to receive(:remote_send).once.with(@ok_response)
+        expect(@call).to receive(:send_status).once.with(OK, 'OK')
+        expect(@call).to receive(:finished).once
+        @client_streamer.run_server_method(@call, method(:fake_clstream))
+      end
+
+    end
+
+    describe 'for server streaming' do
+      before(:each) do
+        @call = double('active_call')
+        allow(@call).to receive(:single_req_view).and_return(@call)
+        allow(@call).to receive(:gc)
+      end
+
+      it 'sends the specified status if BadStatus is raised' do
+        expect(@call).to receive(:remote_read).once.and_return(Object.new)
+        expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK')
+        @server_streamer.run_server_method(@call, method(:bad_status))
+      end
+
+      it 'sends status UNKNOWN if other StandardErrors are raised' do
+        expect(@call).to receive(:remote_read).once.and_return(Object.new)
+        expect(@call).to receive(:send_status) .once.with(UNKNOWN, @no_reason)
+        @server_streamer.run_server_method(@call, method(:other_error))
+      end
+
+      it 'absorbs EventError  with no further action' do
+        expect(@call).to receive(:remote_read).once.and_raise(GRPC::EventError)
+        blk = Proc.new do
+          @server_streamer.run_server_method(@call, method(:fake_svstream))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'absorbs CallError with no further action' do
+        expect(@call).to receive(:remote_read).once.and_raise(GRPC::CallError)
+        blk = Proc.new do
+          @server_streamer.run_server_method(@call, method(:fake_svstream))
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'sends a response and closes the stream if there no errors' do
+        req = Object.new
+        expect(@call).to receive(:remote_read).once.and_return(req)
+        expect(@call).to receive(:remote_send).twice.with(@ok_response)
+        expect(@call).to receive(:send_status).once.with(OK, 'OK')
+        expect(@call).to receive(:finished).once
+        @server_streamer.run_server_method(@call, method(:fake_svstream))
+      end
+
+    end
+
+    describe 'for bidi streamers' do
+      before(:each) do
+        @call = double('active_call')
+        enq_th, rwl_th = double('enqueue_th'), ('read_write_loop_th')
+        allow(enq_th).to receive(:join)
+        allow(rwl_th).to receive(:join)
+        allow(@call).to receive(:gc)
+      end
+
+      it 'sends the specified status if BadStatus is raised' do
+        e = GRPC::BadStatus.new(@bs_code, 'NOK')
+        expect(@call).to receive(:run_server_bidi).and_raise(e)
+        expect(@call).to receive(:send_status).once.with(@bs_code, 'NOK')
+        @bidi_streamer.run_server_method(@call, method(:bad_status_alt))
+      end
+
+      it 'sends status UNKNOWN if other StandardErrors are raised' do
+        expect(@call).to receive(:run_server_bidi).and_raise(StandardError)
+        expect(@call).to receive(:send_status).once.with(UNKNOWN, @no_reason)
+        @bidi_streamer.run_server_method(@call, method(:other_error_alt))
+      end
+
+      it 'closes the stream if there no errors' do
+        req = Object.new
+        expect(@call).to receive(:run_server_bidi)
+        expect(@call).to receive(:send_status).once.with(OK, 'OK')
+        expect(@call).to receive(:finished).once
+        @bidi_streamer.run_server_method(@call, method(:fake_bidistream))
+      end
+
+    end
+
+  end
+
+  describe '#assert_arity_matches' do
+    def no_arg
+    end
+
+    def fake_clstream(arg)
+    end
+
+    def fake_svstream(arg1, arg2)
+    end
+
+    it 'raises when a request_response does not have 2 args' do
+      [:fake_clstream, :no_arg].each do |mth|
+        blk = Proc.new do
+          @request_response.assert_arity_matches(method(mth))
+        end
+        expect(&blk).to raise_error
+      end
+    end
+
+    it 'passes when a request_response has 2 args' do
+      blk = Proc.new do
+        @request_response.assert_arity_matches(method(:fake_svstream))
+      end
+      expect(&blk).to_not raise_error
+    end
+
+    it 'raises when a server_streamer does not have 2 args' do
+      [:fake_clstream, :no_arg].each do |mth|
+        blk = Proc.new do
+          @server_streamer.assert_arity_matches(method(mth))
+        end
+        expect(&blk).to raise_error
+      end
+    end
+
+    it 'passes when a server_streamer has 2 args' do
+      blk = Proc.new do
+        @server_streamer.assert_arity_matches(method(:fake_svstream))
+      end
+      expect(&blk).to_not raise_error
+    end
+
+    it 'raises when a client streamer does not have 1 arg' do
+      [:fake_svstream, :no_arg].each do |mth|
+        blk = Proc.new do
+          @client_streamer.assert_arity_matches(method(mth))
+        end
+        expect(&blk).to raise_error
+      end
+    end
+
+    it 'passes when a client_streamer has 1 arg' do
+      blk = Proc.new do
+        @client_streamer.assert_arity_matches(method(:fake_clstream))
+      end
+      expect(&blk).to_not raise_error
+    end
+
+
+    it 'raises when a bidi streamer does not have 1 arg' do
+      [:fake_svstream, :no_arg].each do |mth|
+        blk = Proc.new do
+          @bidi_streamer.assert_arity_matches(method(mth))
+        end
+        expect(&blk).to raise_error
+      end
+    end
+
+    it 'passes when a bidi streamer has 1 arg' do
+      blk = Proc.new do
+        @bidi_streamer.assert_arity_matches(method(:fake_clstream))
+      end
+      expect(&blk).to_not raise_error
+    end
+
+  end
+
+  describe '#is_request_response?' do
+
+    it 'is true only input and output are both not Streams' do
+      expect(@request_response.is_request_response?).to be(true)
+      expect(@client_streamer.is_request_response?).to be(false)
+      expect(@bidi_streamer.is_request_response?).to be(false)
+      expect(@server_streamer.is_request_response?).to be(false)
+    end
+
+  end
+
+  describe '#is_client_streamer?' do
+
+    it 'is true only when input is a Stream and output is not a Stream' do
+      expect(@client_streamer.is_client_streamer?).to be(true)
+      expect(@request_response.is_client_streamer?).to be(false)
+      expect(@server_streamer.is_client_streamer?).to be(false)
+      expect(@bidi_streamer.is_client_streamer?).to be(false)
+    end
+
+  end
+
+  describe '#is_server_streamer?' do
+
+    it 'is true only when output is a Stream and input is not a Stream' do
+      expect(@server_streamer.is_server_streamer?).to be(true)
+      expect(@client_streamer.is_server_streamer?).to be(false)
+      expect(@request_response.is_server_streamer?).to be(false)
+      expect(@bidi_streamer.is_server_streamer?).to be(false)
+    end
+
+  end
+
+  describe '#is_bidi_streamer?' do
+
+    it 'is true only when output is a Stream and input is a Stream' do
+      expect(@bidi_streamer.is_bidi_streamer?).to be(true)
+      expect(@server_streamer.is_bidi_streamer?).to be(false)
+      expect(@client_streamer.is_bidi_streamer?).to be(false)
+      expect(@request_response.is_bidi_streamer?).to be(false)
+    end
+
+  end
+
+  def fake_reqresp(req, call)
+    @ok_response
+  end
+
+  def fake_clstream(call)
+    @ok_response
+  end
+
+  def fake_svstream(req, call)
+    [@ok_response, @ok_response]
+  end
+
+  def fake_bidistream(an_array)
+    return an_array
+  end
+
+  def bad_status(req, call)
+    raise GRPC::BadStatus.new(@bs_code, 'NOK')
+  end
+
+  def other_error(req, call)
+    raise ArgumentError.new('other error')
+  end
+
+  def bad_status_alt(call)
+    raise GRPC::BadStatus.new(@bs_code, 'NOK')
+  end
+
+  def other_error_alt(call)
+    raise ArgumentError.new('other error')
+  end
+
+end
+
diff --git a/src/ruby/spec/generic/rpc_server_pool_spec.rb b/src/ruby/spec/generic/rpc_server_pool_spec.rb
new file mode 100644
index 0000000..8a185df
--- /dev/null
+++ b/src/ruby/spec/generic/rpc_server_pool_spec.rb
@@ -0,0 +1,153 @@
+# 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 'grpc'
+require 'grpc/generic/rpc_server'
+require 'xray/thread_dump_signal_handler'
+
+Pool = GRPC::RpcServer::Pool
+
+describe Pool do
+
+  describe '#new' do
+
+    it 'raises if a non-positive size is used' do
+      expect { Pool.new(0) }.to raise_error
+      expect { Pool.new(-1) }.to raise_error
+      expect { Pool.new(Object.new) }.to raise_error
+    end
+
+    it 'is constructed OK with a positive size' do
+      expect { Pool.new(1) }.not_to raise_error
+    end
+
+  end
+
+  describe '#jobs_waiting' do
+
+    it 'at start, it is zero' do
+      p = Pool.new(1)
+      expect(p.jobs_waiting).to be(0)
+    end
+
+    it 'it increases, with each scheduled job if the pool is not running' do
+      p = Pool.new(1)
+      job = Proc.new { }
+      expect(p.jobs_waiting).to be(0)
+      5.times do |i|
+        p.schedule(&job)
+        expect(p.jobs_waiting).to be(i + 1)
+      end
+
+    end
+
+    it 'it decreases as jobs are run' do
+      p = Pool.new(1)
+      job = Proc.new { }
+      expect(p.jobs_waiting).to be(0)
+      3.times do |i|
+        p.schedule(&job)
+      end
+      p.start
+      sleep 2
+      expect(p.jobs_waiting).to be(0)
+    end
+
+  end
+
+  describe '#schedule' do
+
+    it 'throws if the pool is already stopped' do
+      p = Pool.new(1)
+      p.stop()
+      job = Proc.new { }
+      expect { p.schedule(&job) }.to raise_error
+    end
+
+    it 'adds jobs that get run by the pool' do
+      p = Pool.new(1)
+      p.start()
+      o, q = Object.new, Queue.new
+      job = Proc.new { q.push(o) }
+      p.schedule(&job)
+      expect(q.pop).to be(o)
+      p.stop
+    end
+
+  end
+
+  describe '#stop' do
+
+    it 'works when there are no scheduled tasks' do
+      p = Pool.new(1)
+      expect { p.stop() }.not_to raise_error
+    end
+
+    it 'stops jobs when there are long running jobs' do
+      p = Pool.new(1)
+      p.start()
+      o, q = Object.new, Queue.new
+      job = Proc.new do
+        sleep(5)  # long running
+        q.push(o)
+      end
+      p.schedule(&job)
+      sleep(1)  # should ensure the long job gets scheduled
+      expect { p.stop() }.not_to raise_error
+    end
+
+  end
+
+  describe '#start' do
+
+    it 'runs pre-scheduled jobs' do
+      p = Pool.new(2)
+      o, q = Object.new, Queue.new
+      n = 5  # arbitrary
+      n.times { p.schedule(o, &q.method(:push)) }
+      p.start
+      n.times { expect(q.pop).to be(o) }
+      p.stop
+    end
+
+    it 'runs jobs as they are scheduled ' do
+      p = Pool.new(2)
+      o, q = Object.new, Queue.new
+      p.start
+      n = 5  # arbitrary
+      n.times do
+        p.schedule(o, &q.method(:push))
+        expect(q.pop).to be(o)
+      end
+      p.stop
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb
new file mode 100644
index 0000000..4e7379b
--- /dev/null
+++ b/src/ruby/spec/generic/rpc_server_spec.rb
@@ -0,0 +1,391 @@
+# 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 'grpc'
+require 'grpc/generic/active_call'
+require 'grpc/generic/client_stub'
+require 'grpc/generic/rpc_server'
+require 'grpc/generic/service'
+require 'xray/thread_dump_signal_handler'
+require_relative '../port_picker'
+
+class EchoMsg
+  def marshal
+    ''
+  end
+
+  def self.unmarshal(o)
+    EchoMsg.new
+  end
+end
+
+class EmptyService
+  include GRPC::GenericService
+end
+
+class NoRpcImplementation
+  include GRPC::GenericService
+  rpc :an_rpc, EchoMsg, EchoMsg
+end
+
+class EchoService
+  include GRPC::GenericService
+  rpc :an_rpc, 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
+
+class SlowService
+  include GRPC::GenericService
+  rpc :an_rpc, EchoMsg, EchoMsg
+
+  def initialize(default_var='ignored')
+  end
+
+  def an_rpc(req, call)
+    delay = 0.25
+    logger.info("starting a slow #{delay} rpc")
+    sleep delay
+    req  # send back the req as the response
+  end
+end
+
+SlowStub = SlowService.rpc_stub_class
+
+module GRPC
+
+  describe RpcServer do
+
+    before(:each) do
+      @method = 'an_rpc_method'
+      @pass = 0
+      @fail = 1
+      @noop = Proc.new { |x| x }
+
+      @server_queue = CompletionQueue.new
+      port = find_unused_tcp_port
+      @host = "localhost:#{port}"
+      @server = GRPC::Server.new(@server_queue, nil)
+      @server.add_http2_port(@host)
+      @ch = GRPC::Channel.new(@host, nil)
+    end
+
+    after(:each) do
+      @server.close
+    end
+
+    describe '#new' do
+
+      it 'can be created with just some args' do
+        opts = {:a_channel_arg => 'an_arg'}
+        blk = Proc.new do
+          RpcServer.new(**opts)
+        end
+        expect(&blk).not_to raise_error
+      end
+
+      it 'can be created with a default deadline' do
+        opts = {:a_channel_arg => 'an_arg', :deadline => 5}
+        blk = Proc.new do
+          RpcServer.new(**opts)
+        end
+        expect(&blk).not_to raise_error
+      end
+
+      it 'can be created with a completion queue override' do
+        opts = {
+          :a_channel_arg => 'an_arg',
+          :completion_queue_override => @server_queue
+        }
+        blk = Proc.new do
+          RpcServer.new(**opts)
+        end
+        expect(&blk).not_to raise_error
+      end
+
+      it 'cannot be created with a bad completion queue override' do
+        blk = Proc.new do
+          opts = {
+            :a_channel_arg => 'an_arg',
+            :completion_queue_override => Object.new
+          }
+          RpcServer.new(**opts)
+        end
+        expect(&blk).to raise_error
+      end
+
+      it 'can be created with a server override' do
+        opts = {:a_channel_arg => 'an_arg', :server_override => @server}
+        blk = Proc.new do
+          RpcServer.new(**opts)
+        end
+        expect(&blk).not_to raise_error
+      end
+
+      it 'cannot be created with a bad server override' do
+        blk = Proc.new do
+          opts = {
+            :a_channel_arg => 'an_arg',
+            :server_override => Object.new
+          }
+          RpcServer.new(**opts)
+        end
+        expect(&blk).to raise_error
+      end
+
+    end
+
+    describe '#stopped?' do
+
+      before(:each) do
+        opts = {:a_channel_arg => 'an_arg', :poll_period => 1}
+        @srv = RpcServer.new(**opts)
+      end
+
+      it 'starts out false' do
+        expect(@srv.stopped?).to be(false)
+      end
+
+      it 'stays false after a #stop is called before #run' do
+        @srv.stop
+        expect(@srv.stopped?).to be(false)
+      end
+
+      it 'stays false after the server starts running' do
+        @srv.handle(EchoService)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        expect(@srv.stopped?).to be(false)
+        @srv.stop
+        t.join
+      end
+
+      it 'is true after a running server is stopped' do
+        @srv.handle(EchoService)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        @srv.stop
+        expect(@srv.stopped?).to be(true)
+        t.join
+      end
+
+    end
+
+    describe '#running?' do
+
+      it 'starts out false' do
+        opts = {:a_channel_arg => 'an_arg', :server_override => @server}
+        r = RpcServer.new(**opts)
+        expect(r.running?).to be(false)
+      end
+
+      it 'is false after run is called with no services registered' do
+        opts = {
+            :a_channel_arg => 'an_arg',
+            :poll_period => 1,
+            :server_override => @server
+        }
+        r = RpcServer.new(**opts)
+        r.run()
+        expect(r.running?).to be(false)
+      end
+
+      it 'is true after run is called with a registered service' do
+        opts = {
+            :a_channel_arg => 'an_arg',
+            :poll_period => 1,
+            :server_override => @server
+        }
+        r = RpcServer.new(**opts)
+        r.handle(EchoService)
+        t = Thread.new { r.run }
+        r.wait_till_running
+        expect(r.running?).to be(true)
+        r.stop
+        t.join
+      end
+
+    end
+
+    describe '#handle' do
+
+      before(:each) do
+        @opts = {:a_channel_arg => 'an_arg', :poll_period => 1}
+        @srv = RpcServer.new(**@opts)
+      end
+
+      it 'raises if #run has already been called' do
+        @srv.handle(EchoService)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        expect { @srv.handle(EchoService) }.to raise_error
+        @srv.stop
+        t.join
+      end
+
+      it 'raises if the server has been run and stopped' do
+        @srv.handle(EchoService)
+        t = Thread.new { @srv.run }
+        @srv.wait_till_running
+        @srv.stop
+        t.join
+        expect { @srv.handle(EchoService) }.to raise_error
+      end
+
+      it 'raises if the service does not include GenericService ' do
+        expect { @srv.handle(Object) }.to raise_error
+      end
+
+      it 'raises if the service does not declare any rpc methods' do
+        expect { @srv.handle(EmptyService) }.to raise_error
+      end
+
+      it 'raises if the service does not define its rpc methods' do
+        expect { @srv.handle(NoRpcImplementation) }.to raise_error
+      end
+
+      it 'raises if a handler method is already registered' do
+        @srv.handle(EchoService)
+        expect { r.handle(EchoService) }.to raise_error
+      end
+
+    end
+
+    describe '#run' do
+
+      before(:each) do
+        @client_opts = {
+            :channel_override => @ch
+        }
+        @marshal = EchoService.rpc_descs[:an_rpc].marshal_proc
+        @unmarshal = EchoService.rpc_descs[:an_rpc].unmarshal_proc(:output)
+        server_opts = {
+            :server_override => @server,
+            :completion_queue_override => @server_queue,
+            :poll_period => 1
+        }
+        @srv = RpcServer.new(**server_opts)
+      end
+
+      describe 'when running' do
+
+        it 'should return NOT_FOUND status for requests on unknown methods' do
+          @srv.handle(EchoService)
+          t = Thread.new { @srv.run }
+          @srv.wait_till_running
+          req = EchoMsg.new
+          blk = Proc.new do
+            cq = CompletionQueue.new
+            stub = ClientStub.new(@host, cq, **@client_opts)
+            stub.request_response('/unknown', req, @marshal, @unmarshal)
+          end
+          expect(&blk).to raise_error BadStatus
+          @srv.stop
+          t.join
+        end
+
+        it 'should obtain responses for multiple sequential requests' do
+          @srv.handle(EchoService)
+          t = Thread.new { @srv.run }
+          @srv.wait_till_running
+          req = EchoMsg.new
+          n = 5  # arbitrary
+          stub = EchoStub.new(@host, **@client_opts)
+          n.times { |x|  expect(stub.an_rpc(req)).to be_a(EchoMsg) }
+          @srv.stop
+          t.join
+        end
+
+        it 'should obtain responses for multiple parallel requests' do
+          @srv.handle(EchoService)
+          t = Thread.new { @srv.run }
+          @srv.wait_till_running
+          req, q = EchoMsg.new, Queue.new
+          n = 5  # arbitrary
+          threads = []
+          n.times do |x|
+            cq = CompletionQueue.new
+            threads << Thread.new do
+              stub = EchoStub.new(@host, **@client_opts)
+              q << stub.an_rpc(req)
+            end
+          end
+          n.times { expect(q.pop).to be_a(EchoMsg) }
+          @srv.stop
+          threads.each { |t| t.join }
+        end
+
+        it 'should return UNAVAILABLE status if there too many jobs' do
+          opts = {
+              :a_channel_arg => 'an_arg',
+              :server_override => @server,
+              :completion_queue_override => @server_queue,
+              :pool_size => 1,
+              :poll_period => 1,
+              :max_waiting_requests => 0
+          }
+          alt_srv = RpcServer.new(**opts)
+          alt_srv.handle(SlowService)
+          t = Thread.new { alt_srv.run }
+          alt_srv.wait_till_running
+          req = EchoMsg.new
+          n = 5  # arbitrary, use as many to ensure the server pool is exceeded
+          threads = []
+          _1_failed_as_unavailable = false
+          n.times do |x|
+            threads << Thread.new do
+              cq = CompletionQueue.new
+              stub = SlowStub.new(@host, **@client_opts)
+              begin
+                stub.an_rpc(req)
+              rescue BadStatus => e
+                _1_failed_as_unavailable = e.code == StatusCodes::UNAVAILABLE
+              end
+            end
+          end
+          threads.each { |t| t.join }
+          alt_srv.stop
+          expect(_1_failed_as_unavailable).to be(true)
+        end
+
+      end
+
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/generic/service_spec.rb b/src/ruby/spec/generic/service_spec.rb
new file mode 100644
index 0000000..4c76881
--- /dev/null
+++ b/src/ruby/spec/generic/service_spec.rb
@@ -0,0 +1,324 @@
+# 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 'grpc'
+require 'grpc/generic/rpc_desc'
+require 'grpc/generic/service'
+
+
+class GoodMsg
+  def marshal
+    ''
+  end
+
+  def self.unmarshal(o)
+    GoodMsg.new
+  end
+end
+
+class EncodeDecodeMsg
+  def encode
+    ''
+  end
+
+  def self.decode(o)
+    GoodMsg.new
+  end
+end
+
+GenericService = GRPC::GenericService
+RpcDesc = GRPC::RpcDesc
+Dsl = GenericService::Dsl
+
+
+describe 'String#underscore' do
+  it 'should convert CamelCase to underscore separated' do
+    expect('AnRPC'.underscore).to eq('an_rpc')
+    expect('AMethod'.underscore).to eq('a_method')
+    expect('PrintHTML'.underscore).to eq('print_html')
+    expect('PrintHTMLBooks'.underscore).to eq('print_html_books')
+  end
+end
+
+describe Dsl do
+
+  it 'can be included in new classes' do
+    blk = Proc.new do
+      c = Class.new { include Dsl }
+    end
+    expect(&blk).to_not raise_error
+  end
+
+end
+
+describe GenericService do
+
+  describe 'including it' do
+
+    it 'adds a class method, rpc' do
+      c = Class.new do
+        include GenericService
+      end
+      expect(c.methods).to include(:rpc)
+    end
+
+    it 'adds rpc descs using the added class method, #rpc' do
+      c = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+      end
+
+      expect(c.rpc_descs).to include(:AnRpc)
+      expect(c.rpc_descs[:AnRpc]).to be_a(RpcDesc)
+    end
+
+    it 'give subclasses access to #rpc_descs' do
+      base = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+      end
+      c = Class.new(base) do
+      end
+      expect(c.rpc_descs).to include(:AnRpc)
+      expect(c.rpc_descs[:AnRpc]).to be_a(RpcDesc)
+    end
+
+  end
+
+  describe '#include' do
+
+    it 'raises if #rpc is missing an arg' do
+      blk = Proc.new do
+        Class.new do
+          include GenericService
+          rpc :AnRpc, GoodMsg
+        end
+      end
+      expect(&blk).to raise_error ArgumentError
+
+      blk = Proc.new do
+        Class.new do
+          include GenericService
+          rpc :AnRpc
+        end
+      end
+      expect(&blk).to raise_error ArgumentError
+    end
+
+    describe 'when #rpc args are incorrect' do
+
+      it 'raises if an arg does not have the marshal or unmarshal methods' do
+        blk = Proc.new do
+          Class.new do
+            include GenericService
+            rpc :AnRpc, GoodMsg, Object
+          end
+        end
+        expect(&blk).to raise_error ArgumentError
+      end
+
+      it 'raises if a type arg only has the marshal method' do
+        class OnlyMarshal
+          def marshal(o)
+            o
+          end
+        end
+
+        blk = Proc.new do
+          Class.new do
+            include GenericService
+            rpc :AnRpc, OnlyMarshal, GoodMsg
+          end
+        end
+        expect(&blk).to raise_error ArgumentError
+      end
+
+      it 'raises if a type arg only has the unmarshal method' do
+        class OnlyUnmarshal
+          def self.ummarshal(o)
+            o
+          end
+        end
+        blk = Proc.new do
+          Class.new do
+            include GenericService
+            rpc :AnRpc, GoodMsg, OnlyUnmarshal
+          end
+        end
+        expect(&blk).to raise_error ArgumentError
+      end
+    end
+
+    it 'is ok for services that expect the default {un,}marshal methods' do
+      blk = Proc.new do
+        Class.new do
+          include GenericService
+          rpc :AnRpc, GoodMsg, GoodMsg
+        end
+      end
+      expect(&blk).not_to raise_error
+    end
+
+    it 'is ok for services that override the default {un,}marshal methods' do
+      blk = Proc.new do
+        Class.new do
+          include GenericService
+          self.marshal_instance_method = :encode
+          self.unmarshal_class_method = :decode
+          rpc :AnRpc, EncodeDecodeMsg, EncodeDecodeMsg
+        end
+      end
+      expect(&blk).not_to raise_error
+    end
+
+  end
+
+  describe '#rpc_stub_class' do
+
+    it 'generates a client class that defines any of the rpc methods' do
+      s = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+        rpc :AServerStreamer, GoodMsg, stream(GoodMsg)
+        rpc :AClientStreamer, stream(GoodMsg), GoodMsg
+        rpc :ABidiStreamer, stream(GoodMsg), stream(GoodMsg)
+      end
+      client_class = s.rpc_stub_class
+      expect(client_class.instance_methods).to include(:an_rpc)
+      expect(client_class.instance_methods).to include(:a_server_streamer)
+      expect(client_class.instance_methods).to include(:a_client_streamer)
+      expect(client_class.instance_methods).to include(:a_bidi_streamer)
+    end
+
+    describe 'the generated instances' do
+
+      it 'can be instanciated with just a hostname' do
+        s = Class.new do
+          include GenericService
+          rpc :AnRpc, GoodMsg, GoodMsg
+          rpc :AServerStreamer, GoodMsg, stream(GoodMsg)
+          rpc :AClientStreamer, stream(GoodMsg), GoodMsg
+          rpc :ABidiStreamer, stream(GoodMsg), stream(GoodMsg)
+        end
+        client_class = s.rpc_stub_class
+        expect { client_class.new('fakehostname') }.not_to raise_error
+      end
+
+      it 'has the methods defined in the service' do
+        s = Class.new do
+          include GenericService
+          rpc :AnRpc, GoodMsg, GoodMsg
+          rpc :AServerStreamer, GoodMsg, stream(GoodMsg)
+          rpc :AClientStreamer, stream(GoodMsg), GoodMsg
+          rpc :ABidiStreamer, stream(GoodMsg), stream(GoodMsg)
+        end
+        client_class = s.rpc_stub_class
+        o = client_class.new('fakehostname')
+        expect(o.methods).to include(:an_rpc)
+        expect(o.methods).to include(:a_bidi_streamer)
+        expect(o.methods).to include(:a_client_streamer)
+        expect(o.methods).to include(:a_bidi_streamer)
+      end
+
+    end
+
+  end
+
+  describe '#assert_rpc_descs_have_methods' do
+
+    it 'fails if there is no instance method for an rpc descriptor' do
+      c1 = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+      end
+      expect { c1.assert_rpc_descs_have_methods }.to raise_error
+
+      c2 = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+        rpc :AnotherRpc, GoodMsg, GoodMsg
+
+        def an_rpc
+        end
+      end
+      expect { c2.assert_rpc_descs_have_methods }.to raise_error
+    end
+
+    it 'passes if there are corresponding methods for each descriptor' do
+      c = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+        rpc :AServerStreamer, GoodMsg, stream(GoodMsg)
+        rpc :AClientStreamer, stream(GoodMsg), GoodMsg
+        rpc :ABidiStreamer, stream(GoodMsg), stream(GoodMsg)
+
+        def an_rpc(req, call)
+        end
+
+        def a_server_streamer(req, call)
+        end
+
+        def a_client_streamer(call)
+        end
+
+        def a_bidi_streamer(call)
+        end
+      end
+      expect { c.assert_rpc_descs_have_methods }.to_not raise_error
+    end
+
+    it 'passes for subclasses of that include GenericService' do
+      base = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+
+        def an_rpc(req, call)
+        end
+      end
+      c = Class.new(base)
+      expect { c.assert_rpc_descs_have_methods }.to_not raise_error
+      expect(c.include?(GenericService)).to be(true)
+    end
+
+    it 'passes if subclasses define the rpc methods' do
+      base = Class.new do
+        include GenericService
+        rpc :AnRpc, GoodMsg, GoodMsg
+      end
+      c = Class.new(base) do
+        def an_rpc(req, call)
+        end
+      end
+      expect { c.assert_rpc_descs_have_methods }.to_not raise_error
+      expect(c.include?(GenericService)).to be(true)
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/metadata_spec.rb b/src/ruby/spec/metadata_spec.rb
new file mode 100644
index 0000000..8465a40
--- /dev/null
+++ b/src/ruby/spec/metadata_spec.rb
@@ -0,0 +1,67 @@
+# 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 'grpc'
+
+describe GRPC::Metadata do
+
+  describe '#new' do
+    it 'should create instances' do
+      expect { GRPC::Metadata.new('a key', 'a value') }.to_not raise_error
+      expect(GRPC::Metadata.new('a key', 'a value')).to be_a(GRPC::Metadata)
+    end
+  end
+
+  describe '#key' do
+    md = GRPC::Metadata.new('a key', 'a value')
+    it 'should be the constructor value' do
+      expect(md.key).to eq('a key')
+    end
+  end
+
+  describe '#value' do
+    md = GRPC::Metadata.new('a key', 'a value')
+    it 'should be the constuctor value' do
+      expect(md.value).to eq('a value')
+    end
+  end
+
+  describe '#dup' do
+    it 'should create a copy that returns the correct key' do
+      md = GRPC::Metadata.new('a key', 'a value')
+      expect(md.dup.key).to eq('a key')
+    end
+
+    it 'should create a copy that returns the correct value' do
+      md = GRPC::Metadata.new('a key', 'a value')
+      expect(md.dup.value).to eq('a value')
+    end
+  end
+
+end
diff --git a/src/ruby/spec/port_picker.rb b/src/ruby/spec/port_picker.rb
new file mode 100644
index 0000000..1b52113
--- /dev/null
+++ b/src/ruby/spec/port_picker.rb
@@ -0,0 +1,45 @@
+# 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 'socket'
+
+# @param [Fixnum] the minimum port number to accept
+# @param [Fixnum] the maximum port number to accept
+# @return [Fixnum ]a free tcp port
+def find_unused_tcp_port(min=32768, max=60000)
+  # Allow the system to assign a port, by specifying 0.
+  # Loop until a port is assigned in the required range
+  loop do
+    socket = Socket.new(:INET, :STREAM, 0)
+    socket.bind(Addrinfo.tcp('127.0.0.1', 0))
+    p = socket.local_address.ip_port
+    socket.close
+    return p if p > min and p < 60000
+  end
+end
diff --git a/src/ruby/spec/server_spec.rb b/src/ruby/spec/server_spec.rb
new file mode 100644
index 0000000..598b7cf
--- /dev/null
+++ b/src/ruby/spec/server_spec.rb
@@ -0,0 +1,185 @@
+# 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 'grpc'
+require 'port_picker'
+
+module GRPC
+
+  describe Server do
+
+    before(:each) do
+      @cq = CompletionQueue.new
+    end
+
+    describe '#start' do
+
+      it 'runs without failing' do
+        blk = Proc.new do
+          s = Server.new(@cq, nil).start
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'fails if the server is closed' do
+        s = Server.new(@cq, nil)
+        s.close
+        expect { s.start }.to raise_error(RuntimeError)
+      end
+
+    end
+
+    describe '#destroy' do
+      it 'destroys a server ok' do
+        s = start_a_server
+        blk = Proc.new { s.destroy }
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can be called more than once without error' do
+        s = start_a_server
+        begin
+          blk = Proc.new { s.destroy }
+          expect(&blk).to_not raise_error
+          blk.call
+          expect(&blk).to_not raise_error
+        ensure
+          s.close
+        end
+      end
+    end
+
+    describe '#close' do
+      it 'closes a server ok' do
+        s = start_a_server
+        begin
+          blk = Proc.new { s.close }
+          expect(&blk).to_not raise_error
+        ensure
+          s.close
+        end
+      end
+
+      it 'can be called more than once without error' do
+        s = start_a_server
+        blk = Proc.new { s.close }
+        expect(&blk).to_not raise_error
+        blk.call
+        expect(&blk).to_not raise_error
+      end
+    end
+
+    describe '#add_http_port' do
+
+      it 'runs without failing' do
+        blk = Proc.new do
+          s = Server.new(@cq, nil)
+          s.add_http2_port('localhost:0')
+          s.close
+        end
+        expect(&blk).to_not raise_error
+      end
+
+      it 'fails if the server is closed' do
+        s = Server.new(@cq, nil)
+        s.close
+        expect { s.add_http2_port('localhost:0') }.to raise_error(RuntimeError)
+      end
+
+    end
+
+    describe '#new' do
+
+      it 'takes a completion queue with nil channel args' do
+        expect { Server.new(@cq, nil) }.to_not raise_error
+      end
+
+      it 'does not take a hash with bad keys as channel args' do
+        blk = construct_with_args(Object.new => 1)
+        expect(&blk).to raise_error TypeError
+        blk = construct_with_args(1 => 1)
+        expect(&blk).to raise_error TypeError
+      end
+
+      it 'does not take a hash with bad values as channel args' do
+        blk = construct_with_args(:symbol => Object.new)
+        expect(&blk).to raise_error TypeError
+        blk = construct_with_args('1' => Hash.new)
+        expect(&blk).to raise_error TypeError
+      end
+
+      it 'can take a hash with a symbol key as channel args' do
+        blk = construct_with_args(:a_symbol => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a string key as channel args' do
+        blk = construct_with_args('a_symbol' => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a string value as channel args' do
+        blk = construct_with_args(:a_symbol => '1')
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a symbol value as channel args' do
+        blk = construct_with_args(:a_symbol => :another_symbol)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with a numeric value as channel args' do
+        blk = construct_with_args(:a_symbol => 1)
+        expect(&blk).to_not raise_error
+      end
+
+      it 'can take a hash with many args as channel args' do
+        args = Hash[127.times.collect { |x| [x.to_s, x] } ]
+        blk = construct_with_args(args)
+        expect(&blk).to_not raise_error
+      end
+
+    end
+
+    def construct_with_args(a)
+      Proc.new { Server.new(@cq, a) }
+    end
+
+    def start_a_server
+      port = find_unused_tcp_port
+      host = "localhost:#{port}"
+      s = Server.new(@cq, nil)
+      s.add_http2_port(host)
+      s.start
+      s
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/spec_helper.rb b/src/ruby/spec/spec_helper.rb
new file mode 100644
index 0000000..3322674
--- /dev/null
+++ b/src/ruby/spec/spec_helper.rb
@@ -0,0 +1,39 @@
+# 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 'rspec'
+require 'logging'
+require 'rspec/logging_helper'
+
+# Configure RSpec to capture log messages for each test. The output from the
+# logs will be stored in the @log_output variable. It is a StringIO instance.
+RSpec.configure do |config|
+  include RSpec::LoggingHelper
+  config.capture_log_messages
+end
diff --git a/src/ruby/spec/status_spec.rb b/src/ruby/spec/status_spec.rb
new file mode 100644
index 0000000..83d4efc
--- /dev/null
+++ b/src/ruby/spec/status_spec.rb
@@ -0,0 +1,161 @@
+# 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 'grpc'
+
+module GRPC
+
+  describe StatusCodes do
+
+    before(:each) do
+      @known_types = {
+        :OK => 0,
+        :CANCELLED => 1,
+        :UNKNOWN => 2,
+        :INVALID_ARGUMENT => 3,
+        :DEADLINE_EXCEEDED => 4,
+        :NOT_FOUND => 5,
+        :ALREADY_EXISTS => 6,
+        :PERMISSION_DENIED => 7,
+        :RESOURCE_EXHAUSTED => 8,
+        :FAILED_PRECONDITION => 9,
+        :ABORTED => 10,
+        :OUT_OF_RANGE => 11,
+        :UNIMPLEMENTED => 12,
+        :INTERNAL => 13,
+        :UNAVAILABLE => 14,
+        :DATA_LOSS => 15,
+        :UNAUTHENTICATED => 16
+      }
+    end
+
+    it 'should have symbols for all the known status codes' do
+      m = StatusCodes
+      syms_and_codes = m.constants.collect { |c| [c, m.const_get(c)] }
+      expect(Hash[syms_and_codes]).to eq(@known_types)
+    end
+
+  end
+
+  describe Status do
+
+    describe '#new' do
+      it 'should create new instances' do
+        expect { Status.new(142, 'test details') }.to_not raise_error
+      end
+    end
+
+    describe '#details' do
+      it 'return the detail' do
+        sts = Status.new(142, 'test details')
+        expect(sts.details).to eq('test details')
+      end
+    end
+
+    describe '#code' do
+      it 'should return the code' do
+        sts = Status.new(142, 'test details')
+        expect(sts.code).to eq(142)
+      end
+    end
+
+    describe '#dup' do
+      it 'should create a copy that returns the correct details' do
+        sts = Status.new(142, 'test details')
+        expect(sts.dup.code).to eq(142)
+      end
+
+      it 'should create a copy that returns the correct code' do
+        sts = Status.new(142, 'test details')
+        expect(sts.dup.details).to eq('test details')
+      end
+    end
+
+
+  end
+
+  describe BadStatus do
+
+    describe '#new' do
+      it 'should create new instances' do
+        expect { BadStatus.new(142, 'test details') }.to_not raise_error
+      end
+    end
+
+    describe '#details' do
+      it 'return the detail' do
+        err = BadStatus.new(142, 'test details')
+        expect(err.details).to eq('test details')
+      end
+    end
+
+    describe '#code' do
+      it 'should return the code' do
+        err = BadStatus.new(142, 'test details')
+        expect(err.code).to eq(142)
+      end
+    end
+
+    describe '#dup' do
+      it 'should create a copy that returns the correct details' do
+        err = BadStatus.new(142, 'test details')
+        expect(err.dup.code).to eq(142)
+      end
+
+      it 'should create a copy that returns the correct code' do
+        err = BadStatus.new(142, 'test details')
+        expect(err.dup.details).to eq('test details')
+      end
+    end
+
+    describe '#to_status' do
+      it 'should create a Status with the same code and details' do
+        err = BadStatus.new(142, 'test details')
+        sts = err.to_status
+        expect(sts.code).to eq(142)
+        expect(sts.details).to eq('test details')
+      end
+
+      it 'should create a copy that returns the correct code' do
+        err = BadStatus.new(142, 'test details')
+        expect(err.dup.details).to eq('test details')
+      end
+    end
+
+    describe 'as an exception' do
+
+      it 'can be raised' do
+        blk = Proc.new { raise BadStatus.new(343, 'status 343') }
+        expect(&blk).to raise_error(BadStatus)
+      end
+    end
+
+  end
+
+end
diff --git a/src/ruby/spec/time_consts_spec.rb b/src/ruby/spec/time_consts_spec.rb
new file mode 100644
index 0000000..2bbcac0
--- /dev/null
+++ b/src/ruby/spec/time_consts_spec.rb
@@ -0,0 +1,95 @@
+# 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 'grpc'
+
+module GRPC
+  describe TimeConsts do
+
+    before(:each) do
+      @known_consts = [:ZERO, :INFINITE_FUTURE, :INFINITE_PAST].sort
+    end
+
+    it 'should have all the known types' do
+      expect(TimeConsts.constants.collect.sort).to eq(@known_consts)
+    end
+
+    describe "#to_time" do
+      it 'converts each constant to a Time' do
+        m = TimeConsts
+        m.constants.each do |c|
+          expect(m.const_get(c).to_time).to be_a(Time)
+        end
+      end
+    end
+
+  end
+
+  describe '#from_relative_time' do
+
+    it 'cannot handle arbitrary objects' do
+      expect { TimeConsts.from_relative_time(Object.new) }.to raise_error
+    end
+
+    it 'preserves TimeConsts' do
+      m = TimeConsts
+      m.constants.each do |c|
+        const = m.const_get(c)
+        expect(TimeConsts.from_relative_time(const)).to be(const)
+      end
+    end
+
+    it 'converts 0 to TimeConsts::ZERO' do
+      expect(TimeConsts.from_relative_time(0)).to eq(TimeConsts::ZERO)
+    end
+
+    it 'converts nil to TimeConsts::ZERO' do
+      expect(TimeConsts.from_relative_time(nil)).to eq(TimeConsts::ZERO)
+    end
+
+    it 'converts negative values to TimeConsts::INFINITE_FUTURE' do
+      [-1, -3.2, -1e6].each do |t|
+        y = TimeConsts.from_relative_time(t)
+        expect(y).to eq(TimeConsts::INFINITE_FUTURE)
+      end
+    end
+
+    it 'converts a positive value to an absolute time' do
+      epsilon = 1
+      [1, 3.2, 1e6].each do |t|
+        want = Time.now + t
+        abs = TimeConsts.from_relative_time(t)
+        expect(abs.to_f).to be_within(epsilon).of(want.to_f)
+      end
+    end
+
+  end
+
+end
+