Use a T_DATA for cross_ractor_require

[Bug #21090]

The struct was previously allocated on the stack, which could be freed
if the Thread is terminated. Moving this to a T_DATA on the heap should
mean this is no longer an issue.

1000.times { Ractor.new { th = Thread.new { require "rbconfig" }; Thread.pass }.take }

Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
This commit is contained in:
John Hawthorn 2025-06-05 13:27:33 -07:00
parent a34fcf401b
commit b28f344312
Notes: git 2025-06-12 20:14:09 +00:00

105
ractor.c
View File

@ -2241,6 +2241,28 @@ struct cross_ractor_require {
ID name; ID name;
}; };
static void
cross_ractor_require_mark(void *ptr)
{
struct cross_ractor_require *crr = (struct cross_ractor_require *)ptr;
rb_gc_mark(crr->port);
rb_gc_mark(crr->result);
rb_gc_mark(crr->exception);
rb_gc_mark(crr->feature);
rb_gc_mark(crr->module);
}
static const rb_data_type_t cross_ractor_require_data_type = {
"ractor/cross_ractor_require",
{
cross_ractor_require_mark,
RUBY_DEFAULT_FREE,
NULL, // memsize
NULL, // compact
},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
static VALUE static VALUE
require_body(VALUE data) require_body(VALUE data)
{ {
@ -2287,8 +2309,11 @@ require_result_copy_resuce(VALUE data, VALUE errinfo)
} }
static VALUE static VALUE
ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE)) ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE))
{ {
struct cross_ractor_require *crr;
TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
// catch any error // catch any error
rb_rescue2(func, (VALUE)crr, rb_rescue2(func, (VALUE)crr,
require_rescue, (VALUE)crr, rb_eException, 0); require_rescue, (VALUE)crr, rb_eException, 0);
@ -2297,43 +2322,49 @@ ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE))
require_result_copy_resuce, (VALUE)crr, rb_eException, 0); require_result_copy_resuce, (VALUE)crr, rb_eException, 0);
ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse); ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse);
RB_GC_GUARD(crr_obj);
return Qnil; return Qnil;
} }
static VALUE static VALUE
ractor_require_func(void *data) ractor_require_func(void *crr_obj)
{ {
struct cross_ractor_require *crr = (struct cross_ractor_require *)data; return ractor_require_protect((VALUE)crr_obj, require_body);
return ractor_require_protect(crr, require_body);
} }
VALUE VALUE
rb_ractor_require(VALUE feature) rb_ractor_require(VALUE feature)
{ {
struct cross_ractor_require *crr;
VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
// TODO: make feature shareable // TODO: make feature shareable
struct cross_ractor_require crr = { crr->feature = feature;
.feature = feature, // TODO: ractor crr->port = ractor_port_new(GET_RACTOR());
.port = ractor_port_new(GET_RACTOR()), crr->result = Qundef;
.result = Qundef, crr->exception = Qundef;
.exception = Qundef,
};
rb_execution_context_t *ec = GET_EC(); rb_execution_context_t *ec = GET_EC();
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor; rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
rb_ractor_interrupt_exec(main_r, ractor_require_func, &crr, 0); rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
// wait for require done // wait for require done
ractor_port_receive(ec, crr.port); ractor_port_receive(ec, crr->port);
ractor_port_close(ec, crr.port); ractor_port_close(ec, crr->port);
if (crr.exception != Qundef) { VALUE exc = crr->exception;
ractor_reset_belonging(crr.exception); VALUE result = crr->result;
rb_exc_raise(crr.exception); RB_GC_GUARD(crr_obj);
if (exc != Qundef) {
ractor_reset_belonging(exc);
rb_exc_raise(exc);
} }
else { else {
RUBY_ASSERT(crr.result != Qundef); RUBY_ASSERT(result != Qundef);
ractor_reset_belonging(crr.result); ractor_reset_belonging(result);
return crr.result; return result;
} }
} }
@ -2352,36 +2383,40 @@ autoload_load_body(VALUE data)
} }
static VALUE static VALUE
ractor_autoload_load_func(void *data) ractor_autoload_load_func(void *crr_obj)
{ {
struct cross_ractor_require *crr = (struct cross_ractor_require *)data; return ractor_require_protect((VALUE)crr_obj, autoload_load_body);
return ractor_require_protect(crr, autoload_load_body);
} }
VALUE VALUE
rb_ractor_autoload_load(VALUE module, ID name) rb_ractor_autoload_load(VALUE module, ID name)
{ {
struct cross_ractor_require crr = { struct cross_ractor_require *crr;
.module = module, VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
.name = name, FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
.port = ractor_port_new(GET_RACTOR()), crr->module = module;
.result = Qundef, crr->name = name;
.exception = Qundef, crr->port = ractor_port_new(GET_RACTOR());
}; crr->result = Qundef;
crr->exception = Qundef;
rb_execution_context_t *ec = GET_EC(); rb_execution_context_t *ec = GET_EC();
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor; rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, &crr, 0); rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
// wait for require done // wait for require done
ractor_port_receive(ec, crr.port); ractor_port_receive(ec, crr->port);
ractor_port_close(ec, crr.port); ractor_port_close(ec, crr->port);
if (crr.exception != Qundef) { VALUE exc = crr->exception;
rb_exc_raise(crr.exception); VALUE result = crr->result;
RB_GC_GUARD(crr_obj);
if (exc != Qundef) {
rb_exc_raise(exc);
} }
else { else {
return crr.result; return result;
} }
} }