Skip to content

Commit b4d2a43

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

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)