In rb_gc_impl_before_fork, it locks the VM and barriers all the Ractors
before calling mmtk_before_fork. However, since rb_mmtk_block_for_gc is
a barrier point, one or more Ractors could be paused there. However,
mmtk_before_fork is not compatible with that because it assumes that the
MMTk workers are idle, but the workers are not idle because they are
busy working on a GC.
This commit essentially implements a trylock. It will optimistically
lock but will release the lock if it detects that any other Ractors are
waiting in rb_mmtk_block_for_gc.
For example, the following script demonstrates the issue:
puts "Hello #{Process.pid}"
100.times do |i|
puts "i = #{i}"
Ractor.new(i) do |j|
puts "Ractor #{j} hello"
1000.times do |i|
s = "#{j}-#{i}"
end
Ractor.receive
puts "Ractor #{j} goodbye"
end
pid = fork { }
puts "Child pid is #{pid}"
_, status = Process.waitpid2 pid
puts status.success?
end
puts "Goodbye"
We can see the MMTk worker thread is waiting to start the GC:
#4 0x00007ffff66538b1 in rb_mmtk_stop_the_world () at gc/mmtk/mmtk.c:101
#5 0x00007ffff6d04caf in mmtk_ruby::collection::{impl#0}::stop_all_mutators<mmtk::scheduler::gc_work::{impl#14}::do_work::{closure_env#0}<mmtk::plan::immix::gc_work::ImmixGCWorkContext<mmtk_ruby::Ruby, 0>>> (_tls=..., mutator_visitor=...) at src/collection.rs:23
However, the mutator thread is stuck in mmtk_before_fork trying to stop
that worker thread:
#4 0x00007ffff6c0b621 in std::sys:🧵:unix::Thread::join () at library/std/src/sys/thread/unix.rs:134
#5 0x00007ffff6658b6e in std:🧵:JoinInner<()>::join<()> (self=...)
#6 0x00007ffff6658d4c in std:🧵:JoinHandle<()>::join<()> (self=...)
#7 0x00007ffff665795e in mmtk_ruby::binding::RubyBinding::join_all_gc_threads (self=0x7ffff72462d0 <mmtk_ruby::BINDING+8>) at src/binding.rs:115
#8 0x00007ffff66561a8 in mmtk_ruby::api::mmtk_before_fork () at src/api.rs:309
#9 0x00007ffff66556ff in rb_gc_impl_before_fork (objspace_ptr=0x555555d17980) at gc/mmtk/mmtk.c:1054
#10 0x00005555556bbc3e in rb_gc_before_fork () at gc.c:5429
https://github.com/ruby/mmtk/commit/1a629504a7
Ruby's Garbage Collectors
This directory contains implementations for Ruby's garbage collector (GC). The GC implementations use the Modular GC API to interact with Ruby. For more details about this API, see the Modular GC API section.
Two GC implementations are included in Ruby:
- Default: The GC implementation that is used by default in Ruby. This GC is stable and production ready. The implementation uses a mark-sweep-compact algorithm.
- MMTk: An experimental implementation using the MMTk framework. The code lives in the ruby/mmtk repository and is synchronized here. MMTk provides a wide variety of GC algorithms to choose from. For usage instructions and current progress, refer to the ruby/mmtk repository.
Building guide
Tip
If you are not sure how to build Ruby, follow the Building Ruby guide.
Important
Ruby's modular GC feature is experimental and subject to change. There may be bugs or performance impacts. Use at your own risk.
Building Ruby with Modular GC
- Configure Ruby with the
--with-modular-gc=<dir>option, wherediris the directory you want to place the built GC libraries into. - Build Ruby as usual.
Building GC implementations shipped with Ruby
- Build your desired GC implementation with
make install-modular-gc MODULAR_GC=<impl>. This will build the GC implementation and place the built library into thedirspecified in step 1.implcan be one of: - Run your desired GC implementation by setting the
RUBY_GC_LIBRARY=<lib>environment variable, wherelibcould bedefault,mmtk, or your own implementation (as long as you place it in thedirspecified in step 1).
Modular GC API
Warning
The Modular GC API is experimental and subject to change without notice.
GC implementations interact with Ruby via the Modular GC API. All implementations must provide the functions in gc/gc_impl.h for Ruby to hook into. GC implementations can use any public C API in Ruby, along with additional APIs defined in gc/gc.h.
Additionally, create an extconf.rb file to build the GC library. This file must use gc/extconf_base.rb and the create_gc_makefile method.