xref: /aosp_15_r20/external/grpc-grpc/src/ruby/ext/grpc/rb_grpc.c (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1 /*
2  *
3  * Copyright 2015 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #include <ruby/ruby.h>
20 
21 #include "rb_grpc.h"
22 
23 #include <math.h>
24 #include <ruby/vm.h>
25 #include <stdbool.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28 
29 #include "rb_call.h"
30 #include "rb_call_credentials.h"
31 #include "rb_channel.h"
32 #include "rb_channel_credentials.h"
33 #include "rb_compression_options.h"
34 #include "rb_event_thread.h"
35 #include "rb_grpc_imports.generated.h"
36 #include "rb_loader.h"
37 #include "rb_server.h"
38 #include "rb_server_credentials.h"
39 #include "rb_xds_channel_credentials.h"
40 #include "rb_xds_server_credentials.h"
41 
42 #include <grpc/grpc.h>
43 #include <grpc/support/log.h>
44 #include <grpc/support/time.h>
45 
46 #ifdef GPR_LINUX
47 #include <sys/syscall.h>
48 #include <unistd.h>
49 #endif
50 
51 static VALUE grpc_rb_cTimeVal = Qnil;
52 
53 static rb_data_type_t grpc_rb_timespec_data_type = {
54     "gpr_timespec",
55     {GRPC_RB_GC_NOT_MARKED,
56      GRPC_RB_GC_DONT_FREE,
57      GRPC_RB_MEMSIZE_UNAVAILABLE,
58      {NULL, NULL}},
59     NULL,
60     NULL,
61 #ifdef RUBY_TYPED_FREE_IMMEDIATELY
62     RUBY_TYPED_FREE_IMMEDIATELY
63 #endif
64 };
65 
66 /* Alloc func that blocks allocation of a given object by raising an
67  * exception. */
grpc_rb_cannot_alloc(VALUE cls)68 VALUE grpc_rb_cannot_alloc(VALUE cls) {
69   rb_raise(rb_eTypeError,
70            "allocation of %s only allowed from the gRPC native layer",
71            rb_class2name(cls));
72   return Qnil;
73 }
74 
75 /* Init func that fails by raising an exception. */
grpc_rb_cannot_init(VALUE self)76 VALUE grpc_rb_cannot_init(VALUE self) {
77   rb_raise(rb_eTypeError,
78            "initialization of %s only allowed from the gRPC native layer",
79            rb_obj_classname(self));
80   return Qnil;
81 }
82 
83 /* Init/Clone func that fails by raising an exception. */
grpc_rb_cannot_init_copy(VALUE copy,VALUE self)84 VALUE grpc_rb_cannot_init_copy(VALUE copy, VALUE self) {
85   (void)self;
86   rb_raise(rb_eTypeError, "Copy initialization of %s is not supported",
87            rb_obj_classname(copy));
88   return Qnil;
89 }
90 
91 /* id_tv_{,u}sec are accessor methods on Ruby Time instances. */
92 static ID id_tv_sec;
93 static ID id_tv_nsec;
94 
95 /**
96  * grpc_rb_time_timeval creates a timeval from a ruby time object.
97  *
98  * This func is copied from ruby source, MRI/source/time.c, which is published
99  * under the same license as the ruby.h, on which the entire extensions is
100  * based.
101  */
grpc_rb_time_timeval(VALUE time,int interval)102 gpr_timespec grpc_rb_time_timeval(VALUE time, int interval) {
103   gpr_timespec t;
104   gpr_timespec* time_const;
105   const char* tstr = interval ? "time interval" : "time";
106   const char* want = " want <secs from epoch>|<Time>|<GRPC::TimeConst.*>";
107 
108   t.clock_type = GPR_CLOCK_REALTIME;
109   switch (TYPE(time)) {
110     case T_DATA:
111       if (CLASS_OF(time) == grpc_rb_cTimeVal) {
112         TypedData_Get_Struct(time, gpr_timespec, &grpc_rb_timespec_data_type,
113                              time_const);
114         t = *time_const;
115       } else if (CLASS_OF(time) == rb_cTime) {
116         t.tv_sec = NUM2INT(rb_funcall(time, id_tv_sec, 0));
117         t.tv_nsec = NUM2INT(rb_funcall(time, id_tv_nsec, 0));
118       } else {
119         rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr,
120                  rb_obj_classname(time), want);
121       }
122       break;
123 
124     case T_FIXNUM:
125       t.tv_sec = FIX2LONG(time);
126       if (interval && t.tv_sec < 0)
127         rb_raise(rb_eArgError, "%s must be positive", tstr);
128       t.tv_nsec = 0;
129       break;
130 
131     case T_FLOAT:
132       if (interval && RFLOAT_VALUE(time) < 0.0)
133         rb_raise(rb_eArgError, "%s must be positive", tstr);
134       else {
135         double f, d;
136 
137         d = modf(RFLOAT_VALUE(time), &f);
138         if (d < 0) {
139           d += 1;
140           f -= 1;
141         }
142         t.tv_sec = (int64_t)f;
143         if (f != t.tv_sec) {
144           rb_raise(rb_eRangeError, "%f out of Time range", RFLOAT_VALUE(time));
145         }
146         t.tv_nsec = (int)(d * 1e9 + 0.5);
147       }
148       break;
149 
150     case T_BIGNUM:
151       t.tv_sec = NUM2LONG(time);
152       if (interval && t.tv_sec < 0)
153         rb_raise(rb_eArgError, "%s must be positive", tstr);
154       t.tv_nsec = 0;
155       break;
156 
157     default:
158       rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr,
159                rb_obj_classname(time), want);
160       break;
161   }
162   return t;
163 }
164 
165 /* id_at is the constructor method of the ruby standard Time class. */
166 static ID id_at;
167 
168 /* id_inspect is the inspect method found on various ruby objects. */
169 static ID id_inspect;
170 
171 /* id_to_s is the to_s method found on various ruby objects. */
172 static ID id_to_s;
173 
174 /* Converts a wrapped time constant to a standard time. */
grpc_rb_time_val_to_time(VALUE self)175 static VALUE grpc_rb_time_val_to_time(VALUE self) {
176   gpr_timespec* time_const = NULL;
177   gpr_timespec real_time;
178   TypedData_Get_Struct(self, gpr_timespec, &grpc_rb_timespec_data_type,
179                        time_const);
180   real_time = gpr_convert_clock_type(*time_const, GPR_CLOCK_REALTIME);
181   return rb_funcall(rb_cTime, id_at, 2, INT2NUM(real_time.tv_sec),
182                     INT2NUM(real_time.tv_nsec / 1000));
183 }
184 
185 /* Invokes inspect on the ctime version of the time val. */
grpc_rb_time_val_inspect(VALUE self)186 static VALUE grpc_rb_time_val_inspect(VALUE self) {
187   return rb_funcall(grpc_rb_time_val_to_time(self), id_inspect, 0);
188 }
189 
190 /* Invokes to_s on the ctime version of the time val. */
grpc_rb_time_val_to_s(VALUE self)191 static VALUE grpc_rb_time_val_to_s(VALUE self) {
192   return rb_funcall(grpc_rb_time_val_to_time(self), id_to_s, 0);
193 }
194 
195 static gpr_timespec zero_realtime;
196 static gpr_timespec inf_future_realtime;
197 static gpr_timespec inf_past_realtime;
198 
199 /* Adds a module with constants that map to gpr's static timeval structs. */
Init_grpc_time_consts()200 static void Init_grpc_time_consts() {
201   VALUE grpc_rb_mTimeConsts =
202       rb_define_module_under(grpc_rb_mGrpcCore, "TimeConsts");
203   grpc_rb_cTimeVal =
204       rb_define_class_under(grpc_rb_mGrpcCore, "TimeSpec", rb_cObject);
205   rb_undef_alloc_func(grpc_rb_cTimeVal);
206   zero_realtime = gpr_time_0(GPR_CLOCK_REALTIME);
207   inf_future_realtime = gpr_inf_future(GPR_CLOCK_REALTIME);
208   inf_past_realtime = gpr_inf_past(GPR_CLOCK_REALTIME);
209   rb_define_const(
210       grpc_rb_mTimeConsts, "ZERO",
211       TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
212                             (void*)&zero_realtime));
213   rb_define_const(
214       grpc_rb_mTimeConsts, "INFINITE_FUTURE",
215       TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
216                             (void*)&inf_future_realtime));
217   rb_define_const(
218       grpc_rb_mTimeConsts, "INFINITE_PAST",
219       TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
220                             (void*)&inf_past_realtime));
221   rb_define_method(grpc_rb_cTimeVal, "to_time", grpc_rb_time_val_to_time, 0);
222   rb_define_method(grpc_rb_cTimeVal, "inspect", grpc_rb_time_val_inspect, 0);
223   rb_define_method(grpc_rb_cTimeVal, "to_s", grpc_rb_time_val_to_s, 0);
224   id_at = rb_intern("at");
225   id_inspect = rb_intern("inspect");
226   id_to_s = rb_intern("to_s");
227   id_tv_sec = rb_intern("tv_sec");
228   id_tv_nsec = rb_intern("tv_nsec");
229 }
230 
231 static bool g_enable_fork_support;
232 
233 #ifdef GPR_LINUX
sys_gettid()234 static long sys_gettid() { return syscall(__NR_gettid); }
can_enable_fork_support()235 static bool can_enable_fork_support() { return true; }
236 #else
sys_gettid()237 static long sys_gettid() { return 0; }
can_enable_fork_support()238 static bool can_enable_fork_support() { return false; }
239 #endif
240 
241 #if GPR_WINDOWS
grpc_ruby_basic_init(void)242 static void grpc_ruby_basic_init(void) {}
grpc_ruby_initial_pid(void)243 static bool grpc_ruby_initial_pid(void) { return true; }
grpc_ruby_initial_thread(void)244 static bool grpc_ruby_initial_thread(void) { return true; }
grpc_ruby_reset_init_state(void)245 static void grpc_ruby_reset_init_state(void) {}
246 #else
247 static pid_t g_init_pid;
248 static long g_init_tid;
249 
grpc_ruby_initial_pid(void)250 static bool grpc_ruby_initial_pid(void) {
251   GPR_ASSERT(g_init_pid != 0);
252   return g_init_pid == getpid();
253 }
254 
grpc_ruby_initial_thread(void)255 static bool grpc_ruby_initial_thread(void) {
256   GPR_ASSERT(g_init_tid != 0);
257   return sys_gettid() == g_init_tid;
258 }
259 
grpc_ruby_reset_init_state(void)260 static void grpc_ruby_reset_init_state(void) {
261   g_init_pid = getpid();
262   g_init_tid = sys_gettid();
263 }
264 
grpc_ruby_basic_init(void)265 static void grpc_ruby_basic_init(void) {
266   GPR_ASSERT(g_init_pid == 0);
267   GPR_ASSERT(g_init_tid == 0);
268   grpc_ruby_reset_init_state();
269   // TODO(apolcyn): ideally, we should share logic with C-core
270   // for determining whether or not fork support is enabled, rather
271   // than parsing the environment variable ourselves.
272   const char* res = getenv("GRPC_ENABLE_FORK_SUPPORT");
273   if (res != NULL && strcmp(res, "1") == 0) {
274     g_enable_fork_support = can_enable_fork_support();
275   }
276 }
277 #endif
278 
279 /* Initialize the GRPC module structs */
280 
281 /* grpc_rb_sNewServerRpc is the struct that holds new server rpc details. */
282 VALUE grpc_rb_sNewServerRpc = Qnil;
283 /* grpc_rb_sStatus is the struct that holds status details. */
284 VALUE grpc_rb_sStatus = Qnil;
285 
286 /* Initialize the GRPC module. */
287 VALUE grpc_rb_mGRPC = Qnil;
288 VALUE grpc_rb_mGrpcCore = Qnil;
289 
290 /* cached Symbols for members in Status struct */
291 VALUE sym_code = Qundef;
292 VALUE sym_details = Qundef;
293 VALUE sym_metadata = Qundef;
294 
295 static gpr_once g_once_init = GPR_ONCE_INIT;
296 static int64_t g_grpc_rb_prefork_pending;          // synchronized by the GIL
297 static int64_t g_grpc_rb_num_fork_unsafe_threads;  // synchronized by the GIL
298 
grpc_ruby_fork_guard()299 void grpc_ruby_fork_guard() {
300   // Check if we're using gRPC between prefork and postfork
301   gpr_once_init(&g_once_init, grpc_ruby_basic_init);
302   if (g_grpc_rb_prefork_pending) {
303     rb_raise(rb_eRuntimeError,
304              "grpc cannot be used between calls to GRPC.prefork and "
305              "GRPC.postfork_child or GRPC.postfork_parent");
306   }
307   if (!grpc_ruby_initial_pid()) {
308     if (g_enable_fork_support) {
309       // Only way we can get here is by enabling for support and forking but not
310       // calling prefork
311       rb_raise(rb_eRuntimeError,
312                "grpc is in a broken state: GRPC.prefork must be called before "
313                "calling fork from a process using grpc");
314     } else {
315       rb_raise(rb_eRuntimeError,
316                "grpc cannot be used before and after forking unless the "
317                "GRPC_ENABLE_FORK_SUPPORT env var is set to \"1\" and the "
318                "platform supports it (linux only)");
319     }
320   }
321 }
322 
323 static VALUE g_bg_thread_init_rb_mu = Qundef;
324 static bool g_bg_thread_init_done;
325 
grpc_ruby_init_threads()326 static void grpc_ruby_init_threads() {
327   // Avoid calling into ruby library (when creating threads here)
328   // in gpr_once_init. In general, it appears to be unsafe to call
329   // into the ruby library while holding a non-ruby mutex, because a gil yield
330   // could end up trying to lock onto that same mutex and deadlocking.
331   gpr_log(GPR_INFO,
332           "GRPC_RUBY: grpc_ruby_init_threads g_bg_thread_init_done=%d",
333           g_bg_thread_init_done);
334   rb_mutex_lock(g_bg_thread_init_rb_mu);
335   if (!g_bg_thread_init_done) {
336     grpc_rb_event_queue_thread_start();
337     grpc_rb_channel_polling_thread_start();
338     g_bg_thread_init_done = true;
339   }
340   rb_mutex_unlock(g_bg_thread_init_rb_mu);
341 }
342 
343 static int64_t g_grpc_ruby_init_count;
344 
grpc_ruby_init()345 void grpc_ruby_init() {
346   gpr_once_init(&g_once_init, grpc_ruby_basic_init);
347   grpc_ruby_fork_guard();
348   grpc_init();
349   grpc_ruby_init_threads();
350   // (only gpr_log after logging has been initialized)
351   gpr_log(GPR_DEBUG,
352           "GRPC_RUBY: grpc_ruby_init - g_enable_fork_support=%d prev "
353           "g_grpc_ruby_init_count:%" PRId64,
354           g_enable_fork_support, g_grpc_ruby_init_count++);
355 }
356 
357 // fork APIs, useable on linux with env var: GRPC_ENABLE_FORK_SUPPORT=1
358 //
359 // Must be called once and only once before forking. Must be called on the
360 // same threads that gRPC was (lazy-)initialized on. One must not call
361 // into the gRPC library during or after prefork has been called, until
362 // the corresponding postfork_{parent,child} APIs have been called.
grpc_rb_prefork(VALUE self)363 static VALUE grpc_rb_prefork(VALUE self) {
364   // This might be the first time we've called into the grpc library, so make
365   // sure basic one-time initialization is taken care of. Note that if this is
366   // the case, then grpc_init() will start up c-core threads; that's OK since
367   // they will be shut down in C-core's pthread_atfork handler.
368   gpr_once_init(&g_once_init, grpc_ruby_basic_init);
369   grpc_init();
370   if (!g_enable_fork_support) {
371     rb_raise(rb_eRuntimeError,
372              "forking with gRPC/Ruby is only supported on linux with env var: "
373              "GRPC_ENABLE_FORK_SUPPORT=1");
374   }
375   if (g_grpc_rb_prefork_pending) {
376     rb_raise(rb_eRuntimeError,
377              "GRPC.prefork already called without a matching "
378              "GRPC.postfork_{parent,child}");
379   }
380   if (!grpc_ruby_initial_thread()) {
381     rb_raise(rb_eRuntimeError,
382              "GRPC.prefork and fork need to be called from the same thread "
383              "that GRPC was initialized on (GRPC lazy-initializes when when "
384              "the first GRPC object is created");
385   }
386   if (g_grpc_rb_num_fork_unsafe_threads > 0) {
387     rb_raise(
388         rb_eRuntimeError,
389         "Detected at least %ld threads actively using grpc, so it is not safe "
390         "call GRPC.prefork or fork. Note that grpc-ruby servers and "
391         "bidirectional "
392         "streams manage background threads and are not fork safe.",
393         g_grpc_rb_num_fork_unsafe_threads);
394   }
395   g_grpc_rb_prefork_pending = true;
396   rb_mutex_lock(g_bg_thread_init_rb_mu);
397   if (g_bg_thread_init_done) {
398     grpc_rb_channel_polling_thread_stop();
399     grpc_rb_event_queue_thread_stop();
400     // all ruby-level background threads joined at this point
401     g_bg_thread_init_done = false;
402   }
403   rb_mutex_unlock(g_bg_thread_init_rb_mu);
404   return Qnil;
405 }
406 
grpc_rb_postfork_child(VALUE self)407 static VALUE grpc_rb_postfork_child(VALUE self) {
408   if (!g_grpc_rb_prefork_pending) {
409     rb_raise(rb_eRuntimeError,
410              "GRPC::postfork_child can only be called once following a "
411              "GRPC::prefork");
412   }
413   if (grpc_ruby_initial_pid()) {
414     rb_raise(rb_eRuntimeError,
415              "GRPC.postfork_child must be called only from the child process "
416              "after a fork");
417   }
418   grpc_ruby_reset_init_state();
419   grpc_ruby_init_threads();
420   g_grpc_rb_prefork_pending = false;
421   return Qnil;
422 }
423 
grpc_rb_postfork_parent(VALUE self)424 static VALUE grpc_rb_postfork_parent(VALUE self) {
425   // TODO(apolcyn): check calling thread vs. thread that gRPC was initialized on
426   if (!g_grpc_rb_prefork_pending) {
427     rb_raise(rb_eRuntimeError,
428              "GRPC::postfork_parent can only be called once following a "
429              "GRPC::prefork");
430   }
431   if (!grpc_ruby_initial_pid()) {
432     rb_raise(rb_eRuntimeError,
433              "GRPC.postfork_parent must be called only from the parent process "
434              "after a fork");
435   }
436   if (!grpc_ruby_initial_thread()) {
437     rb_raise(rb_eRuntimeError,
438              "GRPC.postfork_parent needs to be called from the same thread "
439              "that GRPC.prefork (and fork) was called from");
440   }
441   grpc_ruby_init_threads();
442   g_grpc_rb_prefork_pending = false;
443   return Qnil;
444 }
445 
446 // APIs to mark fork-unsafe sections from C-extension code
grpc_rb_fork_unsafe_begin()447 void grpc_rb_fork_unsafe_begin() { g_grpc_rb_num_fork_unsafe_threads++; }
448 
grpc_rb_fork_unsafe_end()449 void grpc_rb_fork_unsafe_end() { g_grpc_rb_num_fork_unsafe_threads--; }
450 
451 // APIs to mark fork-unsafe sections from ruby code
grpc_rb_fork_unsafe_begin_api()452 static VALUE grpc_rb_fork_unsafe_begin_api() { grpc_rb_fork_unsafe_begin(); }
453 
grpc_rb_fork_unsafe_end_api()454 static VALUE grpc_rb_fork_unsafe_end_api() { grpc_rb_fork_unsafe_end(); }
455 
456 // One-time initialization
Init_grpc_c()457 void Init_grpc_c() {
458   if (!grpc_rb_load_core()) {
459     rb_raise(rb_eLoadError, "Couldn't find or load gRPC's dynamic C core");
460     return;
461   }
462 
463   rb_global_variable(&g_bg_thread_init_rb_mu);
464   g_bg_thread_init_rb_mu = rb_mutex_new();
465 
466   grpc_rb_mGRPC = rb_define_module("GRPC");
467   grpc_rb_mGrpcCore = rb_define_module_under(grpc_rb_mGRPC, "Core");
468   grpc_rb_sNewServerRpc = rb_struct_define(
469       "NewServerRpc", "method", "host", "deadline", "metadata", "call", NULL);
470   rb_global_variable(&grpc_rb_sStatus);
471   grpc_rb_sStatus = rb_const_get(rb_cStruct, rb_intern("Status"));
472   sym_code = ID2SYM(rb_intern("code"));
473   sym_details = ID2SYM(rb_intern("details"));
474   sym_metadata = ID2SYM(rb_intern("metadata"));
475   // init C-defined classes
476   Init_grpc_channel();
477   Init_grpc_call();
478   Init_grpc_call_credentials();
479   Init_grpc_channel_credentials();
480   Init_grpc_xds_channel_credentials();
481   Init_grpc_server();
482   Init_grpc_server_credentials();
483   Init_grpc_xds_server_credentials();
484   Init_grpc_time_consts();
485   Init_grpc_compression_options();
486   // define fork APIs
487   rb_define_module_function(grpc_rb_mGRPC, "prefork", grpc_rb_prefork, 0);
488   rb_define_module_function(grpc_rb_mGRPC, "postfork_child",
489                             grpc_rb_postfork_child, 0);
490   rb_define_module_function(grpc_rb_mGRPC, "postfork_parent",
491                             grpc_rb_postfork_parent, 0);
492   rb_define_module_function(grpc_rb_mGrpcCore, "fork_unsafe_begin",
493                             grpc_rb_fork_unsafe_begin_api, 0);
494   rb_define_module_function(grpc_rb_mGrpcCore, "fork_unsafe_end",
495                             grpc_rb_fork_unsafe_end_api, 0);
496 }
497