Include ivar name and class name in the Ractor isolation error message

- ### Problem

  When a Ractor isolation error happens due to setting an ivar on a
  class, the message doesn't include the name of the ivar and neither
  the name of the class. It makes it difficult to track down where the
  isolation problem comes from.

  I'd like the message to be similar as what we get when a Ractor
  access an unshareable ivar.

  This is the current behaviour

  ```ruby
  class Foo
    def self.foo
      @foo ||= "test"
    end
  end

  Ractor.new do
    Foo.foo # can not set instance variables of classes/modules by non-main Ractors
  end.join

  Foo.foo

  Ractor.new do
    Foo.foo # can not get unshareable values from instance variables of classes/modules from non-main Ractors (@foo from Foo)
  end.join
  ```

  ### Solution

  Modify the error message to include the ivar name and the class/module name.

Co-authored-by: Yaroslav Markin <yaroslav@markin.net>
This commit is contained in:
Edouard CHINandYaroslav Markin authored and Luke Gruber committed 2026-10-22 17:51:23 -04:00
1 parent 7a39a7a957
commit cc57f9c818
2 files changed
+100 -4
Notes: git 2026-10-22 21:51:53 +00:00

No files matched your search

+85
View File
@@ -888,6 +888,91 @@ assert_equal "can not get unshareable values from instance variables of classes/
end
RUBY
# setting an ivar of a class/module from a non-owner Ractor reports the ivar and class
assert_equal "can not set instance variables of classes/modules created by another Ractor (@iv from C)", <<~'RUBY', frozen_string_literal: false
class C
@iv = 'str'
end
r = Ractor.new do
class C
@iv = 'other'
end
end
begin
r.value
rescue Ractor::RemoteError => e
e.cause.message
end
RUBY
# a non-main Ractor can read, set, and remove ivars of classes/modules it created
assert_equal 'ok', <<~'RUBY', frozen_string_literal: false
Ractor.new do
[Class.new, Module.new].each do |mod|
mod.instance_variable_set(:@iv, 'str')
raise unless mod.instance_variable_get(:@iv) == 'str'
raise unless mod.remove_instance_variable(:@iv) == 'str'
end
:ok
end.value
RUBY
# even main cannot modify another Ractor's module; reporting its name must not call to_s
assert_equal 'true', <<~'RUBY', frozen_string_literal: false
port = Ractor::Port.new
r = Ractor.new(port) do |port|
mod = Module.new
mod.set_temporary_name('worker_module')
mod.instance_variable_set(:@iv, 'str')
def mod.to_s = raise('to_s must not be called')
port << mod
Ractor.receive
end
mod = port.receive
begin
messages = [[:instance_variable_set, :@iv, 'other'], [:remove_instance_variable, :@iv]].map do |args|
begin
mod.public_send(*args)
rescue Ractor::IsolationError => e
e.message
end
end
messages == ["can not set instance variables of classes/modules created by another Ractor (@iv from worker_module)"] * 2
ensure
r << nil
r.join
end
RUBY
# normal and JIT-compiled ivar reads report the same names without calling to_s
assert_equal 'true', <<~'RUBY', frozen_string_literal: false
class C
@iv = 'str'
def self.read = @iv
def self.to_s = raise('to_s must not be called')
end
Ractor.new do
messages = []
begin
C.instance_variable_get(:@iv)
rescue Ractor::IsolationError => e
messages << e.message
end
40.times do
begin
C.read
rescue Ractor::IsolationError => e
messages << e.message
end
end
messages == ["can not get unshareable values from instance variables of classes/modules created by another Ractor (@iv from C)"] * 41
end.value
RUBY
# setting an ivar on a shareable but unfrozen object is not allowed, by instance_variable_set
assert_equal "can't modify instance variables of a shareable Ractor", %q{
shared = Ractor.new{}
+15 -4
View File
@@ -1274,7 +1274,13 @@ class_ivar_set_ractor_check(VALUE klass, ID id)
{
if (rb_is_instance_id(id) && // check only normal ivars
UNLIKELY(!rb_class_owned_p(klass))) {
rb_raise(rb_eRactorIsolationError, "can not set instance variables of classes/modules created by another Ractor");
rb_raise(
rb_eRactorIsolationError,
"can not set instance variables of classes/modules "
"created by another Ractor (%"PRIsVALUE" from %"PRIsVALUE")",
rb_id2str(id),
rb_class_path(klass)
);
}
}
@@ -1627,7 +1633,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
"can not get unshareable values from instance variables of classes/modules "
"created by another Ractor (%"PRIsVALUE" from %"PRIsVALUE")",
rb_id2str(id),
obj
rb_class_path(obj)
);
}
}
@@ -1659,8 +1665,13 @@ rb_ivar_get_at(VALUE obj, attr_index_t index, ID id)
VALUE val = rb_imemo_fields_ptr(fields_obj)[index];
if (UNLIKELY(!rb_class_owned_p(obj)) && !rb_ractor_shareable_p(val)) {
rb_raise(rb_eRactorIsolationError,
"can not get unshareable values from instance variables of classes/modules created by another Ractor");
rb_raise(
rb_eRactorIsolationError,
"can not get unshareable values from instance variables of classes/modules "
"created by another Ractor (%"PRIsVALUE" from %"PRIsVALUE")",
rb_id2str(id),
rb_class_path(obj)
);
}
return val;