diff --git a/lib/puma/queue_close.rb b/lib/puma/queue_close.rb index a1d568ccfc..d9a94dc665 100644 --- a/lib/puma/queue_close.rb +++ b/lib/puma/queue_close.rb @@ -5,22 +5,22 @@ module Puma # Add a simple implementation for earlier Ruby versions. # module QueueClose - def initialize - @closed = false - super - end def close + num_waiting.times {push nil} @closed = true end def closed? - @closed + @closed ||= false end def push(object) - @closed ||= false - raise ClosedQueueError if @closed + raise ClosedQueueError if closed? super end alias << push + def pop(non_block=false) + return nil if !non_block && closed? && empty? + super + end end ::Queue.prepend QueueClose end diff --git a/lib/puma/server.rb b/lib/puma/server.rb index 1f1ffc2161..3912699cf6 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -311,6 +311,7 @@ def handle_servers sockets = [check] + @binder.ios pool = @thread_pool queue_requests = @queue_requests + drain = @options[:drain_on_shutdown] ? 0 : nil remote_addr_value = nil remote_addr_header = nil @@ -322,9 +323,10 @@ def handle_servers remote_addr_header = @options[:remote_address_header] end - while @status == :run + while @status == :run || (drain && shutting_down?) begin - ios = IO.select sockets + ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil) + break unless ios ios.first.each do |sock| if sock == check break if handle_check @@ -337,6 +339,7 @@ def handle_servers rescue IO::WaitReadable next end + drain += 1 if shutting_down? client = Client.new io, @binder.env(sock) if remote_addr_value client.peerip = remote_addr_value @@ -351,6 +354,7 @@ def handle_servers end end + @events.debug "Drained #{drain} additional connections." if drain @events.fire :state, @status if queue_requests @@ -553,28 +557,6 @@ def graceful_shutdown $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n" end - if @options[:drain_on_shutdown] - count = 0 - - while true - ios = IO.select @binder.ios, nil, nil, 0 - break unless ios - - ios.first.each do |sock| - begin - if io = sock.accept_nonblock - count += 1 - client = Client.new io, @binder.env(sock) - @thread_pool << client - end - rescue SystemCallError - end - end - end - - @events.debug "Drained #{count} additional connections." - end - if @status != :restart @binder.close end diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb index 7ddcd1b50a..7ed629e4af 100644 --- a/test/test_puma_server.rb +++ b/test/test_puma_server.rb @@ -22,10 +22,9 @@ def teardown @ios.each { |io| io.close if io && !io.closed? } end - def server_run(app: @app, early_hints: false) - @server.app = app + def server_run(**options, &block) + @server = Puma::Server.new block || @app, @events, options @port = (@server.add_tcp_listener @host, 0).addr[1] - @server.early_hints = true if early_hints @server.run sleep 0.15 if Puma.jruby? end @@ -54,7 +53,7 @@ def new_connection end def test_normalize_host_header_missing - server_run app: ->(env) do + server_run do |env| [200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]] end @@ -63,7 +62,7 @@ def test_normalize_host_header_missing end def test_normalize_host_header_hostname - server_run app: ->(env) do + server_run do |env| [200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]] end @@ -75,7 +74,7 @@ def test_normalize_host_header_hostname end def test_normalize_host_header_ipv4 - server_run app: ->(env) do + server_run do |env| [200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]] end @@ -87,7 +86,7 @@ def test_normalize_host_header_ipv4 end def test_normalize_host_header_ipv6 - server_run app: ->(env) do + server_run do |env| [200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]] end @@ -104,7 +103,7 @@ def test_normalize_host_header_ipv6 def test_proper_stringio_body data = nil - server_run app: ->(env) do + server_run do |env| data = env['rack.input'].read [200, {}, ["ok"]] end @@ -123,7 +122,7 @@ def test_proper_stringio_body def test_puma_socket body = "HTTP/1.1 750 Upgraded to Awesome\r\nDone: Yep!\r\n" - server_run app: ->(env) do + server_run do |env| io = env['puma.socket'] io.write body io.close @@ -138,7 +137,7 @@ def test_puma_socket def test_very_large_return giant = "x" * 2056610 - server_run app: ->(env) do + server_run do [200, {}, [giant]] end @@ -179,7 +178,7 @@ def test_respect_x_forwarded_scheme end def test_default_server_port - server_run app: ->(env) do + server_run do |env| [200, {}, [env['SERVER_PORT']]] end @@ -194,7 +193,7 @@ def test_default_server_port end def test_default_server_port_respects_x_forwarded_proto - server_run app: ->(env) do + server_run do |env| [200, {}, [env['SERVER_PORT']]] end @@ -210,7 +209,7 @@ def test_default_server_port_respects_x_forwarded_proto end def test_HEAD_has_no_body - server_run app: ->(env) { [200, {"Foo" => "Bar"}, ["hello"]] } + server_run { [200, {"Foo" => "Bar"}, ["hello"]] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -218,7 +217,7 @@ def test_HEAD_has_no_body end def test_GET_with_empty_body_has_sane_chunking - server_run app: ->(env) { [200, {}, [""]] } + server_run { [200, {}, [""]] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -226,7 +225,7 @@ def test_GET_with_empty_body_has_sane_chunking end def test_early_hints_works - server_run early_hints: true, app: ->(env) do + server_run(early_hints: true) do |env| env['rack.early_hints'].call("Link" => "; rel=preload; as=style\n; rel=preload") [200, { "X-Hello" => "World" }, ["Hello world!"]] end @@ -250,15 +249,15 @@ def test_early_hints_works def test_early_hints_are_ignored_if_connection_lost - def @server.fast_write(*args) - raise Puma::ConnectionError - end - - server_run early_hints: true, app: ->(env) do + server_run(early_hints: true) do |env| env['rack.early_hints'].call("Link" => "; rel=preload") [200, { "X-Hello" => "World" }, ["Hello world!"]] end + def @server.fast_write(*args) + raise Puma::ConnectionError + end + # This request will cause the server to try and send early hints _ = send_http "HEAD / HTTP/1.0\r\n\r\n" @@ -270,7 +269,7 @@ def @server.fast_write(*args) end def test_early_hints_is_off_by_default - server_run app: ->(env) do + server_run do |env| assert_nil env['rack.early_hints'] [200, { "X-Hello" => "World" }, ["Hello world!"]] end @@ -289,7 +288,7 @@ def test_early_hints_is_off_by_default end def test_GET_with_no_body_has_sane_chunking - server_run app: ->(env) { [200, {}, []] } + server_run { [200, {}, []] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -297,8 +296,7 @@ def test_GET_with_no_body_has_sane_chunking end def test_doesnt_print_backtrace_in_production - @server.leak_stack_on_error = false - server_run app: ->(env) { raise "don't leak me bro" } + server_run(environment: :production) { raise "don't leak me bro" } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" @@ -319,9 +317,7 @@ def test_eof_on_connection_close_is_not_logged_as_an_error def test_force_shutdown_custom_error_message handler = lambda {|err, env, status| [500, {"Content-Type" => "application/json"}, ["{}\n"]]} - @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => handler, :force_shutdown_after => 2} - - server_run app: ->(env) do + server_run(lowlevel_error_handler: handler, force_shutdown_after: 2) do @server.stop sleep 5 end @@ -337,7 +333,7 @@ def test_lowlevel_error_message skip_if :windows @server = Puma::Server.new @app, @events, {:force_shutdown_after => 2} - server_run app: ->(env) do + server_run do require 'json' # will raise fatal: machine stack overflow in critical region @@ -353,9 +349,7 @@ def test_lowlevel_error_message end def test_force_shutdown_error_default - @server = Puma::Server.new @app, @events, {:force_shutdown_after => 2} - - server_run app: ->(env) do + server_run(force_shutdown_after: 2) do @server.stop sleep 5 end @@ -368,9 +362,7 @@ def test_force_shutdown_error_default def test_prints_custom_error re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] } - @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re} - - server_run app: ->(env) { raise "don't leak me bro" } + server_run(lowlevel_error_handler: re) { raise "don't leak me bro" } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" @@ -383,9 +375,7 @@ def test_leh_gets_env_as_well [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] } - @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re} - - server_run app: ->(env) { raise "don't leak me bro" } + server_run(lowlevel_error_handler: re) { raise "don't leak me bro" } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" @@ -398,9 +388,7 @@ def test_leh_has_status [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] } - @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re} - - server_run app: ->(env) { raise "don't leak me bro" } + server_run(lowlevel_error_handler: re) { raise "don't leak me bro" } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" @@ -408,7 +396,7 @@ def test_leh_has_status end def test_custom_http_codes_10 - server_run app: ->(env) { [449, {}, [""]] } + server_run { [449, {}, [""]] } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" @@ -416,7 +404,7 @@ def test_custom_http_codes_10 end def test_custom_http_codes_11 - server_run app: ->(env) { [449, {}, [""]] } + server_run { [449, {}, [""]] } data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" @@ -424,7 +412,7 @@ def test_custom_http_codes_11 end def test_HEAD_returns_content_headers - server_run app: ->(env) { [200, {"Content-Type" => "application/pdf", + server_run { [200, {"Content-Type" => "application/pdf", "Content-Length" => "4242"}, []] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -438,7 +426,7 @@ def test_status_hook_fires_when_server_changes_states @events.register(:state) { |s| states << s } - server_run app: ->(env) { [200, {}, [""]] } + server_run { [200, {}, [""]] } _ = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -449,9 +437,8 @@ def test_status_hook_fires_when_server_changes_states assert_equal [:booting, :running, :stop, :done], states end - def test_timeout_in_data_phase - @server.first_data_timeout = 1 - server_run + def test_timeout_in_data_phase(**options) + server_run(first_data_timeout: 1, **options) sock = send_http "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n" @@ -463,8 +450,7 @@ def test_timeout_in_data_phase end def test_timeout_data_no_queue - @server = Puma::Server.new @app, @events, queue_requests: false - test_timeout_in_data_phase + test_timeout_in_data_phase(queue_requests: false) end # https://github.com/puma/puma/issues/2574 @@ -492,7 +478,7 @@ def test_no_timeout_after_data_received_no_queue end def test_http_11_keep_alive_with_body - server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } + server_run { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } sock = send_http "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n" @@ -507,7 +493,7 @@ def test_http_11_keep_alive_with_body end def test_http_11_close_with_body - server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello"]] } + server_run { [200, {"Content-Type" => "plain/text"}, ["hello"]] } data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" @@ -515,7 +501,7 @@ def test_http_11_close_with_body end def test_http_11_keep_alive_without_body - server_run app: ->(env) { [204, {}, []] } + server_run { [204, {}, []] } sock = send_http "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n" @@ -525,7 +511,7 @@ def test_http_11_keep_alive_without_body end def test_http_11_close_without_body - server_run app: ->(env) { [204, {}, []] } + server_run { [204, {}, []] } sock = send_http "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" @@ -535,7 +521,7 @@ def test_http_11_close_without_body end def test_http_10_keep_alive_with_body - server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } + server_run { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } sock = send_http "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" @@ -548,7 +534,7 @@ def test_http_10_keep_alive_with_body end def test_http_10_close_with_body - server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello"]] } + server_run { [200, {"Content-Type" => "plain/text"}, ["hello"]] } data = send_http_and_read "GET / HTTP/1.0\r\nConnection: close\r\n\r\n" @@ -558,7 +544,7 @@ def test_http_10_close_with_body def test_http_10_partial_hijack_with_content_length body_parts = ['abc', 'de'] - server_run app: ->(env) do + server_run do hijack_lambda = proc do | io | io.write(body_parts[0]) io.write(body_parts[1]) @@ -573,7 +559,7 @@ def test_http_10_partial_hijack_with_content_length end def test_http_10_keep_alive_without_body - server_run app: ->(env) { [204, {}, []] } + server_run { [204, {}, []] } sock = send_http "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" @@ -583,7 +569,7 @@ def test_http_10_keep_alive_without_body end def test_http_10_close_without_body - server_run app: ->(env) { [204, {}, []] } + server_run { [204, {}, []] } data = send_http_and_read "GET / HTTP/1.0\r\nConnection: close\r\n\r\n" @@ -591,7 +577,7 @@ def test_http_10_close_without_body end def test_Expect_100 - server_run app: ->(env) { [200, {}, [""]] } + server_run { [200, {}, [""]] } data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nExpect: 100-continue\r\n\r\n" @@ -601,7 +587,7 @@ def test_Expect_100 def test_chunked_request body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -617,7 +603,7 @@ def test_chunked_request def test_large_chunked_request body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -647,7 +633,7 @@ def test_large_chunked_request def test_chunked_request_pause_before_value body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -668,7 +654,7 @@ def test_chunked_request_pause_before_value def test_chunked_request_pause_between_chunks body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -689,7 +675,7 @@ def test_chunked_request_pause_between_chunks def test_chunked_request_pause_mid_count body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -710,7 +696,7 @@ def test_chunked_request_pause_mid_count def test_chunked_request_pause_before_count_newline body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -731,7 +717,7 @@ def test_chunked_request_pause_before_count_newline def test_chunked_request_pause_mid_value body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -752,7 +738,7 @@ def test_chunked_request_pause_mid_value def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -782,7 +768,7 @@ def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk def test_chunked_request_pause_between_closing_cr_lf body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -804,7 +790,7 @@ def test_chunked_request_pause_between_closing_cr_lf def test_chunked_request_pause_before_closing_cr_lf body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -826,7 +812,7 @@ def test_chunked_request_pause_before_closing_cr_lf def test_chunked_request_header_case body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -842,7 +828,7 @@ def test_chunked_request_header_case def test_chunked_keep_alive body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -862,7 +848,7 @@ def test_chunked_keep_alive def test_chunked_keep_alive_two_back_to_back body = nil content_length = nil - server_run app: ->(env) { + server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] [200, {}, [""]] @@ -903,8 +889,7 @@ def test_chunked_keep_alive_two_back_to_back_with_set_remote_address body = nil content_length = nil remote_addr =nil - @server = Puma::Server.new @app, @events, { remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR'} - server_run app: ->(env) { + server_run(remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR') { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] remote_addr = env['REMOTE_ADDR'] @@ -936,7 +921,7 @@ def test_chunked_encoding enc = Encoding::UTF_16LE str = "──иї_テスト──\n".encode enc - server_run app: ->(env) { + server_run { hdrs = {} hdrs['Content-Type'] = "text; charset=#{enc.to_s.downcase}" @@ -958,7 +943,7 @@ def test_chunked_encoding end def test_empty_header_values - server_run app: ->(env) { [200, {"X-Empty-Header" => ""}, []] } + server_run { [200, {"X-Empty-Header" => ""}, []] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -967,7 +952,7 @@ def test_empty_header_values def test_request_body_wait request_body_wait = nil - server_run app: ->(env) { + server_run { |env| request_body_wait = env['puma.request_body_wait'] [204, {}, []] } @@ -985,7 +970,7 @@ def test_request_body_wait def test_request_body_wait_chunked request_body_wait = nil - server_run app: ->(env) { + server_run { |env| request_body_wait = env['puma.request_body_wait'] [204, {}, []] } @@ -1001,8 +986,8 @@ def test_request_body_wait_chunked assert_operator request_body_wait, :>=, 900 end - def test_open_connection_wait - server_run app: ->(_) { [200, {}, ["Hello"]] } + def test_open_connection_wait(**options) + server_run(**options) { [200, {}, ["Hello"]] } s = send_http nil sleep 0.1 s << "GET / HTTP/1.0\r\n\r\n" @@ -1010,13 +995,12 @@ def test_open_connection_wait end def test_open_connection_wait_no_queue - @server = Puma::Server.new @app, @events, queue_requests: false - test_open_connection_wait + test_open_connection_wait(queue_requests: false) end # Rack may pass a newline in a header expecting us to split it. def test_newline_splits - server_run app: ->(_) { [200, {'X-header' => "first line\nsecond line"}, ["Hello"]] } + server_run { [200, {'X-header' => "first line\nsecond line"}, ["Hello"]] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -1024,7 +1008,7 @@ def test_newline_splits end def test_newline_splits_in_early_hint - server_run early_hints: true, app: ->(env) do + server_run(early_hints: true) do |env| env['rack.early_hints'].call({'X-header' => "first line\nsecond line"}) [200, {}, ["Hello world!"]] end @@ -1037,7 +1021,7 @@ def test_newline_splits_in_early_hint # To comply with the Rack spec, we have to split header field values # containing newlines into multiple headers. def assert_does_not_allow_http_injection(app, opts = {}) - server_run(early_hints: opts[:early_hints], app: app) + server_run(early_hints: opts[:early_hints], &app) data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" @@ -1080,10 +1064,9 @@ def assert_does_not_allow_http_injection(app, opts = {}) # Perform a server shutdown while requests are pending (one in app-server response, one still sending client request). def shutdown_requests(s1_complete: true, s1_response: nil, post: false, s2_response: nil, **options) - @server = Puma::Server.new @app, @events, options mutex = Mutex.new app_finished = ConditionVariable.new - server_run app: ->(env) { + server_run(**options) { |env| path = env['REQUEST_PATH'] mutex.synchronize do app_finished.signal @@ -1157,7 +1140,7 @@ def test_force_shutdown end def test_http11_connection_header_queue - server_run app: ->(_) { [200, {}, [""]] } + server_run { [200, {}, [""]] } sock = send_http "GET / HTTP/1.1\r\n\r\n" assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], header(sock) @@ -1169,7 +1152,7 @@ def test_http11_connection_header_queue end def test_http10_connection_header_queue - server_run app: ->(_) { [200, {}, [""]] } + server_run { [200, {}, [""]] } sock = send_http "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n" assert_equal ["HTTP/1.0 200 OK", "Connection: Keep-Alive", "Content-Length: 0"], header(sock) @@ -1180,16 +1163,14 @@ def test_http10_connection_header_queue end def test_http11_connection_header_no_queue - @server = Puma::Server.new @app, @events, queue_requests: false - server_run app: ->(_) { [200, {}, [""]] } + server_run(queue_requests: false) { [200, {}, [""]] } sock = send_http "GET / HTTP/1.1\r\n\r\n" assert_equal ["HTTP/1.1 200 OK", "Connection: close", "Content-Length: 0"], header(sock) sock.close end def test_http10_connection_header_no_queue - @server = Puma::Server.new @app, @events, queue_requests: false - server_run app: ->(_) { [200, {}, [""]] } + server_run(queue_requests: false) { [200, {}, [""]] } sock = send_http "GET / HTTP/1.0\r\n\r\n" assert_equal ["HTTP/1.0 200 OK", "Content-Length: 0"], header(sock) sock.close @@ -1233,9 +1214,7 @@ def test_client_quick_close_no_lowlevel_error_handler_call [500, {"Content-Type" => "application/json"}, ["{}\n"]] } - @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => handler} - - server_run app: ->(env) { [200, {}, ['Hello World']] } + server_run(lowlevel_error_handler: handler) { [200, {}, ['Hello World']] } # valid req & read, close sock = TCPSocket.new @host, @port @@ -1303,4 +1282,34 @@ def test_custom_io_selector assert_equal selector.backend, backend end + + def test_drain_on_shutdown(drain=true) + num_connections = 10 + + wait = Queue.new + server_run(drain_on_shutdown: drain, max_threads: 1) do + wait.pop + [200, {}, ["DONE"]] + end + connections = Array.new(num_connections) {send_http "GET / HTTP/1.0\r\n\r\n"} + @server.stop + wait.close + bad = 0 + connections.each do |s| + begin + assert_match 'DONE', s.read + rescue Errno::ECONNRESET + bad += 1 + end + end + if drain + assert_equal 0, bad + else + refute_equal 0, bad + end + end + + def test_not_drain_on_shutdown + test_drain_on_shutdown false + end end