diff --git a/src/ruby/end2end/forking_client_client.rb b/src/ruby/end2end/forking_client_client.rb new file mode 100755 index 0000000000000000000000000000000000000000..c358d79f5997ebcddde42ce50cee5b3be383f571 --- /dev/null +++ b/src/ruby/end2end/forking_client_client.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +# 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. + +# Prompted by and minimal repro of https://github.com/grpc/grpc/issues/10658 + +require_relative './end2end_common' + +def main + server_port = '' + OptionParser.new do |opts| + opts.on('--client_control_port=P', String) do + STDERR.puts 'client control port not used' + end + opts.on('--server_port=P', String) do |p| + server_port = p + end + end.parse! + + p = fork do + stub = Echo::EchoServer::Stub.new("localhost:#{server_port}", + :this_channel_is_insecure) + stub.echo(Echo::EchoRequest.new(request: 'hello')) + end + + begin + Timeout.timeout(10) do + Process.wait(p) + end + rescue Timeout::Error + STDERR.puts "timeout waiting for forked process #{p}" + Process.kill('SIGKILL', p) + Process.wait(p) + raise 'Timed out waiting for client process. ' \ + 'It likely hangs when using gRPC after loading it and then forking' + end + + client_exit_code = $CHILD_STATUS + fail "forked process failed #{client_exit_code}" if client_exit_code != 0 +end + +main diff --git a/src/ruby/end2end/forking_client_driver.rb b/src/ruby/end2end/forking_client_driver.rb new file mode 100755 index 0000000000000000000000000000000000000000..2c67b33590df696e3e81efa65a71d04001c66eea --- /dev/null +++ b/src/ruby/end2end/forking_client_driver.rb @@ -0,0 +1,69 @@ +#!/usr/bin/env ruby + +# Copyright 2016, 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_relative './end2end_common' + +def main + STDERR.puts 'start server' + server_runner = ServerRunner.new(EchoServerImpl) + server_port = server_runner.run + + # TODO(apolcyn) Can we get rid of this sleep? + # Without it, an immediate call to the just started EchoServer + # fails with UNAVAILABLE + sleep 1 + + STDERR.puts 'start client' + _, client_pid = start_client('forking_client_client.rb', + server_port) + + begin + Timeout.timeout(10) do + Process.wait(client_pid) + end + rescue Timeout::Error + STDERR.puts "timeout wait for client pid #{client_pid}" + Process.kill('SIGKILL', client_pid) + Process.wait(client_pid) + STDERR.puts 'killed client child' + raise 'Timed out waiting for client process. ' \ + 'It likely hangs when requiring grpc, then forking, then using grpc ' + end + + client_exit_code = $CHILD_STATUS + if client_exit_code != 0 + fail "forking client client failed, exit code #{client_exit_code}" + end + + server_runner.stop +end + +main diff --git a/src/ruby/end2end/grpc_class_init_client.rb b/src/ruby/end2end/grpc_class_init_client.rb new file mode 100755 index 0000000000000000000000000000000000000000..ee79292119a0499e0f7784901c14bd132ba9a43b --- /dev/null +++ b/src/ruby/end2end/grpc_class_init_client.rb @@ -0,0 +1,77 @@ +#!/usr/bin/env ruby + +# 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. + +# For GRPC::Core classes, which use the grpc c-core, object init +# is interesting because it's related to overall library init. + +require_relative './end2end_common' + +def main + grpc_class = '' + OptionParser.new do |opts| + opts.on('--grpc_class=P', String) do |p| + grpc_class = p + end + end.parse! + + test_proc = nil + + case grpc_class + when 'channel' + test_proc = proc do + GRPC::Core::Channel.new('dummy_host', nil, :this_channel_is_insecure) + end + when 'server' + test_proc = proc do + GRPC::Core::Server.new({}) + end + when 'channel_credentials' + test_proc = proc do + GRPC::Core::ChannelCredentials.new + end + when 'call_credentials' + test_proc = proc do + GRPC::Core::CallCredentials.new(proc { |noop| noop }) + end + when 'compression_options' + test_proc = proc do + GRPC::Core::CompressionOptions.new + end + else + fail "bad --grpc_class=#{grpc_class} param" + end + + th = Thread.new { test_proc.call } + test_proc.call + th.join +end + +main diff --git a/src/ruby/end2end/grpc_class_init_driver.rb b/src/ruby/end2end/grpc_class_init_driver.rb new file mode 100755 index 0000000000000000000000000000000000000000..764d029f1494df9889069680922669a416cfc0b6 --- /dev/null +++ b/src/ruby/end2end/grpc_class_init_driver.rb @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +# Copyright 2016, 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_relative './end2end_common' + +def main + native_grpc_classes = %w( channel + server + channel_credentials + call_credentials + compression_options ) + + native_grpc_classes.each do |grpc_class| + STDERR.puts 'start client' + this_dir = File.expand_path(File.dirname(__FILE__)) + client_path = File.join(this_dir, 'grpc_class_init_client.rb') + client_pid = Process.spawn(RbConfig.ruby, + client_path, + "--grpc_class=#{grpc_class}") + begin + Timeout.timeout(10) do + Process.wait(client_pid) + end + rescue Timeout::Error + STDERR.puts "timeout waiting for client pid #{client_pid}" + Process.kill('SIGKILL', client_pid) + Process.wait(client_pid) + STDERR.puts 'killed client child' + raise 'Timed out waiting for client process. ' \ + 'It likely hangs when the first constructed gRPC object has ' \ + "type: #{grpc_class}" + end + + client_exit_code = $CHILD_STATUS + fail "client failed, exit code #{client_exit_code}" if client_exit_code != 0 + end +end + +main diff --git a/src/ruby/ext/grpc/rb_call_credentials.c b/src/ruby/ext/grpc/rb_call_credentials.c index 280f21c9731c2412729455587416781ccc1e3c4e..fac2cbb04d1cebef8793392fc9ad58be401ace73 100644 --- a/src/ruby/ext/grpc/rb_call_credentials.c +++ b/src/ruby/ext/grpc/rb_call_credentials.c @@ -221,6 +221,8 @@ static VALUE grpc_rb_call_credentials_init(VALUE self, VALUE proc) { grpc_call_credentials *creds = NULL; grpc_metadata_credentials_plugin plugin; + grpc_ruby_once_init(); + TypedData_Get_Struct(self, grpc_rb_call_credentials, &grpc_rb_call_credentials_data_type, wrapper); @@ -281,8 +283,6 @@ void Init_grpc_call_credentials() { grpc_rb_call_credentials_compose, -1); id_callback = rb_intern("__callback"); - - grpc_rb_event_queue_thread_start(); } /* Gets the wrapped grpc_call_credentials from the ruby wrapper */ diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c index 1c20c8813ff674618dff7878686fd00d2438639d..fb610f548eb23f279a71d8c09381392726950607 100644 --- a/src/ruby/ext/grpc/rb_channel.c +++ b/src/ruby/ext/grpc/rb_channel.c @@ -89,10 +89,14 @@ typedef struct grpc_rb_channel { static void grpc_rb_channel_try_register_connection_polling( grpc_rb_channel *wrapper); static void grpc_rb_channel_safe_destroy(grpc_rb_channel *wrapper); +static void *wait_until_channel_polling_thread_started_no_gil(void*); +static void wait_until_channel_polling_thread_started_unblocking_func(void*); static grpc_completion_queue *channel_polling_cq; static gpr_mu global_connection_polling_mu; +static gpr_cv global_connection_polling_cv; static int abort_channel_polling = 0; +static int channel_polling_thread_started = 0; /* Destroys Channel instances. */ static void grpc_rb_channel_free(void *p) { @@ -166,6 +170,10 @@ static VALUE grpc_rb_channel_init(int argc, VALUE *argv, VALUE self) { grpc_channel_args args; MEMZERO(&args, grpc_channel_args, 1); + grpc_ruby_once_init(); + rb_thread_call_without_gvl(wait_until_channel_polling_thread_started_no_gil, NULL, + wait_until_channel_polling_thread_started_unblocking_func, NULL); + /* "3" == 3 mandatory args */ rb_scan_args(argc, argv, "3", &target, &channel_args, &credentials); @@ -436,6 +444,7 @@ static void grpc_rb_channel_try_register_connection_polling( } gpr_mu_lock(&global_connection_polling_mu); + GPR_ASSERT(channel_polling_thread_started || abort_channel_polling); conn_state = grpc_channel_check_connectivity_state(wrapper->wrapped, 0); if (conn_state != wrapper->current_connectivity_state) { wrapper->current_connectivity_state = conn_state; @@ -469,7 +478,7 @@ static void grpc_rb_channel_safe_destroy(grpc_rb_channel *wrapper) { } // Note this loop breaks out with a single call of -// "grpc_rb_event_unblocking_func". +// "run_poll_channels_loop_no_gil". // This assumes that a ruby call the unblocking func // indicates process shutdown. // In the worst case, this stops polling channel connectivity @@ -477,6 +486,14 @@ static void grpc_rb_channel_safe_destroy(grpc_rb_channel *wrapper) { static void *run_poll_channels_loop_no_gil(void *arg) { grpc_event event; (void)arg; + gpr_log(GPR_DEBUG, "GRPC_RUBY: run_poll_channels_loop_no_gil - begin"); + + gpr_mu_lock(&global_connection_polling_mu); + GPR_ASSERT(!channel_polling_thread_started); + channel_polling_thread_started = 1; + gpr_cv_broadcast(&global_connection_polling_cv); + gpr_mu_unlock(&global_connection_polling_mu); + for (;;) { event = grpc_completion_queue_next( channel_polling_cq, gpr_inf_future(GPR_CLOCK_REALTIME), NULL); @@ -493,10 +510,10 @@ static void *run_poll_channels_loop_no_gil(void *arg) { } // Notify the channel polling loop to cleanup and shutdown. -static void grpc_rb_event_unblocking_func(void *arg) { +static void run_poll_channels_loop_unblocking_func(void *arg) { (void)arg; gpr_mu_lock(&global_connection_polling_mu); - gpr_log(GPR_DEBUG, "GRPC_RUBY: grpc_rb_event_unblocking_func - begin aborting connection polling"); + gpr_log(GPR_DEBUG, "GRPC_RUBY: run_poll_channels_loop_unblocking_func - begin aborting connection polling"); abort_channel_polling = 1; grpc_completion_queue_shutdown(channel_polling_cq); gpr_mu_unlock(&global_connection_polling_mu); @@ -507,10 +524,33 @@ static VALUE run_poll_channels_loop(VALUE arg) { (void)arg; gpr_log(GPR_DEBUG, "GRPC_RUBY: run_poll_channels_loop - create connection polling thread"); rb_thread_call_without_gvl(run_poll_channels_loop_no_gil, NULL, - grpc_rb_event_unblocking_func, NULL); + run_poll_channels_loop_unblocking_func, NULL); + return Qnil; } +static void *wait_until_channel_polling_thread_started_no_gil(void *arg) { + (void)arg; + gpr_log(GPR_DEBUG, "GRPC_RUBY: wait for channel polling thread to start"); + gpr_mu_lock(&global_connection_polling_mu); + while (!channel_polling_thread_started && !abort_channel_polling) { + gpr_cv_wait(&global_connection_polling_cv, &global_connection_polling_mu, + gpr_inf_future(GPR_CLOCK_REALTIME)); + } + gpr_mu_unlock(&global_connection_polling_mu); + + return NULL; +} + +static void wait_until_channel_polling_thread_started_unblocking_func(void* arg) { + (void)arg; + gpr_mu_lock(&global_connection_polling_mu); + gpr_log(GPR_DEBUG, "GRPC_RUBY: wait_until_channel_polling_thread_started_unblocking_func - begin aborting connection polling"); + abort_channel_polling = 1; + gpr_cv_broadcast(&global_connection_polling_cv); + gpr_mu_unlock(&global_connection_polling_mu); +} + /* Temporary fix for * https://github.com/GoogleCloudPlatform/google-cloud-ruby/issues/899. * Transports in idle channels can get destroyed. Normally c-core re-connects, @@ -521,11 +561,26 @@ static VALUE run_poll_channels_loop(VALUE arg) { * calls - so that c-core can reconnect if needed, when there aren't any RPC's. * TODO(apolcyn) remove this when core handles new RPCs on dead connections. */ -static void start_poll_channels_loop() { - channel_polling_cq = grpc_completion_queue_create(NULL); +void grpc_rb_channel_polling_thread_start() { + VALUE background_thread = Qnil; + + GPR_ASSERT(!abort_channel_polling); + GPR_ASSERT(!channel_polling_thread_started); + GPR_ASSERT(channel_polling_cq == NULL); + gpr_mu_init(&global_connection_polling_mu); - abort_channel_polling = 0; - rb_thread_create(run_poll_channels_loop, NULL); + gpr_cv_init(&global_connection_polling_cv); + + channel_polling_cq = grpc_completion_queue_create(NULL); + background_thread = rb_thread_create(run_poll_channels_loop, NULL); + + if (!RTEST(background_thread)) { + gpr_log(GPR_DEBUG, "GRPC_RUBY: failed to spawn channel polling thread"); + gpr_mu_lock(&global_connection_polling_mu); + abort_channel_polling = 1; + gpr_cv_broadcast(&global_connection_polling_cv); + gpr_mu_unlock(&global_connection_polling_mu); + } } static void Init_grpc_propagate_masks() { @@ -597,7 +652,6 @@ void Init_grpc_channel() { id_insecure_channel = rb_intern("this_channel_is_insecure"); Init_grpc_propagate_masks(); Init_grpc_connectivity_states(); - start_poll_channels_loop(); } /* Gets the wrapped channel from the ruby wrapper */ diff --git a/src/ruby/ext/grpc/rb_channel.h b/src/ruby/ext/grpc/rb_channel.h index 77e1f6acbca9dac26fa3f5969f17e5cc86a171fb..fdceb79bca5181d9aabe50c7353f8f05db6f96fe 100644 --- a/src/ruby/ext/grpc/rb_channel.h +++ b/src/ruby/ext/grpc/rb_channel.h @@ -41,6 +41,8 @@ /* Initializes the Channel class. */ void Init_grpc_channel(); +void grpc_rb_channel_polling_thread_start(); + /* Gets the wrapped channel from the ruby wrapper */ grpc_channel* grpc_rb_get_wrapped_channel(VALUE v); diff --git a/src/ruby/ext/grpc/rb_channel_credentials.c b/src/ruby/ext/grpc/rb_channel_credentials.c index 5b7aa3417e6ce300a2bd561eec43d0266ab8b44e..d334c0914861c3812904f8ee44b9189d8750915c 100644 --- a/src/ruby/ext/grpc/rb_channel_credentials.c +++ b/src/ruby/ext/grpc/rb_channel_credentials.c @@ -156,6 +156,9 @@ static VALUE grpc_rb_channel_credentials_init(int argc, VALUE *argv, VALUE self) grpc_ssl_pem_key_cert_pair key_cert_pair; const char *pem_root_certs_cstr = NULL; MEMZERO(&key_cert_pair, grpc_ssl_pem_key_cert_pair, 1); + + grpc_ruby_once_init(); + /* "03" == no mandatory arg, 3 optional */ rb_scan_args(argc, argv, "03", &pem_root_certs, &pem_private_key, &pem_cert_chain); diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c index 6b2467ee4614decf0fee6f5ac28638230cc8df33..b23e82b0dba84ae50e2410d75247070472a37b52 100644 --- a/src/ruby/ext/grpc/rb_compression_options.c +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -100,8 +100,11 @@ static rb_data_type_t grpc_rb_compression_options_data_type = { Allocate the wrapped grpc compression options and initialize it here too. */ static VALUE grpc_rb_compression_options_alloc(VALUE cls) { - grpc_rb_compression_options *wrapper = - gpr_malloc(sizeof(grpc_rb_compression_options)); + grpc_rb_compression_options *wrapper = NULL; + + grpc_ruby_once_init(); + + wrapper = gpr_malloc(sizeof(grpc_rb_compression_options)); wrapper->wrapped = NULL; wrapper->wrapped = gpr_malloc(sizeof(grpc_compression_options)); grpc_compression_options_init(wrapper->wrapped); diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c index 17cd165a91da4abb29e95bd2f3ee588f3841d14e..584b5dbc63d1aa7daec5f3c159767524e6e5d6a7 100644 --- a/src/ruby/ext/grpc/rb_grpc.c +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -50,6 +50,8 @@ #include "rb_server.h" #include "rb_server_credentials.h" #include "rb_compression_options.h" +#include "rb_event_thread.h" +#include "rb_channel.h" static VALUE grpc_rb_cTimeVal = Qnil; @@ -291,17 +293,14 @@ VALUE sym_metadata = Qundef; static gpr_once g_once_init = GPR_ONCE_INIT; -static void grpc_ruby_once_init() { +static void grpc_ruby_once_init_internal() { grpc_init(); + grpc_rb_event_queue_thread_start(); + grpc_rb_channel_polling_thread_start(); atexit(grpc_rb_shutdown); } -void Init_grpc_c() { - if (!grpc_rb_load_core()) { - rb_raise(rb_eLoadError, "Couldn't find or load gRPC's dynamic C core"); - return; - } - +void grpc_ruby_once_init() { /* ruby_vm_at_exit doesn't seem to be working. It would crash once every * blue moon, and some users are getting it repeatedly. See the discussions * - https://github.com/grpc/grpc/pull/5337 @@ -312,7 +311,14 @@ void Init_grpc_c() { * then loaded again by another VM within the same process, we need to * schedule our initialization and destruction only once. */ - gpr_once_init(&g_once_init, grpc_ruby_once_init); + gpr_once_init(&g_once_init, grpc_ruby_once_init_internal); +} + +void Init_grpc_c() { + if (!grpc_rb_load_core()) { + rb_raise(rb_eLoadError, "Couldn't find or load gRPC's dynamic C core"); + return; + } grpc_rb_mGRPC = rb_define_module("GRPC"); grpc_rb_mGrpcCore = rb_define_module_under(grpc_rb_mGRPC, "Core"); diff --git a/src/ruby/ext/grpc/rb_grpc.h b/src/ruby/ext/grpc/rb_grpc.h index 6ea6cbd0b6c98d2655869260e3a01a5762cabb76..4bf11e75df246167353b2c019a4db2838798ff8b 100644 --- a/src/ruby/ext/grpc/rb_grpc.h +++ b/src/ruby/ext/grpc/rb_grpc.h @@ -82,4 +82,6 @@ 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); +void grpc_ruby_once_init(); + #endif /* GRPC_RB_H_ */ diff --git a/src/ruby/ext/grpc/rb_server.c b/src/ruby/ext/grpc/rb_server.c index 7b2f5774aa1170c234bf1fcdb55ef926ff8350bf..2286a99f249988583d6d833e07dbc235fb6ba977 100644 --- a/src/ruby/ext/grpc/rb_server.c +++ b/src/ruby/ext/grpc/rb_server.c @@ -131,11 +131,15 @@ static VALUE grpc_rb_server_alloc(VALUE cls) { Initializes server instances. */ static VALUE grpc_rb_server_init(VALUE self, VALUE channel_args) { - grpc_completion_queue *cq = grpc_completion_queue_create(NULL); + grpc_completion_queue *cq = NULL; grpc_rb_server *wrapper = NULL; grpc_server *srv = NULL; grpc_channel_args args; MEMZERO(&args, grpc_channel_args, 1); + + grpc_ruby_once_init(); + + cq = grpc_completion_queue_create(NULL); TypedData_Get_Struct(self, grpc_rb_server, &grpc_rb_server_data_type, wrapper); grpc_rb_hash_convert_to_channel_args(channel_args, &args); @@ -218,8 +222,6 @@ static VALUE grpc_rb_server_request_call(VALUE self) { return Qnil; } - - /* build the NewServerRpc struct result */ deadline = gpr_convert_clock_type(st.details.deadline, GPR_CLOCK_REALTIME); result = rb_struct_new( diff --git a/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh b/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh index 92d6975707389a3352ed677f319144c398504558..6688025260ff0db6e3bcc2378495992b96786788 100755 --- a/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh +++ b/tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh @@ -39,4 +39,6 @@ ruby src/ruby/end2end/channel_state_driver.rb || EXIT_CODE=1 ruby src/ruby/end2end/channel_closing_driver.rb || EXIT_CODE=1 ruby src/ruby/end2end/sig_int_during_channel_watch_driver.rb || EXIT_CODE=1 ruby src/ruby/end2end/killed_client_thread_driver.rb || EXIT_CODE=1 +ruby src/ruby/end2end/forking_client_driver.rb || EXIT_CODE=1 +ruby src/ruby/end2end/grpc_class_init_driver.rb || EXIT_CODE=1 exit $EXIT_CODE