Skip to content

Commit eceb099

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 5911a52 commit eceb099

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
@@ -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,9 @@ 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,
1286+
"\n from instance variable %"PRIsVALUE" of an instance of %"PRIsVALUE,
1287+
rb_id2str(key), rb_class_real(CLASS_OF(d->obj)));
12801288
d->stop = true;
12811289
return ST_STOP;
12821290
}
@@ -1289,7 +1297,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
12891297
{
12901298
if (RB_SPECIAL_CONST_P(obj)) return 0;
12911299

1292-
switch (data->enter_func(obj)) {
1300+
switch (data->enter_func(obj, data)) {
12931301
case traverse_cont: break;
12941302
case traverse_skip: return 0; // skip children
12951303
case traverse_stop: return 1; // stop search
@@ -1304,9 +1312,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13041312
struct obj_traverse_callback_data d = {
13051313
.stop = false,
13061314
.data = data,
1315+
.obj = obj,
13071316
};
13081317
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1309-
if (d.stop) return 1;
1318+
if (d.stop) {
1319+
return 1;
1320+
}
13101321

13111322
switch (BUILTIN_TYPE(obj)) {
13121323
// no child node
@@ -1328,14 +1339,26 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13281339

13291340
for (int i = 0; i < RARRAY_LENINT(obj); i++) {
13301341
VALUE e = rb_ary_entry(obj, i);
1331-
if (obj_traverse_i(e, data)) return 1;
1342+
if (obj_traverse_i(e, data)) {
1343+
rb_ractor_error_chain_append(data->chain, "\n from Array element at index %d", i);
1344+
return 1;
1345+
}
13321346
}
13331347
}
13341348
break;
13351349

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

13401363
struct obj_traverse_callback_data d = {
13411364
.stop = false,
@@ -1352,7 +1375,14 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13521375
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13531376

13541377
for (long i=0; i<len; i++) {
1355-
if (obj_traverse_i(ptr[i], data)) return 1;
1378+
if (obj_traverse_i(ptr[i], data)) {
1379+
VALUE members = rb_struct_members(obj);
1380+
VALUE member_name = rb_array_const_ptr(members)[i];
1381+
rb_ractor_error_chain_append(data->chain,
1382+
"\n from member %+"PRIsVALUE" of an instance of %"PRIsVALUE,
1383+
member_name, rb_class_real(CLASS_OF(obj)));
1384+
return 1;
1385+
}
13561386
}
13571387
}
13581388
break;
@@ -1423,15 +1453,21 @@ static int
14231453
rb_obj_traverse(VALUE obj,
14241454
rb_obj_traverse_enter_func enter_func,
14251455
rb_obj_traverse_leave_func leave_func,
1426-
rb_obj_traverse_final_func final_func)
1456+
rb_obj_traverse_final_func final_func,
1457+
VALUE *chain,
1458+
VALUE *exception)
14271459
{
14281460
struct obj_traverse_data data = {
14291461
.enter_func = enter_func,
14301462
.leave_func = leave_func,
14311463
.rec = NULL,
1464+
.chain = chain,
1465+
.exception = exception,
14321466
};
14331467

1434-
if (obj_traverse_i(obj, &data)) return 1;
1468+
if (obj_traverse_i(obj, &data)) {
1469+
return 1;
1470+
}
14351471
if (final_func && data.rec) {
14361472
struct rb_obj_traverse_final_data f = {final_func, 0};
14371473
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1456,14 +1492,45 @@ allow_frozen_shareable_p(VALUE obj)
14561492
return false;
14571493
}
14581494

1495+
static VALUE
1496+
try_freeze(VALUE obj)
1497+
{
1498+
rb_funcall(obj, idFreeze, 0);
1499+
return Qtrue;
1500+
}
1501+
1502+
struct rescue_freeze_data {
1503+
VALUE exception;
1504+
};
1505+
1506+
static VALUE
1507+
rescue_freeze(VALUE data, VALUE freeze_exception)
1508+
{
1509+
struct rescue_freeze_data *rescue_freeze_data = (struct rescue_freeze_data *)data;
1510+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("raised calling #freeze"));
1511+
rb_ivar_set(exception, rb_intern("cause"), freeze_exception);
1512+
rescue_freeze_data->exception = exception;
1513+
return Qfalse;
1514+
}
1515+
14591516
static enum obj_traverse_iterator_result
1460-
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1517+
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result, struct obj_traverse_data *data)
14611518
{
14621519
if (!RB_OBJ_FROZEN_RAW(obj)) {
1463-
rb_funcall(obj, idFreeze, 0);
1520+
struct rescue_freeze_data rescue_freeze_data = { 0 };
1521+
if (!rb_rescue(try_freeze, obj, rescue_freeze, (VALUE)&rescue_freeze_data)) {
1522+
if (data->exception) {
1523+
*data->exception = rescue_freeze_data.exception;
1524+
}
1525+
return traverse_stop;
1526+
}
14641527

14651528
if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1466-
rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1529+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("#freeze does not freeze object correctly"));
1530+
if (data->exception) {
1531+
*data->exception = exception;
1532+
}
1533+
return traverse_stop;
14671534
}
14681535

14691536
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1477,7 +1544,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14771544
static int obj_refer_only_shareables_p(VALUE obj);
14781545

14791546
static enum obj_traverse_iterator_result
1480-
make_shareable_check_shareable(VALUE obj)
1547+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14811548
{
14821549
VM_ASSERT(!SPECIAL_CONST_P(obj));
14831550

@@ -1490,7 +1557,8 @@ make_shareable_check_shareable(VALUE obj)
14901557

14911558
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14921559
if (obj_refer_only_shareables_p(obj)) {
1493-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1560+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1561+
if (result == traverse_stop) return traverse_stop;
14941562
RB_OBJ_SET_SHAREABLE(obj);
14951563
return traverse_skip;
14961564
}
@@ -1500,11 +1568,19 @@ make_shareable_check_shareable(VALUE obj)
15001568
}
15011569
}
15021570
else if (rb_obj_is_proc(obj)) {
1503-
rb_proc_ractor_make_shareable(obj, Qundef);
1571+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1572+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1573+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1574+
1575+
if (data->exception) {
1576+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1577+
}
1578+
return traverse_stop;
1579+
}
15041580
return traverse_cont;
15051581
}
15061582
else {
1507-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1583+
return traverse_stop;
15081584
}
15091585
}
15101586

