Park the calling thread in rb_ractor_sched_barrier_join - #17804
Closed
ko1 wants to merge 3 commits into
Closed
Conversation
ractor_sched_barrier_join_wait_locked was passed cr->threads.sched.running, but the joining (calling) thread is not always sched.running: a terminating or blocking thread can join the barrier while another thread of the same Ractor -- e.g. one blocked in IO with live frames on its coroutine stack -- is sched.running. RB_VM_SAVE_MACHINE_CONTEXT(th) then runs on the caller's stack but writes into the other thread's ec, so machine.stack_end ends up pointing into a foreign stack. The barrier GC then scans an invalid range for the blocked thread and misses every reference living only on its machine stack; e.g. the STR_TMPLOCK'd buffer of a blocked IO#read gets swept and is used after free when the read completes (ASAN use-after-poison in rb_str_unlocktmp, 14/20 -> 0/20 with this fix). Pass GET_THREAD() so the join saves and parks the calling thread itself. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An uncounted joiner (a terminating or blocking thread) can reach the scheduler lock after the barrier it observed under the VM lock has already completed. ractor_sched_barrier_join_wait_locked then captures the NEXT serial and sleeps until the next barrier completes -- a hang when no further barrier ever runs (observed as test timeouts with the GC.compact x pipe-read stress), and VM_ASSERT(barrier_waiting) fires on assertion-enabled builds. Re-check the barrier under the scheduler lock and skip joining when it is stale; the caller re-evaluates. Also count a joiner by its own membership in the VM running set instead of the per-Ractor sched.is_running flag: the flag can already be true for a designated successor while the terminating caller has left the set, pushing barrier_waiting_cnt past running_cnt-1 (assertion), or completing the barrier while a genuinely running thread has not joined. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ractor_check_blocking evaluated the will-block condition before RB_VM_LOCKING(). Acquiring the VM lock can join a Ractor barrier and take arbitrarily long; meanwhile a sibling thread can resume (or a designated successor can start running), so the stale decision then marks a Ractor that has running threads as blocking. rb_vm_barrier on such a Ractor fails VM_ASSERT(rb_ractor_status_p(cr, ractor_running)), and vm->ractor.blocking_cnt drifts. Re-evaluate the condition under the VM lock; for a detached caller (a terminating thread whose scheduler slot was already handed over, via rb_ractor_living_threads_remove) additionally skip the transition when a successor thread is running. Pair the release side by keying the blocking->running transition on the status instead of on count arithmetic, so a skipped transition is never "undone" (vm->ractor.blocking_cnt underflow). With this and the previous commit, the 8-Ractor pipe-read x GC.compact stress runs 60/60 with no assertion failures, no hangs and no use-after-free (previously ~40% failed), and the bootstraptest added by the barrier-join fix passes 12/12 (previously timed out). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 11, 2026
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.
ractor_sched_barrier_join_wait_locked was passed cr->threads.sched.running, but the joining (calling) thread is not always sched.running: a terminating or blocking thread can join the barrier while another thread of the same Ractor -- e.g. one blocked in IO with live frames on its coroutine stack -- is sched.running. RB_VM_SAVE_MACHINE_CONTEXT(th) then runs on the caller's stack but writes into the other thread's ec, so machine.stack_end ends up pointing into a foreign stack. The barrier GC then scans an invalid range for the blocked thread and misses every reference living only on its machine stack; e.g. the STR_TMPLOCK'd buffer of a blocked IO#read gets swept and is used after free when the read completes (ASAN use-after-poison in rb_str_unlocktmp, 14/20 -> 0/20 with this fix).
Pass GET_THREAD() so the join saves and parks the calling thread itself.