Merge branch 'handle-long-uploads' of git://github.com/weknowtraining/puma into weknowtraining-handle-long-uploads

This commit is contained in:
Nate Berkopec 2021-05-28 09:15:55 -06:00
commit 3670ea1db6
No known key found for this signature in database
GPG Key ID: 19616755F4328D71
4 changed files with 77 additions and 19 deletions

View File

@ -206,8 +206,9 @@ module Puma
try_to_finish
end
def finish(timeout)
def finish(first_data_timeout, between_bytes_timeout)
return if @ready
timeout = @parsed_bytes > 0 ? between_bytes_timeout : first_data_timeout
IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
end

View File

@ -272,6 +272,12 @@ module Puma
@options[:first_data_timeout] = Integer(seconds)
end
# Define how long the tcp socket stays open, once data has been received.
# @see Puma::Server.new
def between_bytes_timeout(seconds)
@options[:between_bytes_timeout] = Integer(seconds)
end
# Work around leaky apps that leave garbage in Thread locals
# across requests.
def clean_thread_locals(which=true)

View File

@ -40,12 +40,14 @@ module Puma
attr_reader :requests_count # @version 5.0.0
# @todo the following may be deprecated in the future
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
attr_reader :auto_trim_time, :early_hints,
:first_data_timeout, :between_bytes_timeout,
:leak_stack_on_error,
:persistent_timeout, :reaping_time
# @deprecated v6.0.0
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
attr_writer :auto_trim_time, :early_hints,
:first_data_timeout, :between_bytes_timeout,
:leak_stack_on_error, :min_threads, :max_threads,
:persistent_timeout, :reaping_time
@ -84,14 +86,15 @@ module Puma
@options = options
@early_hints = options.fetch :early_hints, nil
@first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
@min_threads = options.fetch :min_threads, 0
@max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
@persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
@queue_requests = options.fetch :queue_requests, true
@max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
@io_selector_backend = options.fetch :io_selector_backend, :auto
@early_hints = options.fetch :early_hints, nil
@first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
@between_bytes_timeout = options.fetch :between_bytes_timeout, @first_data_timeout
@min_threads = options.fetch :min_threads, 0
@max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
@persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
@queue_requests = options.fetch :queue_requests, true
@max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
@io_selector_backend = options.fetch :io_selector_backend, :auto
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
@leak_stack_on_error = @options[:environment] ? temp : true
@ -296,7 +299,7 @@ module Puma
elsif shutdown || client.timeout == 0
client.timeout!
else
client.set_timeout(@first_data_timeout)
client.set_timeout(@between_bytes_timeout)
false
end
rescue StandardError => e
@ -430,7 +433,7 @@ module Puma
end
with_force_shutdown(client) do
client.finish(@first_data_timeout)
client.finish(@first_data_timeout, @between_bytes_timeout)
end
while true

View File

@ -436,24 +436,24 @@ EOF
assert_equal [:booting, :running, :stop, :done], states
end
def test_timeout_in_data_phase(**options)
server_run(first_data_timeout: 1, **options)
def test_timeout_in_data_phase
@server.first_data_timeout = 2
server_run
sock = send_http "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n"
sock << "Hello" unless IO.select([sock], nil, nil, 1.15)
data = sock.gets
data = assert_proper_timeout(@server.first_data_timeout) { sock.gets }
assert_equal "HTTP/1.1 408 Request Timeout\r\n", data
end
def test_timeout_data_no_queue
@server = Puma::Server.new @app, @events, queue_requests: false
test_timeout_in_data_phase(queue_requests: false)
end
# https://github.com/puma/puma/issues/2574
def test_no_timeout_after_data_received
def test_no_timeout_after_data_received_first_data
@server.first_data_timeout = 1
server_run
@ -476,6 +476,44 @@ EOF
test_no_timeout_after_data_received
end
def test_timeout_after_data_received
@server.first_data_timeout = 4
@server.between_bytes_timeout = 2
server_run
sock = send_http "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 100\r\n\r\n"
sleep 0.1
sock << "hello"
sleep 0.1
data = assert_proper_timeout(@server.between_bytes_timeout) { sock.gets }
assert_equal "HTTP/1.1 408 Request Timeout\r\n", data
end
def test_timeout_after_data_received_no_queue
@server = Puma::Server.new @app, @events, queue_requests: false
test_timeout_after_data_received
end
def test_no_timeout_after_data_received
@server.first_data_timeout = 10
@server.between_bytes_timeout = 4
server_run
sock = send_http "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 10\r\n\r\n"
sleep 0.1
sock << "hello"
sleep 2
sock << "world"
data = sock.gets
assert_equal "HTTP/1.1 200 OK\r\n", data
end
def test_http_11_keep_alive_with_body
server_run { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
@ -1331,4 +1369,14 @@ EOF
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
assert_equal "user", data.split("\r\n").last
end
private
def assert_proper_timeout(expected)
now = Time.now
ret = yield
t = Time.now - now
assert_in_delta expected, t, 0.5, "unexpected timeout, #{t} instead of ~#{expected}"
ret
end
end