From e22fc73c66b478a19930788b7d23c6ea48b4bdec Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 13 Jun 2025 14:25:42 +0200 Subject: Fix a race condition in object_id for shareable objects If an object is shareable and has no capacity left, it isn't safe to store the object ID in fields as it requires an object resize which can't be done unless all field reads are synchronized. In this very specific case we create the object_id in advance, before the object is made shareable. --- test/ruby/test_object_id.rb | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) (limited to 'test/ruby') diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb index 44421ea256..018cc81496 100644 --- a/test/ruby/test_object_id.rb +++ b/test/ruby/test_object_id.rb @@ -198,3 +198,80 @@ class TestObjectIdTooComplexGeneric < TestObjectId end end end + +class TestObjectIdRactor < Test::Unit::TestCase + def test_object_id_race_free + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + N = 10_000 + objs = Ractor.make_shareable(N.times.map { MyClass.new }) + results = 4.times.map{ + Ractor.new(objs) { |objs| + vars = [] + ids = [] + objs.each do |obj| + vars << obj.a << obj.b << obj.c + ids << obj.object_id + end + [vars, ids] + } + }.map(&:value) + assert_equal 1, results.uniq.size + end; + end + + def test_external_object_id_ractor_move + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + obj = Ractor.make_shareable(MyClass.new) + object_id = obj.object_id + obj = Ractor.new { Ractor.receive }.send(obj, move: true).value + assert_equal object_id, obj.object_id + end; + end + + def test_object_id_race_free_with_stress_compact + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + N = 20 + objs = Ractor.make_shareable(N.times.map { MyClass.new }) + + GC.stress = true + GC.auto_compact = true if GC.respond_to?(:auto_compact=) + + results = 4.times.map{ + Ractor.new(objs) { |objs| + vars = [] + ids = [] + objs.each do |obj| + vars << obj.a << obj.b << obj.c + ids << obj.object_id + end + [vars, ids] + } + }.map(&:value) + assert_equal 1, results.uniq.size + end; + end +end -- cgit v1.2.3