[Ruby] Fix memory leak in grpc_rb_call_run_batch (#33368)

The function grpc_rb_call_run_batch has many places that could raise
errors, including in child functions. Since a raised error will longjump
out of the function, it will cause memory leaks since the function
cannot perform any clean up. This commit fixes the issue by wrapping the
whole function in an rb_ensure, which will ensure that a cleanup
function is ran before the error is propagated upwards.
pull/33393/head
Peter Zhu 1 year ago committed by GitHub
parent 4fd6dc2066
commit a4ab4d0033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 99
      src/ruby/ext/grpc/rb_call.c

@ -801,6 +801,54 @@ static VALUE grpc_run_batch_stack_build_result(run_batch_stack* st) {
return result;
}
struct call_run_batch_args {
grpc_rb_call* call;
unsigned write_flag;
VALUE ops_hash;
run_batch_stack* st;
};
static VALUE grpc_rb_call_run_batch_try(VALUE value_args) {
struct call_run_batch_args* args = (struct call_run_batch_args*)value_args;
void* tag = (void*)&args->st;
grpc_event ev;
grpc_call_error err;
args->st = gpr_malloc(sizeof(run_batch_stack));
grpc_run_batch_stack_init(args->st, args->write_flag);
grpc_run_batch_stack_fill_ops(args->st, args->ops_hash);
/* call grpc_call_start_batch, then wait for it to complete using
* pluck_event */
err = grpc_call_start_batch(args->call->wrapped, args->st->ops,
args->st->op_num, tag, NULL);
if (err != GRPC_CALL_OK) {
rb_raise(grpc_rb_eCallError,
"grpc_call_start_batch failed with %s (code=%d)",
grpc_call_error_detail_of(err), err);
}
ev = rb_completion_queue_pluck(args->call->queue, tag,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
if (!ev.success) {
rb_raise(grpc_rb_eCallError, "call#run_batch failed somehow");
}
/* Build and return the BatchResult struct result,
if there is an error, it's reflected in the status */
return grpc_run_batch_stack_build_result(args->st);
}
static VALUE grpc_rb_call_run_batch_ensure(VALUE value_args) {
struct call_run_batch_args* args = (struct call_run_batch_args*)value_args;
if (args->st) {
grpc_run_batch_stack_cleanup(args->st);
gpr_free(args->st);
}
return Qnil;
}
/* call-seq:
ops = {
GRPC::Core::CallOps::SEND_INITIAL_METADATA => <op_value>,
@ -819,56 +867,29 @@ static VALUE grpc_run_batch_stack_build_result(run_batch_stack* st) {
Only one operation of each type can be active at once in any given
batch */
static VALUE grpc_rb_call_run_batch(VALUE self, VALUE ops_hash) {
run_batch_stack* st = NULL;
grpc_rb_call* call = NULL;
grpc_event ev;
grpc_call_error err;
VALUE result = Qnil;
VALUE rb_write_flag = rb_ivar_get(self, id_write_flag);
unsigned write_flag = 0;
void* tag = (void*)&st;
grpc_ruby_fork_guard();
if (RTYPEDDATA_DATA(self) == NULL) {
rb_raise(grpc_rb_eCallError, "Cannot run batch on closed call");
return Qnil;
}
grpc_rb_call* call = NULL;
TypedData_Get_Struct(self, grpc_rb_call, &grpc_call_data_type, call);
/* Validate the ops args, adding them to a ruby array */
if (TYPE(ops_hash) != T_HASH) {
rb_raise(rb_eTypeError, "call#run_batch: ops hash should be a hash");
return Qnil;
}
if (rb_write_flag != Qnil) {
write_flag = NUM2UINT(rb_write_flag);
}
st = gpr_malloc(sizeof(run_batch_stack));
grpc_run_batch_stack_init(st, write_flag);
grpc_run_batch_stack_fill_ops(st, ops_hash);
/* call grpc_call_start_batch, then wait for it to complete using
* pluck_event */
err = grpc_call_start_batch(call->wrapped, st->ops, st->op_num, tag, NULL);
if (err != GRPC_CALL_OK) {
grpc_run_batch_stack_cleanup(st);
gpr_free(st);
rb_raise(grpc_rb_eCallError,
"grpc_call_start_batch failed with %s (code=%d)",
grpc_call_error_detail_of(err), err);
return Qnil;
}
ev = rb_completion_queue_pluck(call->queue, tag,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
if (!ev.success) {
rb_raise(grpc_rb_eCallError, "call#run_batch failed somehow");
}
/* Build and return the BatchResult struct result,
if there is an error, it's reflected in the status */
result = grpc_run_batch_stack_build_result(st);
grpc_run_batch_stack_cleanup(st);
gpr_free(st);
return result;
VALUE rb_write_flag = rb_ivar_get(self, id_write_flag);
struct call_run_batch_args args = {
.call = call,
.write_flag = rb_write_flag == Qnil ? 0 : NUM2UINT(rb_write_flag),
.ops_hash = ops_hash,
.st = NULL};
return rb_ensure(grpc_rb_call_run_batch_try, (VALUE)&args,
grpc_rb_call_run_batch_ensure, (VALUE)&args);
}
static void Init_grpc_write_flags() {

Loading…
Cancel
Save