diff options
| author | Joshua Young <djry1999@gmail.com> | 2025-10-25 10:10:06 +0530 |
|---|---|---|
| committer | Nobuyoshi Nakada <nobu.nakada@gmail.com> | 2025-10-27 07:59:43 +0900 |
| commit | 52ea222027c7315a5d66f0d7b4ab73c1cc0c7344 (patch) | |
| tree | b44a0371538f1f84ab9e891a4a2325d90326dbd2 | |
| parent | b66fbd59ae5eb4ef994e0a1c007caaf8fbd3c897 (diff) | |
Fix segfault when moving nested objects between ractors during GC
Fixes a segmentation fault when moving nested objects between ractors with GC stress enabled and YJIT.
The issue is a timing problem: `move_enter` allocates new object shells but leaves their contents uninitialized until `move_leave` copies the actual data. If GC runs between these steps (which GC stress makes likely), it tries to follow what appear to be object pointers but are actually uninitialized memory, encountering null or invalid addresses.
The fix zero-initializes the object contents immediately after allocation in `move_enter`, ensuring the GC finds safe null pointers instead of garbage data.
The crash reproduced most consistently with nested hashes and YJIT, likely because nested structures create multiple uninitialized objects simultaneously while YJIT's memory usage increases the probability of GC triggering during moves.
| -rw-r--r-- | ractor.c | 4 | ||||
| -rw-r--r-- | test/ruby/test_ractor.rb | 13 |
2 files changed, 16 insertions, 1 deletions
@@ -1940,8 +1940,10 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data) } else { VALUE type = RB_BUILTIN_TYPE(obj); + size_t slot_size = rb_gc_obj_slot_size(obj); type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0; - NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0); + NEWOBJ_OF(moved, struct RBasic, 0, type, slot_size, 0); + MEMZERO(&moved[1], char, slot_size - sizeof(*moved)); data->replacement = (VALUE)moved; return traverse_cont; } diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index c4154cd263..ccc551acef 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -99,6 +99,19 @@ class TestRactor < Test::Unit::TestCase RUBY end + def test_move_nested_hash_during_gc_with_yjit + original_gc_stress = GC.stress + assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }]) + GC.stress = true + hash = { foo: { bar: "hello" }, baz: { qux: "there" } } + result = Ractor.new { Ractor.receive }.send(hash, move: true).value + assert_equal "hello", result[:foo][:bar] + assert_equal "there", result[:baz][:qux] + RUBY + ensure + GC.stress = original_gc_stress + end + def test_fork_raise_isolation_error assert_ractor(<<~'RUBY') ractor = Ractor.new do |
