Project

General

Profile

Actions

Bug #22242

closed

SEGV in method dispatch (`vm_call_iseq_setup_kwparm_nokwarg` / `def_iseq_ptr`) on Ruby 4.0.6 — Rails CI

Bug #22242: SEGV in method dispatch (`vm_call_iseq_setup_kwparm_nokwarg` / `def_iseq_ptr`) on Ruby 4.0.6 — Rails CI

Added by yahonda (Yasuo Honda) 24 days ago. Updated 9 days ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux]
[ruby-core:126374]

Description

Recently Rails CI sometimes got SEGV.

Steps to reproduce

Run the Rails (rails/rails main branch) ActiveRecord test suite on Ruby 4.0.6, as Rails CI
does. For example:

$ git clone https://github.com/rails/rails.git
$ cd rails && bundle install
$ cd activerecord
$ bundle exec rake sqlite3_mem:test   # also reproduced with trilogy:test and mysql2:test

The crash is intermittent (a small percentage of Rails CI runs) and the crashing test is
different every time, so it may take many runs to reproduce.

Rails CI log files for the five occurrences observed so far are attached, with Buildkite
permalinks to the segfault in each job:

Expected behavior

It should always pass.

Actual behavior

The test process intermittently dies with [BUG] Segmentation fault. Every crash has the
same signature: SEGV inside the interpreter's method dispatch while reading the iseq out of
the callable method entry taken from an inline call cache — def_iseq_ptr(vm_cc_cme(cc)->def)
— in either vm_call_iseq_setup_kwparm_nokwarg (vm_insnhelper.c:3116-3117) or
vm_call_iseq_setup_normal (vm_insnhelper.c:3496):

Rails CI build Job Crashing test Fault address Crash frame
132123 activerecord sqlite3_mem (4.0) NestedRelationScopingTest#test_nested_scoped_create 0x17 vm_call_iseq_setup_kwparm_nokwarg vm_insnhelper.c:3116
132137 activerecord trilogy (4.0) [mysql_5_7] CompatibilityTest5_1#test_datetime_doesnt_set_precision_on_change_column 0x08 vm_call_iseq_setup_normal vm_insnhelper.c:3496
132289 activerecord mysql2 (4.0) [mariadb] OptimisticLockingTest#test_polymorphic_destroy_with_dependencies_and_lock_version 0x17b9 vm_call_iseq_setup_kwparm_nokwarg vm_insnhelper.c:3117
132357 activerecord sqlite3_mem (4.0) IntegrationTest#test_cache_version_format_is_not_too_precise 0x1109 vm_call_iseq_setup_kwparm_nokwarg vm_insnhelper.c:3117
132357 activerecord trilogy (4.0) [mysql_5_7] ActiveRecord::ConnectionAdapters::ConnectionHandlersShardingDbTest#test_establish_connection_using_3_levels_config_with_shards_and_replica 0x18 vm_call_iseq_setup_kwparm_nokwarg vm_insnhelper.c:3116

The crash-site frames of the C-level backtraces, showing that all five crashes are
duplicates of the same bug (full crash dumps are in the attached log files):

Build 132123, activerecord sqlite3_mem (4.0):

/usr/local/lib/libruby.so.4.0(def_iseq_ptr+0x0) [0x7fa976246636] /usr/src/ruby/vm_insnhelper.c:3116
/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_kwparm_nokwarg) /usr/src/ruby/vm_insnhelper.c:3116
/usr/local/lib/libruby.so.4.0(vm_sendish+0xb0) [0x7fa97625ecb5] /usr/src/ruby/vm_insnhelper.c:6134
/usr/local/lib/libruby.so.4.0(vm_exec_core) /usr/src/ruby/insns.def:904

Build 132137, activerecord trilogy (4.0) [mysql_5_7]:

/usr/local/lib/libruby.so.4.0(def_iseq_ptr+0xd) [0x7ff68de8a350] /usr/src/ruby/vm_core.h:626
/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_normal) /usr/src/ruby/vm_insnhelper.c:3496
/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_normal_0start_0params_0locals) /usr/src/ruby/vm_call_iseq_optimized.inc:14
/usr/local/lib/libruby.so.4.0(vm_sendish+0xce) [0x7ff68de9692e] /usr/src/ruby/vm_insnhelper.c:6134

Build 132289, activerecord mysql2 (4.0) [mariadb]:

/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_kwparm_nokwarg+0x2e) [0x7fb4fd53763e] /usr/src/ruby/vm_insnhelper.c:3117
/usr/local/lib/libruby.so.4.0(vm_sendish+0xb0) [0x7fb4fd54fcb5] /usr/src/ruby/vm_insnhelper.c:6134
/usr/local/lib/libruby.so.4.0(vm_exec_core) /usr/src/ruby/insns.def:904
/usr/local/lib/libruby.so.4.0(vm_exec_loop+0xa) [0x7fb4fd5565ca] /usr/src/ruby/vm.c:2825

