Project

General

Profile

Actions

Bug #19154

open

Specify require and autoload guarantees in ractors

Added by fxn (Xavier Noria) about 2 years ago. Updated 9 months ago.

Status:
Assigned
Target version:
-
ruby -v:
ruby 3.2.0preview3 (2022-11-27) [x86_64-darwin22]
[ruby-core:111027]

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 open0 closed)

Related to Ruby master - Bug #17420: Unsafe mutation of $" when doing non-RubyGems require in RactorAssignedko1 (Koichi Sasada)Actions

Updated by fxn (Xavier Noria) about 2 years ago

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.

Updated by luke-gru (Luke Gruber) almost 2 years ago

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.

Updated by luke-gru (Luke Gruber) almost 2 years ago

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.

Updated by Eregon (Benoit Daloze) almost 2 years ago

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).

Actions #5

Updated by Eregon (Benoit Daloze) almost 2 years ago

  • Related to Bug #17420: Unsafe mutation of $" when doing non-RubyGems require in Ractor added

Updated by fxn (Xavier Noria) almost 2 years ago

I think require/autoload in a Ractor should simply be forbidden.

That would be a resolution for this ticket.

Updated by Eregon (Benoit Daloze) almost 2 years ago

  • Assignee set to ko1 (Koichi Sasada)

Updated by luke-gru (Luke Gruber) almost 2 years ago

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.

Updated by fxn (Xavier Noria) almost 2 years ago

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.

Actions #10

Updated by hsbt (Hiroshi SHIBATA) 9 months ago

  • Status changed from Open to Assigned
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0