experimental optimizations in the connection call loop.

skip entering the read/write hot loops when the connection interests
specifically say so. this led to a overhaul of how interests are
calculated.
This commit is contained in:
HoneyryderChuck 2021-02-12 10:33:16 +00:00
parent ad88aa27cf
commit 4bab923f25
5 changed files with 71 additions and 17 deletions

View File

@ -170,7 +170,7 @@ module HTTPX
end
# if the write buffer is full, we drain it
return :w if @write_buffer.full?
return :w unless @write_buffer.empty?
return @parser.interests if @parser
@ -254,8 +254,14 @@ module HTTPX
loop do
parser.consume
# we exit if there's no more data to process
if @pending.size.zero? && @inflight.zero?
# we exit if there's no more requests to process
#
# this condition takes into account:
#
# * the number of inflight requests
# * the number of pending requests
# * whether the write buffer has bytes (i.e. for close handshake)
if @pending.size.zero? && @inflight.zero? && @write_buffer.empty?
log(level: 3) { "NO MORE REQUESTS..." }
return
end
@ -265,7 +271,14 @@ module HTTPX
read_drained = false
write_drained = nil
# dread
#
# tight read loop.
#
# read as much of the socket as possible.
#
# this tight loop reads all the data it can from the socket and pipes it to
# its parser.
#
loop do
siz = @io.read(@window_size, @read_buffer)
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes..." }
@ -276,6 +289,7 @@ module HTTPX
return
end
# socket has been drained. mark and exit the read loop.
if siz.zero?
read_drained = @read_buffer.empty?
break
@ -283,16 +297,27 @@ module HTTPX
parser << @read_buffer.to_s
# continue reading if possible.
break if interests == :w
# exit the read loop if connection is preparing to be closed
break if @state == :closing || @state == :closed
# for HTTP/2, we just want to write goaway frame
end unless @state == :closing
# exit #consume altogether if all outstanding requests have been dealt with
return if @pending.size.zero? && @inflight.zero?
end unless (interests == :w || @state == :closing) && !epiped
# dwrite
#
# tight write loop.
#
# flush as many bytes as the sockets allow.
#
loop do
# buffer has been drainned, mark and exit the write loop.
if @write_buffer.empty?
# we only mark as drained on the first loop
write_drained = write_drained.nil? && @inflight.positive?
break
end
@ -305,21 +330,28 @@ module HTTPX
return
end
# socket closed for writing. mark and exit the write loop.
if siz.zero?
write_drained = !@write_buffer.empty?
break
end
break if @state == :closing || @state == :closed
# exit write loop if marked to consume from peer, or is closing.
break if interests == :r || @state == :closing || @state == :closed
write_drained = false
end
end unless interests == :r
# return if socket is drained
if read_drained && write_drained
log(level: 3) { "WAITING FOR EVENTS..." }
return
end
next unless (interests != :r || read_drained) &&
(interests != :w || write_drained)
# gotta go back to the event loop. It happens when:
#
# * the socket is drained of bytes or it's not the interest of the conn to read;
# * theres nothing more to write, or it's not in the interest of the conn to write;
log(level: 3) { "(#{interests}): WAITING FOR EVENTS..." }
return
end
end
end

View File

@ -42,7 +42,7 @@ module HTTPX
return @buffer.empty? ? :r : :rw
end
return :w unless @pending.empty?
return :w if !@pending.empty? && can_buffer_more_requests?
return :w if @streams.each_key.any? { |r| r.interests == :w }
@ -70,10 +70,14 @@ module HTTPX
@connection << data
end
def can_buffer_more_requests?
@handshake_completed &&
@streams.size < @max_concurrent_requests &&
@streams.size < @max_requests
end
def send(request)
if !@handshake_completed ||
@streams.size >= @max_concurrent_requests ||
@streams.size >= @max_requests
unless can_buffer_more_requests?
@pending << request
return
end

View File

@ -16,6 +16,14 @@ module HTTPX
Error = Socks4Error
module ConnectionMethods
def interests
if @state == :connecting
return @write_buffer.empty? ? :r : :w
end
super
end
private
def transition(nextstate)

View File

@ -35,6 +35,14 @@ module HTTPX
super || @state == :authenticating || @state == :negotiating
end
def interests
if @state == :connecting || @state == :authenticating || @state == :negotiating
return @write_buffer.empty? ? :r : :w
end
super
end
private
def transition(nextstate)

View File

@ -23,6 +23,8 @@ module HTTPX
def <<: (String) -> void
def can_buffer_more_requests: () -> bool
def send: (Request) -> void
def consume: () -> void