Skip to content

Commit d37bed4

Browse files
committed
Show chain of references in Ractor errors
Improve the messages of exceptions raised by the Ractor implementation. 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 aa31754 commit d37bed4

File tree

9 files changed

+302
-49
lines changed

9 files changed

+302
-49
lines changed

bootstraptest/test_ractor.rb

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

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

1236+
static inline void
1237+
chain_append(VALUE *chain_ptr, const char *fmt, ...)
1238+
{
1239+
if (!chain_ptr) return;
1240+
1241+
va_list args;
1242+
va_start(args, fmt);
1243+
1244+
if (NIL_P(*chain_ptr)) {
1245+
*chain_ptr = rb_str_new_cstr("\n");
1246+
}
1247+
else {
1248+
rb_str_cat_cstr(*chain_ptr, "\n");
1249+
}
1250+
rb_str_vcatf(*chain_ptr, fmt, args);
1251+
1252+
va_end(args);
1253+
}
1254+
12341255
static int obj_traverse_i(VALUE obj, struct obj_traverse_data *data);
12351256

12361257
static int
@@ -1239,11 +1260,13 @@ obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
12391260
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12401261

12411262
if (obj_traverse_i(key, d->data)) {
1263+
chain_append(d->data->chain, " from hash key %+"PRIsVALUE, key);
12421264
d->stop = true;
12431265
return ST_STOP;
12441266
}
12451267

12461268
if (obj_traverse_i(val, d->data)) {
1269+
chain_append(d->data->chain, " from hash value at key %+"PRIsVALUE, key);
12471270
d->stop = true;
12481271
return ST_STOP;
12491272
}
@@ -1277,6 +1300,7 @@ obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
12771300
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12781301

12791302
if (obj_traverse_i(val, d->data)) {
1303+
chain_append(d->data->chain, " from instance variable %"PRIsVALUE, rb_id2str(key));
12801304
d->stop = true;
12811305
return ST_STOP;
12821306
}
@@ -1289,7 +1313,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
12891313
{
12901314
if (RB_SPECIAL_CONST_P(obj)) return 0;
12911315

1292-
switch (data->enter_func(obj)) {
1316+
switch (data->enter_func(obj, data)) {
12931317
case traverse_cont: break;
12941318
case traverse_skip: return 0; // skip children
12951319
case traverse_stop: return 1; // stop search
@@ -1306,7 +1330,9 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13061330
.data = data,
13071331
};
13081332
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1309-
if (d.stop) return 1;
1333+
if (d.stop) {
1334+
return 1;
1335+
}
13101336

13111337
switch (BUILTIN_TYPE(obj)) {
13121338
// no child node
@@ -1328,14 +1354,21 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13281354

13291355
for (int i = 0; i < RARRAY_LENINT(obj); i++) {
13301356
VALUE e = rb_ary_entry(obj, i);
1331-
if (obj_traverse_i(e, data)) return 1;
1357+
if (obj_traverse_i(e, data)) {
1358+
chain_append(data->chain, " from array element at index %d", i);
1359+
return 1;
1360+
}
13321361
}
13331362
}
13341363
break;
13351364

13361365
case T_HASH:
13371366
{
1338-
if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1367+
const VALUE ifnone = RHASH_IFNONE(obj);
1368+
if (obj_traverse_i(ifnone, data)) {
1369+
chain_append(data->chain, " from hash default value");
1370+
return 1;
1371+
}
13391372

13401373
struct obj_traverse_callback_data d = {
13411374
.stop = false,
@@ -1352,7 +1385,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13521385
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13531386

13541387
for (long i=0; i<len; i++) {
1355-
if (obj_traverse_i(ptr[i], data)) return 1;
1388+
if (obj_traverse_i(ptr[i], data)) {
1389+
VALUE members = rb_struct_members(obj);
1390+
VALUE member_name = rb_array_const_ptr(members)[i];
1391+
chain_append(data->chain, " from struct member %+"PRIsVALUE, member_name);
1392+
return 1;
1393+
}
13561394
}
13571395
}
13581396
break;
@@ -1423,15 +1461,21 @@ static int
14231461
rb_obj_traverse(VALUE obj,
14241462
rb_obj_traverse_enter_func enter_func,
14251463
rb_obj_traverse_leave_func leave_func,
1426-
rb_obj_traverse_final_func final_func)
1464+
rb_obj_traverse_final_func final_func,
1465+
VALUE *chain,
1466+
VALUE *exception)
14271467
{
14281468
struct obj_traverse_data data = {
14291469
.enter_func = enter_func,
14301470
.leave_func = leave_func,
14311471
.rec = NULL,
1472+
.chain = chain,
1473+
.exception = exception,
14321474
};
14331475

1434-
if (obj_traverse_i(obj, &data)) return 1;
1476+
if (obj_traverse_i(obj, &data)) {
1477+
return 1;
1478+
}
14351479
if (final_func && data.rec) {
14361480
struct rb_obj_traverse_final_data f = {final_func, 0};
14371481
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1456,14 +1500,45 @@ allow_frozen_shareable_p(VALUE obj)
14561500
return false;
14571501
}
14581502

1503+
static VALUE
1504+
try_freeze(VALUE obj)
1505+
{
1506+
rb_funcall(obj, idFreeze, 0);
1507+
return Qtrue;
1508+
}
1509+
1510+
struct rescue_freeze_data {
1511+
VALUE exception;
1512+
};
1513+
1514+
static VALUE
1515+
rescue_freeze(VALUE data, VALUE freeze_exception)
1516+
{
1517+
struct rescue_freeze_data *rescue_freeze_data = (struct rescue_freeze_data *)data;
1518+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("raised calling #freeze"));
1519+
rb_ivar_set(exception, rb_intern("cause"), freeze_exception);
1520+
rescue_freeze_data->exception = exception;
1521+
return Qfalse;
1522+
}
1523+
14591524
static enum obj_traverse_iterator_result
1460-
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1525+
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result, struct obj_traverse_data *data)
14611526
{
14621527
if (!RB_OBJ_FROZEN_RAW(obj)) {
1463-
rb_funcall(obj, idFreeze, 0);
1528+
struct rescue_freeze_data rescue_freeze_data = { 0 };
1529+
if (!rb_rescue(try_freeze, obj, rescue_freeze, (VALUE)&rescue_freeze_data)) {
1530+
if (data->exception) {
1531+
*data->exception = rescue_freeze_data.exception;
1532+
}
1533+
return traverse_stop;
1534+
}
14641535

14651536
if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1466-
rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1537+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("#freeze does not freeze object correctly"));
1538+
if (data->exception) {
1539+
*data->exception = exception;
1540+
}
1541+
return traverse_stop;
14671542
}
14681543

14691544
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1477,7 +1552,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14771552
static int obj_refer_only_shareables_p(VALUE obj);
14781553

14791554
static enum obj_traverse_iterator_result
1480-
make_shareable_check_shareable(VALUE obj)
1555+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14811556
{
14821557
VM_ASSERT(!SPECIAL_CONST_P(obj));
14831558

@@ -1490,7 +1565,8 @@ make_shareable_check_shareable(VALUE obj)
14901565

14911566
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14921567
if (obj_refer_only_shareables_p(obj)) {
1493-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1568+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1569+
if (result == traverse_stop) return traverse_stop;
14941570
RB_OBJ_SET_SHAREABLE(obj);
14951571
return traverse_skip;
14961572
}
@@ -1500,11 +1576,19 @@ make_shareable_check_shareable(VALUE obj)
15001576
}
15011577
}
15021578
else if (rb_obj_is_proc(obj)) {
1503-
rb_proc_ractor_make_shareable(obj, Qundef);
1579+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1580+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1581+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1582+
1583+
if (data->exception) {
1584+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1585+
}
1586+
return traverse_stop;
1587+
}
15041588
return traverse_cont;
15051589
}
15061590
else {
1507-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1591+
return traverse_stop;
15081592
}
15091593
}
15101594

