Subject changed from exited Ractors' memory is reclaimed only by a global GC, so memory grows without bound. to Heaps of exited Ractors are reclaimed only by a global GC, so memory grows without bound with short-lived Ractors
ruby -v changed from 4.1.0-preview1 to ruby 4.1.0preview1 (2026-10-11 master 2d08b5f42f)
With the per-Ractor GC in 4.1.0-preview1, a program that keeps creating
short-lived Ractors grows its RSS by ~140 MB per 1,000 exited Ractors.
As I understand the NEWS, a dead Ractor's heap is reclaimed by a global
GC, but nothing starts one when Ractors exit, so the heaps pile up.
In 8,000 Ractors only one global GC ran, and memory kept growing after
it. Calling GC.start after each round of 1,000 keeps RSS flat, so
the memory is reclaimable. The automatic trigger doesn't seem to
account for it.
A server that runs a Ractor per connection hits this directly: it ran
out of memory (killed by the kernel's OOM killer in an 8 GB container)
a few seconds after its first few thousand connections closed. On 4.0
the same server stays flat.
# Rounds of 1,000 short-lived Ractors, each allocating a little and exiting.# Pass "gc" to run GC.start after each round.Warning[:experimental]=falsedefrss_mb=File.read("/proc/self/status")[/VmRSS:\s+(\d+)/,1].to_i/1024defgc_stat=(GC.stat(scope: :global)rescueGC.stat)8.timesdo|round|Array.new(1000){Ractor.new{Array.new(900){"x"*30}.size}}.each(&:join)GC.startifARGV[0]=="gc"stat=gc_statputs"round #{round}: rss #{rss_mb} MB, Ractor.count #{Ractor.count}, "\"GCs #{stat[:count]}, global GCs #{stat.fetch(:global_gc_count,"-")}"end
RSS bounded without an explicit GC.start, for example a global GC (or
reclaiming dead heaps without one) triggered by the memory or the count
of exited Ractors' heaps waiting to be reclaimed.
Also worth a look: even with GC.start every round, RSS levels off at
~435 MB, against ~44 MB on 4.0.7.