Skip to content

Commit 10d0782

Browse files
etiennebarrieparacycle
authored andcommitted
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 3fd1be9 commit 10d0782

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
@@ -1215,7 +1215,8 @@ enum obj_traverse_iterator_result {
12151215
traverse_stop,
12161216
};
12171217

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

@@ -1226,13 +1227,15 @@ struct obj_traverse_data {
12261227
rb_obj_traverse_leave_func leave_func;
12271228

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

1232-
12331235
struct obj_traverse_callback_data {
12341236
bool stop;
12351237
struct obj_traverse_data *data;
1238+
VALUE obj;
12361239
};
12371240

12381241
static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
@@ -1243,11 +1246,13 @@ obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
12431246
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12441247

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

12501254
if (obj_traverse_i(val, d->data)) {
1255+
rb_ractor_error_chain_append(d->data->chain, "\n from Hash value at key %+"PRIsVALUE, key);
12511256
d->stop = true;
12521257
return ST_STOP;
12531258
}
@@ -1281,6 +1286,9 @@ obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
12811286
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12821287

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

1296-
switch (data->enter_func(obj)) {
1304+
switch (data->enter_func(obj, data)) {
12971305
case traverse_cont: break;
12981306
case traverse_skip: return 0; // skip children
12991307
case traverse_stop: return 1; // stop search
@@ -1308,9 +1316,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13081316
struct obj_traverse_callback_data d = {
13091317
.stop = false,
13101318
.data = data,
1319+
.obj = obj,
13111320
};
13121321
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1313-
if (d.stop) return 1;
1322+
if (d.stop) {
1323+
return 1;
1324+
}
13141325

13151326
switch (BUILTIN_TYPE(obj)) {
13161327
// no child node
@@ -1332,14 +1343,26 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13321343

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

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

13441367
struct obj_traverse_callback_data d = {
13451368
.stop = false,
@@ -1356,7 +1379,14 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13561379
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13571380

13581381
for (long i=0; i<len; i++) {
1359-
if (obj_traverse_i(ptr[i], data)) return 1;
1382+
if (obj_traverse_i(ptr[i], data)) {
1383+
VALUE members = rb_struct_members(obj);
1384+
VALUE member_name = rb_array_const_ptr(members)[i];
1385+
rb_ractor_error_chain_append(data->chain,
1386+
"\n from member %+"PRIsVALUE" of an instance of %"PRIsVALUE,
1387+
member_name, rb_class_real(CLASS_OF(obj)));
1388+
return 1;
1389+
}
13601390
}
13611391
}
13621392
break;
@@ -1427,15 +1457,21 @@ static int
14271457
rb_obj_traverse(VALUE obj,
14281458
rb_obj_traverse_enter_func enter_func,
14291459
rb_obj_traverse_leave_func leave_func,
1430-
rb_obj_traverse_final_func final_func)
1460+
rb_obj_traverse_final_func final_func,
1461+
VALUE *chain,
1462+
VALUE *exception)
14311463
{
14321464
struct obj_traverse_data data = {
14331465
.enter_func = enter_func,
14341466
.leave_func = leave_func,
14351467
.rec = NULL,
1468+
.chain = chain,
1469+
.exception = exception,
14361470
};
14371471

1438-
if (obj_traverse_i(obj, &data)) return 1;
1472+
if (obj_traverse_i(obj, &data)) {
1473+
return 1;
1474+
}
14391475
if (final_func && data.rec) {
14401476
struct rb_obj_traverse_final_data f = {final_func, 0};
14411477
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1460,14 +1496,45 @@ allow_frozen_shareable_p(VALUE obj)
14601496
return false;
14611497
}
14621498

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

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

14731540
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1481,7 +1548,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14811548
static int obj_refer_only_shareables_p(VALUE obj);
14821549

14831550
static enum obj_traverse_iterator_result
1484-
make_shareable_check_shareable(VALUE obj)
1551+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14851552
{
14861553
VM_ASSERT(!SPECIAL_CONST_P(obj));
14871554

@@ -1494,7 +1561,8 @@ make_shareable_check_shareable(VALUE obj)
14941561

14951562
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14961563
if (obj_refer_only_shareables_p(obj)) {
1497-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1564+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1565+
if (result == traverse_stop) return traverse_stop;
14981566
RB_OBJ_SET_SHAREABLE(obj);
14991567
return traverse_skip;
15001568
}
@@ -1504,11 +1572,19 @@ make_shareable_check_shareable(VALUE obj)
15041572
}
15051573
}
15061574
else if (rb_obj_is_proc(obj)) {
1507-
rb_proc_ractor_make_shareable(obj, Qundef);
1575+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1576+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1577+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1578+
1579+
if (data->exception) {
1580+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1581+
}
1582+
return traverse_stop;
1583+
}
15081584
return traverse_cont;
15091585
}
15101586
else {
1511-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1587+
return traverse_stop;
15121588
}
15131589
}
15141590

@@ -1533,7 +1609,7 @@ make_shareable_check_shareable(VALUE obj)
15331609
break;
15341610
}
15351611

1536-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1612+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15371613
}
15381614

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

@@ -1583,7 +1670,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15831670
}
15841671

15851672
static enum obj_traverse_iterator_result
1586-
shareable_p_enter(VALUE obj)
1673+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15871674
{
15881675
if (RB_OBJ_SHAREABLE_P(obj)) {
15891676
return traverse_skip;
@@ -1604,11 +1691,9 @@ shareable_p_enter(VALUE obj)
16041691
}
16051692

16061693
bool
1607-
rb_ractor_shareable_p_continue(VALUE obj)
1694+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16081695
{
1609-
if (rb_obj_traverse(obj,
1610-
shareable_p_enter, null_leave,
1611-
mark_shareable)) {
1696+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16121697
return false;
16131698
}
16141699
else {
@@ -1624,7 +1709,7 @@ rb_ractor_setup_belonging(VALUE obj)
16241709
}
16251710

16261711
static enum obj_traverse_iterator_result
1627-
reset_belonging_enter(VALUE obj)
1712+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16281713
{
16291714
if (rb_ractor_shareable_p(obj)) {
16301715
return traverse_skip;
@@ -1646,7 +1731,7 @@ static VALUE
16461731
ractor_reset_belonging(VALUE obj)
16471732
{
16481733
#if RACTOR_CHECK_MODE > 0
1649-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1734+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16501735
#endif
16511736
return obj;
16521737
}

0 commit comments

Comments
 (0)