Skip to content

Commit 47d9a53

Browse files
committed
Show chain of references in Ractor errors
When an object fails to be made shareable with `Ractor.make_shareable` or when an unshareable object is accessed through module constants or module instance variables, the error message now includes the chain of references that leads to the unshareable value.
1 parent 5299276 commit 47d9a53

File tree

9 files changed

+298
-50
lines changed

9 files changed

+298
-50
lines changed

bootstraptest/test_ractor.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,48 @@ def self.fstr = @fstr
986986
a + b + c + d + e + f
987987
}
988988

989+
assert_equal <<-output.chomp, %q{
990+
from Hash default proc
991+
from instance variable @ivar of an instance of Foo
992+
from block self #<Foo @ivar={}>
993+
from Hash default proc
994+
from instance variable @ivar of an instance of Foo
995+
from instance variable @foo of an instance of Bar
996+
output
997+
class Foo
998+
def initialize
999+
@ivar = Hash.new { |h, k| h[k] = [] } # the default proc holds self, an instance of Foo
1000+
end
1001+
def inspect = "#<Foo @ivar=#{@ivar.inspect}>"
1002+
end
1003+
1004+
class Bar
1005+
def initialize
1006+
@foo = Foo.new # holds an instance of an object that owns a Proc
1007+
end
1008+
def inspect = "#<Bar @foo=#{@foo.inspect}>"
1009+
end
1010+
1011+
begin
1012+
Ractor.make_shareable Bar.new
1013+
rescue Ractor::Error
1014+
$!.to_s.lines[1..].join
1015+
end
1016+
}
1017+
1018+
assert_equal '[true, true]', %q{
1019+
class Foo
1020+
undef_method :freeze
1021+
end
1022+
1023+
begin
1024+
Ractor.make_shareable Foo.new
1025+
rescue Ractor::Error
1026+
cause = $!.cause
1027+
[cause.class == NoMethodError, cause.name == :freeze]
1028+
end
1029+
}
1030+
9891031
assert_equal '["instance-variable", "instance-variable", nil]', %q{
9901032
class C
9911033
@iv1 = ""

include/ruby/ractor.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ RBIMPL_SYMBOL_EXPORT_END()
248248
static inline bool
249249
rb_ractor_shareable_p(VALUE obj)
250250
{
251-
bool rb_ractor_shareable_p_continue(VALUE obj);
251+
bool rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain);
252252

253253
if (RB_SPECIAL_CONST_P(obj)) {
254254
return true;
@@ -257,7 +257,7 @@ rb_ractor_shareable_p(VALUE obj)
257257
return true;
258258
}
259259
else {
260-
return rb_ractor_shareable_p_continue(obj);
260+
return rb_ractor_shareable_p_continue(obj, NULL);
261261
}
262262
}
263263

ractor.c

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,8 @@ enum obj_traverse_iterator_result {
12111211
traverse_stop,
12121212
};
12131213

1214-
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj);
1214+
struct obj_traverse_data;
1215+
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj, struct obj_traverse_data *data);
12151216
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_leave_func)(VALUE obj);
12161217
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_final_func)(VALUE obj);
12171218

@@ -1222,13 +1223,15 @@ struct obj_traverse_data {
12221223
rb_obj_traverse_leave_func leave_func;
12231224

12241225
st_table *rec;
1225-
VALUE rec_hash;
1226+
VALUE rec_hash; // objects seen during traversal
1227+
VALUE *chain; // reference chain string built during unwinding (NULL if not needed)
1228+
VALUE *exception; // exception raised trying to freeze an object
12261229
};
12271230

1228-
12291231
struct obj_traverse_callback_data {
12301232
bool stop;
12311233
struct obj_traverse_data *data;
1234+
VALUE obj;
12321235
};
12331236

12341237
static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
@@ -1239,11 +1242,13 @@ obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
12391242
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12401243

12411244
if (obj_traverse_i(key, d->data)) {
1245+
rb_ractor_error_chain_append(d->data->chain, "\n from Hash key %+"PRIsVALUE, key);
12421246
d->stop = true;
12431247
return ST_STOP;
12441248
}
12451249

12461250
if (obj_traverse_i(val, d->data)) {
1251+
rb_ractor_error_chain_append(d->data->chain, "\n from Hash value at key %+"PRIsVALUE, key);
12471252
d->stop = true;
12481253
return ST_STOP;
12491254
}
@@ -1277,6 +1282,7 @@ obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
12771282
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12781283

