Skip to content

Commit f44c160

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 83713db commit f44c160

File tree

9 files changed

+281
-50
lines changed

9 files changed

+281
-50
lines changed

bootstraptest/test_ractor.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,46 @@ 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+
begin
1010+
Ractor.make_shareable Bar.new
1011+
rescue Ractor::Error
1012+
$!.to_s.lines[1..].join.chomp
1013+
end
1014+
}
1015+
1016+
assert_equal '[true, true]', %q{
1017+
class Foo
1018+
undef_method :freeze
1019+
end
1020+
1021+
begin
1022+
Ractor.make_shareable Foo.new
1023+
rescue Ractor::Error
1024+
cause = $!.cause
1025+
[cause.class == NoMethodError, cause.name == :freeze]
1026+
end
1027+
}
1028+
9891029
assert_equal '["instance-variable", "instance-variable", nil]', %q{
9901030
class C
9911031
@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: 102 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,10 +1223,11 @@ 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;
@@ -1239,11 +1241,13 @@ obj_hash_traverse_i(VALUE key, VALUE val, VALUE ptr)
12391241
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12401242

12411243
if (obj_traverse_i(key, d->data)) {
1244+
rb_ractor_error_chain_append(d->data->chain, "\n from hash key %+"PRIsVALUE, key);
12421245
d->stop = true;
12431246
return ST_STOP;
12441247
}
12451248

12461249
if (obj_traverse_i(val, d->data)) {
1250+
rb_ractor_error_chain_append(d->data->chain, "\n from hash value at key %+"PRIsVALUE, key);
12471251
d->stop = true;
12481252
return ST_STOP;
12491253
}
@@ -1277,6 +1281,7 @@ obj_traverse_ivar_foreach_i(ID key, VALUE val, st_data_t ptr)
12771281
struct obj_traverse_callback_data *d = (struct obj_traverse_callback_data *)ptr;
12781282

12791283
if (obj_traverse_i(val, d->data)) {
1284+
rb_ractor_error_chain_append(d->data->chain, "\n from instance variable %"PRIsVALUE, rb_id2str(key));
12801285
d->stop = true;
12811286
return ST_STOP;
12821287
}
@@ -1289,7 +1294,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
12891294
{
12901295
if (RB_SPECIAL_CONST_P(obj)) return 0;
12911296

1292-
switch (data->enter_func(obj)) {
1297+
switch (data->enter_func(obj, data)) {
12931298
case traverse_cont: break;
12941299
case traverse_skip: return 0; // skip children
12951300
case traverse_stop: return 1; // stop search
@@ -1306,7 +1311,9 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13061311
.data = data,
13071312
};
13081313
rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d);
1309-
if (d.stop) return 1;
1314+
if (d.stop) {
1315+
return 1;
1316+
}
13101317

13111318
switch (BUILTIN_TYPE(obj)) {
13121319
// no child node
@@ -1328,14 +1335,21 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13281335

13291336
for (int i = 0; i < RARRAY_LENINT(obj); i++) {
13301337
VALUE e = rb_ary_entry(obj, i);
1331-
if (obj_traverse_i(e, data)) return 1;
1338+
if (obj_traverse_i(e, data)) {
1339+
rb_ractor_error_chain_append(data->chain, "\n from array element at index %d", i);
1340+
return 1;
1341+
}
13321342
}
13331343
}
13341344
break;
13351345

13361346
case T_HASH:
13371347
{
1338-
if (obj_traverse_i(RHASH_IFNONE(obj), data)) return 1;
1348+
const VALUE ifnone = RHASH_IFNONE(obj);
1349+
if (obj_traverse_i(ifnone, data)) {
1350+
rb_ractor_error_chain_append(data->chain, "\n from hash default value");
1351+
return 1;
1352+
}
13391353

13401354
struct obj_traverse_callback_data d = {
13411355
.stop = false,
@@ -1352,7 +1366,12 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
13521366
const VALUE *ptr = RSTRUCT_CONST_PTR(obj);
13531367

13541368
for (long i=0; i<len; i++) {
1355-
if (obj_traverse_i(ptr[i], data)) return 1;
1369+
if (obj_traverse_i(ptr[i], data)) {
1370+
VALUE members = rb_struct_members(obj);
1371+
VALUE member_name = rb_array_const_ptr(members)[i];
1372+
rb_ractor_error_chain_append(data->chain, "\n from struct member %+"PRIsVALUE, member_name);
1373+
return 1;
1374+
}
13561375
}
13571376
}
13581377
break;
@@ -1423,15 +1442,21 @@ static int
14231442
rb_obj_traverse(VALUE obj,
14241443
rb_obj_traverse_enter_func enter_func,
14251444
rb_obj_traverse_leave_func leave_func,
1426-
rb_obj_traverse_final_func final_func)
1445+
rb_obj_traverse_final_func final_func,
1446+
VALUE *chain,
1447+
VALUE *exception)
14271448
{
14281449
struct obj_traverse_data data = {
14291450
.enter_func = enter_func,
14301451
.leave_func = leave_func,
14311452
.rec = NULL,
1453+
.chain = chain,
1454+
.exception = exception,
14321455
};
14331456

1434-
if (obj_traverse_i(obj, &data)) return 1;
1457+
if (obj_traverse_i(obj, &data)) {
1458+
return 1;
1459+
}
14351460
if (final_func && data.rec) {
14361461
struct rb_obj_traverse_final_data f = {final_func, 0};
14371462
st_foreach(data.rec, obj_traverse_final_i, (st_data_t)&f);
@@ -1456,14 +1481,45 @@ allow_frozen_shareable_p(VALUE obj)
14561481
return false;
14571482
}
14581483

1484+
static VALUE
1485+
try_freeze(VALUE obj)
1486+
{
1487+
rb_funcall(obj, idFreeze, 0);
1488+
return Qtrue;
1489+
}
1490+
1491+
struct rescue_freeze_data {
1492+
VALUE exception;
1493+
};
1494+
1495+
static VALUE
1496+
rescue_freeze(VALUE data, VALUE freeze_exception)
1497+
{
1498+
struct rescue_freeze_data *rescue_freeze_data = (struct rescue_freeze_data *)data;
1499+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("raised calling #freeze"));
1500+
rb_ivar_set(exception, rb_intern("cause"), freeze_exception);
1501+
rescue_freeze_data->exception = exception;
1502+
return Qfalse;
1503+
}
1504+
14591505
static enum obj_traverse_iterator_result
1460-
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result)
1506+
make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result, struct obj_traverse_data *data)
14611507
{
14621508
if (!RB_OBJ_FROZEN_RAW(obj)) {
1463-
rb_funcall(obj, idFreeze, 0);
1509+
struct rescue_freeze_data rescue_freeze_data = { 0 };
1510+
if (!rb_rescue(try_freeze, obj, rescue_freeze, (VALUE)&rescue_freeze_data)) {
1511+
if (data->exception) {
1512+
*data->exception = rescue_freeze_data.exception;
1513+
}
1514+
return traverse_stop;
1515+
}
14641516

14651517
if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) {
1466-
rb_raise(rb_eRactorError, "#freeze does not freeze object correctly");
1518+
VALUE exception = rb_exc_new3(rb_eRactorError, rb_str_new_cstr("#freeze does not freeze object correctly"));
1519+
if (data->exception) {
1520+
*data->exception = exception;
1521+
}
1522+
return traverse_stop;
14671523
}
14681524

14691525
if (RB_OBJ_SHAREABLE_P(obj)) {
@@ -1477,7 +1533,7 @@ make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_resu
14771533
static int obj_refer_only_shareables_p(VALUE obj);
14781534

14791535
static enum obj_traverse_iterator_result
1480-
make_shareable_check_shareable(VALUE obj)
1536+
make_shareable_check_shareable(VALUE obj, struct obj_traverse_data *data)
14811537
{
14821538
VM_ASSERT(!SPECIAL_CONST_P(obj));
14831539

@@ -1490,7 +1546,8 @@ make_shareable_check_shareable(VALUE obj)
14901546

14911547
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
14921548
if (obj_refer_only_shareables_p(obj)) {
1493-
make_shareable_check_shareable_freeze(obj, traverse_skip);
1549+
enum obj_traverse_iterator_result result = make_shareable_check_shareable_freeze(obj, traverse_skip, data);
1550+
if (result == traverse_stop) return traverse_stop;
14941551
RB_OBJ_SET_SHAREABLE(obj);
14951552
return traverse_skip;
14961553
}
@@ -1500,11 +1557,19 @@ make_shareable_check_shareable(VALUE obj)
15001557
}
15011558
}
15021559
else if (rb_obj_is_proc(obj)) {
1503-
rb_proc_ractor_make_shareable(obj, Qundef);
1560+
if (!rb_proc_ractor_make_shareable_continue(obj, Qundef, data->chain)) {
1561+
rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(obj);
1562+
if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet");
1563+
1564+
if (data->exception) {
1565+
*data->exception = rb_exc_new3(rb_eRactorIsolationError, rb_sprintf("Proc's self is not shareable: %" PRIsVALUE, obj));
1566+
}
1567+
return traverse_stop;
1568+
}
15041569
return traverse_cont;
15051570
}
15061571
else {
1507-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1572+
return traverse_stop;
15081573
}
15091574
}
15101575

