From 3c6c503c5097819db6355fca904ae132e2cdcceb Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 26 Oct 2026 22:12:50 -0700 Subject: [PATCH] Wait for a pending GC before re-disabling GC after GC.start GC.start runs even while GC is disabled, so mmtk_handle_user_collection_request enables GC around the collection and disables it again afterward. While GC is enabled, another Ractor's GC.start can request a GC that hasn't run yet when this thread tries to re-disable GC. disable_collection fails in that state, and we panicked with "failed to re-disable GC after GC". This showed up as an intermittent crash of test_ractor_nt_switch.rb's gc_finalize_deferred test on the MMTk CI. Instead of panicking, block for the pending GC and try again. Since a GC is already requested, the retry joins it instead of starting a new one. --- gc/mmtk/src/api.rs | 10 +++++++--- test/mmtk/test_gc.rb | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 test/mmtk/test_gc.rb diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index ef0d7bf..80b0a43 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -305,9 +305,13 @@ pub extern "C" fn mmtk_handle_user_collection_request( crate::mmtk().handle_user_collection_request(tls, force, exhaustive); if gc_was_disabled { - crate::mmtk() - .disable_collection() - .unwrap_or_else(|_| panic!("failed to re-disable GC after GC")); + // While GC was enabled, another Ractor may have requested a GC that hasn't run yet. + // MMTk can't disable GC until that GC runs, and it needs this thread to stop first. + // So block for it and try again. Since a GC is already requested, this waits for that + // GC instead of starting a new one. + while crate::mmtk().disable_collection().is_err() { + crate::mmtk().handle_user_collection_request(tls, true, false); + } } } diff --git a/test/mmtk/test_gc.rb b/test/mmtk/test_gc.rb new file mode 100644 index 0000000..abbe531 --- /dev/null +++ b/test/mmtk/test_gc.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative "helper" + +module MMTk + class TestGC < TestCase + def test_start_from_ractors_while_disabled + assert_separately([], <<~RUBY) + Warning[:experimental] = false + + GC.disable + ractors = 4.times.map do + Ractor.new do + 10.times { GC.start } + end + end + ractors.each(&:join) + + assert_equal(true, GC.enable) + RUBY + end + end +end