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 <grpc/grpc.h>
24 #include <grpc/support/log.h>
25 #include <grpc/support/time.h>
26 #include <math.h>
27 #include <ruby/vm.h>
28 #include <stdbool.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31
32 #include "rb_call.h"
33 #include "rb_call_credentials.h"
34 #include "rb_channel.h"
35 #include "rb_channel_credentials.h"
36 #include "rb_compression_options.h"
37 #include "rb_event_thread.h"
38 #include "rb_grpc_imports.generated.h"
39 #include "rb_loader.h"
40 #include "rb_server.h"
41 #include "rb_server_credentials.h"
42 #include "rb_xds_channel_credentials.h"
43 #include "rb_xds_server_credentials.h"
44
45 #ifdef GPR_LINUX
46 #include <sys/syscall.h>
47 #include <unistd.h>
48 #endif
49
50 static VALUE grpc_rb_cTimeVal = Qnil;
51
52 static rb_data_type_t grpc_rb_timespec_data_type = {
53 "gpr_timespec",
54 {GRPC_RB_GC_NOT_MARKED,
55 GRPC_RB_GC_DONT_FREE,
56 GRPC_RB_MEMSIZE_UNAVAILABLE,
57 {NULL, NULL}},
58 NULL,
59 NULL,
60 #ifdef RUBY_TYPED_FREE_IMMEDIATELY
61 RUBY_TYPED_FREE_IMMEDIATELY
62 #endif
63 };
64
65 /* Alloc func that blocks allocation of a given object by raising an
66 * exception. */
grpc_rb_cannot_alloc(VALUE cls)67 VALUE grpc_rb_cannot_alloc(VALUE cls) {
68 rb_raise(rb_eTypeError,
69 "allocation of %s only allowed from the gRPC native layer",
70 rb_class2name(cls));
71 return Qnil;
72 }
73
74 /* Init func that fails by raising an exception. */
grpc_rb_cannot_init(VALUE self)75 VALUE grpc_rb_cannot_init(VALUE self) {
76 rb_raise(rb_eTypeError,
77 "initialization of %s only allowed from the gRPC native layer",
78 rb_obj_classname(self));
79 return Qnil;
80 }
81
82 /* Init/Clone func that fails by raising an exception. */
grpc_rb_cannot_init_copy(VALUE copy,VALUE self)83 VALUE grpc_rb_cannot_init_copy(VALUE copy, VALUE self) {
84 (void)self;
85 rb_raise(rb_eTypeError, "Copy initialization of %s is not supported",
86 rb_obj_classname(copy));
87 return Qnil;
88 }
89
90 /* id_tv_{,u}sec are accessor methods on Ruby Time instances. */
91 static ID id_tv_sec;
92 static ID id_tv_nsec;
93
94 /**
95 * grpc_rb_time_timeval creates a timeval from a ruby time object.
96 *
97 * This func is copied from ruby source, MRI/source/time.c, which is published
98 * under the same license as the ruby.h, on which the entire extensions is
99 * based.
100 */
grpc_rb_time_timeval(VALUE time,int interval)101 gpr_timespec grpc_rb_time_timeval(VALUE time, int interval) {
102 gpr_timespec t;
103 gpr_timespec* time_const;
104 const char* tstr = interval ? "time interval" : "time";
105 const char* want = " want <secs from epoch>|<Time>|<GRPC::TimeConst.*>";
106
107 t.clock_type = GPR_CLOCK_REALTIME;
108 switch (TYPE(time)) {
109 case T_DATA:
110 if (CLASS_OF(time) == grpc_rb_cTimeVal) {
111 TypedData_Get_Struct(time, gpr_timespec, &grpc_rb_timespec_data_type,
112 time_const);
113 t = *time_const;
114 } else if (CLASS_OF(time) == rb_cTime) {
115 t.tv_sec = NUM2INT(rb_funcall(time, id_tv_sec, 0));
116 t.tv_nsec = NUM2INT(rb_funcall(time, id_tv_nsec, 0));
117 } else {
118 rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr,
119 rb_obj_classname(time), want);
120 }
121 break;
122
123 case T_FIXNUM:
124 t.tv_sec = FIX2LONG(time);
125 if (interval && t.tv_sec < 0)
126 rb_raise(rb_eArgError, "%s must be positive", tstr);
127 t.tv_nsec = 0;
128 break;
129
130 case T_FLOAT:
131 if (interval && RFLOAT_VALUE(time) < 0.0)
132 rb_raise(rb_eArgError, "%s must be positive", tstr);
133 else {
134 double f, d;
135
136 d = modf(RFLOAT_VALUE(time), &f);
137 if (d < 0) {
138 d += 1;
139 f -= 1;
140 }
141 t.tv_sec = (int64_t)f;
142 if (f != t.tv_sec) {
143 rb_raise(rb_eRangeError, "%f out of Time range", RFLOAT_VALUE(time));
144 }
145 t.tv_nsec = (int)(d * 1e9 + 0.5);
146 }
147 break;
148
149 case T_BIGNUM:
150 t.tv_sec = NUM2LONG(time);
151 if (interval && t.tv_sec < 0)
152 rb_raise(rb_eArgError, "%s must be positive", tstr);
153 t.tv_nsec = 0;
154 break;
155
156 default:
157 rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr,
158 rb_obj_classname(time), want);
159 break;
160 }
161 return t;
162 }
163
164 /* id_at is the constructor method of the ruby standard Time class. */
165 static ID id_at;
166
167 /* id_inspect is the inspect method found on various ruby objects. */
168 static ID id_inspect;
169
170 /* id_to_s is the to_s method found on various ruby objects. */
171 static ID id_to_s;
172
173 /* Converts a wrapped time constant to a standard time. */
grpc_rb_time_val_to_time(VALUE self)174 static VALUE grpc_rb_time_val_to_time(VALUE self) {
175 gpr_timespec* time_const = NULL;
176 gpr_timespec real_time;
177 TypedData_Get_Struct(self, gpr_timespec, &grpc_rb_timespec_data_type,
178 time_const);
179 real_time = gpr_convert_clock_type(*time_const, GPR_CLOCK_REALTIME);
180 return rb_funcall(rb_cTime, id_at, 2, INT2NUM(real_time.tv_sec),
181 INT2NUM(real_time.tv_nsec / 1000));
182 }
183
184 /* Invokes inspect on the ctime version of the time val. */
grpc_rb_time_val_inspect(VALUE self)185 static VALUE grpc_rb_time_val_inspect(VALUE self) {
186 return rb_funcall(grpc_rb_time_val_to_time(self), id_inspect, 0);
187 }
188
189 /* Invokes to_s on the ctime version of the time val. */
grpc_rb_time_val_to_s(VALUE self)190 static VALUE grpc_rb_time_val_to_s(VALUE self) {
191 return rb_funcall(grpc_rb_time_val_to_time(self), id_to_s, 0);
192 }
193
194 static gpr_timespec zero_realtime;
195 static gpr_timespec inf_future_realtime;
196 static gpr_timespec inf_past_realtime;
197
198 /* Adds a module with constants that map to gpr's static timeval structs. */
Init_grpc_time_consts()199 static void Init_grpc_time_consts() {
200 VALUE grpc_rb_mTimeConsts =
201 rb_define_module_under(grpc_rb_mGrpcCore, "TimeConsts");
202 grpc_rb_cTimeVal =
203 rb_define_class_under(grpc_rb_mGrpcCore, "TimeSpec", rb_cObject);
204 rb_undef_alloc_func(grpc_rb_cTimeVal);
205 zero_realtime = gpr_time_0(GPR_CLOCK_REALTIME);
206 inf_future_realtime = gpr_inf_future(GPR_CLOCK_REALTIME);
207 inf_past_realtime = gpr_inf_past(GPR_CLOCK_REALTIME);
208 rb_define_const(
209 grpc_rb_mTimeConsts, "ZERO",
210 TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
211 (void*)&zero_realtime));
212 rb_define_const(
213 grpc_rb_mTimeConsts, "INFINITE_FUTURE",
214 TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
215 (void*)&inf_future_realtime));
216 rb_define_const(
217 grpc_rb_mTimeConsts, "INFINITE_PAST",
218 TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type,
219 (void*)&inf_past_realtime));
220 rb_define_method(grpc_rb_cTimeVal, "to_time", grpc_rb_time_val_to_time, 0);
221 rb_define_method(grpc_rb_cTimeVal, "inspect", grpc_rb_time_val_inspect, 0);
222 rb_define_method(grpc_rb_cTimeVal, "to_s", grpc_rb_time_val_to_s, 0);
223 id_at = rb_intern("at");
224 id_inspect = rb_intern("inspect");
225 id_to_s = rb_intern("to_s");
226 id_tv_sec = rb_intern("tv_sec");
227 id_tv_nsec = rb_intern("tv_nsec");
228 }
229
230 static bool g_enable_fork_support;
231
232 #ifdef GPR_LINUX
sys_gettid()233 static long sys_gettid() { return syscall(__NR_gettid); }
can_enable_fork_support()234 static bool can_enable_fork_support() { return true; }
235 #else
sys_gettid()236 static long sys_gettid() { return 0; }
can_enable_fork_support()237 static bool can_enable_fork_support() { return false; }
238 #endif
239
240 #if GPR_WINDOWS
grpc_ruby_basic_init(void)241 static void grpc_ruby_basic_init(void) {}
grpc_ruby_initial_pid(void)242 static bool grpc_ruby_initial_pid(void) { return true; }
grpc_ruby_initial_thread(void)243 static bool grpc_ruby_initial_thread(void) { return true; }
grpc_ruby_reset_init_state(void)244 static void grpc_ruby_reset_init_state(void) {}
245 #else
246 static pid_t g_init_pid;
247 static long g_init_tid;
248
grpc_ruby_initial_pid(void)249 static bool grpc_ruby_initial_pid(void) {
250 GRPC_RUBY_ASSERT(g_init_pid != 0);
251 return g_init_pid == getpid();
252 }
253
grpc_ruby_initial_thread(void)254 static bool grpc_ruby_initial_thread(void) {
255 GRPC_RUBY_ASSERT(g_init_tid != 0);
256 return sys_gettid() == g_init_tid;
257 }
258
grpc_ruby_reset_init_state(void)259 static void grpc_ruby_reset_init_state(void) {
260 g_init_pid = getpid();
261 g_init_tid = sys_gettid();
262 }
263
grpc_ruby_basic_init(void)264 static void grpc_ruby_basic_init(void) {
265 GRPC_RUBY_ASSERT(g_init_pid == 0);
266 GRPC_RUBY_ASSERT(g_init_tid == 0);
267 grpc_ruby_reset_init_state();
268 // TODO(apolcyn): ideally, we should share logic with C-core
269 // for determining whether or not fork support is enabled, rather
270 // than parsing the environment variable ourselves.
271 const char* res = getenv("GRPC_ENABLE_FORK_SUPPORT");
272 if (res != NULL && strcmp(res, "1") == 0) {
273 g_enable_fork_support = can_enable_fork_support();
274 }
275 }
276 #endif
277
278 /* Initialize the GRPC module structs */
279
280 /* grpc_rb_sNewServerRpc is the struct that holds new server rpc details. */
281 VALUE grpc_rb_sNewServerRpc = Qnil;
282 /* grpc_rb_sStatus is the struct that holds status details. */
283 VALUE grpc_rb_sStatus = Qnil;
284
285 /* Initialize the GRPC module. */
286 VALUE grpc_rb_mGRPC = Qnil;
287 VALUE grpc_rb_mGrpcCore = Qnil;
288
289 /* cached Symbols for members in Status struct */
290 VALUE sym_code = Qundef;
291 VALUE sym_details = Qundef;
292 VALUE sym_metadata = Qundef;
293
294 static gpr_once g_once_init = GPR_ONCE_INIT;
295 static int64_t g_grpc_rb_prefork_pending; // synchronized by the GIL
296 static int64_t g_grpc_rb_num_fork_unsafe_threads; // synchronized by the GIL
297
grpc_ruby_fork_guard()298 void grpc_ruby_fork_guard() {
299 // Check if we're using gRPC between prefork and postfork
300 gpr_once_init(&g_once_init, grpc_ruby_basic_init);
301 if (g_grpc_rb_prefork_pending) {
302 rb_raise(rb_eRuntimeError,
303 "grpc cannot be used between calls to GRPC.prefork and "
304 "GRPC.postfork_child or GRPC.postfork_parent");
305 }
306 if (!grpc_ruby_initial_pid()) {
307 if (g_enable_fork_support) {
308 // Only way we can get here is by enabling for support and forking but not
309 // calling prefork
310 rb_raise(rb_eRuntimeError,
311 "grpc is in a broken state: GRPC.prefork must be called before "
312 "calling fork from a process using grpc");
313 } else {
314 rb_raise(rb_eRuntimeError,
315 "grpc cannot be used before and after forking unless the "
316 "GRPC_ENABLE_FORK_SUPPORT env var is set to \"1\" and the "
317 "platform supports it (linux only)");
318 }
319 }
320 }
321
322 static VALUE g_bg_thread_init_rb_mu = Qundef;
323 static bool g_bg_thread_init_done;
324
grpc_ruby_init_threads()325 static void grpc_ruby_init_threads() {
326 // Avoid calling into ruby library (when creating threads here)
327 // in gpr_once_init. In general, it appears to be unsafe to call
328 // into the ruby library while holding a non-ruby mutex, because a gil yield
329 // could end up trying to lock onto that same mutex and deadlocking.
330 grpc_absl_log_int(GPR_DEBUG,
331 "GRPC_RUBY: grpc_ruby_init_threads g_bg_thread_init_done=",
332 g_bg_thread_init_done);
333 rb_mutex_lock(g_bg_thread_init_rb_mu);
334 if (!g_bg_thread_init_done) {
335 grpc_rb_event_queue_thread_start();
336 grpc_rb_channel_polling_thread_start();
337 g_bg_thread_init_done = true;
338 }
339 rb_mutex_unlock(g_bg_thread_init_rb_mu);
340 }
341
342 static int64_t g_grpc_ruby_init_count;
343
grpc_ruby_init()344 void grpc_ruby_init() {
345 gpr_once_init(&g_once_init, grpc_ruby_basic_init);
346 grpc_ruby_fork_guard();
347 grpc_init();
348 grpc_ruby_init_threads();
349 // (only log after logging has been initialized)
350 grpc_absl_log_int(GPR_DEBUG,
351 "GRPC_RUBY: grpc_ruby_init - g_enable_fork_support=",
352 g_enable_fork_support);
353 grpc_absl_log_int(GPR_DEBUG,
354 "prev g_grpc_ruby_init_count:", 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