Build 132357, activerecord sqlite3_mem (4.0):

/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_kwparm_nokwarg+0x2e) [0x7ff26cc2163e] /usr/src/ruby/vm_insnhelper.c:3117
/usr/local/lib/libruby.so.4.0(vm_sendish+0xb0) [0x7ff26cc39cb5] /usr/src/ruby/vm_insnhelper.c:6134
/usr/local/lib/libruby.so.4.0(vm_exec_core) /usr/src/ruby/insns.def:904
/usr/local/lib/libruby.so.4.0(vm_exec_loop+0xa) [0x7ff26cc405ca] /usr/src/ruby/vm.c:2825

Build 132357, activerecord trilogy (4.0) [mysql_5_7]:

/usr/local/lib/libruby.so.4.0(def_iseq_ptr+0x0) [0x7fa242c65636] /usr/src/ruby/vm_insnhelper.c:3116
/usr/local/lib/libruby.so.4.0(vm_call_iseq_setup_kwparm_nokwarg) /usr/src/ruby/vm_insnhelper.c:3116
/usr/local/lib/libruby.so.4.0(vm_sendish+0xb0) [0x7fa242c7dcb5] /usr/src/ruby/vm_insnhelper.c:6134
/usr/local/lib/libruby.so.4.0(vm_exec_core) /usr/src/ruby/insns.def:904

Files

rails-ci-ruby406-segv-logs.zip (942 KB) rails-ci-ruby406-segv-logs.zip Attached 5 log files yahonda (Yasuo Honda), 08/12/2026 01:52 AM
rails_build_132611_activerecord-mysql2-4-dot-0.log.zip (314 KB) rails_build_132611_activerecord-mysql2-4-dot-0.log.zip yahonda (Yasuo Honda), 08/18/2026 06:48 AM

Updated by luke-gru (Luke Gruber) 24 days ago Actions #1

  • Subject changed from laky SEGV in method dispatch (`vm_call_iseq_setup_kwparm_nokwarg` / `def_iseq_ptr`) on Ruby 4.0.6 — Rails CI to SEGV in method dispatch (`vm_call_iseq_setup_kwparm_nokwarg` / `def_iseq_ptr`) on Ruby 4.0.6 — Rails CI

Updated by luke-gru (Luke Gruber) 23 days ago Actions #2 [ruby-core:126378]

Thank you for the report. Has rails ran CI with other 4.0 releases? If so, did you notice any similar crashes for 4.0 releases preceding 4.0.6?

Updated by yahonda (Yasuo Honda) 17 days ago Actions #3 [ruby-core:126432]

Yes. Rails CI builds its test images FROM ruby:3.3 / ruby:3.4 / ruby:4.0, so each
lane automatically follows the latest patch release.

As far as I remember, this signature had not appeared on any 4.0 release before 4.0.6 — that is a recollection rather than a statistical claim, but I do not think I would have missed anything like the six SEGVs (2026-08-06 to 2026-08-16) that followed the switch to 4.0.6.

One more SEGV with the same signature occurred after the five I reported: build 132611
(activerecord mysql2, 2026-08-16) —
https://buildkite.com/rails/rails/builds/132611#01a00964-d6c3-4664-a380-d0c89f15eecc/L8913

Updated by kamipo (Ryuta Kamizono) 11 days ago Actions #4 [ruby-core:126478]

A VM_CHECK_MODE build turns this from a rare SEGV in random places into an
assertion at the moment the corrupted call cache is used, and the assertion
says the cc is holding a reclaimed cme.

RUBY_CONFIGURE_OPTS="cppflags=-DVM_CHECK_MODE=1" ruby-build 4.0.6 ~/.rbenv/versions/4.0.6-vmcheck
cd activerecord && bundle exec rake sqlite3_mem:test TESTOPTS="--seed=$RANDOM"

One hit in 53 full-suite runs (arm64-darwin25; lower rate than on CI here):

/vm_insnhelper.c:131: Assertion Failed: callable_method_entry_p:IMEMO_TYPE_P((VALUE)cme, imemo_ment): imemo_type:svar
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin25]
c:0082 METHOD .../active_support/notifications/fanout.rb:176   # Time.now

So vm_cc_cme(cc) points at an object whose imemo type is svar: the method
entry was collected and its slot reused while a live cc still refers to it.
The assertion fires from vm_sendish, i.e. at the
METHOD_ENTRY_INVALIDATED(vm_cc_cme(cc)) check in vm_search_method_fastpath,
which is already reading freed memory. That is the invariant
rb_imemo_mark_and_move() documents for imemo_callcache, so something frees
a cme that a live, non-invalidated cc still refers to — which also explains why
the fault addresses in this ticket vary so much.

The crashes I have looked at are all on a plain Time.now call. Time.now is
def self.now(in: nil) in <internal:timev>, i.e. keyword-only parameters, so
it always takes the kwparm_nokwarg fast path, and it is also constantly
redefined by ActiveSupport::Testing::TimeHelpers, which is presumably why
this mid is the one that surfaces it.

