Skip to content

Commit 00f9a78

Browse files
hsbtclaude
authored andcommitted
[Backport #22223] Check SO_ERROR after waiting for nonblocking connect
Darwin 27 answers the retry connect(2) on a refused nonblocking socket with EISCONN, so the retry idiom in Addrinfo#connect_internal returned an unconnected socket. SO_ERROR still holds the real error, so consult it after wait_writable, as wait_connectable() in init.c already does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 41d79e8 commit 00f9a78

2 files changed

Lines changed: 17 additions & 0 deletions

File tree

ext/socket/lib/socket.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ def connect_internal(local_addrinfo, timeout=nil) # :yields: socket
6363
when :wait_writable
6464
sock.wait_writable(timeout) or
6565
raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}"
66+
# Check SO_ERROR instead of relying on the connect_nonblock retry;
67+
# some kernels (e.g. Darwin 27) answer the retry connect(2) with
68+
# EISCONN even when the connection has failed. [Bug #22223]
69+
err = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).int
70+
unless err.zero?
71+
raise SystemCallError.new("connect(2) for #{self.ip_address}:#{self.ip_port}", err)
72+
end
6673
end while true
6774
else
6875
sock.connect(self)

test/socket/test_socket.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,16 @@ def test_connect_timeout
624624
sock.close if sock && ! sock.closed?
625625
end
626626

627+
def test_connect_timeout_connection_refused
628+
server = TCPServer.new("127.0.0.1", 0)
629+
port = server.addr[1]
630+
server.close
631+
632+
assert_raise(Errno::ECONNREFUSED) do
633+
Socket.tcp("127.0.0.1", port, connect_timeout: 5)
634+
end
635+
end unless /mswin|mingw/ =~ RUBY_PLATFORM
636+
627637
def test_getifaddrs
628638
begin
629639
list = Socket.getifaddrs

0 commit comments

Comments
 (0)