Skip to content

Commit 52a2c15

Browse files
etiennebarrieparacycle
authored andcommitted
Put the reference chain in RactorError#detailed_message
1 parent 5fd43a3 commit 52a2c15

7 files changed

Lines changed: 112 additions & 29 deletions

File tree

bootstraptest/test_ractor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ def inspect = "#<Foo @ivar=#{@ivar.inspect}>"
10061006
begin
10071007
Ractor.make_shareable(Bar.new(Foo.new))
10081008
rescue Ractor::Error
1009-
$!.to_s.lines[1..].join
1009+
$!.detailed_message.lines[1..].join
10101010
end
10111011
}
10121012

ractor.c

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ static VALUE rb_eRactorMovedError;
3333
static VALUE rb_eRactorClosedError;
3434
static VALUE rb_cRactorMovedObject;
3535

36+
static ID id_reference_chain;
37+
3638
static void vm_ractor_blocking_cnt_inc(rb_vm_t *vm, rb_ractor_t *r, const char *file, int line);
3739

3840

@@ -1038,13 +1040,37 @@ ractor_moved_missing(int argc, VALUE *argv, VALUE self)
10381040
*
10391041
*/
10401042

1043+
/*
1044+
* call-seq:
1045+
* detailed_message(highlight: false, **kwargs) -> string
1046+
*
1047+
* Returns the message string with the reference chain appended.
1048+
*/
1049+
static VALUE
1050+
ractor_error_detailed_message(int argc, VALUE *argv, VALUE exc)
1051+
{
1052+
// Call super to get the base detailed_message
1053+
VALUE base_message = rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS);
1054+
1055+
VALUE chain = rb_attr_get(exc, id_reference_chain);
1056+
if (NIL_P(chain)) {
1057+
return base_message;
1058+
}
1059+
1060+
return rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, base_message, chain);
1061+
}
1062+
10411063
void
10421064
Init_Ractor(void)
10431065
{
10441066
rb_cRactor = rb_define_class("Ractor", rb_cObject);
10451067
rb_undef_alloc_func(rb_cRactor);
10461068

1069+
id_reference_chain = rb_intern_const("reference_chain");
1070+
10471071
rb_eRactorError = rb_define_class_under(rb_cRactor, "Error", rb_eRuntimeError);
1072+
rb_define_method(rb_eRactorError, "detailed_message", ractor_error_detailed_message, -1);
1073+
10481074
rb_eRactorIsolationError = rb_define_class_under(rb_cRactor, "IsolationError", rb_eRactorError);
10491075
rb_eRactorRemoteError = rb_define_class_under(rb_cRactor, "RemoteError", rb_eRactorError);
10501076
rb_eRactorMovedError = rb_define_class_under(rb_cRactor, "MovedError", rb_eRactorError);
@@ -1631,13 +1657,16 @@ rb_ractor_make_shareable(VALUE obj)
16311657
VALUE exception = Qfalse;
16321658
if (rb_obj_traverse(obj, make_shareable_check_shareable, null_leave, mark_shareable, &chain, &exception)) {
16331659
if (exception) {
1634-
VALUE id_mesg = rb_intern("mesg");
1635-
VALUE message = rb_attr_get(exception, id_mesg);
1636-
message = rb_sprintf("%"PRIsVALUE"%"PRIsVALUE, message, chain);
1637-
rb_ivar_set(exception, id_mesg, message);
1660+
if (!NIL_P(chain)) {
1661+
rb_ivar_set(exception, id_reference_chain, chain);
1662+
}
16381663
rb_exc_raise(exception);
16391664
}
1640-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE"%"PRIsVALUE, obj, chain);
1665+
exception = rb_exc_new3(rb_eRactorError, rb_sprintf("can not make shareable object for %+"PRIsVALUE, obj));
1666+
if (!NIL_P(chain)) {
1667+
rb_ivar_set(exception, id_reference_chain, chain);
1668+
}
1669+
rb_exc_raise(exception);
16411670
}
16421671
RB_GC_GUARD(chain);
16431672
RB_GC_GUARD(exception);
@@ -1671,6 +1700,23 @@ rb_ractor_ensure_main_ractor(const char *msg)
16711700
}
16721701
}
16731702

1703+
NORETURN(void rb_ractor_raise_isolation_error_with_chain(VALUE klass, VALUE chain, const char *fmt, ...));
1704+
1705+
void
1706+
rb_ractor_raise_isolation_error_with_chain(VALUE klass, VALUE chain, const char *fmt, ...)
1707+
{
1708+
va_list args;
1709+
va_start(args, fmt);
1710+
VALUE message = rb_vsprintf(fmt, args);
1711+
va_end(args);
1712+
1713+
VALUE exception = rb_exc_new_str(klass, message);
1714+
if (!NIL_P(chain)) {
1715+
rb_ivar_set(exception, id_reference_chain, chain);
1716+
}
1717+
rb_exc_raise(exception);
1718+
}
1719+
16741720
static enum obj_traverse_iterator_result
16751721
shareable_p_enter(VALUE obj, struct obj_traverse_data *data)
16761722
{
@@ -2782,6 +2828,34 @@ rb_ractor_isolation_violation(const char *fmt, ...)
27822828
rb_ractor_isolation_violation_str(message);
27832829
}
27842830