@@ -1529,7 +1594,7 @@ make_shareable_check_shareable(VALUE obj)
15291594
break;
15301595
}
15311596

1532-
return make_shareable_check_shareable_freeze(obj, traverse_cont);
1597+
return make_shareable_check_shareable_freeze(obj, traverse_cont, data);
15331598
}
15341599

15351600
static enum obj_traverse_iterator_result
@@ -1546,9 +1611,20 @@ mark_shareable(VALUE obj)
15461611
VALUE
15471612
rb_ractor_make_shareable(VALUE obj)
15481613
{
1549-
rb_obj_traverse(obj,
1550-
make_shareable_check_shareable,
1551-
null_leave, mark_shareable);
1614+
VALUE chain = Qnil;
1615+
VALUE exception = Qfalse;
1616+
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
1617+
if (exception) {
1618+
VALUE id_mesg = rb_intern("mesg");
1619+
VALUE message = rb_attr_get(exception, id_mesg);
1620+
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1621+
rb_ivar_set(exception, id_mesg, message);
1622+
rb_exc_raise(exception);
1623+
}
1624+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1625+
}
1626+
RB_GC_GUARD(chain);
1627+
RB_GC_GUARD(exception);
15521628
return obj;
15531629
}
15541630

@@ -1579,7 +1655,7 @@ rb_ractor_ensure_main_ractor(const char *msg)
15791655
}
15801656

