selector: use poll when possible, instead of IO.select

Using IO.select is costly for the one-request/one-origin case, as we
have to build array to pass, and we receive arrays, and that generates a
lot of needless garbage when we only request once. Instead, it now uses
IO#wait_readable and IO#wait_writable, which does not require extra
arrays, and uses poll under the hood.
This commit is contained in:
HoneyryderChuck 2020-04-12 03:28:06 +01:00
parent 2c5757b1a5
commit ed23525daf

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "io/wait"
class HTTPX::Selector class HTTPX::Selector
READABLE = %i[rw r].freeze READABLE = %i[rw r].freeze
WRITABLE = %i[rw w].freeze WRITABLE = %i[rw w].freeze
@ -11,7 +13,7 @@ class HTTPX::Selector
# I/O monitor # I/O monitor
# #
class Monitor class Monitor
attr_accessor :io, :readiness attr_accessor :io
def initialize(io, reactor) def initialize(io, reactor)
@io = io @io = io
@ -19,18 +21,6 @@ class HTTPX::Selector
@closed = false @closed = false
end end
def interests
@io.interests
end
def readable?
READABLE.include?(@io.interests)
end
def writable?
WRITABLE.include?(@io.interests)
end
# closes +@io+, deregisters from reactor (unless +deregister+ is false) # closes +@io+, deregisters from reactor (unless +deregister+ is false)
def close(deregister = true) def close(deregister = true)
return if @closed return if @closed
@ -74,41 +64,40 @@ class HTTPX::Selector
# waits for read/write events for +interval+. Yields for monitors of # waits for read/write events for +interval+. Yields for monitors of
# selected IO objects. # selected IO objects.
# #
def select(interval) def select(interval, &block)
return select_one(interval, &block) if @selectables.size == 1
begin begin
r = nil r = nil
w = nil w = nil
@selectables.each do |io, monitor| @selectables.each_key do |io|
(r ||= []) << io if monitor.interests == :r || monitor.interests == :rw (r ||= []) << io if io.interests == :r || io.interests == :rw
(w ||= []) << io if monitor.interests == :w || monitor.interests == :rw (w ||= []) << io if io.interests == :w || io.interests == :rw
monitor.readiness = nil
end end
readers, writers = IO.select(r, w, nil, interval) readers, writers = IO.select(r, w, nil, interval)
if readers.nil? && writers.nil? raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
end
rescue IOError, SystemCallError rescue IOError, SystemCallError
@selectables.reject! { |io, _| io.closed? } @selectables.reject! { |io, _| io.closed? }
retry retry
end end
readers.each do |io| readers.each do |io|
monitor = io.closed? ? @selectables.delete(io) : @selectables[io] monitor = @selectables[io]
next unless monitor next unless monitor
monitor.readiness = writers.delete(io) ? :rw : :r # so that we don't yield 2 times
writers.delete(io)
yield monitor yield monitor
end if readers end if readers
writers.each do |io| writers.each do |io|
monitor = io.closed? ? @selectables.delete(io) : @selectables[io] monitor = @selectables[io]
next unless monitor next unless monitor
# don't double run this, the last iteration might have run this task already
monitor.readiness = :w
yield monitor yield monitor
end if writers end if writers
end end
@ -116,4 +105,27 @@ class HTTPX::Selector
# Closes the selector. # Closes the selector.
# #
def close; end def close; end
private
def select_one(interval)
io, monitor = @selectables.first
case io.interests
when :r
result = io.to_io.wait_readable(interval)
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result
when :w
result = io.to_io.wait_writable(interval)
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result
when :rw
readers, writers = IO.select([io], [io], nil, interval)
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
end
yield monitor
rescue IOError, SystemCallError
@selectables.reject! { |ios, _| ios.closed? }
end
end end