[ruby-core:124887] [Ruby Bug#21926] Thread#value on popen3 wait thread hangs in finalizer
Issue #21926 has been reported by stevecrozz (Stephen Crosby). ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926 * Author: stevecrozz (Stephen Crosby) * Status: Open * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
Issue #21926 has been updated by luke-gru (Luke Gruber). Backport changed from 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN to 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: REQUIRED, 4.0: REQUIRED Thank you for the nice reproduction! We have a fix coming, and it will probably be backported to 3.4 and 4.0 as well. The fix is at https://github.com/ruby/ruby/pull/16307. ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926#change-116593 * Author: stevecrozz (Stephen Crosby) * Status: Open * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: REQUIRED, 4.0: REQUIRED ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
Issue #21926 has been updated by luke-gru (Luke Gruber). A note to whoever backports this (which could be me, I'm just waiting ~ 1 week and checking various CIs after the merge): https://github.com/ruby/ruby/pull/16362 should be backported as well, they are really 1 fix in 2 separate commits. ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926#change-116664 * Author: stevecrozz (Stephen Crosby) * Status: Closed * Assignee: luke-gru (Luke Gruber) * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
Issue #21926 has been updated by k0kubun (Takashi Kokubun). Backport changed from 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED to 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE ruby_4_0 commit:a601b899a35c796775309dca01a6d5e64be14c44 merged revision(s) commit:08372635f7ec09f7115bd254246ebd637499651c. ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926#change-116716 * Author: stevecrozz (Stephen Crosby) * Status: Closed * Assignee: luke-gru (Luke Gruber) * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
Issue #21926 has been updated by jhawthorn (John Hawthorn). Just for the record, this code should not hang and the fix is good, but the pattern in the reproduction isn't safe. The defined finalizer could potentially be run on any thread including the wait_thread, which would result in the error `Target thread must not be current thread (ThreadError)`. ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926#change-116869 * Author: stevecrozz (Stephen Crosby) * Status: Closed * Assignee: luke-gru (Luke Gruber) * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
Issue #21926 has been updated by nagachika (Tomoyuki Chikanaga). Backport changed from 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE to 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: DONTNEED, 4.0: DONE I suppose this issue isn't reproducible on `ruby_3_4`. ---------------------------------------- Bug #21926: Thread#value on popen3 wait thread hangs in finalizer https://bugs.ruby-lang.org/issues/21926#change-117395 * Author: stevecrozz (Stephen Crosby) * Status: Closed * Assignee: luke-gru (Luke Gruber) * ruby -v: 3.3.7 * Backport: 3.2: UNKNOWN, 3.3: REQUIRED, 3.4: DONTNEED, 4.0: DONE ---------------------------------------- Calling Thread#value on an Open3.popen3 wait thread from a finalizer completes in Ruby 3.2 but hangs in Ruby 3.3+. See repro.rb below. When the Ruby process hangs in these conditions, it no longer responds to signals and it seems to be unable to run any other threads. This affects the schmooze gem (and potentially other code using Open3.popen3 with finalizers), causing test suites to hang intermittently. ``` ruby # repro.rb require 'open3' class ProcessWrapper def initialize @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread)) end def self.make_finalizer(stdin, stdout, stderr, wait_thread) proc do stdin.close rescue nil stdout.close rescue nil stderr.close rescue nil wait_thread.value # Hangs here in Ruby 3.3+ end end end 100.times { ProcessWrapper.new } GC.stress = true 1000.times { Object.new } puts "done" ``` ## Environment - Linux x86_64 - Tested on Ruby 3.2.7, 3.3.7, 3.4.8 -- https://bugs.ruby-lang.org/
participants (5)
-
jhawthorn (John Hawthorn) -
k0kubun (Takashi Kokubun) -
luke-gru (Luke Gruber) -
nagachika (Tomoyuki Chikanaga) -
stevecrozz (Stephen Crosby)