Merge remote-tracking branch 'upstream/v1.3.x' into upmerge_v1.3.x
diff --git a/src/ruby/end2end/forking_client_client.rb b/src/ruby/end2end/forking_client_client.rb
new file mode 100755
index 0000000..c358d79
--- /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 0000000..2c67b33
--- /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 0000000..ee79292
--- /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 0000000..764d029
--- /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 d654550..7a5a745 100644
--- a/src/ruby/ext/grpc/rb_call_credentials.c
+++ b/src/ruby/ext/grpc/rb_call_credentials.c
@@ -223,6 +223,8 @@
   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);
 
@@ -283,8 +285,6 @@
                    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 ecfffd1..a802183 100644
--- a/src/ruby/ext/grpc/rb_channel.c
+++ b/src/ruby/ext/grpc/rb_channel.c
@@ -89,10 +89,14 @@
 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,11 @@
   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);
 
@@ -440,6 +449,7 @@
   }
   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;
@@ -473,7 +483,7 @@
 }
 
 // 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
@@ -481,6 +491,14 @@
 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);
@@ -500,7 +518,7 @@
 }
 
 // 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,
@@ -518,10 +536,37 @@
       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,
@@ -532,11 +577,26 @@
  * 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_for_next(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_for_next(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() {
@@ -608,7 +668,6 @@
   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 77e1f6a..fdceb79 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 f30426b..db713ed 100644
--- a/src/ruby/ext/grpc/rb_channel_credentials.c
+++ b/src/ruby/ext/grpc/rb_channel_credentials.c
@@ -161,6 +161,9 @@
   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 7de3c3c..45c963d 100644
--- a/src/ruby/ext/grpc/rb_compression_options.c
+++ b/src/ruby/ext/grpc/rb_compression_options.c
@@ -100,8 +100,11 @@
    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 d45a5ea..5be8861 100644
--- a/src/ruby/ext/grpc/rb_grpc.c
+++ b/src/ruby/ext/grpc/rb_grpc.c
@@ -47,6 +47,7 @@
 #include "rb_channel.h"
 #include "rb_channel_credentials.h"
 #include "rb_compression_options.h"
+#include "rb_event_thread.h"
 #include "rb_loader.h"
 #include "rb_server.h"
 #include "rb_server_credentials.h"
@@ -289,17 +290,14 @@
 
 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
@@ -310,7 +308,14 @@
    * 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 2ee1faa..8538a74 100644
--- a/src/ruby/ext/grpc/rb_grpc.h
+++ b/src/ruby/ext/grpc/rb_grpc.h
@@ -82,4 +82,6 @@
 /* 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 ef57d5b..d7408f6 100644
--- a/src/ruby/ext/grpc/rb_server.c
+++ b/src/ruby/ext/grpc/rb_server.c
@@ -132,11 +132,15 @@
 
   Initializes server instances. */
 static VALUE grpc_rb_server_init(VALUE self, VALUE channel_args) {
-  grpc_completion_queue *cq = grpc_completion_queue_create_for_pluck(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_for_pluck(NULL);
   TypedData_Get_Struct(self, grpc_rb_server, &grpc_rb_server_data_type,
                        wrapper);
   grpc_rb_hash_convert_to_channel_args(channel_args, &args);