15811657
static enum obj_traverse_iterator_result
1582-
shareable_p_enter(VALUE obj)
1658+
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
15831659
{
15841660
if (RB_OBJ_SHAREABLE_P(obj)) {
15851661
return traverse_skip;
@@ -1600,11 +1676,9 @@ shareable_p_enter(VALUE obj)
16001676
}
16011677

16021678
bool
1603-
rb_ractor_shareable_p_continue(VALUE obj)
1679+
rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain)
16041680
{
1605-
if (rb_obj_traverse(obj,
1606-
shareable_p_enter, null_leave,
1607-
mark_shareable)) {
1681+
if (rb_obj_traverse(obj, shareable_p_enter, null_leave, mark_shareable, chain, NULL)) {
16081682
return false;
16091683
}
16101684
else {
@@ -1620,7 +1694,7 @@ rb_ractor_setup_belonging(VALUE obj)
16201694
}
16211695

16221696
static enum obj_traverse_iterator_result
1623-
reset_belonging_enter(VALUE obj)
1697+
reset_belonging_enter(VALUE obj, struct obj_traverse_data *data)
16241698
{
16251699
if (rb_ractor_shareable_p(obj)) {
16261700
return traverse_skip;
@@ -1642,7 +1716,7 @@ static VALUE
16421716
ractor_reset_belonging(VALUE obj)
16431717
{
16441718
#if RACTOR_CHECK_MODE > 0
1645-
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL);
1719+
rb_obj_traverse(obj, reset_belonging_enter, null_leave, NULL, NULL, NULL);
16461720
#endif
16471721
return obj;
16481722
}

ractor_core.h

Lines changed: 19 additions & 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
@@ -270,6 +270,24 @@ rb_ractor_targeted_hooks_cnt(rb_ractor_t *cr)
270270
return cr->pub.targeted_hooks_cnt;
271271
}
272272

273+
static inline void
274+
rb_ractor_error_chain_append(VALUE *chain_ptr, const char *fmt, ...)
275+
{
276+
if (!chain_ptr) return;
277+
278+
va_list args;
279+
va_start(args, fmt);
280+
281+
if (NIL_P(*chain_ptr)) {
282+
*chain_ptr = rb_vsprintf(fmt, args);
283+
}
284+
else {
285+
rb_str_vcatf(*chain_ptr, fmt, args);
286+
}
287+
288+
va_end(args);
289+
}
290+
273291
#if RACTOR_CHECK_MODE > 0
274292
# define RACTOR_BELONGING_ID(obj) (*(uint32_t *)(((uintptr_t)(obj)) + rb_gc_obj_slot_size(obj)))
275293

0 commit comments

Comments
 (0)