Skip to content

Commit ea006ed

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 implementation calls rb_fiber_scheduler_block (e.g. a worker-pool scheduler). When the fiber is suspended, the C frame of rb_fiber_scheduler_blocking_operation_wait is no longer active. In optimised builds, may be held only in a machine register that the conservative GC does not scan for suspended fibers, allowing the object to be collected before get_blocking_operation() is called at line 1104. RB_GC_GUARD(blocking_operation) after rb_funcall forces the compiler to keep the VALUE on the stack (via a volatile read), ensuring it is always reachable as a GC root regardless of register allocation. Confirmed by GC.disable workaround in socketry/io-event#170 which prevents the crash by stopping GC during the blocking_operation_wait call. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9072aa9 commit ea006ed

1 file changed

Lines changed: 8 additions & 0 deletions

File tree

scheduler.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,14 @@ 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 implementation causes a fiber switch during the above call
1104+
// (e.g. via rb_fiber_scheduler_block in a worker-pool implementation), the
1105+
// calling fiber's C frame is suspended. With optimised builds, `blocking_operation`
1106+
// may only be in a machine register that is not saved/scanned by the conservative GC,
1107+
// allowing the object to be collected. RB_GC_GUARD forces the compiler to keep the
1108+
// VALUE on the stack so the GC can always find it as a root.
1109+
RB_GC_GUARD(blocking_operation);
1110+
11031111
// Get the operation data to check if it was executed
11041112
rb_fiber_scheduler_blocking_operation_t *operation = get_blocking_operation(blocking_operation);
11051113
rb_atomic_t current_status = RUBY_ATOMIC_LOAD(operation->status);

0 commit comments

Comments
 (0)