Skip to content

Commit ac1e07d

Browse files
Fix GC safety of blocking_operation in rb_fiber_scheduler_blocking_operation_wait
rb_funcall(scheduler, :blocking_operation_wait, 1, blocking_operation) can cause a fiber switch if the scheduler calls rb_fiber_scheduler_block. When the fiber is suspended, the C frame of rb_fiber_scheduler_blocking_operation_wait is no longer active. In optimised builds (-O3 --enable-shared), blocking_operation may be held only in a machine register not saved/scanned by the conservative GC, allowing it to be collected. get_blocking_operation() at line 1104 then reads freed/reused memory, crashing with rb_unexpected_object_type. Confirmed by reproducing the crash using: ./configure --enable-shared --disable-install-doc --enable-yjit cppflags=-DENABLE_PATH_CHECK=0 RB_GC_GUARD(blocking_operation) after rb_funcall forces the compiler to keep the VALUE on the stack (volatile read), ensuring the GC always finds it. See: socketry/io-event#170 socketry/io-event#171 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 97aa28a commit ac1e07d

1 file changed

Lines changed: 7 additions & 0 deletions

File tree

scheduler.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,13 @@ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*functi
11001100

11011101
VALUE result = rb_funcall(scheduler, id_blocking_operation_wait, 1, blocking_operation);
11021102

1103+
// If the scheduler causes a fiber switch during the call above (e.g. via
1104+
// rb_fiber_scheduler_block in a worker-pool), the calling fiber's C frame
1105+
// is suspended. With optimised builds, `blocking_operation` may only be in
1106+
// a machine register not scanned by the conservative GC, allowing collection.
1107+
// RB_GC_GUARD forces it onto the stack so it is always reachable as a root.
1108+
RB_GC_GUARD(blocking_operation);
1109+
11031110
// Get the operation data to check if it was executed
11041111
rb_fiber_scheduler_blocking_operation_t *operation = get_blocking_operation(blocking_operation);
11051112
rb_atomic_t current_status = RUBY_ATOMIC_LOAD(operation->status);

0 commit comments

Comments
 (0)