Skip to content

Commit 2fdc1fa

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 86dba8c commit 2fdc1fa

File tree

9 files changed

+301
-50
lines changed

9 files changed

+301
-50
lines changed

bootstraptest/test_ractor.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,43 @@ 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's self (an instance of Foo)
993+
from Hash default proc
994+
from instance variable @ivar of an instance of Foo
995+
from member :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+
Bar = Data.define(:foo)
1005+
1006+
begin
1007+
Ractor.make_shareable(Bar.new(Foo.new))
1008+
rescue Ractor::Error
1009+
$!.to_s.lines[1..].join
1010+
end
1011+
}
1012+
1013+
assert_equal '[true, true]', %q{
1014+
class Foo
1015+
undef_method :freeze
1016+
end
1017+
1018+
begin
1019+
Ractor.make_shareable Foo.new
1020+
rescue Ractor::Error
1021+
cause = $!.cause
1022+
[cause.class == NoMethodError, cause.name == :freeze]
1023+
end
1024+
}
1025+
9891026
assert_equal '["instance-variable", "instance-variable", nil]', %q{
9901027
class C
9911028
@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: 113 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,8 @@ enum obj_traverse_iterator_result {
12161216
traverse_stop,
12171217
};
12181218

1219-
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj);
1219+
struct obj_traverse_data;
1220+
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_enter_func)(VALUE obj, struct obj_traverse_data *data);
12201221
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_leave_func)(VALUE obj);
12211222
typedef enum obj_traverse_iterator_result (*rb_obj_traverse_final_func)(VALUE obj);
12221223

@@ -1227,13 +1228,15 @@ struct obj_traverse_data {
12271228
rb_obj_traverse_leave_func leave_func;
12281229

12291230
st_table *rec;
1230-
VALUE rec_hash;
1231+
VALUE rec_hash; // objects seen during traversal
1232+
VALUE *chain; // reference chain string built during unwinding (NULL if not needed)
1233+
VALUE *exception; // exception raised trying to freeze an object
12311234
};
12321235

1233-
12341236
struct obj_traverse_callback_data {
12351237
bool stop;
12361238
struct obj_traverse_data *data;
1239+
VALUE obj;
12371240
};
12381241

12391242
static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
@@ -1244,11 +1247,13 @@ obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
12441247
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12451248

12461249
if (obj_traverse_i(key, d->data)) {
1250+
rb_ractor_error_chain_append(d->data->chain, "\n from Hash key %+"PRIsVALUE, key);
12471251
d->stop = true;
12481252
return ST_STOP;
12491253
}
12501254

12511255
if (obj_traverse_i(val, d->data)) {
1256+
rb_ractor_error_chain_append(d->data->chain, "\n from Hash value at key %+"PRIsVALUE, key);
12521257
d->stop = true;
12531258
return ST_STOP;
12541259
}
@@ -1282,6 +1287,9 @@ obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
12821287
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12831288

