Bug #22291
openInstance variables should be forbidden on Ractor-shareable objects
Description
Usually Ractor-shareable objects are frozen, but we have a number of objects which are shareable even though they aren't frozen (and likely will have more in the future from C extensions).
- Classes/Modules (ignored in this issue, they have special ivar handling)
RactorRactor::Port- Isolated procs/lambdas from
Ractor.shareable_proc ENV
These are set shareable without the usual checks that all referenced objects are also shareable. This causes a potential issue, because these objects could have unshareable instance variables, breaking the Ractor invariant (potentially causing race conditions and segmentation faults).
Currently, we attempt to avoid this by having a check on ivars read that forbids reading these from a non-main Ractor
R = Ractor.new {}
R.instance_variable_set(:@iv, +"mutable")
Ractor.new { R.instance_variable_get(:@iv) }.value
#=> Ractor::IsolationError: can not access instance variables of shareable objects from non-main Ractors
This is detected by checking on ivar read for objects which are shareable but not frozen.
However this isn't sound because most of these unshareable objects can be frozen, turning the check off.
R = Ractor.new {}
R.instance_variable_set(:@iv, +"mutable")
R.freeze
Ractor.new { R.instance_variable_get(:@iv) }.value
# => no error, created Ractor got access to an object it shouldn't
(weird quirk: ENV#freeze raises, but you can Kernel.instance_method(:freeze).bind_call(ENV) so it's still an issue)
I propose that we fix this by forbidding instance variable writes to objects which are frozen. This removes the need to check on read and maintains the Ractor invariant.
R = Ractor.new {}
R.instance_variable_set(:@iv, 123)
#=> Ractor::IsolationError: can not set instance variables of shareable Ractor objects
Ractor.new {
R.instance_variable_get(:@foo) # => always nil, because ivars are forbidden
}
These objects behave essentially as though they're frozen, but only the IVs are frozen.
This is an important issue to solve both for correctness, and because I want us to decide the semantics so that they can be implemented in ZJIT. Currently ZJIT won't compile ivar reads on multi-ractor mode because of this issue.
(I will link a patch implementing this shortly)
Updated by Eregon (Benoit Daloze) about 4 hours ago
jhawthorn (John Hawthorn) wrote:
I propose that we fix this by forbidding instance variable writes to objects which are frozen.
... to objects which are shareable, right?
(it's already forbidden on frozen objects)
I think this makes sense but it makes me wonder, should making these objects shareable also freeze them? (e.g. for Ractor, Ractor::Port, Ractor.shareable_proc, ENV)
That would simplify the model.
Classes/Modules are already special so that would be the exception we probably need to keep.
IIRC @ko1 (Koichi Sasada) once said that Ractor instances being shareable yet not frozen was intentional to allow ivars on them.