From cb080346a437fee4f8ad2a887d45fc82d9f4873c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 17 Sep 2021 04:54:14 +1200 Subject: [PATCH] More elaborate exception handling. (#2700) * More elaborate exception handling. Fixes #2699. * Add TestIntegrationSingle#test_closed_listener Co-authored-by: MSP-Greg --- lib/puma/server.rb | 5 ++++- test/helpers/integration.rb | 8 ++++++-- test/rackup/close_listeners.ru | 6 ++++++ test/test_integration_single.rb | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 test/rackup/close_listeners.ru diff --git a/lib/puma/server.rb b/lib/puma/server.rb index 1309eb84..00429496 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -356,7 +356,10 @@ module Puma pool << client end end - rescue Object => e + rescue IOError, Errno::EBADF + # In the case that any of the sockets are unexpectedly close. + raise + rescue StandardError => e @events.unknown_error e, nil, "Listen loop" end end diff --git a/test/helpers/integration.rb b/test/helpers/integration.rb index c013c7de..49a48e31 100644 --- a/test/helpers/integration.rb +++ b/test/helpers/integration.rb @@ -50,7 +50,7 @@ class TestIntegration < Minitest::Test assert(system(*args, out: File::NULL, err: File::NULL)) end - def cli_server(argv, unix: false, config: nil) + def cli_server(argv, unix: false, config: nil, merge_err: false) if config config_file = Tempfile.new(%w(config .rb)) config_file.write config @@ -64,7 +64,11 @@ class TestIntegration < Minitest::Test @tcp_port = UniquePort.call cmd = "#{BASE} #{puma_path} #{config} -b tcp://#{HOST}:#{@tcp_port} #{argv}" end - @server = IO.popen(cmd, "r") + if merge_err + @server = IO.popen(cmd, "r", :err=>[:child, :out]) + else + @server = IO.popen(cmd, "r") + end wait_for_server_to_boot @pid = @server.pid @server diff --git a/test/rackup/close_listeners.ru b/test/rackup/close_listeners.ru new file mode 100644 index 00000000..d7d37225 --- /dev/null +++ b/test/rackup/close_listeners.ru @@ -0,0 +1,6 @@ +require 'objspace' + +run lambda { |env| + ios = ObjectSpace.each_object(::TCPServer).to_a.tap { |a| a.each(&:close) } + [200, [], ["#{ios.inspect}\n"]] +} diff --git a/test/test_integration_single.rb b/test/test_integration_single.rb index 09e28c79..f931e1a3 100644 --- a/test/test_integration_single.rb +++ b/test/test_integration_single.rb @@ -178,4 +178,29 @@ class TestIntegrationSingle < TestIntegration @server.close unless @server.closed? @server = nil end + + # listener is closed 'externally' while Puma is in the IO.select statement + def test_closed_listener + skip_unless_signal_exist? :TERM + + cli_server "test/rackup/close_listeners.ru", merge_err: true + read_body connect + + begin + Timeout.timeout(5) do + begin + Process.kill :SIGTERM, @pid + rescue Errno::ESRCH + end + begin + Process.wait2 @pid + rescue Errno::ECHILD + end + end + rescue Timeout::Error + Process.kill :SIGKILL, @pid + assert false, "Process froze" + end + assert true + end end