fix for loop on resolution and retry on new connection

A certain behaviour was observed, when performing some tests using the
hackernews script, where after a failed request on a non-initiated
connection, a new DNS resolution would be emitted, although the
connection still had other IPs to try on. This led to a cascading
behaviour where the DNS response would fill up the connection with the
same repeated IPs and trigger coalescing, which would loop indefinitely
after emitting the resolve event.

This was fixed by not allowing DNS resolution on already resolved names,
to propagate to connections which already contain the advertised IPs.

This seems to address the github issue 5, which description matches the
observed behaviour.
This commit is contained in:
HoneyryderChuck 2022-07-29 13:43:26 +01:00
parent 534b3eb91b
commit b0777c61e5
8 changed files with 50 additions and 15 deletions

View File

@ -83,22 +83,41 @@ module HTTPX
end
module ArrayExtensions
refine Array do
module FilterMap
refine Array do
def filter_map
return to_enum(:filter_map) unless block_given?
def filter_map
return to_enum(:filter_map) unless block_given?
each_with_object([]) do |item, res|
processed = yield(item)
res << processed if processed
each_with_object([]) do |item, res|
processed = yield(item)
res << processed if processed
end
end
end unless Array.method_defined?(:filter_map)
end
def sum(accumulator = 0, &block)
values = block_given? ? map(&block) : self
values.inject(accumulator, :+)
module Sum
refine Array do
def sum(accumulator = 0, &block)
values = block_given? ? map(&block) : self
values.inject(accumulator, :+)
end
end unless Array.method_defined?(:sum)
end
module Intersect
refine Array do
def intersect?(arr)
if size < arr.size
smaller = self
else
smaller, arr = arr, self
end
(array & smaller).size > 0
end
end unless Array.method_defined?(:intersect?)
end
end
module IOExtensions

View File

@ -72,7 +72,7 @@ module HTTPX
end
module ResponseBodyMethods
using ArrayExtensions
using ArrayExtensions::FilterMap
attr_reader :encodings

View File

@ -7,7 +7,7 @@ require "httpx/resolver"
module HTTPX
class Pool
using ArrayExtensions
using ArrayExtensions::FilterMap
extend Forwardable
def_delegator :@timers, :after

View File

@ -6,7 +6,7 @@ require "resolv"
module HTTPX
class Resolver::Multi
include Callbacks
using ArrayExtensions
using ArrayExtensions::FilterMap
attr_reader :resolvers

View File

@ -8,6 +8,8 @@ module HTTPX
include Callbacks
include Loggable
using ArrayExtensions::Intersect
RECORD_TYPES = {
Socket::AF_INET6 => Resolv::DNS::Resource::IN::AAAA,
Socket::AF_INET => Resolv::DNS::Resource::IN::A,
@ -48,6 +50,10 @@ module HTTPX
addresses.map! do |address|
address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
end
# double emission check
return if connection.addresses && !addresses.intersect?(connection.addresses)
log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
if @pool && # if triggered by early resolve, pool may not be here yet
!connection.io &&
@ -56,8 +62,12 @@ module HTTPX
addresses.first.to_s != connection.origin.host.to_s
log { "resolver: A response, applying resolution delay..." }
@pool.after(0.05) do
connection.addresses = addresses
emit(:resolve, connection)
# double emission check
unless connection.addresses && addresses.intersect?(connection.addresses)
connection.addresses = addresses
emit(:resolve, connection)
end
end
else
connection.addresses = addresses

View File

@ -310,9 +310,12 @@ module HTTPX
class ErrorResponse
include Loggable
extend Forwardable
attr_reader :request, :error
def_delegator :@request, :uri
def initialize(request, error, options)
@request = request
@error = error

View File

@ -9,7 +9,7 @@ module HTTPX::Transcoder
module_function
class Encoder
using HTTPX::ArrayExtensions
using HTTPX::ArrayExtensions::Sum
extend Forwardable
def_delegator :@raw, :to_s

View File

@ -96,6 +96,7 @@ module HTTPX
class ErrorResponse
include _Response
include Loggable
extend Forwardable
@options: Options
@error: Exception
@ -104,6 +105,8 @@ module HTTPX
def status: () -> (Integer | _ToS)
def uri: () -> URI::Generic
private
def initialize: (Request, Exception, options) -> untyped