Flush the current Ractor's stdout/stderr before spawning

prefork() flushed rb_stdout and rb_stderr, which are the main Ractor's IOs.
From another Ractor that takes the write_lock of an IO the Ractor does not
own. When a main Ractor thread holds it (any thread logging to $stdout will
do), the spawning Ractor's only thread sleeps on the mutex and
rb_check_deadlock, which only looks at the Ractor's own threads, raises
"No live threads left. Deadlock?" (fatal) from Kernel#spawn/system/popen.

Use rb_ractor_stdout()/rb_ractor_stderr() so each Ractor flushes its own.

[Bug #22434]
This commit is contained in:
John Hawthorn committed 2026-10-20 01:18:41 -07:00
1 parent 6aa5fae6d9
commit 28f2eae4cf
2 files changed
+15 -2
Notes: git 2026-10-20 08:19:18 +00:00

No files matched your search

+4 -2
View File
@@ -3669,9 +3669,11 @@ handle_fork_error(int err, struct rb_process_status *status, int *ep, volatile i
return -1;
}
/* Flush the current Ractor's own stdio: taking the write_lock of an IO owned
* by another Ractor can block on a thread that rb_check_deadlock can't see. */
#define prefork() ( \
rb_io_flush(rb_stdout), \
rb_io_flush(rb_stderr) \
rb_io_flush(rb_ractor_stdout()), \
rb_io_flush(rb_ractor_stderr()) \
)
/*
+11
View File
@@ -2,6 +2,17 @@
require 'test/unit'
class TestRactor < Test::Unit::TestCase
# Spawning from a Ractor must not block on the main Ractor's $stdout
def test_spawn_does_not_contend_on_main_ractors_stdout
assert_separately([], <<~'RUBY', timeout: 60)
$stdout = File.open(File::NULL, "w")
writer = Thread.new { loop { $stdout.write("x" * 100 + "\n"); $stdout.flush } }
r = Ractor.new { 20.times { system(EnvUtil.rubybin, "-e", "") }; :done }
assert_equal :done, r.value
writer.kill.join
RUBY
end
def test_shareability_of_iseq_proc
assert_raise Ractor::IsolationError do
foo = []