From 1313e3fcdbdae47c9a3b68c0d2e757eafee17399 Mon Sep 17 00:00:00 2001 From: "William T. Nelson" <35801+wtn@users.noreply.github.com> Date: Sun, 25 Oct 2026 23:07:03 -0500 Subject: [PATCH 1/2] Close stdin before terminating a timed-out memory leak child With a sample range, assert_no_memory_leak keeps the child's stdin open, and the child's ensure clause reads a "continue" line from it after each sample. When the timeout fires while the child is running the sampled code, EnvUtil.terminate sends SIGTERM, and the child's ensure clause then blocks reading stdin, which was closed only after terminate returned. SIGTERM cannot end the child, so terminate escalates to SIGABRT and then waits for the aborted child without a time limit. On ruby/ruby's YJIT macOS CI jobs the child sometimes did not exit after the RUBY_ON_BUG core dump, and the test worker hung until the runner killed it 20 minutes later. In other runs the test passed but left a crash report for the child. Close stdin first. The child's read then returns nil, and its ensure clause breaks out of the sample loop. The break discards the SignalException raised by SIGTERM, so the child exits with status 0. If SIGTERM arrives before the child reaches the sample loop, the child still dies from SIGTERM. The existing timeout test now also checks that the child exited normally or by SIGTERM. Co-Authored-By: Claude Opus 5.5 --- lib/core_assertions.rb | 2 +- test/test_core_assertions.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/core_assertions.rb b/lib/core_assertions.rb index 1e4a67a..07b0cb1 100644 --- a/lib/core_assertions.rb +++ b/lib/core_assertions.rb @@ -324,12 +324,12 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: fal end rescue Timeout::Error timed_out = Time.now + in_p.close status = begin EnvUtil.terminate(pid, opt.fetch(:signal, :TERM), opt[:pgroup], opt.fetch(:reprieve, 1)) rescue Errno::ESRCH, Errno::ECHILD warn "child not found: #{pid}: #$!" end - in_p.close end stdout = out_reader.value if timed_out diff --git a/test/test_core_assertions.rb b/test/test_core_assertions.rb index 197bbc7..b876021 100644 --- a/test/test_core_assertions.rb +++ b/test/test_core_assertions.rb @@ -106,9 +106,10 @@ def test_assert_no_memory_leak_rejects_ranges_without_enough_samples end def test_assert_no_memory_leak_applies_timeout_to_sample_range - assert_raise(Timeout::Error) do + error = assert_raise(Timeout::Error) do assert_no_memory_leak([], "", "sleep", samples: 2..5, timeout: 0.1) end + assert_match(/^pid \d+ (?:exit 0|killed by SIGTERM)/, error.message) end def test_assert_separately_runs_assertions_in_child_ruby From f2f7691ad786ed3eec35b3d56d652710d0e52b7e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 26 Oct 2026 17:20:17 -0700 Subject: [PATCH 2/2] Revert a failing test change --- test/test_core_assertions.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_core_assertions.rb b/test/test_core_assertions.rb index b876021..197bbc7 100644 --- a/test/test_core_assertions.rb +++ b/test/test_core_assertions.rb @@ -106,10 +106,9 @@ def test_assert_no_memory_leak_rejects_ranges_without_enough_samples end def test_assert_no_memory_leak_applies_timeout_to_sample_range - error = assert_raise(Timeout::Error) do + assert_raise(Timeout::Error) do assert_no_memory_leak([], "", "sleep", samples: 2..5, timeout: 0.1) end - assert_match(/^pid \d+ (?:exit 0|killed by SIGTERM)/, error.message) end def test_assert_separately_runs_assertions_in_child_ruby