aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ruby
diff options
context:
space:
mode:
Diffstat (limited to 'src/ruby')
-rw-r--r--src/ruby/ext/grpc/rb_byte_buffer.c1
-rw-r--r--src/ruby/ext/grpc/rb_server.c28
-rw-r--r--src/ruby/lib/grpc/errors.rb156
-rw-r--r--src/ruby/lib/grpc/generic/active_call.rb4
-rw-r--r--src/ruby/lib/grpc/generic/bidi_call.rb5
-rw-r--r--src/ruby/lib/grpc/generic/rpc_desc.rb2
-rw-r--r--src/ruby/lib/grpc/generic/service.rb5
-rw-r--r--src/ruby/pb/grpc/health/checker.rb4
-rw-r--r--src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb7
-rwxr-xr-xsrc/ruby/pb/test/client.rb178
-rwxr-xr-xsrc/ruby/pb/test/server.rb27
-rw-r--r--src/ruby/qps/client.rb6
-rw-r--r--src/ruby/spec/error_sanity_spec.rb64
-rw-r--r--src/ruby/spec/generic/client_stub_spec.rb9
-rw-r--r--src/ruby/spec/generic/rpc_desc_spec.rb16
-rw-r--r--src/ruby/spec/generic/rpc_server_pool_spec.rb12
-rw-r--r--src/ruby/spec/generic/rpc_server_spec.rb8
-rw-r--r--src/ruby/spec/pb/health/checker_spec.rb8
-rw-r--r--src/ruby/spec/spec_helper.rb2
19 files changed, 479 insertions, 63 deletions
diff --git a/src/ruby/ext/grpc/rb_byte_buffer.c b/src/ruby/ext/grpc/rb_byte_buffer.c
index f97890e4a2..47fd6d9120 100644
--- a/src/ruby/ext/grpc/rb_byte_buffer.c
+++ b/src/ruby/ext/grpc/rb_byte_buffer.c
@@ -65,5 +65,6 @@ VALUE grpc_rb_byte_buffer_to_s(grpc_byte_buffer *buffer) {
GRPC_SLICE_LENGTH(next));
grpc_slice_unref(next);
}
+ grpc_byte_buffer_reader_destroy(&reader);
return rb_string;
}
diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c
index 2a6a246e67..c7b112c94b 100644
--- a/src/ruby/ext/grpc/rb_server.c
+++ b/src/ruby/ext/grpc/rb_server.c
@@ -37,6 +37,7 @@
#include "rb_server.h"
#include <grpc/grpc.h>
+#include <grpc/support/atm.h>
#include <grpc/grpc_security.h>
#include <grpc/support/log.h>
#include "rb_call.h"
@@ -59,22 +60,26 @@ typedef struct grpc_rb_server {
/* The actual server */
grpc_server *wrapped;
grpc_completion_queue *queue;
+ gpr_atm shutdown_started;
} grpc_rb_server;
static void destroy_server(grpc_rb_server *server, gpr_timespec deadline) {
grpc_event ev;
- if (server->wrapped != NULL) {
- grpc_server_shutdown_and_notify(server->wrapped, server->queue, NULL);
- ev = rb_completion_queue_pluck(server->queue, NULL, deadline, NULL);
- if (ev.type == GRPC_QUEUE_TIMEOUT) {
- grpc_server_cancel_all_calls(server->wrapped);
- rb_completion_queue_pluck(server->queue, NULL,
- gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+ // This can be started by app or implicitly by GC. Avoid a race between these.
+ if (gpr_atm_full_fetch_add(&server->shutdown_started, (gpr_atm)1) == 0) {
+ if (server->wrapped != NULL) {
+ grpc_server_shutdown_and_notify(server->wrapped, server->queue, NULL);
+ ev = rb_completion_queue_pluck(server->queue, NULL, deadline, NULL);
+ if (ev.type == GRPC_QUEUE_TIMEOUT) {
+ grpc_server_cancel_all_calls(server->wrapped);
+ rb_completion_queue_pluck(server->queue, NULL,
+ gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
+ }
+ grpc_server_destroy(server->wrapped);
+ grpc_rb_completion_queue_destroy(server->queue);
+ server->wrapped = NULL;
+ server->queue = NULL;
}
- grpc_server_destroy(server->wrapped);
- grpc_rb_completion_queue_destroy(server->queue);
- server->wrapped = NULL;
- server->queue = NULL;
}
}
@@ -115,6 +120,7 @@ static const rb_data_type_t grpc_rb_server_data_type = {
static VALUE grpc_rb_server_alloc(VALUE cls) {
grpc_rb_server *wrapper = ALLOC(grpc_rb_server);
wrapper->wrapped = NULL;
+ wrapper->shutdown_started = (gpr_atm)0;
return TypedData_Wrap_Struct(cls, &grpc_rb_server_data_type, wrapper);
}
diff --git a/src/ruby/lib/grpc/errors.rb b/src/ruby/lib/grpc/errors.rb
index 23b2bb7e12..f6998e17c4 100644
--- a/src/ruby/lib/grpc/errors.rb
+++ b/src/ruby/lib/grpc/errors.rb
@@ -35,9 +35,18 @@ module GRPC
# 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.
+ #
+ # There is also subclass of BadStatus in this module for each GRPC status.
+ # E.g., the GRPC::Cancelled class corresponds to status CANCELLED.
+ #
+ # See
+ # https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/status.h
+ # for detailed descriptions of each status code.
class BadStatus < StandardError
attr_reader :code, :details, :metadata
+ include GRPC::Core::StatusCodes
+
# @param code [Numeric] the status code
# @param details [String] the details of the exception
# @param metadata [Hash] the error's metadata
@@ -55,9 +64,152 @@ module GRPC
def to_status
Struct::Status.new(code, details, @metadata)
end
+
+ def self.new_status_exception(code, details = 'unkown cause', metadata = {})
+ codes = {}
+ codes[OK] = Ok
+ codes[CANCELLED] = Cancelled
+ codes[UNKNOWN] = Unknown
+ codes[INVALID_ARGUMENT] = InvalidArgument
+ codes[DEADLINE_EXCEEDED] = DeadlineExceeded
+ codes[NOT_FOUND] = NotFound
+ codes[ALREADY_EXISTS] = AlreadyExists
+ codes[PERMISSION_DENIED] = PermissionDenied
+ codes[UNAUTHENTICATED] = Unauthenticated
+ codes[RESOURCE_EXHAUSTED] = ResourceExhausted
+ codes[FAILED_PRECONDITION] = FailedPrecondition
+ codes[ABORTED] = Aborted
+ codes[OUT_OF_RANGE] = OutOfRange
+ codes[UNIMPLEMENTED] = Unimplemented
+ codes[INTERNAL] = Internal
+ codes[UNIMPLEMENTED] = Unimplemented
+ codes[UNAVAILABLE] = Unavailable
+ codes[DATA_LOSS] = DataLoss
+
+ if codes[code].nil?
+ BadStatus.new(code, details, metadata)
+ else
+ codes[code].new(details, metadata)
+ end
+ end
+ end
+
+ # GRPC status code corresponding to status OK
+ class Ok < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::OK, details, metadata)
+ end
end
- # Cancelled is an exception class that indicates that an rpc was cancelled.
- class Cancelled < StandardError
+ # GRPC status code corresponding to status CANCELLED
+ class Cancelled < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::CANCELLED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status UNKNOWN
+ class Unknown < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::UNKNOWN, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status INVALID_ARGUMENT
+ class InvalidArgument < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::INVALID_ARGUMENT, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status DEADLINE_EXCEEDED
+ class DeadlineExceeded < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::DEADLINE_EXCEEDED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status NOT_FOUND
+ class NotFound < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::NOT_FOUND, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status ALREADY_EXISTS
+ class AlreadyExists < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::ALREADY_EXISTS, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status PERMISSION_DENIED
+ class PermissionDenied < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::PERMISSION_DENIED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status UNAUTHENTICATED
+ class Unauthenticated < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::UNAUTHENTICATED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status RESOURCE_EXHAUSTED
+ class ResourceExhausted < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::RESOURCE_EXHAUSTED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status FAILED_PRECONDITION
+ class FailedPrecondition < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::FAILED_PRECONDITION, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status ABORTED
+ class Aborted < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::ABORTED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status OUT_OF_RANGE
+ class OutOfRange < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::OUT_OF_RANGE, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status UNIMPLEMENTED
+ class Unimplemented < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::UNIMPLEMENTED, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status INTERNAL
+ class Internal < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::INTERNAL, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status UNAVAILABLE
+ class Unavailable < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::UNAVAILABLE, details, metadata)
+ end
+ end
+
+ # GRPC status code corresponding to status DATA_LOSS
+ class DataLoss < BadStatus
+ def initialize(details = 'unknown cause', metadata = {})
+ super(Core::StatusCodes::DATA_LOSS, details, metadata)
+ end
end
end
diff --git a/src/ruby/lib/grpc/generic/active_call.rb b/src/ruby/lib/grpc/generic/active_call.rb
index f5c426ebfc..3b31f77ec0 100644
--- a/src/ruby/lib/grpc/generic/active_call.rb
+++ b/src/ruby/lib/grpc/generic/active_call.rb
@@ -43,8 +43,8 @@ class Struct
GRPC.logger.debug("Failing with status #{status}")
# raise BadStatus, propagating the metadata if present.
md = status.metadata
- fail GRPC::BadStatus.new(status.code, status.details, md),
- "status code: #{status.code}, details: #{status.details}"
+ fail GRPC::BadStatus.new_status_exception(
+ status.code, status.details, md)
end
status
end
diff --git a/src/ruby/lib/grpc/generic/bidi_call.rb b/src/ruby/lib/grpc/generic/bidi_call.rb
index d7cd9e6df2..adc77bbf5d 100644
--- a/src/ruby/lib/grpc/generic/bidi_call.rb
+++ b/src/ruby/lib/grpc/generic/bidi_call.rb
@@ -200,6 +200,7 @@ module GRPC
if is_client
batch_result = @call.run_batch(RECV_STATUS_ON_CLIENT => nil)
@call.status = batch_result.status
+ @call.trailing_metadata = @call.status.metadata if @call.status
batch_result.check_status
GRPC.logger.debug("bidi-read-loop: done status #{@call.status}")
end
@@ -219,6 +220,10 @@ module GRPC
GRPC.logger.debug('bidi-read-loop: finished')
@reads_complete = true
finished
+ # Make sure that the write loop is done done before finishing the call.
+ # Note that blocking is ok at this point because we've already received
+ # a status
+ @enq_th.join if is_client
end
end
end
diff --git a/src/ruby/lib/grpc/generic/rpc_desc.rb b/src/ruby/lib/grpc/generic/rpc_desc.rb
index cd17aed8e7..d46c4a1b5c 100644
--- a/src/ruby/lib/grpc/generic/rpc_desc.rb
+++ b/src/ruby/lib/grpc/generic/rpc_desc.rb
@@ -119,7 +119,7 @@ module GRPC
# Send back a UNKNOWN status to the client
GRPC.logger.warn("failed handler: #{active_call}; sending status:UNKNOWN")
GRPC.logger.warn(e)
- send_status(active_call, UNKNOWN, 'unkown error handling call on server')
+ send_status(active_call, UNKNOWN, "#{e.class}: #{e.message}")
end
def assert_arity_matches(mth)
diff --git a/src/ruby/lib/grpc/generic/service.rb b/src/ruby/lib/grpc/generic/service.rb
index 7cb9f1cc99..f5a6b49eb4 100644
--- a/src/ruby/lib/grpc/generic/service.rb
+++ b/src/ruby/lib/grpc/generic/service.rb
@@ -110,8 +110,9 @@ module GRPC
rpc_descs[name] = RpcDesc.new(name, input, output,
marshal_class_method,
unmarshal_class_method)
- define_method(name) do
- fail GRPC::BadStatus, GRPC::Core::StatusCodes::UNIMPLEMENTED
+ define_method(GenericService.underscore(name.to_s).to_sym) do |_, _|
+ fail GRPC::BadStatus.new_status_exception(
+ GRPC::Core::StatusCodes::UNIMPLEMENTED)
end
end
diff --git a/src/ruby/pb/grpc/health/checker.rb b/src/ruby/pb/grpc/health/checker.rb
index 4bce1744c4..6b2d852ebf 100644
--- a/src/ruby/pb/grpc/health/checker.rb
+++ b/src/ruby/pb/grpc/health/checker.rb
@@ -52,7 +52,9 @@ module Grpc
@status_mutex.synchronize do
status = @statuses["#{req.service}"]
end
- fail GRPC::BadStatus, StatusCodes::NOT_FOUND if status.nil?
+ if status.nil?
+ fail GRPC::BadStatus.new_status_exception(StatusCodes::NOT_FOUND)
+ end
HealthCheckResponse.new(status: status)
end
diff --git a/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb b/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
index fde328e4c5..7d1072e512 100644
--- a/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
+++ b/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb
@@ -54,6 +54,10 @@ module Grpc
rpc :EmptyCall, Empty, Empty
# One request followed by one response.
rpc :UnaryCall, SimpleRequest, SimpleResponse
+ # One request followed by one response. Response has cache control
+ # headers set such that a caching HTTP proxy (such as GFE) can
+ # satisfy subsequent requests.
+ rpc :CacheableUnaryCall, SimpleRequest, SimpleResponse
# One request followed by a sequence of responses (streamed download).
# The server returns the payload with client desired type and sizes.
rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse)
@@ -69,6 +73,9 @@ module Grpc
# stream of responses are returned to the client when the server starts with
# first request.
rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
+ # The test server will not implement this method. It will be used
+ # to test the behavior when clients call unimplemented methods.
+ rpc :UnimplementedCall, Empty, Empty
end
Stub = Service.rpc_stub_class
diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb
index 1e3ae65630..9c4ee9c6f2 100755
--- a/src/ruby/pb/test/client.rb
+++ b/src/ruby/pb/test/client.rb
@@ -158,14 +158,26 @@ def create_stub(opts)
GRPC.logger.info("... connecting securely to #{address}")
stub_opts[:channel_args].merge!(compression_channel_args)
- Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts)
+ if opts.test_case == "unimplemented_service"
+ Grpc::Testing::UnimplementedService::Stub.new(address, creds, **stub_opts)
+ else
+ Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts)
+ end
else
GRPC.logger.info("... connecting insecurely to #{address}")
- Grpc::Testing::TestService::Stub.new(
- address,
- :this_channel_is_insecure,
- channel_args: compression_channel_args
- )
+ if opts.test_case == "unimplemented_service"
+ Grpc::Testing::UnimplementedService::Stub.new(
+ address,
+ :this_channel_is_insecure,
+ channel_args: compression_channel_args
+ )
+ else
+ Grpc::Testing::TestService::Stub.new(
+ address,
+ :this_channel_is_insecure,
+ channel_args: compression_channel_args
+ )
+ end
end
end
@@ -459,11 +471,8 @@ class NamedTests
deadline = GRPC::Core::TimeConsts::from_relative_time(1)
resps = @stub.full_duplex_call(enum.each_item, deadline: deadline)
resps.each { } # wait to receive each request (or timeout)
- fail 'Should have raised GRPC::BadStatus(DEADLINE_EXCEEDED)'
- rescue GRPC::BadStatus => e
- assert("#{__callee__}: status was wrong") do
- e.code == GRPC::Core::StatusCodes::DEADLINE_EXCEEDED
- end
+ fail 'Should have raised GRPC::DeadlineExceeded'
+ rescue GRPC::DeadlineExceeded
end
def empty_stream
@@ -505,6 +514,153 @@ class NamedTests
op.wait
end
+ def unimplemented_method
+ begin
+ resp = @stub.unimplemented_call(Empty.new)
+ rescue GRPC::Unimplemented => e
+ return
+ rescue Exception => e
+ fail AssertionError, "Expected BadStatus. Received: #{e.inspect}"
+ end
+ fail AssertionError, "GRPC::Unimplemented should have been raised. Was not."
+ end
+
+ def unimplemented_service
+ begin
+ resp = @stub.unimplemented_call(Empty.new)
+ rescue GRPC::Unimplemented => e
+ return
+ rescue Exception => e
+ fail AssertionError, "Expected BadStatus. Received: #{e.inspect}"
+ end
+ fail AssertionError, "GRPC::Unimplemented should have been raised. Was not."
+ end
+
+ def status_code_and_message
+
+ # Function wide constants.
+ message = "test status method"
+ code = GRPC::Core::StatusCodes::UNKNOWN
+
+ # Testing with UnaryCall.
+ payload = Payload.new(type: :COMPRESSABLE, body: nulls(1))
+ echo_status = EchoStatus.new(code: code, message: message)
+ req = SimpleRequest.new(response_type: :COMPRESSABLE,
+ response_size: 1,
+ payload: payload,
+ response_status: echo_status)
+ seen_correct_exception = false
+ begin
+ resp = @stub.unary_call(req)
+ rescue GRPC::Unknown => e
+ if e.details != message
+ fail AssertionError,
+ "Expected message #{message}. Received: #{e.details}"
+ end
+ seen_correct_exception = true
+ rescue Exception => e
+ fail AssertionError, "Expected BadStatus. Received: #{e.inspect}"
+ end
+
+ if not seen_correct_exception
+ fail AssertionError, "Did not see expected status from UnaryCall"
+ end
+
+ # testing with FullDuplex
+ req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters
+ duplex_req = req_cls.new(payload: Payload.new(body: nulls(1)),
+ response_type: :COMPRESSABLE,
+ response_parameters: [p_cls.new(size: 1)],
+ response_status: echo_status)
+ seen_correct_exception = false
+ begin
+ resp = @stub.full_duplex_call([duplex_req])
+ resp.each { |r| }
+ rescue GRPC::Unknown => e
+ if e.details != message
+ fail AssertionError,
+ "Expected message #{message}. Received: #{e.details}"
+ end
+ seen_correct_exception = true
+ rescue Exception => e
+ fail AssertionError, "Expected BadStatus. Received: #{e.inspect}"
+ end
+
+ if not seen_correct_exception
+ fail AssertionError, "Did not see expected status from FullDuplexCall"
+ end
+
+ end
+
+
+ def custom_metadata
+
+ # Function wide constants
+ req_size, wanted_response_size = 271_828, 314_159
+ initial_metadata_key = "x-grpc-test-echo-initial"
+ initial_metadata_value = "test_initial_metadata_value"
+ trailing_metadata_key = "x-grpc-test-echo-trailing-bin"
+ trailing_metadata_value = "\x0a\x0b\x0a\x0b\x0a\x0b"
+
+ metadata = {
+ initial_metadata_key => initial_metadata_value,
+ trailing_metadata_key => trailing_metadata_value
+ }
+
+ # Testing with UnaryCall
+ payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
+ req = SimpleRequest.new(response_type: :COMPRESSABLE,
+ response_size: wanted_response_size,
+ payload: payload)
+
+ op = @stub.unary_call(req, metadata: metadata, return_op: true)
+ op.execute
+ if not op.metadata.has_key?(initial_metadata_key)
+ fail AssertionError, "Expected initial metadata. None received"
+ elsif op.metadata[initial_metadata_key] != metadata[initial_metadata_key]
+ fail AssertionError,
+ "Expected initial metadata: #{metadata[initial_metadata_key]}. "\
+ "Received: #{op.metadata[initial_metadata_key]}"
+ end
+ if not op.trailing_metadata.has_key?(trailing_metadata_key)
+ fail AssertionError, "Expected trailing metadata. None received"
+ elsif op.trailing_metadata[trailing_metadata_key] !=
+ metadata[trailing_metadata_key]
+ fail AssertionError,
+ "Expected trailing metadata: #{metadata[trailing_metadata_key]}. "\
+ "Received: #{op.trailing_metadata[trailing_metadata_key]}"
+ end
+
+ # Testing with FullDuplex
+ req_cls, p_cls = StreamingOutputCallRequest, ResponseParameters
+ duplex_req = req_cls.new(payload: Payload.new(body: nulls(req_size)),
+ response_type: :COMPRESSABLE,
+ response_parameters: [p_cls.new(size: wanted_response_size)])
+
+ duplex_op = @stub.full_duplex_call([duplex_req], metadata: metadata,
+ return_op: true)
+ resp = duplex_op.execute
+ resp.each { |r| } # ensures that the server sends trailing data
+ duplex_op.wait
+ if not duplex_op.metadata.has_key?(initial_metadata_key)
+ fail AssertionError, "Expected initial metadata. None received"
+ elsif duplex_op.metadata[initial_metadata_key] !=
+ metadata[initial_metadata_key]
+ fail AssertionError,
+ "Expected initial metadata: #{metadata[initial_metadata_key]}. "\
+ "Received: #{duplex_op.metadata[initial_metadata_key]}"
+ end
+ if not duplex_op.trailing_metadata[trailing_metadata_key]
+ fail AssertionError, "Expected trailing metadata. None received"
+ elsif duplex_op.trailing_metadata[trailing_metadata_key] !=
+ metadata[trailing_metadata_key]
+ fail AssertionError,
+ "Expected trailing metadata: #{metadata[trailing_metadata_key]}. "\
+ "Received: #{duplex_op.trailing_metadata[trailing_metadata_key]}"
+ end
+
+ end
+
def all
all_methods = NamedTests.instance_methods(false).map(&:to_s)
all_methods.each do |m|
diff --git a/src/ruby/pb/test/server.rb b/src/ruby/pb/test/server.rb
index 3f1e0a1ccf..cc9b872648 100755
--- a/src/ruby/pb/test/server.rb
+++ b/src/ruby/pb/test/server.rb
@@ -129,6 +129,27 @@ def nulls(l)
[].pack('x' * l).force_encoding('ascii-8bit')
end
+def maybe_echo_metadata(_call)
+
+ # these are consistent for all interop tests
+ initial_metadata_key = "x-grpc-test-echo-initial"
+ trailing_metadata_key = "x-grpc-test-echo-trailing-bin"
+
+ if _call.metadata.has_key?(initial_metadata_key)
+ _call.metadata_to_send[initial_metadata_key] = _call.metadata[initial_metadata_key]
+ end
+ if _call.metadata.has_key?(trailing_metadata_key)
+ _call.output_metadata[trailing_metadata_key] = _call.metadata[trailing_metadata_key]
+ end
+end
+
+def maybe_echo_status_and_message(req)
+ unless req.response_status.nil?
+ fail GRPC::BadStatus.new_status_exception(
+ req.response_status.code, req.response_status.message)
+ end
+end
+
# A FullDuplexEnumerator passes requests to a block and yields generated responses
class FullDuplexEnumerator
include Grpc::Testing
@@ -143,6 +164,7 @@ class FullDuplexEnumerator
begin
cls = StreamingOutputCallResponse
@requests.each do |req|
+ maybe_echo_status_and_message(req)
req.response_parameters.each do |params|
resp_size = params.size
GRPC.logger.info("read a req, response size is #{resp_size}")
@@ -170,6 +192,8 @@ class TestTarget < Grpc::Testing::TestService::Service
end
def unary_call(simple_req, _call)
+ maybe_echo_metadata(_call)
+ maybe_echo_status_and_message(simple_req)
req_size = simple_req.response_size
SimpleResponse.new(payload: Payload.new(type: :COMPRESSABLE,
body: nulls(req_size)))
@@ -189,7 +213,8 @@ class TestTarget < Grpc::Testing::TestService::Service
end
end
- def full_duplex_call(reqs)
+ def full_duplex_call(reqs, _call)
+ maybe_echo_metadata(_call)
# reqs is a lazy Enumerator of the requests sent by the client.
FullDuplexEnumerator.new(reqs).each_item
end
diff --git a/src/ruby/qps/client.rb b/src/ruby/qps/client.rb
index 8aed866da5..817192626b 100644
--- a/src/ruby/qps/client.rb
+++ b/src/ruby/qps/client.rb
@@ -134,6 +134,7 @@ class BenchmarkClient
resp = stub.streaming_call(q.each_item)
start = Time.now
q.push(req)
+ pushed_sentinal = false
resp.each do |r|
@histogram.add((Time.now-start)*1e9)
if !@done
@@ -141,8 +142,9 @@ class BenchmarkClient
start = Time.now
q.push(req)
else
- q.push(self)
- break
+ q.push(self) unless pushed_sentinal
+ # Continue polling on the responses to consume and release resources
+ pushed_sentinal = true
end
end
end
diff --git a/src/ruby/spec/error_sanity_spec.rb b/src/ruby/spec/error_sanity_spec.rb
new file mode 100644
index 0000000000..77e94a8816
--- /dev/null
+++ b/src/ruby/spec/error_sanity_spec.rb
@@ -0,0 +1,64 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require 'grpc'
+
+StatusCodes = GRPC::Core::StatusCodes
+
+describe StatusCodes do
+ # convert upper snake-case to camel case.
+ # e.g., DEADLINE_EXCEEDED -> DeadlineExceeded
+ def upper_snake_to_camel(name)
+ name.to_s.split('_').map(&:downcase).map(&:capitalize).join('')
+ end
+
+ StatusCodes.constants.each do |status_name|
+ it 'there is a subclass of BadStatus corresponding to StatusCode: ' \
+ "#{status_name} that has code: #{StatusCodes.const_get(status_name)}" do
+ camel_case = upper_snake_to_camel(status_name)
+ error_class = GRPC.const_get(camel_case)
+ # expect the error class to be a subclass of BadStatus
+ expect(error_class < GRPC::BadStatus)
+
+ error_object = error_class.new
+ # check that the code matches the int value of the error's constant
+ status_code = StatusCodes.const_get(status_name)
+ expect(error_object.code).to eq(status_code)
+
+ # check default parameters
+ expect(error_object.details).to eq('unknown cause')
+ expect(error_object.metadata).to eq({})
+
+ # check that the BadStatus factory for creates the correct
+ # exception too
+ from_factory = GRPC::BadStatus.new_status_exception(status_code)
+ expect(from_factory.is_a?(error_class)).to be(true)
+ end
+ end
+end
diff --git a/src/ruby/spec/generic/client_stub_spec.rb b/src/ruby/spec/generic/client_stub_spec.rb
index 607a4a3c5d..b51b291cbd 100644
--- a/src/ruby/spec/generic/client_stub_spec.rb
+++ b/src/ruby/spec/generic/client_stub_spec.rb
@@ -190,15 +190,14 @@ describe 'ClientStub' do
end
creds = GRPC::Core::CallCredentials.new(failing_auth)
- error_occured = false
+ unauth_error_occured = false
begin
get_response(stub, credentials: creds)
- rescue GRPC::BadStatus => e
- error_occured = true
- expect(e.code).to eq(GRPC::Core::StatusCodes::UNAUTHENTICATED)
+ rescue GRPC::Unauthenticated => e
+ unauth_error_occured = true
expect(e.details.include?(error_message)).to be true
end
- expect(error_occured).to eq(true)
+ expect(unauth_error_occured).to eq(true)
# Kill the server thread so tests can complete
th.kill
diff --git a/src/ruby/spec/generic/rpc_desc_spec.rb b/src/ruby/spec/generic/rpc_desc_spec.rb
index a3f0efa603..1ace7211e9 100644
--- a/src/ruby/spec/generic/rpc_desc_spec.rb
+++ b/src/ruby/spec/generic/rpc_desc_spec.rb
@@ -48,7 +48,6 @@ describe GRPC::RpcDesc do
@bidi_streamer = RpcDesc.new('ss', Stream.new(Object.new),
Stream.new(Object.new), 'encode', 'decode')
@bs_code = INTERNAL
- @no_reason = 'unkown error handling call on server'
@ok_response = Object.new
end
@@ -62,8 +61,9 @@ describe GRPC::RpcDesc do
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,
- false, metadata: {})
+ expect(@call).to receive(:send_status).once.with(UNKNOWN,
+ arg_error_msg,
+ false, metadata: {})
this_desc.run_server_method(@call, method(:other_error))
end
@@ -112,7 +112,7 @@ describe GRPC::RpcDesc do
end
it 'sends status UNKNOWN if other StandardErrors are raised' do
- expect(@call).to receive(:send_status).once.with(UNKNOWN, @no_reason,
+ expect(@call).to receive(:send_status).once.with(UNKNOWN, arg_error_msg,
false, metadata: {})
@client_streamer.run_server_method(@call, method(:other_error_alt))
end
@@ -174,8 +174,9 @@ describe GRPC::RpcDesc do
end
it 'sends status UNKNOWN if other StandardErrors are raised' do
+ error_msg = arg_error_msg(StandardError.new)
expect(@call).to receive(:run_server_bidi).and_raise(StandardError)
- expect(@call).to receive(:send_status).once.with(UNKNOWN, @no_reason,
+ expect(@call).to receive(:send_status).once.with(UNKNOWN, error_msg,
false, metadata: {})
@bidi_streamer.run_server_method(@call, method(:other_error_alt))
end
@@ -342,4 +343,9 @@ describe GRPC::RpcDesc do
def other_error_alt(_call)
fail(ArgumentError, 'other error')
end
+
+ def arg_error_msg(error = nil)
+ error ||= ArgumentError.new('other error')
+ "#{error.class}: #{error.message}"
+ end
end
diff --git a/src/ruby/spec/generic/rpc_server_pool_spec.rb b/src/ruby/spec/generic/rpc_server_pool_spec.rb
index 48ccaee510..69e8222cb9 100644
--- a/src/ruby/spec/generic/rpc_server_pool_spec.rb
+++ b/src/ruby/spec/generic/rpc_server_pool_spec.rb
@@ -94,18 +94,6 @@ describe GRPC::Pool do
expect(q.pop).to be(o)
p.stop
end
-
- it 'it throws an error if all of the workers have tasks to do' do
- p = Pool.new(5)
- p.start
- job = proc {}
- 5.times do
- expect(p.ready_for_work?).to be(true)
- p.schedule(&job)
- end
- expect { p.schedule(&job) }.to raise_error
- expect { p.schedule(&job) }.to raise_error
- end
end
describe '#stop' do
diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb
index c5694790fd..806ea8ce9f 100644
--- a/src/ruby/spec/generic/rpc_server_spec.rb
+++ b/src/ruby/spec/generic/rpc_server_spec.rb
@@ -408,21 +408,21 @@ describe GRPC::RpcServer do
req = EchoMsg.new
n = 20 # arbitrary, use as many to ensure the server pool is exceeded
threads = []
- bad_status_code = nil
+ one_failed_as_unavailable = false
n.times do
threads << Thread.new do
stub = SlowStub.new(alt_host, :this_channel_is_insecure)
begin
stub.an_rpc(req)
- rescue GRPC::BadStatus => e
- bad_status_code = e.code
+ rescue GRPC::ResourceExhausted
+ one_failed_as_unavailable = true
end
end
end
threads.each(&:join)
alt_srv.stop
t.join
- expect(bad_status_code).to be(StatusCodes::RESOURCE_EXHAUSTED)
+ expect(one_failed_as_unavailable).to be(true)
end
end
diff --git a/src/ruby/spec/pb/health/checker_spec.rb b/src/ruby/spec/pb/health/checker_spec.rb
index 4711e09e88..719510001c 100644
--- a/src/ruby/spec/pb/health/checker_spec.rb
+++ b/src/ruby/spec/pb/health/checker_spec.rb
@@ -122,7 +122,7 @@ describe Grpc::Health::Checker do
checker.check(HCReq.new(service: t[:service]), nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
- expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+ expect(&blk).to raise_error GRPC::NotFound, expected_msg
end
end
end
@@ -141,7 +141,7 @@ describe Grpc::Health::Checker do
checker.check(HCReq.new(service: t[:service]), nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
- expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+ expect(&blk).to raise_error GRPC::NotFound, expected_msg
end
end
end
@@ -163,7 +163,7 @@ describe Grpc::Health::Checker do
checker.check(HCReq.new(service: t[:service]), nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
- expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+ expect(&blk).to raise_error GRPC::NotFound, expected_msg
end
end
end
@@ -214,7 +214,7 @@ describe Grpc::Health::Checker do
stub.check(HCReq.new(service: 'unknown'))
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
- expect(&blk).to raise_error GRPC::BadStatus, expected_msg
+ expect(&blk).to raise_error GRPC::NotFound, expected_msg
@srv.stop
t.join
end
diff --git a/src/ruby/spec/spec_helper.rb b/src/ruby/spec/spec_helper.rb
index c891c1bf5e..c2be0afa72 100644
--- a/src/ruby/spec/spec_helper.rb
+++ b/src/ruby/spec/spec_helper.rb
@@ -67,3 +67,5 @@ RSpec.configure do |config|
end
RSpec::Expectations.configuration.warn_about_potential_false_positives = false
+
+Thread.abort_on_exception = true