@@ -1529,7 +1605,7 @@ make_shareable_check_shareable(VALUE obj)
15291605
break;
15301606
}
15311607

1532-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1608+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15331609
}
15341610

15351611
static enum obj_traverse_iterator_result
@@ -1546,9 +1622,20 @@ mark_shareable(VALUE obj)
15461622
VALUE
15471623
rb_ractor_make_shareable(VALUE obj)
15481624
{
1549-
rb_obj_traverse(obj,
1550-
make_shareable_check_shareable,
1551-
null_leave, mark_shareable);
1625+
VALUE chain = Qnil;
1626+
VALUE exception = Qfalse;
1627+
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
1628+
if (exception) {
1629+
VALUE id_mesg = rb_intern("mesg");
1630+
VALUE message = rb_attr_get(exception, id_mesg);
1631+
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1632+
rb_ivar_set(exception, id_mesg, message);
1633+
rb_exc_raise(exception);
1634+
}
1635+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1636+
}
1637+
RB_GC_GUARD(chain);
1638+
RB_GC_GUARD(exception);
15521639
return obj;
15531640
}
15541641

@@ -1579,7 +1666,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15791666
}
15801667

15811668
static enum obj_traverse_iterator_result
1582-
shareable_p_enter(VALUE obj)
1669+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15831670
{
15841671
if (RB_OBJ_SHAREABLE_P(obj)) {
15851672
return traverse_skip;
@@ -1600,11 +1687,9 @@ shareable_p_enter(VALUE obj)
16001687
}
16011688

16021689
bool
1603-
rb_ractor_shareable_p_continue(VALUE obj)
1690+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16041691
{
1605-
if (rb_obj_traverse(obj,
1606-
shareable_p_enter, null_leave,
1607-
mark_shareable)) {
1692+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16081693
return false;
16091694
}
16101695
else {
@@ -1620,7 +1705,7 @@ rb_ractor_setup_belonging(VALUE obj)
16201705
}
16211706

16221707
static enum obj_traverse_iterator_result
1623-
reset_belonging_enter(VALUE obj)
1708+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16241709
{
16251710
if (rb_ractor_shareable_p(obj)) {
16261711
return traverse_skip;
@@ -1642,7 +1727,7 @@ static VALUE
16421727
ractor_reset_belonging(VALUE obj)
16431728
{
16441729
#if RACTOR_CHECK_MODE > 0
1645-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1730+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16461731
#endif
16471732
return obj;
16481733
}

0 commit comments

Comments
 (0)