From: "altruby (Alt Ruby) via ruby-core" Date: 2026-11-03T17:16:34+00:00 Subject: [ruby-core:127316] [Ruby Bug#22499] SIGSEGV in vm_exec_core inside Monitor#synchronize (4.1.0preview1) Issue #22499 has been updated by altruby (Alt Ruby). This is the most minimal example I have found so far and it reproduces. It requires the JIT. But it also requires access to an LLM provider (i use deepseek) and a GitHub Personal Access token. If I can narrow it further or someone else can I'll update the issue. Confirmed on ``` ruby 4.1.0dev (2026-11-03T13:03:54Z master f2c2d46517) +PRISM [amd64-freebsd16.0] ``` **reproducation.rb** Set `$DEEPSEEK_API_KEY` and `$GITHUB_PAT`. Or `$OPENAI_API_KEY`, etc. ```ruby # frozen_string_literal: true require "bundler/inline" gemfile do source "https://rubygems.org" gem "llm.rb", github: "r-uby-dev/llm" end require "llm" require "monitor" require "securerandom" class Stream < LLM::Stream def on_tool_call(tool) if tool.mcp? puts "queue #{tool.name} on ractor" queue << tool.task(:ractor) else puts "queue #{tool.name} on thread" queue << tool.task(:thread) end end end llm = LLM.deepseek mcp = LLM::MCP.http( url: "https://api.githubcopilot.com/mcp", headers: {"Authorization" => "Bearer #{ENV['GITHUB_PAT']}"} ) agent = LLM::Agent.new(llm, tools: mcp.tools, stream: Stream.new) agent.talk "what's happening on ruby/ruby?" ``` **Run it** RUBY_YJIT_ENABLE=1 ruby reproduction.rb ---------------------------------------- Bug #22499: SIGSEGV in vm_exec_core inside Monitor#synchronize (4.1.0preview1) https://bugs.ruby-lang.org/issues/22499#change-119744 * Author: altruby (Alt Ruby) * Status: Open * ruby -v: 4.1.0preview1 (2026-10-11 master 2d08b5f42f) +PRISM [x86_64-freebsd16.0] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Summary ------- Ruby 4.1.0preview1 segfaults inside a Monitor#synchronize block while dispatching an instruction in vm_exec_core. The crash is reproducible and three core dumps show the same shape: the VM calls a code address that lives in a writable, non-executable page. Environment ----------- ``` ruby 4.1.0preview1 (2026-10-11 master 2d08b5f42f) +PRISM [x86_64-freebsd16.0] system FreeBSD 16.0-CURRENT, x86_64 built with ruby-install 0.10.1, rbenv, no JIT (yjit not enabled) app que worker process: "que ./config" (Foreman-launched; threads named worker.rb:41, locker.rb:180, task.rb:157, "AR Pool Reaper") gems pg (pg_ext.so), active_record, que ``` Reproduction ------------ Reproduces on every run of the worker, typically within minutes: ``` cd ~/projects/r.uby.dev RBENV_VERSION=4.1.0-preview1-debug \ RUBY_DEBUG=1 \ MALLOC_CONF=junk:true \ bundle exec foreman start ``` Cores are written to /var/coredumps/1001: * ruby.39219.core 22:53 * ruby.84525.core 23:08 * ruby.41759.core 23:23 Backtrace (debug build, -O0 -g3, gdb) ------------------------------------- ``` #2 ruby_default_signal (sig=11) at signal.c:421 #3 rb_bug_for_fatal_signal (sig=11, fmt="Segmentation fault at %p%s") at error.c:1171 #4 sigsegv (sig=11, info=0x826129830, ctx=0x8261294c0) at signal.c:977 #5 libthr.so.3 signal trampoline #7 #8 0x0000000827846220 in ?? () <- heap #9 0x000000084dcb9758 in ?? () <- the ec #10 0x0000000827945d60 in ?? () <- heap #11 vm_exec_core (ec=0x84dcb9758) at insns.def:864 864 JIT_EXEC(ec, val); #12 rb_vm_exec (ec=0x84dcb9758) at vm.c:2926 #13 invoke_iseq_block_from_c (...) at vm.c:1934 #14 invoke_block_from_c_bh (...) at vm.c:1948 #15 vm_yield_with_cref (...) at vm.c:1991 #16 vm_yield (...) at vm.c:1999 #17 rb_ec_yield (ec=0x84dcb9758, val=36) at vm_eval.c:1392 #18 do_ec_yield (_ec=35664926552) at thread_sync.c:635 #19 rb_ec_ensure (b_proc=do_ec_yield, e_proc_arg=monitor_sync_ensure) at eval.c:1177 #20 rb_monitor_synchronize (monitor=35684680768) at thread_sync.c:1514 ``` Control frame at the fault: ``` cfp = { pc = 0x847848b00, sp = 0x827846470, _iseq = 0x81b7683e0, self = 35684811280, ep = 0x827846468, block_code = 0x0, jit_return = 0x81d860126 } ``` Ruby-level frame recovered from the iseq location: ``` location = { pathobj = 34995792800, label = 34849573248, first_lineno = 52, node_id = 252, code_location = { beg_pos = {lineno = 52, column = 32}, end_pos = {lineno = 55, column = 11} } } ``` which is the body of Turn::Watch#follow in the application: ```ruby def follow(agent_id) start! queue = Queue.new synchronize { watchers(agent_id) << queue } <- crashed here queue end def synchronize(&b) MUTEX.synchronize(&b) end ``` Evidence of memory corruption ----------------------------- 1. The faulting instruction pointer is not in any loaded image. From the first core: mc_rip = 0x826449d80 memory region 0x826449d80 -> [0x8263a7000-0x8264f8000) rw- A writable, non-executable page: the VM called a value that is not code. 2. The iseq's path object is corrupt in the core. It reports len = 125 but its heap pointer reads back as 0x6f722f656d6f682f, which is the ASCII text "/home/ro" interpreted as an address: {basic = {flags = 7534522177132955941, klass = 34820395680}, len = 125, as = {heap = {ptr = 0x6f722f656d6f682f , ...}}} That run was started with MALLOC_CONF=junk:true, so freed objects are filled with junk; this looks like a freed or overwritten object. 3. Frame #7's return address is 0x00007ffffffff2e3 in the earlier cores - a sentinel, not a real return address. Context: ractors and llm.rb ---------------------------- The process runs llm.rb (gem "llm.rb", github: "r-uby-dev/llm") as its agent runtime, and the workload that triggers this spawns tool calls in child ractors. From the application's stream handler: ```ruby def on_tool_call(tool) write(tool: tool.name) stop_if_cancelled! queue << (tool.mcp? ? tool.task(:ractor) : tool.task(:async)) end ``` so MCP tool calls run on ractors while an agent turn is streaming, and the crashing path is the SSE route that follows a turn: ```ruby queue = Raven::Turn::Watch.follow(agent.id) if agent ``` That is the same process, same threads, same time as the ractor-spawned tool calls. Ractor machinery is visible in the first core - a scheduler thread parked in: ``` rb_native_cond_wait ractor_sched_deq (thread_sched.c:1388) nt_shared_loop (thread_sched.c:2074) nt_start (thread_sched.c:2021) ``` Caveat, stated plainly: the faulting thread is a worker thread inside Monitor#synchronize, not a ractor body, and we have not reproduced this without llm.rb or without ractors in the process. What we can say is that the crash happens in a process that is running ractor-spawned tool calls concurrently, on a 4.1 preview whose scheduler is Ractor-aware. Other threads ------------- None of the other 12 threads are in the faulting path. They are parked in syscalls: _umtx_op_err (condvars/mutexes), __sys_kevent, __sys_ppoll, plus pg_ext.so threads in pgconn_wait_for_notify waiting for a socket. They inherit the process SIGSEGV, they did not raise it. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/