Issue #21953 has been updated by tikkss (Tsutomu Katsube). I describe the use case of test-unit. Now, we are implementing `Ractor` based parallel test runner. The tests are run on non-main ractors. We have met a lot of `Ractor::IsolationError`. Each time, We have tried using `freeze` or `make_shareable`, but since writing to class variables or class instance variables within a non-main ractor is not permitted, that code simply won't work. Therefore, by allowing writes to these class variables and class instance variables within a `Ruby::Box` created inside a non-main ractor, the above code will work. This is also effective from the perspective of test isolation. You might argue, "Why not run it in a multi-process?" However, compared to multi-process, ractor has advantages in terms of low overhead and memory usage. Details follow below. A main ractor has the following roles: * Receives a ready signal from non-main ractors then sends a test name to non-main ractors * Receives a test result from non-main ractors then collects it * Receives an event from non-main ractors then emits it Non-main ractors have the following roles: * Collect an entire test suite on each non-main ractors * Send a ready signal to a main ractor * Receive a test name from a main ractor then search a test in an entire test suite and **run it** * Send a test result to a main ractor * Send an event to a main ractor A main ractor and non-main ractors have a 1:N relationship. Communication between a main ractor and non-main ractors is handled the following ways: * A shared `Ractor::Port` object creating in a main ractor * A Default `Ractor::Port` object on each non-main ractors ---------------------------------------- Feature #21953: Allow accessing unshareable objects within a Ractor-local Ruby Box https://bugs.ruby-lang.org/issues/21953#change-117130 * Author: tikkss (Tsutomu Katsube) * Status: Open ---------------------------------------- ### Status Currently, non-main ractors prohibit access to the following objects to prevent data races: * Global variables * Class variables * Unshareable class instance variables * Unshareable constants ### Proposal I would like to propose that allow reading/writing unshareable objects inside a Ruby Box created in a non-main ractor: ```ruby # lib/x.rb class X # can write unshareable objects XXX = "1" @@cvar = "1" @ivar = "1" class << self def cvar; @@cvar; end def ivar; @ivar; end end end # can read unshareable objects $LOAD_PATH # => returns $LOAD_PATH includes lib directory X.cvar # => "1" X.ivar # => "1" X::XXX # => "1" # main.rb Ractor.new do local_box = Ruby::Box.new local_box.eval <<~RUBY base_dir = File.expand_path(File.dirname(__FILE__)) lib_dir = File.join(base_dir, "lib") # can write unshareable objects $LOAD_PATH.unshift(lib_dir) RUBY local_box.require("x") x = local_box::X.new end.join ``` Ruby Box can isolate global/class variables, class/module definitions from other boxes. A Ruby Box created inside a non-main ractor cannot be accessed from other ractor. If that is the case, wouldn’t it be fine to access unshareable objects inside that box? So I would like to propose that allow reading/writing unshareable objects inside a Ruby Box created in a non-main ractor. ### Background We are working on implementing a Ractor based parallel test runner for the test-unit gem. Ractor is a great for parallel processing. However, many existing libraries still rely on class variables or class instance variables for configuration. Ideally, we should reduce the use of class variables or class instance variables. Currently, we tried fixing several non-shareable objects, but we could not resolve all of them yet. We will continue working on this issue, but we are also exploring other approaches. This is the idea begind this proposal. I think the work needed to make objects shareable when running exisiting libraries with Ractor can be reduced. ### FAQ Q: Can we create a Ruby Box inside a non-main ractor? A: Yes: ```ruby Ractor.new {Ruby::Box.new}.join ``` Q: Is a Ruby Box created in a non-main ractor truly inaccessible from other ractor? A: No. I'm not sure if it's intentional, but a Ruby Box is a shareable object. Also, it can be accessed from the main Ractor by using `Ractor#value`: ```ruby Ractor.shareable?(Ruby::Box.new) # => true ``` ```ruby Ractor.new {Ruby::Box.new}.value # => #<Ruby::Box:3,user,optional> ``` To implement this proposal, Ruby Box may need to be an unshareable object, and passing it with `Ractor#value` may need to be disallowed. -- https://bugs.ruby-lang.org/