From eb4a5501e17dba3ba97a71f57d7c15f9f6f57b2e Mon Sep 17 00:00:00 2001 From: Jon Rowe Date: Fri, 28 Aug 2020 20:58:23 +0100 Subject: [PATCH] [core] Merge pull request rspec/rspec-core#2739 from rspec/detach-process-to-avoid-zombie Detach bisect subprocesses to avoid making zombie processes --- This commit was imported from https://github.com/rspec/rspec-core/commit/efbac949c3b0be730d7fb27938415974cbf24d03. --- rspec-core/Changelog.md | 2 + .../lib/rspec/core/bisect/fork_runner.rb | 7 ++- rspec-core/spec/integration/bisect_spec.rb | 46 ++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/rspec-core/Changelog.md b/rspec-core/Changelog.md index 320d958c8..56b290cb3 100644 --- a/rspec-core/Changelog.md +++ b/rspec-core/Changelog.md @@ -6,6 +6,8 @@ Bug Fixes: * Ensure custom error codes are returned from bisect runs. (Jon Rowe, #2732) * Ensure `RSpec::Core::Configuration` predicate config methods return booleans. (Marc-André Lafortune, #2736) +* Prevent `rspec --bisect` from generating zombie processes while executing + bisect runs. (Benoit Tigeot, Jon Rowe, #2739) ### 3.9.2 / 2020-05-02 [Full Changelog](http://github.com/rspec/rspec-core/compare/v3.9.1...v3.9.2) diff --git a/rspec-core/lib/rspec/core/bisect/fork_runner.rb b/rspec-core/lib/rspec/core/bisect/fork_runner.rb index b772e81af..4641a1442 100644 --- a/rspec-core/lib/rspec/core/bisect/fork_runner.rb +++ b/rspec-core/lib/rspec/core/bisect/fork_runner.rb @@ -91,9 +91,12 @@ def initialize(runner, channel) end def dispatch_specs(run_descriptor) - fork { run_specs(run_descriptor) } + pid = fork { run_specs(run_descriptor) } # We don't use Process.waitpid here as it was causing bisects to - # block due to the file descriptor limit on OSX / Linux. + # block due to the file descriptor limit on OSX / Linux. We need + # to detach the process to avoid having zombie processes + # consuming slots in the kernel process table during bisect runs. + Process.detach(pid) end private diff --git a/rspec-core/spec/integration/bisect_spec.rb b/rspec-core/spec/integration/bisect_spec.rb index 1d3a86439..820ca4d8c 100644 --- a/rspec-core/spec/integration/bisect_spec.rb +++ b/rspec-core/spec/integration/bisect_spec.rb @@ -33,7 +33,7 @@ def bisect(cli_args, expected_status=nil) end end - context "when the bisect commasaturingnd is long" do + context "when the bisect command saturates the pipe" do # On OSX and Linux a file descriptor limit meant that the bisect process got stuck at a certain limit. # This test demonstrates that we can run large bisects above this limit (found to be at time of commit). # See: https://github.com/rspec/rspec-core/pull/2669 @@ -41,6 +41,50 @@ def bisect(cli_args, expected_status=nil) output = bisect(%W[spec/rspec/core/resources/blocking_pipe_bisect_spec.rb_], 1) expect(output).to include("No failures found.") end + + it 'does not leave zombie processes', :unless => RSpec::Support::OS.windows? do + bisect(['--format', 'json', 'spec/rspec/core/resources/blocking_pipe_bisect_spec.rb_'], 1) + + zombie_process = RSpecChildProcess.new(Process.pid).zombie_process + expect(zombie_process).to eq([]), <<-MSG + Expected no zombie processes got #{zombie_process.count}: + #{zombie_process} + MSG + end + end + + class RSpecChildProcess + Ps = Struct.new(:pid, :ppid, :state, :command) + + def initialize(pid) + @list = child_process_list(pid) + end + + def zombie_process + @list.select { |child_process| child_process.state =~ /Z/ } + end + + private + + def child_process_list(pid) + childs_process_list = [] + ps_pipe = `ps -o pid=,ppid=,state=,args= | grep #{pid}` + + ps_pipe.split(/\n/).map do |line| + ps_part = line.lstrip.split(/\s+/) + + next unless ps_part[1].to_i == pid + + child_process = Ps.new + child_process.pid = ps_part[0] + child_process.ppid = ps_part[1] + child_process.state = ps_part[2] + child_process.command = ps_part[3..-1].join(' ') + + childs_process_list << child_process + end + childs_process_list + end end end end