12841289
if (obj_traverse_i(val, d->data)) {
1290+
rb_ractor_error_chain_append(d->data->chain,
1291+
"\n from instance variable %"PRIsVALUE" of an instance of %"PRIsVALUE,
1292+
rb_id2str(key), rb_class_real(CLASS_OF(d->obj)));
12851293
d->stop = true;
12861294
return ST_STOP;
12871295
}
@@ -1294,7 +1302,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
12941302
{
12951303
if (RB_SPECIAL_CONST_P(obj)) return 0;
12961304

1297-
switch (data->enter_func(obj)) {
1305+
switch (data->enter_func(obj, data)) {
12981306
case traverse_cont: break;
12991307
case traverse_skip: return 0; // skip children
13001308
case traverse_stop: return 1; // stop search
@@ -1309,9 +1317,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13091317
struct obj_traverse_callback_data d = {
13101318
.stop = false,
13111319
.data = data,
1320+
.obj = obj,
13121321
};
13131322
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1314-
if (d.stop) return 1;
1323+
if (d.stop) {
1324+
return 1;
1325+
}
13151326

13161327
switch (BUILTIN_TYPE(obj)) {
13171328
// no child node
@@ -1333,14 +1344,26 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13331344

13341345
for (int i = 0; i < RARRAY_LENINT(obj); i++) {
13351346
VALUE e = rb_ary_entry(obj, i);
1336-
if (obj_traverse_i(e, data)) return 1;
1347+
if (obj_traverse_i(e, data)) {
1348+
rb_ractor_error_chain_append(data->chain, "\n from Array element at index %d", i);
1349+
return 1;
1350+
}
13371351
}
13381352
}
13391353
break;
13401354

13411355
case T_HASH:
13421356
{
1343-
if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1357+
const VALUE ifnone = RHASH_IFNONE(obj);
1358+
if (obj_traverse_i(ifnone, data)) {
1359+
if (RB_FL_TEST_RAW(obj, RHASH_PROC_DEFAULT)) {
1360+
rb_ractor_error_chain_append(data->chain, "\n from Hash default proc");
1361+
}
1362+
else {
1363+
rb_ractor_error_chain_append(data->chain, "\n from Hash default value");
1364+
}
1365+
return 1;
1366+
}
13441367

13451368
struct obj_traverse_callback_data d = {
13461369
.stop = false,
@@ -1357,7 +1380,14 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13571380
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13581381

13591382
for (long i=0; i<len; i++) {
1360-
if (obj_traverse_i(ptr[i], data)) return 1;
1383+
if (obj_traverse_i(ptr[i], data)) {
1384+
VALUE members = rb_struct_members(obj);
1385+
VALUE member_name = rb_array_const_ptr(members)[i];
1386+
rb_ractor_error_chain_append(data->chain,
1387+
"\n from member %+"PRIsVALUE" of an instance of %"PRIsVALUE,
1388+
member_name, rb_class_real(CLASS_OF(obj)));
1389+
return 1;
1390+
}
13611391
}
13621392
}
13631393
break;
@@ -1428,15 +1458,21 @@ static int
14281458
rb_obj_traverse(VALUE obj,
14291459
rb_obj_traverse_enter_func enter_func,
14301460
rb_obj_traverse_leave_func leave_func,
1431-
rb_obj_traverse_final_func final_func)
1461+
rb_obj_traverse_final_func final_func,
1462+
VALUE *chain,
1463+
VALUE *exception)
14321464
{
14331465
struct obj_traverse_data data = {
14341466
.enter_func = enter_func,
14351467
.leave_func = leave_func,
14361468
.rec = NULL,
1469+
.chain = chain,
1470+
.exception = exception,
14371471
};
14381472

1439-
if (obj_traverse_i(obj, &data)) return 1;
1473+
if (obj_traverse_i(obj, &data)) {
1474+
return 1;
1475+
}
14401476
if (final_func && data.rec) {
14411477
struct rb_obj_traverse_final_data f = {final_func, 0};
14421478
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1461,14 +1497,45 @@ allow_frozen_shareable_p(VALUE obj)
14611497
return false;
14621498
}
14631499

1500+
static VALUE
1501+
try_freeze(VALUE obj)
1502+
{
1503+
rb_funcall(obj, idFreeze, 0);
1504+
return Qtrue;
1505+
}
1506+
1507+
struct rescue_freeze_data {
1508+
VALUE exception;
1509+
};
1510+
1511+
static VALUE
1512+
rescue_freeze(VALUE data, VALUE freeze_exception)
1513+
{
1514+
struct rescue_freeze_data *rescue_freeze_data = (struct rescue_freeze_data *)data;
1515+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("raised calling #freeze"));
1516+
rb_ivar_set(exception, rb_intern("cause"), freeze_exception);
1517+
rescue_freeze_data->exception = exception;
1518+
return Qfalse;
1519+
}
1520+
14641521
static enum obj_traverse_iterator_result
1465-
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1522+
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result, struct obj_traverse_data *data)
14661523
{
14671524
if (!RB_OBJ_FROZEN_RAW(obj)) {
1468-
rb_funcall(obj, idFreeze, 0);
1525+
struct rescue_freeze_data rescue_freeze_data = { 0 };
1526+
if (!rb_rescue(try_freeze, obj, rescue_freeze, (VALUE)&rescue_freeze_data)) {
1527+
if (data->exception) {
1528+
*data->exception = rescue_freeze_data.exception;
1529+
}
1530+
return traverse_stop;
1531+
}
14691532

14701533
if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1471-
rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1534+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("#freeze does not freeze object correctly"));
1535+
if (data->exception) {
1536+
*data->exception = exception;
1537+
}
1538+
return traverse_stop;
14721539
}
14731540

14741541
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1482,7 +1549,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14821549
static int obj_refer_only_shareables_p(VALUE obj);
14831550

14841551
static enum obj_traverse_iterator_result
1485-
make_shareable_check_shareable(VALUE obj)
1552+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14861553
{
14871554
VM_ASSERT(!SPECIAL_CONST_P(obj));
14881555

@@ -1495,7 +1562,8 @@ make_shareable_check_shareable(VALUE obj)
14951562

14961563
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14971564
if (obj_refer_only_shareables_p(obj)) {
1498-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1565+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1566+
if (result == traverse_stop) return traverse_stop;
14991567
RB_OBJ_SET_SHAREABLE(obj);
15001568
return traverse_skip;
15011569
}
@@ -1505,11 +1573,19 @@ make_shareable_check_shareable(VALUE obj)
15051573
}
15061574
}
15071575
else if (rb_obj_is_proc(obj)) {
1508-
rb_proc_ractor_make_shareable(obj, Qundef);
1576+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1577+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1578+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1579+
1580+
if (data->exception) {
1581+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1582+
}
1583+
return traverse_stop;
1584+
}
15091585
return traverse_cont;
15101586
}
15111587
else {
1512-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1588+
return traverse_stop;
15131589
}
15141590
}
15151591

