Bug #21803
open`Addrinfo#connect_internal` should raise `IO::TimeoutError` on user-specified timeouts
Description
Background & Proposal¶
Addrinfo#connect_internal is a core internal method that is called by both
Addrinfo#connect and Addrinfo#connect_from.
It receives a user-specified timeout value from these methods and currently raises
Errno::ETIMEDOUT when that timeout expires.
def connect_internal(local_addrinfo, timeout=nil) # :yields: socket
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
begin
sock.ipv6only! if self.ipv6?
sock.bind local_addrinfo if local_addrinfo
if timeout
case sock.connect_nonblock(self, exception: false)
when 0 # success or EISCONN, other errors raise
break
when :wait_writable
sock.wait_writable(timeout) or
raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}"
end while true
else
sock.connect(self)
end
However, this timeout is not caused by a system call returning Errno::ETIMEDOUT.
It is a Ruby-level timeout triggered by explicitly waiting with a user-specified
timeout value.
This distinction was discussed in another issue:
Introduce general IO#timeout and IO#timeout= for blocking operations)
After that discussion, Support IO#timeout for rsock_connect introduced a change so that rsock_connect() raises IO::TimeoutError
when a user-specified timeout expires.
In addition, TCPSocket.new and Socket.tcp were updated to align with this behavior, so that they raise IO::TimeoutError when a user-specified timeout occurs:
Socket.tcp and TCPSocket.new raise IO::TimeoutError with user specified timeout
However, the implementation of Socket.tcp still partially relies on
Addrinfo#connect_internal, specifically in cases where a synchronous connection
attempt is made to a single destination.
As a result, depending on the execution path, a user-specified timeout may raise
either IO::TimeoutError or Errno::ETIMEDOUT.
This inconsistency is undesirable. Therefore, I propose that Addrinfo#connect_internal should raise IO::TimeoutError (instead of Errno::ETIMEDOUT) when a user-specified timeout expires, to make the behavior consistent across the socket APIs.
No data to display