12791284
if (obj_traverse_i(val, d->data)) {
1285+
rb_ractor_error_chain_append(d->data->chain, "\n from instance variable %"PRIsVALUE" of an instance of %"PRIsVALUE, rb_id2str(key), rb_class_real(CLASS_OF(d->obj)));
12801286
d->stop = true;
12811287
return ST_STOP;
12821288
}
@@ -1289,7 +1295,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
12891295
{
12901296
if (RB_SPECIAL_CONST_P(obj)) return 0;
12911297

1292-
switch (data->enter_func(obj)) {
1298+
switch (data->enter_func(obj, data)) {
12931299
case traverse_cont: break;
12941300
case traverse_skip: return 0; // skip children
12951301
case traverse_stop: return 1; // stop search
@@ -1304,9 +1310,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13041310
struct obj_traverse_callback_data d = {
13051311
.stop = false,
13061312
.data = data,
1313+
.obj = obj,
13071314
};
13081315
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1309-
if (d.stop) return 1;
1316+
if (d.stop) {
1317+
return 1;
1318+
}
13101319

13111320
switch (BUILTIN_TYPE(obj)) {
13121321
// no child node
@@ -1328,14 +1337,25 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13281337

13291338
for (int i = 0; i < RARRAY_LENINT(obj); i++) {
13301339
VALUE e = rb_ary_entry(obj, i);
1331-
if (obj_traverse_i(e, data)) return 1;
1340+
if (obj_traverse_i(e, data)) {
1341+
rb_ractor_error_chain_append(data->chain, "\n from Array element at index %d", i);
1342+
return 1;
1343+
}
13321344
}
13331345
}
13341346
break;
13351347

13361348
case T_HASH:
13371349
{
1338-
if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1350+
const VALUE ifnone = RHASH_IFNONE(obj);
1351+
if (obj_traverse_i(ifnone, data)) {
1352+
if (RB_FL_TEST_RAW(obj, RHASH_PROC_DEFAULT)) {
1353+
rb_ractor_error_chain_append(data->chain, "\n from Hash default proc");
1354+
} else {
1355+
rb_ractor_error_chain_append(data->chain, "\n from Hash default value");
1356+
}
1357+
return 1;
1358+
}
13391359

13401360
struct obj_traverse_callback_data d = {
13411361
.stop = false,
@@ -1352,7 +1372,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13521372
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13531373

13541374
for (long i=0; i<len; i++) {
1355-
if (obj_traverse_i(ptr[i], data)) return 1;
1375+
if (obj_traverse_i(ptr[i], data)) {
1376+
VALUE members = rb_struct_members(obj);
1377+
VALUE member_name = rb_array_const_ptr(members)[i];
1378+
rb_ractor_error_chain_append(data->chain, "\n from struct member %+"PRIsVALUE, member_name);
1379+
return 1;
1380+
}
13561381
}
13571382
}
13581383
break;
@@ -1423,15 +1448,21 @@ static int
14231448
rb_obj_traverse(VALUE obj,
14241449
rb_obj_traverse_enter_func enter_func,
14251450
rb_obj_traverse_leave_func leave_func,
1426-
rb_obj_traverse_final_func final_func)
1451+
rb_obj_traverse_final_func final_func,
1452+
VALUE *chain,
1453+
VALUE *exception)
14271454
{
14281455
struct obj_traverse_data data = {
14291456
.enter_func = enter_func,
14301457
.leave_func = leave_func,
14311458
.rec = NULL,
1459+
.chain = chain,
1460+
.exception = exception,
14321461
};
14331462

1434-
if (obj_traverse_i(obj, &data)) return 1;
1463+
if (obj_traverse_i(obj, &data)) {
1464+
return 1;
1465+
}
14351466
if (final_func && data.rec) {
14361467
struct rb_obj_traverse_final_data f = {final_func, 0};
14371468
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1456,14 +1487,45 @@ allow_frozen_shareable_p(VALUE obj)
14561487
return false;
14571488
}
14581489

1490+
static VALUE
1491+
try_freeze(VALUE obj)
1492+
{
1493+
rb_funcall(obj, idFreeze, 0);
1494+
return Qtrue;
1495+
}
1496+
1497+
struct rescue_freeze_data {
1498+
VALUE exception;
1499+
};
1500+
1501+
static VALUE
1502+
rescue_freeze(VALUE data, VALUE freeze_exception)
1503+
{
1504+
struct rescue_freeze_data *rescue_freeze_data = (struct rescue_freeze_data *)data;
1505+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("raised calling #freeze"));
1506+
rb_ivar_set(exception, rb_intern("cause"), freeze_exception);
1507+
rescue_freeze_data->exception = exception;
1508+
return Qfalse;
1509+
}
1510+
14591511
static enum obj_traverse_iterator_result
1460-
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1512+
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result, struct obj_traverse_data *data)
14611513
{
14621514
if (!RB_OBJ_FROZEN_RAW(obj)) {
1463-
rb_funcall(obj, idFreeze, 0);
1515+
struct rescue_freeze_data rescue_freeze_data = { 0 };
1516+
if (!rb_rescue(try_freeze, obj, rescue_freeze, (VALUE)&rescue_freeze_data)) {
1517+
if (data->exception) {
1518+
*data->exception = rescue_freeze_data.exception;
1519+
}
1520+
return traverse_stop;
1521+
}
14641522

14651523
if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1466-
rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1524+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("#freeze does not freeze object correctly"));
1525+
if (data->exception) {
1526+
*data->exception = exception;
1527+
}
1528+
return traverse_stop;
14671529
}
14681530

14691531
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1477,7 +1539,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14771539
static int obj_refer_only_shareables_p(VALUE obj);
14781540

14791541
static enum obj_traverse_iterator_result
1480-
make_shareable_check_shareable(VALUE obj)
1542+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14811543
{
14821544
VM_ASSERT(!SPECIAL_CONST_P(obj));
14831545

@@ -1490,7 +1552,8 @@ make_shareable_check_shareable(VALUE obj)
14901552

14911553
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14921554
if (obj_refer_only_shareables_p(obj)) {
1493-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1555+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1556+
if (result == traverse_stop) return traverse_stop;
14941557
RB_OBJ_SET_SHAREABLE(obj);
14951558
return traverse_skip;
14961559
}
@@ -1500,11 +1563,19 @@ make_shareable_check_shareable(VALUE obj)
15001563
}
15011564
}
15021565
else if (rb_obj_is_proc(obj)) {
1503-
rb_proc_ractor_make_shareable(obj, Qundef);
1566+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1567+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1568+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1569+
1570+
if (data->exception) {
1571+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1572+
}
1573+
return traverse_stop;
1574+
}
15041575
return traverse_cont;
15051576
}
15061577
else {
1507-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1578+
return traverse_stop;
15081579
}
15091580
}
15101581