Updated by yahonda (Yasuo Honda) 11 days ago Actions #5 [ruby-core:126489]

The crash does not reproduce on master, so bisecting from the 4.0 branch point (d375bcc965d4) gives the first fixed commit:

1da540aeaa1507af4c050bf8a6ee2802e65f9aac "Invalidate CC on cme free as well" (gc.c, +3/-1)

The boundary is sharp between 1da540aeaa and its parent commit 3c251f79a3d9, each built and run through the reproducer up to 1,200 times (the per-commit budget for the bisect): the parent crashes with the same signature (at run 101), while 1da540aeaa completes all 1,200 runs cleanly.

Your Time.now observation also explains the reproducer's two files. A per-file bisect of the Active Record suite narrows CI to the smallest reliable reproducer (one process per run, official ruby:4.0.6 image, recent rails main, in-memory SQLite; the crash is not commit-specific):

test files crashes / runs
primary_keys_test.rb + timestamp_test.rb, --seed 57420 162 / 400 (≈40%)
8-file control set without primary_keys_test.rb 0 / 37,239

primary_keys_test.rb is the file that enters multi-Ractor mode (its test_primary_key_can_be_read_from_a_ractor_* tests). timestamp_test.rb is the file that drives ActiveSupport::Testing::TimeHelpers, i.e. the constant Time.now redefinition you point to. Dropping either file gives zero crashes. The rate is very seed-sensitive: the favorable order from --seed 57420 crashes about 40% of runs, whereas a random seed is around 0.78% (~1 in 130). Consistently, in all six Rails CI failures inspected so far (builds 132123, 132137, 132655, 132673, 132698, 132752), the failure occurred between 1.4 s and about 130 s after the Ractor tests had run in the same process. Since the initial report, this signature keeps recurring — ten and counting.

Reproducer. The bug is not specific to this commit; the commit is pinned only so that --seed 57420 reliably reproduces the same test order — a seed fixes the order only relative to a given set of tests — and that order crashes fast (about 40% of runs here, usually within a few iterations). On any other recent checkout, drop the git checkout and --seed and loop with a random seed instead (~0.78%, ~10 minutes).

git clone https://github.com/rails/rails && cd rails
git checkout 1f0c247be3da        # not commit-specific; pins so --seed 57420 gives the same test order
bundle install
cd activerecord
export ARCONN=sqlite3_mem
while :; do
  bin/test test/cases/primary_keys_test.rb test/cases/timestamp_test.rb --seed 57420 >/tmp/run.log 2>&1
  grep -qE "\[BUG\]|Segmentation fault" /tmp/run.log && break
done
tail -60 /tmp/run.log

Updated by jhawthorn (John Hawthorn) 10 days ago · Edited Actions #6 [ruby-core:126490]

I've managed to reproduce this under rr locally through the Active Record test suite with VM_CHECK_MODE enabled. I don't have a more reliable reproduction yet or a fix.

What the recording seems to show is that the ccs entries become orphaned in multi-ractor mode via a vm_cc_table_dup_i since that skips invalidated CMEs (a change I made in https://github.com/ruby/ruby/pull/15652, though that was to avoid a different crash) however that breaks our invariant that all "valid" CC's have their CME marked via the ccs table. So my change there is wrong (but also we probably shouldn't revert it because of what it fixed).

This is also made more complicated because there's a latent bug in cc_is_active, where it always returns false, which . This should be "harmless" (bad for performance and CoW memory usage) because it simply clears the iseq's inline callcaches more often than it should, but is likely part of why this is hard to reproduce. I was planning to merge a fix for this into HEAD https://github.com/ruby/ruby/pull/18360

I do think 1da540aeaa1507af4c050bf8a6ee2802e65f9aac may fix this, but that's a tough one to backport. The weakref mechanism on Ruby HEAD makes that pretty cheap, but it's twice as expensive on 4.0 (which records the edges rather than the parent objects which have a weakref). Also, though it seems safe it was not the intended invalidation mechanism. I'd like to investigate more, a simpler reproduction would go a long way because it's very tricky to get call caches working correctly.

Updated by jhawthorn (John Hawthorn) 9 days ago Actions #7

  • Status changed from Open to Closed

Applied in changeset git|f95e71cc1e2a082393d58db1eb6f57b09694f8cb.


[Bug #22242] Invalidate CCs on cc_table_dup

When a Ractor is started, rather than modifying the cc table in place we
make a copy and CAS the table. When making the copy we previously
skipped cc_entries with an invalidated CME. However, that left the CCs
associated with that entry orphaned. CCs do not mark their CME, and the
CAS removed the table from the live set, so nothing invalidated the CC
and it ended up pointing at a garbage object.

We believe this bug is dormant in HEAD because handle_weak_references
for imemo_callcache will invalidate when the cme is collected.

Co-authored-by: Luke Gruber

Actions

Also available in: PDF Atom