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