diff --git a/lib/http/timeout/global.rb b/lib/http/timeout/global.rb index 1078d889..41386b61 100644 --- a/lib/http/timeout/global.rb +++ b/lib/http/timeout/global.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -require "timeout" require "io/wait" +require "resolv" +require "timeout" require "http/timeout/null" @@ -12,6 +13,9 @@ def initialize(*args) super @timeout = @time_left = options.fetch(:global_timeout) + @dns_resolver = options.fetch(:dns_resolver) do + ::Resolv.method(:getaddresses) + end end # To future me: Don't remove this again, past you was smarter. @@ -19,14 +23,28 @@ def reset_counter @time_left = @timeout end - def connect(socket_class, host, port, nodelay = false) + def connect(socket_class, host_name, *args) + connect_operation = lambda do |host_address| + ::Timeout.timeout(@time_left, TimeoutError) do + super(socket_class, host_address, *args) + end + end + host_addresses = @dns_resolver.call(host_name) + # ensure something to iterates + trying_targets = host_addresses.empty? ? [host_name] : host_addresses reset_timer - ::Timeout.timeout(@time_left, TimeoutError) do - @socket = socket_class.open(host, port) - @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay + trying_iterator = trying_targets.lazy + error = nil + begin + connect_operation.call(trying_iterator.next).tap do + log_time + end + rescue TimeoutError => e + error = e + retry + rescue ::StopIteration + raise error end - - log_time end def connect_ssl diff --git a/lib/http/timeout/per_operation.rb b/lib/http/timeout/per_operation.rb index a5e594fb..9411f78d 100644 --- a/lib/http/timeout/per_operation.rb +++ b/lib/http/timeout/per_operation.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "resolv" require "timeout" require "http/timeout/null" @@ -17,12 +18,29 @@ def initialize(*args) @read_timeout = options.fetch(:read_timeout, READ_TIMEOUT) @write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT) @connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT) + @dns_resolver = options.fetch(:dns_resolver) do + ::Resolv.method(:getaddresses) + end end - def connect(socket_class, host, port, nodelay = false) - ::Timeout.timeout(@connect_timeout, TimeoutError) do - @socket = socket_class.open(host, port) - @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay + def connect(socket_class, host_name, *args) + connect_operation = lambda do |host_address| + ::Timeout.timeout(@connect_timeout, TimeoutError) do + super(socket_class, host_address, *args) + end + end + host_addresses = @dns_resolver.call(host_name) + # ensure something to iterates + trying_targets = host_addresses.empty? ? [host_name] : host_addresses + trying_iterator = trying_targets.lazy + error = nil + begin + connect_operation.call(trying_iterator.next) + rescue TimeoutError => e + error = e + retry + rescue ::StopIteration + raise error end end