Skip to content

Commit

Permalink
Use mutex for out of band hook
Browse files Browse the repository at this point in the history
Prevent overlapping requests from being processed during
out of band hook.
  • Loading branch information
wjordan committed Apr 15, 2020
1 parent 17ea126 commit 64db4f0
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 14 deletions.
1 change: 0 additions & 1 deletion lib/puma/const.rb
Expand Up @@ -186,7 +186,6 @@ module Const
STOP_COMMAND = "?".freeze
HALT_COMMAND = "!".freeze
RESTART_COMMAND = "R".freeze
OUT_OF_BAND_COMMAND = "O".freeze

RACK_INPUT = "rack.input".freeze
RACK_URL_SCHEME = "rack.url_scheme".freeze
Expand Down
15 changes: 6 additions & 9 deletions lib/puma/server.rb
Expand Up @@ -224,12 +224,6 @@ def run(background=true)
@reactor.add client
end
end

if @options[:out_of_band]
@thread_pool.with_mutex do
notify_safely(OUT_OF_BAND_COMMAND) if @thread_pool.busy_threads == 1
end
end
end

@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
Expand Down Expand Up @@ -346,9 +340,6 @@ def handle_check
when RESTART_COMMAND
@status = :restart
return true
when OUT_OF_BAND_COMMAND
@options[:out_of_band].each(&:call) if @options[:out_of_band]
return false
end

return false
Expand Down Expand Up @@ -445,6 +436,12 @@ def process_client(client, buffer)
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

Expand Down
44 changes: 40 additions & 4 deletions test/test_puma_server.rb
Expand Up @@ -989,13 +989,28 @@ 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 = -> {@oob_count += 1; @oob_finished.signal}
oob_wait = options.delete(:oob_wait)
oob = -> do
puts 'oob'
in_oob.synchronize do
@mutex.synchronize do
puts 'oob enter'
@oob_count += 1
@oob_finished.signal
@oob_finished.wait(@mutex, 1) if oob_wait
puts 'oob 2'
end
end
puts 'oob exit'
end
@server = Puma::Server.new @app, @events, out_of_band: [oob], **options
@server.min_threads = 5
@server.max_threads = 5
@mutex = Mutex.new
server_run app: ->(_) do
raise 'OOB conflict' if in_oob.locked?
@mutex.synchronize {@request_count += 1}
[200, {}, [""]]
end
Expand All @@ -1007,7 +1022,7 @@ def test_out_of_band
oob_server queue_requests: false
n.times do
@mutex.synchronize do
send_http "HEAD / HTTP/1.0\r\n\r\n"
send_http "GET / HTTP/1.0\r\n\r\n"
@oob_finished.wait(@mutex, 1)
end
end
Expand All @@ -1021,7 +1036,7 @@ def test_out_of_band_stream
n = 100
threads = 10
oob_server
req = "HEAD / HTTP/1.1\r\n"
req = "GET / HTTP/1.1\r\n"
@mutex.synchronize do
Array.new(threads) do
Thread.new do
Expand All @@ -1033,4 +1048,25 @@ def test_out_of_band_stream
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

0 comments on commit 64db4f0

Please sign in to comment.