Skip to content

rb_fiber_scheduler_blocking_operation_wait can garbage collect blocking_operation incorrectly.#16908

Merged
ioquatix merged 1 commit intoruby:masterfrom
samuel-williams-shopify:bug/blocking-operation-gc
May 10, 2026
Merged

rb_fiber_scheduler_blocking_operation_wait can garbage collect blocking_operation incorrectly.#16908
ioquatix merged 1 commit intoruby:masterfrom
samuel-williams-shopify:bug/blocking-operation-gc

Conversation

@samuel-williams-shopify
Copy link
Copy Markdown
Contributor

@samuel-williams-shopify samuel-williams-shopify commented May 9, 2026

rb_fiber_scheduler_blocking_operation_wait (scheduler.c) creates blocking_operation as a C-local VALUE, then immediately calls rb_funcall(scheduler, :blocking_operation_wait, 1, blocking_operation).

test/tcp_socket.rb:48: [BUG] Segmentation fault at 0x0000000000000000
ruby 4.1.0dev (2026-05-09T13:08:44Z :detached: 56cd26f835) +PRISM [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0003 p:---- s:0010 e:000009 l:y b:---- r:(nil) CFUNC  :close
c:0002 p:0010 s:0006 e:000005 l:n b:---- r:(nil) BLOCK  test/tcp_socket.rb:48 [FINISH]
c:0001 p:---- s:0003 e:000002 l:y b:---- r:(nil) DUMMY  [FINISH]

-- Ruby level backtrace information ----------------------------------------
test/tcp_socket.rb:48:in 'block (4 levels) in <top (required)>'
test/tcp_socket.rb:48:in 'close'

-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 3
Note that the Fiber scheduler is enabled

-- Machine register context ------------------------------------------------
 RIP: 0x00007fc90791509b RBP: 0x00007fc8eb48fce0 RSP: 0x00007fc8ec645eb8
 RAX: 0x00007fc907e552e0 RBX: 0x00007fc8ec646010 RCX: 0x7465730078756e89
 RDX: 0x00007fc8ec645ee8 RDI: 0x7465730078756e69 RSI: 0x0000000000000d41
  R8: 0x0000000000000000  R9: 0x00007fc8ec645ee8 R10: 0x000000000000000c
 R11: 0x0000000000000010 R12: 0x0000000000000d41 R13: 0x00007fc8ec645ee8
 R14: 0x00007fc8eb48fcf8 R15: 0x00007fc907e552e0 EFL: 0x0000000000010206

-- C level backtrace information -------------------------------------------
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_print_backtrace+0x14) [0x7fc90799e59f] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_dump.c:1108
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_vm_bugreport) /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_dump.c:1456
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_bug_for_fatal_signal+0x109) [0x7fc907754f29] /home/runner/work/ruby-dev-builder/ruby-dev-builder/error.c:1140
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(sigsegv+0x46) [0x7fc9078cd6f6] /home/runner/work/ruby-dev-builder/ruby-dev-builder/signal.c:948
/lib/x86_64-linux-gnu/libc.so.6(0x7fc907245330) [0x7fc907245330]
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rbimpl_typeddata_get_data+0x4) [0x7fc90791509b] ./include/ruby/internal/core/rtypeddata.h:606
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(RTYPEDDATA_GET_DATA) ./include/ruby/internal/core/rtypeddata.h:615
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(managed_id_table_ptr) /home/runner/work/ruby-dev-builder/ruby-dev-builder/id_table.c:361
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_managed_id_table_lookup) /home/runner/work/ruby-dev-builder/ruby-dev-builder/id_table.c:402
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(vm_lookup_cc+0x131) [0x7fc90796b151] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_insnhelper.c:2138
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(vm_search_cc+0x46) [0x7fc90796f7e6] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_insnhelper.c:2187
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_vm_search_method_slowpath+0x5) [0x7fc90797050b] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_insnhelper.c:2214
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(vm_search_method_slowpath0) /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_insnhelper.c:2233
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(gccct_method_search_slowpath) /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_eval.c:418
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(gccct_method_search+0x72) [0x7fc90798db8a] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_eval.c:493
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_funcallv_scope) /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_eval.c:1063
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_funcallv) /home/runner/work/ruby-dev-builder/ruby-dev-builder/vm_eval.c:1084
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(RB_SPECIAL_CONST_P+0x0) [0x7fc9078ecc74] /home/runner/work/ruby-dev-builder/ruby-dev-builder/string.c:1859
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rbimpl_RB_TYPE_P_fastpath) ./include/ruby/internal/value_type.h:349
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(RB_TYPE_P) ./include/ruby/internal/value_type.h:379
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_obj_as_string_result) /home/runner/work/ruby-dev-builder/ruby-dev-builder/string.c:1866
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_obj_as_string) /home/runner/work/ruby-dev-builder/ruby-dev-builder/string.c:1860
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(ruby__sfvextra+0x6e) [0x7fc9078cf35e] /home/runner/work/ruby-dev-builder/ruby-dev-builder/sprintf.c:1126
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(BSD_vfprintf+0x28bc) [0x7fc9078d206c] /home/runner/work/ruby-dev-builder/ruby-dev-builder/vsnprintf.c:830
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(RB_FL_TEST_RAW+0x0) [0x7fc9078d2d06] /home/runner/work/ruby-dev-builder/ruby-dev-builder/sprintf.c:1166
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(RSTRING_PTR) ./include/ruby/internal/core/rstring.h:383
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(ruby_vsprintf0) /home/runner/work/ruby-dev-builder/ruby-dev-builder/sprintf.c:1168
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_enc_vsprintf+0x59) [0x7fc9078d3089] /home/runner/work/ruby-dev-builder/ruby-dev-builder/sprintf.c:1195
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_raise+0x9f) [0x7fc90775692f] /home/runner/work/ruby-dev-builder/ruby-dev-builder/error.c:3858
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(displaying_class_of+0x0) [0x7fc907756fe8] /home/runner/work/ruby-dev-builder/ruby-dev-builder/error.c:1335
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_unexpected_object_type) /home/runner/work/ruby-dev-builder/ruby-dev-builder/error.c:1335
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rbimpl_check_typeddata+0xf) [0x7fc9078c236f] ./include/ruby/internal/core/rtypeddata.h:731
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(get_blocking_operation) /home/runner/work/ruby-dev-builder/ruby-dev-builder/scheduler.c:123
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_fiber_scheduler_blocking_operation_wait) /home/runner/work/ruby-dev-builder/ruby-dev-builder/scheduler.c:1104
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_nogvl+0x4af) [0x7fc90792388f] /home/runner/work/ruby-dev-builder/ruby-dev-builder/thread.c:1604
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(fptr_finalize_flush+0x231) [0x7fc9077aeb81] /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5523
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(free_io_buffer+0x0) [0x7fc9077af095] /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5635
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(fptr_finalize) /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5636
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_io_fptr_cleanup) /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5648
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(io_close_fptr) /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5765
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_io_close) /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5783
/home/runner/.rubies/ruby-head/lib/libruby.so.4.1(rb_io_close_m+0x28) [0x7fc9077af498] /home/runner/work/ruby-dev-builder/ruby-dev-builder/io.c:5830
... snip ...

