/* * * Copyright 2015 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include <ruby/ruby.h> #include "rb_grpc.h" #include "rb_grpc_imports.generated.h" #include <math.h> #include <ruby/vm.h> #include <stdbool.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <grpc/grpc.h> #include <grpc/support/log.h> #include <grpc/support/time.h> #include "rb_call.h" #include "rb_call_credentials.h" #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" static VALUE grpc_rb_cTimeVal = Qnil; static rb_data_type_t grpc_rb_timespec_data_type = { "gpr_timespec", {GRPC_RB_GC_NOT_MARKED, GRPC_RB_GC_DONT_FREE, GRPC_RB_MEMSIZE_UNAVAILABLE, {NULL, NULL}}, NULL, NULL, #ifdef RUBY_TYPED_FREE_IMMEDIATELY RUBY_TYPED_FREE_IMMEDIATELY #endif }; /* Alloc func that blocks allocation of a given object by raising an * exception. */ VALUE grpc_rb_cannot_alloc(VALUE cls) { rb_raise(rb_eTypeError, "allocation of %s only allowed from the gRPC native layer", rb_class2name(cls)); return Qnil; } /* Init func that fails by raising an exception. */ VALUE grpc_rb_cannot_init(VALUE self) { rb_raise(rb_eTypeError, "initialization of %s only allowed from the gRPC native layer", rb_obj_classname(self)); return Qnil; } /* Init/Clone func that fails by raising an exception. */ VALUE grpc_rb_cannot_init_copy(VALUE copy, VALUE self) { (void)self; rb_raise(rb_eTypeError, "Copy initialization of %s is not supported", rb_obj_classname(copy)); return Qnil; } /* id_tv_{,u}sec are accessor methods on Ruby Time instances. */ static ID id_tv_sec; static ID id_tv_nsec; /** * grpc_rb_time_timeval creates a timeval from a ruby time object. * * This func is copied from ruby source, MRI/source/time.c, which is published * under the same license as the ruby.h, on which the entire extensions is * based. */ gpr_timespec grpc_rb_time_timeval(VALUE time, int interval) { gpr_timespec t; gpr_timespec* time_const; const char* tstr = interval ? "time interval" : "time"; const char* want = " want <secs from epoch>|<Time>|<GRPC::TimeConst.*>"; t.clock_type = GPR_CLOCK_REALTIME; switch (TYPE(time)) { case T_DATA: if (CLASS_OF(time) == grpc_rb_cTimeVal) { TypedData_Get_Struct(time, gpr_timespec, &grpc_rb_timespec_data_type, time_const); t = *time_const; } else if (CLASS_OF(time) == rb_cTime) { t.tv_sec = NUM2INT(rb_funcall(time, id_tv_sec, 0)); t.tv_nsec = NUM2INT(rb_funcall(time, id_tv_nsec, 0)); } else { rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr, rb_obj_classname(time), want); } break; case T_FIXNUM: t.tv_sec = FIX2LONG(time); if (interval && t.tv_sec < 0) rb_raise(rb_eArgError, "%s must be positive", tstr); t.tv_nsec = 0; break; case T_FLOAT: if (interval && RFLOAT_VALUE(time) < 0.0) rb_raise(rb_eArgError, "%s must be positive", tstr); else { double f, d; d = modf(RFLOAT_VALUE(time), &f); if (d < 0) { d += 1; f -= 1; } t.tv_sec = (int64_t)f; if (f != t.tv_sec) { rb_raise(rb_eRangeError, "%f out of Time range", RFLOAT_VALUE(time)); } t.tv_nsec = (int)(d * 1e9 + 0.5); } break; case T_BIGNUM: t.tv_sec = NUM2LONG(time); if (interval && t.tv_sec < 0) rb_raise(rb_eArgError, "%s must be positive", tstr); t.tv_nsec = 0; break; default: rb_raise(rb_eTypeError, "bad input: (%s)->c_timeval, got <%s>,%s", tstr, rb_obj_classname(time), want); break; } return t; } static void Init_grpc_status_codes() { /* Constants representing the status codes or grpc_status_code in status.h */ VALUE grpc_rb_mStatusCodes = rb_define_module_under(grpc_rb_mGrpcCore, "StatusCodes"); rb_define_const(grpc_rb_mStatusCodes, "OK", INT2NUM(GRPC_STATUS_OK)); rb_define_const(grpc_rb_mStatusCodes, "CANCELLED", INT2NUM(GRPC_STATUS_CANCELLED)); rb_define_const(grpc_rb_mStatusCodes, "UNKNOWN", INT2NUM(GRPC_STATUS_UNKNOWN)); rb_define_const(grpc_rb_mStatusCodes, "INVALID_ARGUMENT", INT2NUM(GRPC_STATUS_INVALID_ARGUMENT)); rb_define_const(grpc_rb_mStatusCodes, "DEADLINE_EXCEEDED", INT2NUM(GRPC_STATUS_DEADLINE_EXCEEDED)); rb_define_const(grpc_rb_mStatusCodes, "NOT_FOUND", INT2NUM(GRPC_STATUS_NOT_FOUND)); rb_define_const(grpc_rb_mStatusCodes, "ALREADY_EXISTS", INT2NUM(GRPC_STATUS_ALREADY_EXISTS)); rb_define_const(grpc_rb_mStatusCodes, "PERMISSION_DENIED", INT2NUM(GRPC_STATUS_PERMISSION_DENIED)); rb_define_const(grpc_rb_mStatusCodes, "UNAUTHENTICATED", INT2NUM(GRPC_STATUS_UNAUTHENTICATED)); rb_define_const(grpc_rb_mStatusCodes, "RESOURCE_EXHAUSTED", INT2NUM(GRPC_STATUS_RESOURCE_EXHAUSTED)); rb_define_const(grpc_rb_mStatusCodes, "FAILED_PRECONDITION", INT2NUM(GRPC_STATUS_FAILED_PRECONDITION)); rb_define_const(grpc_rb_mStatusCodes, "ABORTED", INT2NUM(GRPC_STATUS_ABORTED)); rb_define_const(grpc_rb_mStatusCodes, "OUT_OF_RANGE", INT2NUM(GRPC_STATUS_OUT_OF_RANGE)); rb_define_const(grpc_rb_mStatusCodes, "UNIMPLEMENTED", INT2NUM(GRPC_STATUS_UNIMPLEMENTED)); rb_define_const(grpc_rb_mStatusCodes, "INTERNAL", INT2NUM(GRPC_STATUS_INTERNAL)); rb_define_const(grpc_rb_mStatusCodes, "UNAVAILABLE", INT2NUM(GRPC_STATUS_UNAVAILABLE)); rb_define_const(grpc_rb_mStatusCodes, "DATA_LOSS", INT2NUM(GRPC_STATUS_DATA_LOSS)); } /* id_at is the constructor method of the ruby standard Time class. */ static ID id_at; /* id_inspect is the inspect method found on various ruby objects. */ static ID id_inspect; /* id_to_s is the to_s method found on various ruby objects. */ static ID id_to_s; /* Converts a wrapped time constant to a standard time. */ static VALUE grpc_rb_time_val_to_time(VALUE self) { gpr_timespec* time_const = NULL; gpr_timespec real_time; TypedData_Get_Struct(self, gpr_timespec, &grpc_rb_timespec_data_type, time_const); real_time = gpr_convert_clock_type(*time_const, GPR_CLOCK_REALTIME); return rb_funcall(rb_cTime, id_at, 2, INT2NUM(real_time.tv_sec), INT2NUM(real_time.tv_nsec / 1000)); } /* Invokes inspect on the ctime version of the time val. */ static VALUE grpc_rb_time_val_inspect(VALUE self) { return rb_funcall(grpc_rb_time_val_to_time(self), id_inspect, 0); } /* Invokes to_s on the ctime version of the time val. */ static VALUE grpc_rb_time_val_to_s(VALUE self) { return rb_funcall(grpc_rb_time_val_to_time(self), id_to_s, 0); } static gpr_timespec zero_realtime; static gpr_timespec inf_future_realtime; static gpr_timespec inf_past_realtime; /* Adds a module with constants that map to gpr's static timeval structs. */ static void Init_grpc_time_consts() { VALUE grpc_rb_mTimeConsts = rb_define_module_under(grpc_rb_mGrpcCore, "TimeConsts"); grpc_rb_cTimeVal = rb_define_class_under(grpc_rb_mGrpcCore, "TimeSpec", rb_cObject); zero_realtime = gpr_time_0(GPR_CLOCK_REALTIME); inf_future_realtime = gpr_inf_future(GPR_CLOCK_REALTIME); inf_past_realtime = gpr_inf_past(GPR_CLOCK_REALTIME); rb_define_const( grpc_rb_mTimeConsts, "ZERO", TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type, (void*)&zero_realtime)); rb_define_const( grpc_rb_mTimeConsts, "INFINITE_FUTURE", TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type, (void*)&inf_future_realtime)); rb_define_const( grpc_rb_mTimeConsts, "INFINITE_PAST", TypedData_Wrap_Struct(grpc_rb_cTimeVal, &grpc_rb_timespec_data_type, (void*)&inf_past_realtime)); rb_define_method(grpc_rb_cTimeVal, "to_time", grpc_rb_time_val_to_time, 0); rb_define_method(grpc_rb_cTimeVal, "inspect", grpc_rb_time_val_inspect, 0); rb_define_method(grpc_rb_cTimeVal, "to_s", grpc_rb_time_val_to_s, 0); id_at = rb_intern("at"); id_inspect = rb_intern("inspect"); id_to_s = rb_intern("to_s"); id_tv_sec = rb_intern("tv_sec"); id_tv_nsec = rb_intern("tv_nsec"); } #if GPR_WINDOWS static void grpc_ruby_set_init_pid(void) {} static bool grpc_ruby_forked_after_init(void) { return false; } #else static pid_t grpc_init_pid; static void grpc_ruby_set_init_pid(void) { GPR_ASSERT(grpc_init_pid == 0); grpc_init_pid = getpid(); } static bool grpc_ruby_forked_after_init(void) { GPR_ASSERT(grpc_init_pid != 0); return grpc_init_pid != getpid(); } #endif static void grpc_rb_shutdown(void) { if (!grpc_ruby_forked_after_init()) grpc_shutdown(); } /* Initialize the GRPC module structs */ /* grpc_rb_sNewServerRpc is the struct that holds new server rpc details. */ VALUE grpc_rb_sNewServerRpc = Qnil; /* grpc_rb_sStatus is the struct that holds status details. */ VALUE grpc_rb_sStatus = Qnil; /* Initialize the GRPC module. */ VALUE grpc_rb_mGRPC = Qnil; VALUE grpc_rb_mGrpcCore = Qnil; /* cached Symbols for members in Status struct */ VALUE sym_code = Qundef; VALUE sym_details = Qundef; VALUE sym_metadata = Qundef; static gpr_once g_once_init = GPR_ONCE_INIT; static void grpc_ruby_once_init_internal() { grpc_ruby_set_init_pid(); grpc_init(); atexit(grpc_rb_shutdown); } void grpc_ruby_fork_guard() { if (grpc_ruby_forked_after_init()) { rb_raise(rb_eRuntimeError, "grpc cannot be used before and after forking"); } } static VALUE bg_thread_init_rb_mu = Qundef; static int bg_thread_init_done = 0; 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 * - https://bugs.ruby-lang.org/issues/12095 * * In order to still be able to handle the (unlikely) situation where the * extension is loaded by a first Ruby VM that is subsequently destroyed, * 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_internal); // Avoid calling calling into ruby library (when creating threads here) // in gpr_once_init. In general, it appears to be unsafe to call // into the ruby library while holding a non-ruby mutex, because a gil yield // could end up trying to lock onto that same mutex and deadlocking. rb_mutex_lock(bg_thread_init_rb_mu); if (!bg_thread_init_done) { grpc_rb_event_queue_thread_start(); grpc_rb_channel_polling_thread_start(); bg_thread_init_done = 1; } rb_mutex_unlock(bg_thread_init_rb_mu); } void Init_grpc_c() { if (!grpc_rb_load_core()) { rb_raise(rb_eLoadError, "Couldn't find or load gRPC's dynamic C core"); return; } rb_global_variable(&bg_thread_init_rb_mu); bg_thread_init_rb_mu = rb_mutex_new(); grpc_rb_mGRPC = rb_define_module("GRPC"); grpc_rb_mGrpcCore = rb_define_module_under(grpc_rb_mGRPC, "Core"); grpc_rb_sNewServerRpc = rb_struct_define( "NewServerRpc", "method", "host", "deadline", "metadata", "call", NULL); grpc_rb_sStatus = rb_struct_define("Status", "code", "details", "metadata", NULL); sym_code = ID2SYM(rb_intern("code")); sym_details = ID2SYM(rb_intern("details")); sym_metadata = ID2SYM(rb_intern("metadata")); Init_grpc_channel(); Init_grpc_call(); Init_grpc_call_credentials(); Init_grpc_channel_credentials(); Init_grpc_server(); Init_grpc_server_credentials(); Init_grpc_status_codes(); Init_grpc_time_consts(); Init_grpc_compression_options(); }