rb_fiber_scheduler_blocking_operation_wait can garbage collect blocking_operation incorrectly.#16908
Merged
ioquatix merged 1 commit intoruby:masterfrom May 10, 2026
Conversation
ea006ed to
97aa28a
Compare
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>
0c183c9 to
f647780
Compare
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>
f647780 to
7a16070
Compare
rb_fiber_scheduler_blocking_operation_wait can garbage collect blocking_operation incorrectly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
rb_fiber_scheduler_blocking_operation_wait(scheduler.c) createsblocking_operationas a C-localVALUE, then immediately callsrb_funcall(scheduler, :blocking_operation_wait, 1, blocking_operation).While we are not 100% sure that this a problem, the proposed fix seems correct and at worst is benign.