Bug #21848
closedGC compaction freezes during code reload with 100% CPU in rb_vm_ci_free -> st_general_delete
Description
Ruby 4.0.1 freezes with 100% CPU usage when garbage collection runs during Rails code reloading (hot reload in development). The process becomes unresponsive and requires kill -9 to terminate.
Environment:
- Ruby: 4.0.1 (Homebrew, ARM64)
- Platform: macOS 15.6.1 (Darwin 24.6.0, Apple Silicon)
- Rails: 8.x with Puma 7.2.0
- Mode: Development with code reloading enabled
Steps to reproduce:
- Run a Rails application with Puma in development mode (bundle exec rails server -e development -b 0.0.0.0)
- Make a several code change to trigger hot reload, reload the site in the browser in between
- Process freezes with ~100% CPU
Expected behavior:
Code reloads successfully, GC completes normally.
Actual behavior:
Process freezes indefinitely at 100% CPU. Stack trace from sample shows GC stuck in hash table lookup while freeing VM call info:
gc_sweep (in libruby.4.0.dylib)
gc_compact_plane
gc_sweep_page
gc_sweep_plane
rb_gc_obj_free_vm_weak_references
rb_vm_ci_free
st_general_delete
find_table_bin_ind
vm_ci_hash_cmp <-- stuck here, spinning
Full relevant stack (Puma worker thread):
Thread: puma srv tp 005
rb_ary_each -> rb_yield -> invoke_block_from_c_bh -> rb_vm_exec
-> int_upto -> rb_yield (nested loops)
-> vm_search_method_slowpath0 -> rb_vm_search_method_slowpath
-> newobj_of -> newobj_cache_miss -> gc_continue
-> gc_sweep -> gc_compact_plane -> gc_sweep_page
-> gc_sweep_plane -> rb_gc_obj_free_vm_weak_references
-> rb_vm_ci_free -> st_general_delete
-> find_table_bin_ind -> vm_ci_hash_cmp
Workaround:
Disabling auto-compaction prevents the freeze:
GC.auto_compact = false
Analysis:
The freeze occurs during GC sweep/compaction when freeing cached VM call info entries (rb_vm_ci_free). This is triggered by code reloading which invalidates method caches. The GC appears to get stuck in an infinite loop or
extremely slow operation in find_table_bin_ind while looking up entries in the call info hash table.
Files
Updated by mame (Yusuke Endoh) 6 months ago
- Status changed from Open to Assigned
- Assignee set to tenderlovemaking (Aaron Patterson)
Updated by luke-gru (Luke Gruber) about 2 months ago
This bug may be related to #22104.
Updated by mame (Yusuke Endoh) about 1 month ago
- Status changed from Assigned to Feedback
- Assignee deleted (
tenderlovemaking (Aaron Patterson))
Can we close this, assuming it was fixed by #22104? The fix will be included in 4.0.6. Could you try it once it is out? Setting this to Feedback for now.
Updated by henrik (Henrik A) 19 days ago
- ruby -v changed from ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin24] to ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin25]
Still reproducible on ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin25]
(Homebrew, macOS Darwin 25.5.0). Rails 8.1.2 dev server, Puma 8.0.2.
Same symptom: during code reloading a thread pegs one core at 100% CPU and never recovers.
Only with GC.auto_compact = true.
#22104 did change something: the old leaf (rb_vm_ci_free -> st_general_delete) is gone. The
same compacting-sweep path now dies one frame over.
#0 rb_class_classext_free + 92
#1 rb_class_classext_foreach + 104
#2 rb_gc_obj_free + 148
#3 gc_sweep_plane + 312
#4 gc_sweep_page + 260
#5 gc_compact_plane + 424
#6 gc_sweep + 1144
#7 gc_continue + 304
#8 newobj_cache_miss + 324
#9 newobj_of + 140
#10 rb_str_buf_new + 92
#11 rb_str_concat_literals + 76
#12 vm_exec_core + 16120
#13 rb_vm_exec + 508
Above that, three nested rounds of rb_vm_opt_getconstant_path -> rb_const_search_from ->
rb_autoload_load -> rb_require_string -> load_iseq_eval (autoload triggering autoload).
It is a use-after-free, not a loop. The thread re-faults on one instruction forever:
stop reason = EXC_BAD_ACCESS (code=2, address=0x127dbfe38)
rb_class_classext_free+92: ldr x10, [x9, #0x68]
x9 = 0x0000000127dbfdd0
x9 itself is already in an unmapped page, so the struct is gone before it is walked:
The fault lands inside GC mid-sweep, so the SEGV handler cannot progress and the instruction
retries indefinitely: hang at 100% CPU, no crash dump. Two sample(1) runs 10s apart put 100%
of samples in that same leaf.
Reproduces reliably here, happy to test a patch.
Updated by henrik (Henrik A) 19 days ago
- File sample-33291-b.txt sample-33291-b.txt added
- File sample-33291.txt sample-33291.txt added