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

Merge pull request #1857 from Jesus/master

Avoid blocking on SSL's `#read_nonblock`
This commit is contained in:
Evan Phoenix 2019-08-03 11:57:52 -07:00 committed by GitHub
commit 0a94347ab4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 20 deletions

View file

@ -54,22 +54,21 @@ module Puma
output = engine_read_all output = engine_read_all
return output if output return output if output
begin
data = @socket.read_nonblock(size, exception: false) data = @socket.read_nonblock(size, exception: false)
if data == :wait_readable || data == :wait_writable if data == :wait_readable || data == :wait_writable
if @socket.to_io.respond_to?(data) # It would make more sense to let @socket.read_nonblock raise
@socket.to_io.__send__(data) # EAGAIN if necessary but it seems like it'll misbehave on Windows.
elsif data == :wait_readable # I don't have a Windows machine to debug this so I can't explain
IO.select([@socket.to_io]) # exactly whats happening in that OS. Please let me know if you
else # find out!
IO.select(nil, [@socket.to_io]) #
end # In the meantime, we can emulate the correct behavior by
elsif !data # capturing :wait_readable & :wait_writable and raising EAGAIN
# ourselves.
raise IO::EAGAINWaitReadable
elsif data.nil?
return nil return nil
else
break
end end
end while true
@engine.inject(data) @engine.inject(data)
output = engine_read_all output = engine_read_all

View file

@ -34,8 +34,8 @@ class TestPumaServerSSL < Minitest::Test
def setup def setup
return if DISABLE_SSL return if DISABLE_SSL
port = UniquePort.call @port = UniquePort.call
host = "127.0.0.1" @host = "127.0.0.1"
app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }
@ -53,10 +53,10 @@ class TestPumaServerSSL < Minitest::Test
@events = SSLEventsHelper.new STDOUT, STDERR @events = SSLEventsHelper.new STDOUT, STDERR
@server = Puma::Server.new app, @events @server = Puma::Server.new app, @events
@ssl_listener = @server.add_ssl_listener host, port, ctx @ssl_listener = @server.add_ssl_listener @host, @port, ctx
@server.run @server.run
@http = Net::HTTP.new host, port @http = Net::HTTP.new @host, @port
@http.use_ssl = true @http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end end
@ -80,6 +80,25 @@ class TestPumaServerSSL < Minitest::Test
assert_equal "https", body assert_equal "https", body
end end
def test_request_wont_block_thread
# Open a connection and give enough data to trigger a read, then wait
ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
socket = OpenSSL::SSL::SSLSocket.new TCPSocket.new(@host, @port), ctx
socket.write "x"
sleep 0.1
# Capture the amount of threads being used after connecting and being idle
thread_pool = @server.instance_variable_get(:@thread_pool)
busy_threads = thread_pool.spawned - thread_pool.waiting
socket.close
# The thread pool should be empty since the request would block on read
# and our request should have been moved to the reactor.
assert busy_threads.zero?, "Our connection is monopolizing a thread"
end
def test_very_large_return def test_very_large_return
giant = "x" * 2056610 giant = "x" * 2056610