Bug #19154
open
Specify require and autoload guarantees in ractors
Added by fxn (Xavier Noria) almost 2 years ago.
Updated 8 months ago.
Description
Given a file c.rb
:
class C
end
the following script:
r1 = Ractor.new do
require './c.rb'
end
r2 = Ractor.new do
require './c.rb'
end
r1.take
r2.take
raises:
% ruby -v foo.rb
ruby 3.2.0preview3 (2022-11-27) [x86_64-darwin22]
foo.rb:1: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
#<Thread:0x000000010fee2928 run> terminated with exception (report_on_exception is true):
#<Thread:0x00000001102acfe0 run> terminated with exception (report_on_exception is true):
<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:164:in `ensure in require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from <internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:167:in `require'
from foo.rb:6:in `block in <main>'
<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:37:in `require'<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:164:in `ensure in require': : can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from <internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:167:in `require'
can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError) from foo.rb:2:in `block in <main>'
<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:37:in `require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from foo.rb:2:in `block in <main>'
from foo.rb:6:in `block in <main>'
<internal:ractor>:698:in `take': thrown by remote Ractor. (Ractor::RemoteError)
from foo.rb:9:in `<main>'
<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:164:in `ensure in require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from <internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:167:in `require'
from foo.rb:2:in `block in <main>'
<internal:/Users/fxn/.rbenv/versions/3.2.0-preview3/lib/ruby/3.2.0+3/rubygems/core_ext/kernel_require.rb>:37:in `require': can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
from foo.rb:2:in `block in <main>'
Would it be possible to have documentation about their interaction?
This is important also to understand autoloading within ractors, since constant references may trigger require
calls.
Related issues
1 (1 open — 0 closed)
Besides this particular error, I am wondering about concurrency guarantees in parallel requires to the same file, or parallel references to a constant for which there is an autoload.
I've taken a look at this a bit and got this working:
require 'rubygems' # get rubygems version of require
rs = []
100.times do
rs << Ractor.new do
require './c.rb'
end
end
rs.each(&:take)
p C
In file c.rb, it's
class C
puts "GOT HERE"
end
And it works correctly, only printing "GOT HERE" once and defining C once.
I've had to make several small changes to the interpreter to enable setting ivars and class variables on classes and modules that are marked shareable
with a new API. Otherwise the rubygems internals weren't allowed to run in non-main ractors.
I added the API of Ractor.force_shareable!(obj)
that forces the shareable flag on the object and all nested objects that it contains. This is necessary for certain
objects like monitor objects and other deeply nested objects. As long as there's a mutex somewhere protecting these mutations it's safe. There is a mutex around rubygems require. I only had to add Ractor.force_shareable!
to 2 or 3 places in the rubygems code and requiring gems now works across Ractors. I didn't add any tests but I might add some if other people are interested in this work.
Also I'll take a look at autoload
. I know for some, forcing objects to be marked as shareable is a bit unsavory but as long as you know what you're doing it should be okay, like the rust escape hatch of unsafe
.
I'll make a proof of concept pull request soon.
I've also got parallel references to constant for which there is autoload working, with a few tweaks to the VM needed.
rs = []
autoload :C, './test3.rb'
1000.times do
rs << Ractor.new do
C
C
C
end
end
p rs.map(&:take)
For example, this now works.
I guess the next thing would be allowing calls to autoload
itself in the Ractors, but I don't think this is going to be hard.
luke-gru (Luke Gruber) wrote in #note-2:
As long as there's a mutex somewhere protecting these mutations it's safe. There is a mutex around rubygems require. I only had to add Ractor.force_shareable!
to 2 or 3 places in the rubygems code and requiring gems now works across Ractors. I didn't add any tests but I might add some if other people are interested in this work.
That's completely unsafe. You can look at the implementation of Mutex and you can see it relies on the GVL. With Ractors there is not a single GVL, so simply said Mutex (and Monitor too) does not prevent concurrent access with Ractors.
That's one big reason why one cannot share a Mutex/Monitor.
Also sharing a Mutex/Monitor would be a clear violation of the Ractor programming model.
I think require
/autoload
in a Ractor should simply be forbidden (raise an exception), see #17420.
It cannot work since it needs access to $LOADED_FEATURES, and that's owned by the main Ractor and is unsafe to access for any other Ractor (#17420).
- Related to Bug #17420: Unsafe mutation of $" when doing non-RubyGems require in Ractor added
I think require/autoload in a Ractor should simply be forbidden.
That would be a resolution for this ticket.
- Assignee set to ko1 (Koichi Sasada)
Ah okay, I see thanks Eregon, I was confused b/t VM lock and GVL.
There could be cross-ractor mutexes and monitors if they locked the VM lock, no? Then if rubygems used these, then it would be safe?
I'm not saying it's a good idea, just that it's possible :)
I imagine it's a bit of a pain to have to eager autoload everything before you start a Ractor, but if that's what needs to be done then
c'est la vie.
I imagine it's a bit of a pain to have to eager autoload everything before you start a Ractor, but if that's what needs to be done then
c'est la vie.
This is a delicate point, in my view, because you cannot eager load what you do not control. And you do not control when your Ractor needs to invoke 3rd party APIs.
- Status changed from Open to Assigned
Also available in: Atom
PDF
Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0