@@ -1529,7 +1600,7 @@ make_shareable_check_shareable(VALUE obj)
15291600
break;
15301601
}
15311602

1532-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1603+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15331604
}
15341605

15351606
static enum obj_traverse_iterator_result
@@ -1546,9 +1617,20 @@ mark_shareable(VALUE obj)
15461617
VALUE
15471618
rb_ractor_make_shareable(VALUE obj)
15481619
{
1549-
rb_obj_traverse(obj,
1550-
make_shareable_check_shareable,
1551-
null_leave, mark_shareable);
1620+
VALUE chain = Qnil;
1621+
VALUE exception = Qfalse;
1622+
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
1623+
if (exception) {
1624+
VALUE id_mesg = rb_intern("mesg");
1625+
VALUE message = rb_attr_get(exception, id_mesg);
1626+
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1627+
rb_ivar_set(exception, id_mesg, message);
1628+
rb_exc_raise(exception);
1629+
}
1630+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1631+
}
1632+
RB_GC_GUARD(chain);
1633+
RB_GC_GUARD(exception);
15521634
return obj;
15531635
}
15541636

@@ -1579,7 +1661,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15791661
}
15801662

15811663
static enum obj_traverse_iterator_result
1582-
shareable_p_enter(VALUE obj)
1664+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15831665
{
15841666
if (RB_OBJ_SHAREABLE_P(obj)) {
15851667
return traverse_skip;
@@ -1600,11 +1682,9 @@ shareable_p_enter(VALUE obj)
16001682
}
16011683

16021684
bool
1603-
rb_ractor_shareable_p_continue(VALUE obj)
1685+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16041686
{
1605-
if (rb_obj_traverse(obj,
1606-
shareable_p_enter, null_leave,
1607-
mark_shareable)) {
1687+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16081688
return false;
16091689
}
16101690
else {
@@ -1620,7 +1700,7 @@ rb_ractor_setup_belonging(VALUE obj)
16201700
}
16211701

16221702
static enum obj_traverse_iterator_result
1623-
reset_belonging_enter(VALUE obj)
1703+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16241704
{
16251705
if (rb_ractor_shareable_p(obj)) {
16261706
return traverse_skip;
@@ -1642,7 +1722,7 @@ static VALUE
16421722
ractor_reset_belonging(VALUE obj)
16431723
{
16441724
#if RACTOR_CHECK_MODE > 0
1645-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1725+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16461726
#endif
16471727
return obj;
16481728
}

0 commit comments

Comments
 (0)