[ruby/net-http] open: Never call Timeout.timeout in rescue clause

The try-open_timeout-then-fallback-to-timeout introduced in
https://github.com/ruby/net-http/commit/1903cedd8cd0 works well, but when it errors
due to any reason in Rubies which do not support `open_timeout`, it
spits the rescued ArgumentError that is unrelated to user code and not
actionable.

    Net::HTTP.start('foo.bar', 80)

    /.../net-http-0.8.0/lib/net/http.rb:1691:in 'TCPSocket#initialize': Failed to open TCP connection to foo.bar:80 (getaddrinfo(3): nodename nor servname provided, or not known) (Socket::ResolutionError)
            from /.../net-http-0.8.0/lib/net/http.rb:1691:in 'IO.open'
            from /.../net-http-0.8.0/lib/net/http.rb:1691:in 'block in Net::HTTP#connect'
            from /.../timeout-0.4.4/lib/timeout.rb:188:in 'block in Timeout.timeout'
            from /.../timeout-0.4.4/lib/timeout.rb:195:in 'Timeout.timeout'
            from /.../net-http-0.8.0/lib/net/http.rb:1690:in 'Net::HTTP#connect'
            from /.../net-http-0.8.0/lib/net/http.rb:1655:in 'Net::HTTP#do_start'
            from /.../net-http-0.8.0/lib/net/http.rb:1635:in 'Net::HTTP#start'
            from /.../net-http-0.8.0/lib/net/http.rb:1064:in 'Net::HTTP.start'
            (snip)
    /.../net-http-0.8.0/lib/net/http.rb:1682:in 'TCPSocket#initialize': unknown keyword: :open_timeout (ArgumentError)

              sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            from /.../net-http-0.8.0/lib/net/http.rb:1682:in 'IO.open'
            from /.../net-http-0.8.0/lib/net/http.rb:1682:in 'Net::HTTP#connect'
            from /.../net-http-0.8.0/lib/net/http.rb:1655:in 'Net::HTTP#do_start'
            from /.../net-http-0.8.0/lib/net/http.rb:1635:in 'Net::HTTP#start'
            from /.../net-http-0.8.0/lib/net/http.rb:1064:in 'Net::HTTP.start'
            (snip)
            ... 8 levels...

This patch suppresses the ArgumentError by moving the retry out of the
rescue clause.

https://github.com/ruby/net-http/commit/86232d62f5
This commit is contained in:
Daisuke Aritomo 2025-12-04 21:52:53 +09:00 committed by git
parent f179885d3c
commit ea415e9636

View File

@ -1674,30 +1674,7 @@ module Net #:nodoc:
debug "opening connection to #{conn_addr}:#{conn_port}..."
begin
s =
case @tcpsocket_supports_open_timeout
when nil, true
begin
# Use built-in timeout in TCPSocket.open if available
sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
@tcpsocket_supports_open_timeout = true
sock
rescue ArgumentError => e
raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)'))
@tcpsocket_supports_open_timeout = false
# Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout
Timeout.timeout(@open_timeout, Net::OpenTimeout) {
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
}
end
when false
# The current Ruby is known to not support TCPSocket(open_timeout:).
# Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue.
Timeout.timeout(@open_timeout, Net::OpenTimeout) {
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
}
end
s = timeouted_connect(conn_addr, conn_port)
rescue => e
e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions
raise e, "Failed to open TCP connection to " +
@ -1795,6 +1772,29 @@ module Net #:nodoc:
end
private :connect
def timeouted_connect(conn_addr, conn_port)
if @tcpsocket_supports_open_timeout == nil || @tcpsocket_supports_open_timeout == true
# Try to use built-in open_timeout in TCPSocket.open if:
# - The current Ruby runtime is known to support it, or
# - It is unknown whether the current Ruby runtime supports it (so we'll try).
begin
sock = TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
@tcpsocket_supports_open_timeout = true
return sock
rescue ArgumentError => e
raise if !(e.message.include?('unknown keyword: :open_timeout') || e.message.include?('wrong number of arguments (given 5, expected 2..4)'))
@tcpsocket_supports_open_timeout = false
end
end
# This Ruby runtime is known not to support `TCPSocket(open_timeout:)`.
# Directly fall back to Timeout.timeout to avoid performance penalty incured by rescue.
Timeout.timeout(@open_timeout, Net::OpenTimeout) {
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
}
end
private :timeouted_connect
def on_connect
end
private :on_connect