While we are not 100% sure that this a problem, the proposed fix seems correct and at worst is benign.

@samuel-williams-shopify samuel-williams-shopify force-pushed the bug/blocking-operation-gc branch from ea006ed to 97aa28a Compare May 9, 2026 13:43
samuel-williams-shopify added a commit to samuel-williams-shopify/ruby that referenced this pull request May 10, 2026
…eration_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, blocking_operation may not be reachable via the
conservative GC scan of the suspended fiber's C stack.

rb_gc_register_address pins blocking_operation in the global GC root list,
which is always walked regardless of fiber state. The address is kept
registered through the last implicit use of the VALUE — including all accesses
via the raw  C pointer derived from it — so that a compacting GC
cannot move the object and leave  dangling.

Confirmed by reproducing the crash in io-event CI:
  ./configure --enable-shared --disable-install-doc --enable-yjit
See: socketry/io-event#171
     ruby#16908

Co-authored-by: Cursor <cursoragent@cursor.com>
samuel-williams-shopify added a commit to samuel-williams-shopify/ruby that referenced this pull request May 10, 2026
…eration_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, blocking_operation may not be reachable via the
conservative GC scan of the suspended fiber's C stack.

rb_gc_register_address pins blocking_operation in the global GC root list,
which is always walked regardless of fiber state. The address is kept
registered through the last implicit use of the VALUE — including all accesses
via the raw  C pointer derived from it — so that a compacting GC
cannot move the object and leave  dangling.

Confirmed by reproducing the crash in io-event CI:
  ./configure --enable-shared --disable-install-doc --enable-yjit
See: socketry/io-event#171
     ruby#16908

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract the raw operation pointer before rb_funcall so it is obtained
while the GVL is held and no fiber switch has occurred yet.

Place RB_GC_GUARD(blocking_operation) after the last implicit use of the
VALUE — all accesses via the derived `operation` pointer — so the
compiler cannot treat blocking_operation as dead before this point,
keeping it reachable as a GC root through rb_funcall and through all
subsequent uses of the raw pointer.

See: socketry/io-event#172
Co-authored-by: Cursor <cursoragent@cursor.com>
@samuel-williams-shopify samuel-williams-shopify changed the title [Bug] rb_fiber_scheduler_blocking_operation_wait does not GC-root blocking_operation during rb_funcall rb_fiber_scheduler_blocking_operation_wait can garbage collect blocking_operation incorrectly. May 10, 2026
@ioquatix ioquatix merged commit 8351378 into ruby:master May 10, 2026
105 of 107 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants