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

Fix out_of_band hook (#2218)

* Fix out_of_band hook
Add server tests to ensure oob hooks fire as expected.

* Use mutex for out of band hook
Prevent overlapping requests from being processed during
out of band hook.
This commit is contained in:
Will Jordan 2020-04-15 18:01:12 -07:00 committed by GitHub
parent 67f9b1f3f2
commit df9cdc703b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 9 deletions

View file

@ -31,6 +31,7 @@
* Set `Connection: closed` header when queue requests is disabled (#2216)
* Pass queued requests to thread pool on server shutdown (#2122)
* Fixed a few minor concurrency bugs in ThreadPool that may have affected non-GVL Rubies (#2220)
* Fix `out_of_band` hook never executed if the number of worker threads is > 1 (#2177)
* Refactor
* Remove unused loader argument from Plugin initializer (#2095)

View file

@ -288,10 +288,7 @@ module Puma
end
pool << client
busy_threads = pool.wait_until_not_full
if busy_threads == 0
@options[:out_of_band].each(&:call) if @options[:out_of_band]
end
pool.wait_until_not_full
end
rescue SystemCallError
# nothing
@ -439,6 +436,12 @@ module Puma
rescue StandardError => e
@events.unknown_error self, e, "Client"
end
if @options[:out_of_band]
@thread_pool.with_mutex do
@options[:out_of_band].each(&:call) if @thread_pool.busy_threads == 1
end
end
end
end

View file

@ -82,6 +82,10 @@ module Puma
waiting + (@max - spawned)
end
def busy_threads
with_mutex { @spawned - @waiting + @todo.size }
end
# :nodoc:
#
# Must be called with @mutex held!
@ -188,9 +192,6 @@ module Puma
# method would not block and another request would be added into the reactor
# by the server. This would continue until a fully bufferend request
# makes it through the reactor and can then be processed by the thread pool.
#
# Returns the current number of busy threads, or +nil+ if shutting down.
#
def wait_until_not_full
with_mutex do
while true
@ -200,8 +201,7 @@ module Puma
# is work queued that cannot be handled by waiting
# threads, then accept more work until we would
# spin up the max number of threads.
busy_threads = @spawned - @waiting + @todo.size
return busy_threads if @max > busy_threads
return if busy_threads < @max
@not_full.wait @mutex
end

View file

@ -984,4 +984,85 @@ EOF
assert_equal ["HTTP/1.0 200 OK", "Content-Length: 0"], header(sock)
sock.close
end
def oob_server(**options)
@request_count = 0
@oob_count = 0
@start = Time.now
in_oob = Mutex.new
@mutex = Mutex.new
@oob_finished = ConditionVariable.new
oob_wait = options.delete(:oob_wait)
oob = -> do
in_oob.synchronize do
@mutex.synchronize do
@oob_count += 1
@oob_finished.signal
@oob_finished.wait(@mutex, 1) if oob_wait
end
end
end
@server = Puma::Server.new @app, @events, out_of_band: [oob], **options
@server.min_threads = 5
@server.max_threads = 5
server_run app: ->(_) do
raise 'OOB conflict' if in_oob.locked?
@mutex.synchronize {@request_count += 1}
[200, {}, [""]]
end
end
# Sequential requests should trigger out_of_band hooks after every request.
def test_out_of_band
n = 100
oob_server queue_requests: false
n.times do
@mutex.synchronize do
send_http "GET / HTTP/1.0\r\n\r\n"
@oob_finished.wait(@mutex, 1)
end
end
assert_equal n, @request_count
assert_equal n, @oob_count
end
# Streaming requests on parallel connections without delay should trigger
# out_of_band hooks only once after the final request.
def test_out_of_band_stream
n = 100
threads = 10
oob_server
req = "GET / HTTP/1.1\r\n"
@mutex.synchronize do
Array.new(threads) do
Thread.new do
send_http "#{req}\r\n" * (n/threads-1) + "#{req}Connection: close\r\n\r\n"
end
end.each(&:join)
@oob_finished.wait(@mutex, 1)
end
assert_equal n, @request_count
assert_equal 1, @oob_count
end
def test_out_of_band_overlapping_requests
oob_server oob_wait: true
sock = nil
sock2 = nil
@mutex.synchronize do
sock2 = send_http "GET / HTTP/1.0\r\n"
sleep 0.01
sock = send_http "GET / HTTP/1.0\r\n\r\n"
# Request 1 processed
@oob_finished.wait(@mutex) # enter oob
sock2 << "\r\n"
sleep 0.01
@oob_finished.signal # exit oob
# Request 2 processed
@oob_finished.wait(@mutex) # enter oob
@oob_finished.signal # exit oob
end
assert_match(/200/, sock.read)
assert_match(/200/, sock2.read)
end
end