Bug #21398
openRactor.select hangs when multiple threads submit heavy jobs concurrently
Description
When multiple threads run heavy Ractor-based jobs at the same time, Ractor.select(*workers)
can hang indefinitely without any crash, exception, or deadlock being reported.
This issue does not occur when only one thread is running similar jobs — the hang happens only when four or more threads submit large jobs concurrently to a shared Ractor pipeline.
Steps to Reproduce¶
Run the script below in Ruby 3.4.2 or later.
In some runs, one or more threads will hang forever at Ractor.select.
The issue reproduces more easily on machines with multiple cores and sufficient memory.
Expected Behavior¶
All calls to Ractor.select(*workers) should return when any worker Ractor yields a result. All jobs should complete.
Actual Behavior¶
-
In some runs (especially with multiple threads),
Ractor.select
hangs and never returns. -
There is no crash or exception; the process remains alive but blocked.
-
All Ractors and threads are created and started correctly.
-
This behavior does not occur when only one thread performs the same job sequentially.
-
It appears that
Ractor.select
does not always behave reliably under concurrent multithreaded usage with heavy Ractor pipelines.
Reproducible Example:
THREADS = 4
JOBS_PER_THREAD = 10
ARRAY_SIZE = 10_000
def ractor_job(job_count, array_size)
pipe = Ractor.new do
loop { Ractor.yield Ractor.receive }
end
workers = (1..4).map do
Ractor.new(pipe) do |pipe|
while job = pipe.take
result = job.map { |x| x * 2 }.sum
Ractor.yield result
end
end
end
jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
jobs.each { |job| pipe.send(job) }
results = []
jobs.size.times do
puts "Waiting for results..."
_ractor, result = Ractor.select(*workers)
puts "Received result: #{result}"
results << result
end
results
end
threads = []
THREADS.times do
threads << Thread.new do
ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
end
end
threads.each(&:join)
puts "All threads finished."
Updated by luke-gru (Luke Gruber) 1 day ago
· Edited
Thank you for the report.
Is it possible for you to try to install ruby-head (3.5.0dev) and try this script with the recent changes to ractors (ractor ports)? I'm having trouble reproducing it on my end on ruby-head.
Here's the script with modifications for working with ports:
THREADS = 4
JOBS_PER_THREAD = 10
ARRAY_SIZE = 10_000
def ractor_job(job_count, array_size)
port = Ractor::Port.new
workers = (1..4).map do |i|
Ractor.new(port) do |job_port|
while job = Ractor.receive
result = job.map { |x| x * 2 }.sum
job_port.send result
end
end
end
jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
jobs.each_with_index do |job, i|
w_idx = i % 4
workers[w_idx].send(job)
end
results = []
jobs.size.times do
puts "Waiting for results..."
_ractor, result = Ractor.select(port)
puts "Received result: #{result}"
results << result
end
results
end
threads = []
THREADS.times do
threads << Thread.new do
ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
end
end
threads.each(&:join)
puts "All threads finished."
If you're unfamiliar with ractor ports, here's the ticket for the feature: https://bugs.ruby-lang.org/issues/21262
Updated by arino.tamada (有乃 玉田) about 22 hours ago
Thank you for your response.
I have tried running the script with Ruby 3.5.0-dev
(ruby-head) and using Ractor::Port
as in your example.
I can confirm that the issue does not reproduce in my environment with these recent changes.
The script completes successfully and does not hang at Ractor.select
.
Thank you for your support and the improvements to Ractor!