@@ -1534,7 +1610,7 @@ make_shareable_check_shareable(VALUE obj)
15341610
break;
15351611
}
15361612

1537-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1613+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15381614
}
15391615

15401616
static enum obj_traverse_iterator_result
@@ -1551,9 +1627,20 @@ mark_shareable(VALUE obj)
15511627
VALUE
15521628
rb_ractor_make_shareable(VALUE obj)
15531629
{
1554-
rb_obj_traverse(obj,
1555-
make_shareable_check_shareable,
1556-
null_leave, mark_shareable);
1630+
VALUE chain = Qnil;
1631+
VALUE exception = Qfalse;
1632+
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
1633+
if (exception) {
1634+
VALUE id_mesg = rb_intern("mesg");
1635+
VALUE message = rb_attr_get(exception, id_mesg);
1636+
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1637+
rb_ivar_set(exception, id_mesg, message);
1638+
rb_exc_raise(exception);
1639+
}
1640+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1641+
}
1642+
RB_GC_GUARD(chain);
1643+
RB_GC_GUARD(exception);
15571644
return obj;
15581645
}
15591646

@@ -1584,7 +1671,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15841671
}
15851672

15861673
static enum obj_traverse_iterator_result
1587-
shareable_p_enter(VALUE obj)
1674+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15881675
{
15891676
if (RB_OBJ_SHAREABLE_P(obj)) {
15901677
return traverse_skip;
@@ -1605,11 +1692,9 @@ shareable_p_enter(VALUE obj)
16051692
}
16061693

16071694
bool
1608-
rb_ractor_shareable_p_continue(VALUE obj)
1695+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16091696
{
1610-
if (rb_obj_traverse(obj,
1611-
shareable_p_enter, null_leave,
1612-
mark_shareable)) {
1697+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16131698
return false;
16141699
}
16151700
else {
@@ -1625,7 +1710,7 @@ rb_ractor_setup_belonging(VALUE obj)
16251710
}
16261711

16271712
static enum obj_traverse_iterator_result
1628-
reset_belonging_enter(VALUE obj)
1713+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16291714
{
16301715
if (rb_ractor_shareable_p(obj)) {
16311716
return traverse_skip;
@@ -1647,7 +1732,7 @@ static VALUE
16471732
ractor_reset_belonging(VALUE obj)
16481733
{
16491734
#if RACTOR_CHECK_MODE > 0
1650-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1735+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16511736
#endif
16521737
return obj;
16531738
}

0 commit comments

Comments
 (0)