Skip to content

Commit 52ea222

Browse files
joshuay03nobu
authored andcommitted
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.
1 parent b66fbd5 commit 52ea222

File tree

2 files changed

+16
-1
lines changed

2 files changed

+16
-1
lines changed

ractor.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,8 +1940,10 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data)
19401940
}
19411941
else {
19421942
VALUE type = RB_BUILTIN_TYPE(obj);
1943+
size_t slot_size = rb_gc_obj_slot_size(obj);
19431944
type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0;
1944-
NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0);
1945+
NEWOBJ_OF(moved, struct RBasic, 0, type, slot_size, 0);
1946+
MEMZERO(&moved[1], char, slot_size - sizeof(*moved));
19451947
data->replacement = (VALUE)moved;
19461948
return traverse_cont;
19471949
}

test/ruby/test_ractor.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,19 @@ def initialize(*)
9999
RUBY
100100
end
101101

102+
def test_move_nested_hash_during_gc_with_yjit
103+
original_gc_stress = GC.stress
104+
assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }])
105+
GC.stress = true
106+
hash = { foo: { bar: "hello" }, baz: { qux: "there" } }
107+
result = Ractor.new { Ractor.receive }.send(hash, move: true).value
108+
assert_equal "hello", result[:foo][:bar]
109+
assert_equal "there", result[:baz][:qux]
110+
RUBY
111+
ensure
112+
GC.stress = original_gc_stress
113+
end
114+
102115
def test_fork_raise_isolation_error
103116
assert_ractor(<<~'RUBY')
104117
ractor = Ractor.new do

0 commit comments

Comments
 (0)