1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Refactor drain on shutdown (#2600)

* Refactor test_puma_server#server_run for option arguments

* Refactor drain_on_shutdown into main server accept loop
Also add test coverage for drain_on_shutdown option.
This commit is contained in:
Will Jordan 2021-04-30 15:22:59 -07:00 committed by GitHub
parent 3e80f7c704
commit ff17194228
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 127 deletions

View file

@ -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

View file

@ -311,6 +311,7 @@ module Puma
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 @@ module Puma
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 @@ module Puma
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 @@ module Puma
end
end
@events.debug "Drained #{drain} additional connections." if drain
@events.fire :state, @status
if queue_requests
@ -553,28 +557,6 @@ module Puma
$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

View file

@ -22,10 +22,9 @@ class TestPumaServer < Minitest::Test
@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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
def test_very_large_return
giant = "x" * 2056610
server_run app: ->(env) do
server_run do
[200, {}, [giant]]
end
@ -179,7 +178,7 @@ class TestPumaServer < Minitest::Test
end
def test_default_server_port
server_run app: ->(env) do
server_run do |env|
[200, {}, [env['SERVER_PORT']]]
end
@ -194,7 +193,7 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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 @@ class TestPumaServer < Minitest::Test
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" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
[200, { "X-Hello" => "World" }, ["Hello world!"]]
end
@ -250,15 +249,15 @@ EOF
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" => "</script.js>; 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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
[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 @@ EOF
[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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
@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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
# 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 @@ EOF
# 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 @@ EOF
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 @@ EOF
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 @@ EOF
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 @@ EOF
[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 @@ EOF
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