Bug #19369
openSmall corner-case issue that breaks Ractor isolation: change cloned object from another thread
Description
I was looking into how objects are traversed for deep cloning and I came up with a way to break it. I don't think it'll ever happen in real life so it's not really an issue, just
an interesting case. Run with warnings disabled.
obj = Object.new
p "unshareable obj:", obj
UNSHAREABLE = obj
GO = false
SET = false
class Object
attr_accessor :unshareable
def initialize_clone(orig)
puts "Clone called for #{orig.inspect}, self = #{self.inspect}"
_self = self
if orig == UNSHAREABLE
t = Thread.new do
puts "In thread"
Thread.pass until GO
puts "Setting unshareable!"
# this must be done in separate thread to bypass object traversal deep-cloning
_self.unshareable = UNSHAREABLE
Object.const_set(:SET, true)
end
end
super(orig)
end
end
r = Ractor.new(obj) do |o|
puts "from r#{Ractor.current.object_id} obj #{o.inspect}"
GO = true
loop until SET
p "from ractor, got unshareable:", o.unshareable
end
r.take
Updated by luke-gru (Luke Gruber) almost 2 years ago
If you wanted to fix this one way would be to disable thread creation in the current ractor (main in this case) while deep-cloning in Ractor.new
or Ractor#send
. Another idea would be to not call initialize_clone
during deep-cloning for Ractor.new
or Ractor#send
, but that would be a negative change IMO.
BTW, forgot to mention to run the above ruby code with warnings disabled.
Updated by luke-gru (Luke Gruber) almost 2 years ago
I made a patch for this here: https://github.com/luke-gru/ruby/commit/ad760be949c2a35aac71ac0ff8dea4ec3169f589. I'll send PR if it's an acceptable solution.
Edit: My commit doesn't actually fix this in the proper way, because you could do:
def initialize_clone(orig)
if orig == UNSHAREABLE
begin
Thread.new { }
rescue ThreadError
t = Thread.new do
"..."
end
end
end
end
A proper is a bit tricky.
Updated by hsbt (Hiroshi SHIBATA) almost 2 years ago
- Status changed from Open to Assigned
- Assignee set to ko1 (Koichi Sasada)