@@ -1529,7 +1613,7 @@ make_shareable_check_shareable(VALUE obj)
15291613
break;
15301614
}
15311615

1532-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1616+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15331617
}
15341618

15351619
static enum obj_traverse_iterator_result
@@ -1546,9 +1630,20 @@ mark_shareable(VALUE obj)
15461630
VALUE
15471631
rb_ractor_make_shareable(VALUE obj)
15481632
{
1549-
rb_obj_traverse(obj,
1550-
make_shareable_check_shareable,
1551-
null_leave, mark_shareable);
1633+
VALUE chain = Qnil;
1634+
VALUE exception = Qfalse;
1635+
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
1636+
if (exception) {
1637+
VALUE id_mesg = rb_intern("mesg");
1638+
VALUE message = rb_attr_get(exception, id_mesg);
1639+
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1640+
rb_ivar_set(exception, id_mesg, message);
1641+
rb_exc_raise(exception);
1642+
}
1643+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1644+
}
1645+
RB_GC_GUARD(chain);
1646+
RB_GC_GUARD(exception);
15521647
return obj;
15531648
}
15541649

@@ -1579,7 +1674,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15791674
}
15801675

15811676
static enum obj_traverse_iterator_result
1582-
shareable_p_enter(VALUE obj)
1677+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15831678
{
15841679
if (RB_OBJ_SHAREABLE_P(obj)) {
15851680
return traverse_skip;
@@ -1600,11 +1695,9 @@ shareable_p_enter(VALUE obj)
16001695
}
16011696

16021697
bool
1603-
rb_ractor_shareable_p_continue(VALUE obj)
1698+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16041699
{
1605-
if (rb_obj_traverse(obj,
1606-
shareable_p_enter, null_leave,
1607-
mark_shareable)) {
1700+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16081701
return false;
16091702
}
16101703
else {
@@ -1620,7 +1713,7 @@ rb_ractor_setup_belonging(VALUE obj)
16201713
}
16211714

16221715
static enum obj_traverse_iterator_result
1623-
reset_belonging_enter(VALUE obj)
1716+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16241717
{
16251718
if (rb_ractor_shareable_p(obj)) {
16261719
return traverse_skip;
@@ -1642,7 +1735,7 @@ static VALUE
16421735
ractor_reset_belonging(VALUE obj)
16431736
{
16441737
#if RACTOR_CHECK_MODE > 0
1645-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1738+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16461739
#endif
16471740
return obj;
16481741
}

ractor_core.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ st_table *rb_ractor_targeted_hooks(rb_ractor_t *cr);
149149
RUBY_SYMBOL_EXPORT_BEGIN
150150
void rb_ractor_finish_marking(void);
151151

152-
bool rb_ractor_shareable_p_continue(VALUE obj);
152+
bool rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain);
153153

154154
// THIS FUNCTION SHOULD NOT CALL WHILE INCREMENTAL MARKING!!
155155
// This function is for T_DATA::free_func

0 commit comments

Comments
 (0)