2831+
void
2832+
rb_ractor_isolation_violation_with_chain(VALUE chain, const char *fmt, ...)
2833+
{
2834+
va_list args;
2835+
va_start(args, fmt);
2836+
VALUE message = rb_vsprintf(fmt, args);
2837+
va_end(args);
2838+
2839+
if (rb_thread_ractor_isolation_check_p()) {
2840+
// Warning path: append the chain inline so it shows next to the
2841+
// warning. There's no exception object to attach @reference_chain to.
2842+
if (!NIL_P(chain)) {
2843+
rb_str_append(message, chain);
2844+
}
2845+
rb_category_warn(RB_WARN_CATEGORY_RACTOR_ISOLATION, "%s", StringValueCStr(message));
2846+
return;
2847+
}
2848+
2849+
// Raise path: keep the chain on the exception's @reference_chain so it
2850+
// surfaces via Exception#detailed_message (matches the design of
2851+
// rb_ractor_raise_isolation_error_with_chain).
2852+
VALUE exception = rb_exc_new_str(rb_eRactorIsolationError, message);
2853+
if (!NIL_P(chain)) {
2854+
rb_ivar_set(exception, id_reference_chain, chain);
2855+
}
2856+
rb_exc_raise(exception);
2857+
}
2858+
27852859
static VALUE
27862860
ractor_check_isolation_ensure(VALUE prev_state)
27872861
{

ractor_core.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,18 @@ bool rb_thread_ractor_isolation_check_p(void);
183183
PRINTF_ARGS(void rb_ractor_isolation_violation(const char *fmt, ...), 1, 2);
184184
void rb_ractor_isolation_violation_str(VALUE message);
185185

186+
/* Like rb_ractor_isolation_violation, but also carries the reference chain
187+
* built by rb_ractor_shareable_p_continue. In check_isolation mode the chain
188+
* is appended to the warning message; otherwise it is attached to the raised
189+
* Ractor::IsolationError as @reference_chain (so Exception#detailed_message
190+
* surfaces it). */
191+
PRINTF_ARGS(void rb_ractor_isolation_violation_with_chain(VALUE chain, const char *fmt, ...), 2, 3);
192+
186193
RUBY_SYMBOL_EXPORT_BEGIN
187194
void rb_ractor_finish_marking(void);
188195

189196
bool rb_ractor_shareable_p_continue(VALUE obj, VALUE *chain);
197+
NORETURN(void rb_ractor_raise_isolation_error_with_chain(VALUE klass, VALUE chain, const char *fmt, ...));
190198

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

test/ruby/test_ractor.rb

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -298,40 +298,40 @@ def initialize
298298
@unshareable = -> {}
299299
end
300300
end.new
301-
assert_unshareable(obj, /from instance variable @unshareable of an instance of #<Class:/)
301+
assert_unshareable(obj, detailed_message: /from instance variable @unshareable of an instance of #<Class:/)
302302
end
303303

304304
def test_error_includes_array_index
305-
assert_unshareable([0, -> {}], /from Array element at index 1/)
305+
assert_unshareable([0, -> {}], detailed_message: /from Array element at index 1/)
306306
end
307307

308308
def test_error_includes_hash_key_and_value
309-
assert_unshareable({ unshareable: -> {} }, /from Hash value at key :unshareable/)
309+
assert_unshareable({ unshareable: -> {} }, detailed_message: /from Hash value at key :unshareable/)
310310
end
311311

312312
def test_error_includes_hash_unshareable_key
313-
assert_unshareable({ -> {} => true }, /from Hash key #<Proc:0x[[:xdigit:]]+ #{__FILE__}:#{__LINE__}/)
313+
assert_unshareable({ -> {} => true }, detailed_message: /from Hash key #<Proc:0x[[:xdigit:]]+ #{__FILE__}:#{__LINE__}/)
314314
end
315315

316316
def test_error_includes_hash_default_proc
317317
h = Hash.new {}
318-
assert_unshareable(h, /from Hash default proc/)
318+
assert_unshareable(h, detailed_message: /from Hash default proc/)
319319
end
320320

321321
def test_error_includes_hash_default_value
322322
h = Hash.new(Mutex.new)
323-
assert_unshareable(h, /from Hash default value/, exception: Ractor::Error)
323+
assert_unshareable(h, detailed_message: /from Hash default value/, exception: Ractor::Error)
324324
end
325325

326326
S = Struct.new(:member)
327327
def test_error_includes_struct_member
328328
s = S.new(-> {})
329-
assert_unshareable(s, /from member :member of an instance of TestRactor::S/)
329+
assert_unshareable(s, detailed_message: /from member :member of an instance of TestRactor::S/)
330330
end
331331

332332
def test_error_includes_block_self
333333
pr = -> {}
334-
assert_unshareable(pr, /from block's self \(an instance of #{self.class.name}\)/)
334+
assert_unshareable(pr, detailed_message: /from block's self \(an instance of #{self.class.name}\)/)
335335
end
336336

337337
def test_error_wraps_freeze_error
@@ -357,7 +357,7 @@ def test_error_for_module_instance_variable
357357
$!
358358
end.value
359359
assert_kind_of Ractor::IsolationError, e
360-
assert_match(/from Hash default proc/, e.message)
360+
assert_match(/from Hash default proc/, e.detailed_message)
361361
RUBY
362362
end
363363

@@ -373,7 +373,7 @@ module ModuleWithUnshareableConstant
373373
$!
374374
end.value
375375
assert_kind_of(Ractor::IsolationError, e)
376-
assert_match(/from Hash default proc/, e.message)
376+
assert_match(/from Hash default proc/, e.detailed_message)
377377
RUBY
378378
end
379379

@@ -383,11 +383,12 @@ def assert_make_shareable(obj)
383383
assert Ractor.shareable?(obj), "object didn't become shareable"
384384
end
385385

386-
def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError)
386+
def assert_unshareable(obj, msg=//, detailed_message: nil, exception: Ractor::IsolationError)
387387
refute Ractor.shareable?(obj), "object is already shareable"
388388
e = assert_raise_with_message(exception, msg) do
389389
Ractor.make_shareable(obj)
390390
end
391+
assert_match(detailed_message, e.detailed_message) if detailed_message
391392
refute Ractor.shareable?(obj), "despite raising, object became shareable"
392393
e
393394
end

variable.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,9 +1470,9 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
14701470
UNLIKELY(rb_ractor_isolation_check_active())) {
14711471
VALUE chain = Qnil;
14721472
if (!rb_ractor_shareable_p_continue(val, &chain)) {
1473-
rb_ractor_isolation_violation(
1474-
"can not get unshareable values from instance variables of classes/modules from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")%"PRIsVALUE,
1475-
rb_id2str(id), obj, chain);
1473+
rb_ractor_isolation_violation_with_chain(chain,
1474+
"can not get unshareable values from instance variables of classes/modules from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")",
1475+
rb_id2str(id), obj);
14761476
}
14771477
}
14781478
return val;
@@ -3448,9 +3448,9 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility)
34483448
if (UNLIKELY(rb_ractor_isolation_check_active())) {
34493449
VALUE chain = Qnil;
34503450
if (!rb_ractor_shareable_p_continue(c, &chain)) {
3451-
rb_ractor_isolation_violation(
3452-
"can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.%"PRIsVALUE,
3453-
rb_class_path(found_in), rb_id2str(id), chain);
3451+
rb_ractor_isolation_violation_with_chain(chain,
3452+
"can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.",
3453+
rb_class_path(found_in), rb_id2str(id));
34543454
}
34553455
}
34563456
return c;

vm.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,9 +1584,9 @@ rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self)
15841584
{
15851585
VALUE chain = Qnil;
15861586
if (!rb_proc_ractor_make_shareable_continue(self, replace_self, &chain)) {
1587-
rb_ractor_isolation_violation(
1588-
"Proc's self is not shareable: %" PRIsVALUE "%"PRIsVALUE,
1589-
self, chain);
1587+
rb_ractor_isolation_violation_with_chain(chain,
1588+
"Proc's self is not shareable: %" PRIsVALUE,
1589+
self);
15901590
}
15911591
return self;
15921592
}

vm_insnhelper.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,9 +1145,9 @@ vm_get_ev_const(rb_execution_context_t *ec, VALUE orig_klass, ID id, bool allow_
11451145
if (UNLIKELY(rb_ractor_isolation_check_active())) {
11461146
VALUE chain = Qnil;
11471147
if (!rb_ractor_shareable_p_continue(val, &chain)) {
1148-
rb_ractor_isolation_violation(
1149-
"can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.%"PRIsVALUE,
1150-
rb_class_path(klass), rb_id2str(id), chain);
1148+
rb_ractor_isolation_violation_with_chain(chain,
1149+
"can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.",
1150+
rb_class_path(klass), rb_id2str(id));
11511151
}
11521152
}
11531153
return val;

0 commit comments

Comments
 (0)