[pull] master from ruby:master - #1315
Merged
Merged
Conversation
The test added by 6f176c8 warmed two ractors before dropping RLIMIT_NPROC, assuming every subsequent Ractor.new would attempt to spawn a shared native thread and raise ThreadError. However, native_thread_check_and_create_shared widens the pool only while snt_cnt < max_cpu, so on 2-vCPU CI machines (rubyci ubuntu-no-yjit, ubuntu2204) the two warm ractors already saturated the pool: pthread_create was never attempted, nothing raised, and the test failed with "0 of 20 raised". Warm a single ractor instead. snt_cnt (1) then stays below max_cpu on any host with two or more CPUs, so every attempt deterministically reaches the failing pthread_create, while one parked shared native thread is still around to exercise the publish-before-widen race the test guards against. On a single-CPU host widening can never be attempted, so accept zero raises as well; a mixed count still fails, which is what a broken snt_cnt rollback would produce. Verified with RUBY_MAX_CPU=1/2/unset: the new test passes 5/5 in each configuration on master, and still segfaults 5/5 on the pre-fix code (3c66369), so the original regression remains covered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reuse downloaded files and source repositories across runner platforms to reduce network-dependent fetches.
Since "Ractor: the dying thread collects its own objspace post-mortem" (e768a87), a dying Ractor thread runs rb_ractor_postmortem_free() after thread_sched_to_dead() (its GVL is gone) and after rb_ractor_living_threads_remove() -> vm_remove_ractor(), which unlinks the Ractor, decrements vm->ractor.cnt and signals the terminate waiter. From that point nothing holds the main Ractor back, but the epilogue still frees through VM-global state: rb_fiber_free_body() -> cont_free() takes jit_cont_lock and returns the stack to the shared fiber pool, and both frees update the main objspace's malloc accounting. The coroutine epilogue has the same hazard and is covered: coroutine_thread_terminated() bumps vm->ractor.sched.winding_cnt and ruby_vm_destruct() spins in rb_thread_sched_wait_winding() until the reclaim is done. The non-coroutine path -- the ordinary Ractor.new-spawned dedicated thread -- was not counted, so a main Ractor that exits while the dying thread is still in the epilogue tears that state down underneath it. Count the epilogue in winding_cnt as well. The increment happens before the Ractor stops being counted, so there is no window where the VM sees neither the Ractor nor the epilogue. vm->ractor.sched exists only on the pthread backend, so the count gets the same shape its waiter has: real work in thread_pthread.c, a no-op in thread_win32.c and thread_none.c, where rb_thread_sched_wait_winding never waits either. Reproduced deterministically by delaying the epilogue (50ms) and the process exit (400ms) on top of e768a87: 6/6 runs of `Ractor.new{ 1 }.value` died with [BUG] pthread_mutex_lock: Invalid argument (EINVAL) rb_native_mutex_lock (thread_pthread.c:128) jit_cont_free (cont.c:1419) cont_free (cont.c:1185) rb_ractor_postmortem_free (ractor.c:683) thread_start_func_2 (thread.c:851) i.e. the epilogue locking jit_cont_lock after rb_jit_cont_finish() destroyed it. With this change the same harness is clean 6/6, and test/ruby/test_ractor.rb, test/objspace/test_ractor.rb, test_thread.rb and the strscan/digest/io-wait/io-console Ractor tests pass. Without the injected delay the window is never entered on x86_64 (0 hits in 15 runs of those test files, instrumented), which fits the failures being confined to the slow emulated runners. This is the most likely cause of the heap corruption CI has been reporting since e768a87 landed (2026-08-10 18:33 UTC): every failing case is a child process that runs one Ractor and exits. Error: TestIOWaitInRactor#test_ractor [test/io/wait/test_ractor.rb:9]: pid 68552 exit 0 | free(): corrupted unsorted chunks 1. [2/2] Assertion for "stderr" | <[]> expected but was | <["free(): corrupted unsorted chunks"]>. (also TestStringScannerRactor#test_ractor, TestDigestRactor::*, TestIOConsoleInRactor#test_ractor -- 16 of the 63 master runs since 2026-08-10, none in the 87 runs before it) TestObjSpaceRactor#test_copy_finalizer [test/objspace/test_ractor.rb:57]: ==96541==ERROR: AddressSanitizer: heap-use-after-free on address 0x520000000088 WRITE of size 8 at 0x520000000088 thread T5 CI: https://github.com/ruby/ruby/actions/runs/31886577248/job/95016642538 CI: https://github.com/ruby/ruby/actions/runs/31427322788 (ASAN, x86_64) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )