[ruby-core:122683] [Ruby Bug#21504] [Ractor] Process.waitpid blocks ractor, new NT doesn't pick up other ractors

Issue #21504 has been reported by luke-gru (Luke Gruber). ---------------------------------------- Bug #21504: [Ractor] Process.waitpid blocks ractor, new NT doesn't pick up other ractors https://bugs.ruby-lang.org/issues/21504 * Author: luke-gru (Luke Gruber) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- The following code hangs when run with `RUBY_MAX_CPU=2 make run`: Note: `RUBY_MAX_CPU` is set to 2 so that only 1 non-main ractor can run at once. test.rb: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 io = IO.popen("ruby -e 'sleep'") Process.wait(io.pid) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` The timer thread should create a new NT to compensate for the dedicated task, and the new NT should be able to pick up the other runnable ractor. In contrast, the following works fine: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 r, w = IO.pipe r.read(1) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` -- https://bugs.ruby-lang.org/

Issue #21504 has been updated by jhawthorn (John Hawthorn). Assignee set to ractor ---------------------------------------- Bug #21504: [Ractor] Process.waitpid blocks ractor, new NT doesn't pick up other ractors https://bugs.ruby-lang.org/issues/21504#change-113963 * Author: luke-gru (Luke Gruber) * Status: Open * Assignee: ractor * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- The following code hangs when run with `RUBY_MAX_CPU=2 make run`: Note: `RUBY_MAX_CPU` is set to 2 so that only 1 non-main ractor can run at once. test.rb: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 io = IO.popen("ruby -e 'sleep'") Process.wait(io.pid) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` The timer thread should create a new NT to compensate for the dedicated task, and the new NT should be able to pick up the other runnable ractor. In contrast, the following works fine: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 r, w = IO.pipe r.read(1) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` -- https://bugs.ruby-lang.org/

Issue #21504 has been updated by luke-gru (Luke Gruber). This is due to IO (ex: IO#read) registering wait events with the timer thread. When it does this, it wakes the timer thread up. This makes the timer thread check if any new NT needs to be created, and creates one if necessary. A quick fix would be to wake the timer thread when calling Process#waitpid, but there could be other methods affected as well. ---------------------------------------- Bug #21504: [Ractor] Process.waitpid blocks ractor, new NT doesn't pick up other ractors https://bugs.ruby-lang.org/issues/21504#change-114108 * Author: luke-gru (Luke Gruber) * Status: Open * Assignee: ractor * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- The following code hangs when run with `RUBY_MAX_CPU=2 make run`: Note: `RUBY_MAX_CPU` is set to 2 so that only 1 non-main ractor can run at once. test.rb: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 io = IO.popen("ruby -e 'sleep'") Process.wait(io.pid) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` The timer thread should create a new NT to compensate for the dedicated task, and the new NT should be able to pick up the other runnable ractor. In contrast, the following works fine: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 r, w = IO.pipe r.read(1) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` -- https://bugs.ruby-lang.org/

Issue #21504 has been updated by luke-gru (Luke Gruber). This is actually a more pervasive problem than I first realized, because only sometimes does `IO#read` register with the timer thread. With a plain `IO#read` without arguments, it does not wake the timer thread and can block the nt forever as well. ---------------------------------------- Bug #21504: [Ractor] Process.waitpid blocks ractor, new NT doesn't pick up other ractors https://bugs.ruby-lang.org/issues/21504#change-114121 * Author: luke-gru (Luke Gruber) * Status: Open * Assignee: ractor * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- The following code hangs when run with `RUBY_MAX_CPU=2 make run`: Note: `RUBY_MAX_CPU` is set to 2 so that only 1 non-main ractor can run at once. test.rb: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 io = IO.popen("ruby -e 'sleep'") Process.wait(io.pid) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` The timer thread should create a new NT to compensate for the dedicated task, and the new NT should be able to pick up the other runnable ractor. In contrast, the following works fine: ```ruby rs = [] 2.times do |i| rs << Ractor.new(i) do |i| if i == 0 r, w = IO.pipe r.read(1) # block forever else sleep 1 # make sure first ractor blocks forever first $stderr.puts "Running r #{i}" 100_000.times do [nil] * 1_000 end $stderr.puts "done r #{i}" end end end while rs.size == 2 r, obj = Ractor.select(*rs) rs.delete(r) end ``` -- https://bugs.ruby-lang.org/
participants (2)
-
jhawthorn (John Hawthorn)
-
luke-gru (Luke Gruber)