mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
6777c771d8
Previously, the app thread would be in charge of reading the request directly from the client. This resulted in a set of slow clients being able to completely starve the app thread pool and prevent any further connections from being handled. This new organization uses a seperate reactor thread that is in charge of responding when a client has more data, buffering the data and attempting to parse the data. When the data represents a fully realized request, only then is it handed to the app thread pool. This means we trust apps to not starve the pool, but don't trust clients.
82 lines
1.8 KiB
Ruby
82 lines
1.8 KiB
Ruby
module Puma
|
|
class Reactor
|
|
DefaultSleepFor = 5
|
|
|
|
def initialize(events, app_pool)
|
|
@events = events
|
|
@app_pool = app_pool
|
|
|
|
@mutex = Mutex.new
|
|
@ready, @trigger = IO.pipe
|
|
@input = []
|
|
@sleep_for = DefaultSleepFor
|
|
@timeouts = []
|
|
end
|
|
|
|
def run
|
|
sockets = [@ready]
|
|
|
|
while true
|
|
ready = IO.select sockets, nil, nil, @sleep_for
|
|
|
|
if ready and reads = ready[0]
|
|
reads.each do |c|
|
|
if c == @ready
|
|
@mutex.synchronize do
|
|
@ready.read(1) # drain
|
|
sockets += @input
|
|
@input.clear
|
|
end
|
|
else
|
|
begin
|
|
if c.try_to_finish
|
|
@app_pool << c
|
|
sockets.delete c
|
|
end
|
|
# The client doesn't know HTTP well
|
|
rescue HttpParserError => e
|
|
@events.parse_error self, c.env, e
|
|
|
|
rescue EOFError
|
|
c.close
|
|
sockets.delete c
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
unless @timeouts.empty?
|
|
now = Time.now
|
|
|
|
while @timeouts.first.timeout_at < now
|
|
c = @timeouts.shift
|
|
sockets.delete c
|
|
c.close
|
|
|
|
if @timeouts.empty?
|
|
@sleep_for = DefaultSleepFor
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def run_in_thread
|
|
@thread = Thread.new { run }
|
|
end
|
|
|
|
def add(c)
|
|
@mutex.synchronize do
|
|
@input << c
|
|
@trigger << "!"
|
|
|
|
if c.timeout_at
|
|
@timeouts << c
|
|
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
|
@sleep_for = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|