diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index ff35682486..bd73f49ece 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -892,7 +892,6 @@ init_fast_fallback_inetsock_internal(VALUE v) } status = rb_thread_fd_select(nfds, &arg->readfds, &arg->writefds, NULL, delay_p); - syscall = "select(2)"; now = current_clocktime_ts(); if (is_timeout_tv(resolution_delay_expires_at, now)) { @@ -998,9 +997,11 @@ init_fast_fallback_inetsock_internal(VALUE v) if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err && arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err != EAI_ADDRFAMILY) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v4.finished || resolution_store.v4.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v6.has_error = true; } else { resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai; @@ -1015,9 +1016,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v6.finished || resolution_store.v6.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v4.has_error = true; } else { resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai; @@ -1057,9 +1060,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v6.finished = true; if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v4.finished || resolution_store.v4.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v6.has_error = true; } else { resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai; @@ -1075,9 +1080,11 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + if (!resolution_store.v6.finished || resolution_store.v6.has_error) { + last_error.type = RESOLUTION_ERROR; + last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; + syscall = "getaddrinfo(3)"; + } resolution_store.v4.has_error = true; } else { resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai; diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 46879c19c2..4ebc437a99 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -833,7 +833,7 @@ class Socket < BasicSocket if except_sockets&.any? except_sockets.each do |except_socket| failed_ai = connecting_sockets.delete except_socket - sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_CONNECT_TIME) + sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) except_socket.close ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int) @@ -862,7 +862,10 @@ class Socket < BasicSocket unless (Socket.const_defined?(:EAI_ADDRFAMILY)) && (result.is_a?(Socket::ResolutionError)) && (result.error_code == Socket::EAI_ADDRFAMILY) - last_error = result + other = family_name == :ipv6 ? :ipv4 : :ipv6 + if !resolution_store.resolved?(other) || !resolution_store.resolved_successfully?(other) + last_error = result + end end else resolution_store.add_resolved(family_name, result) @@ -1068,7 +1071,7 @@ class Socket < BasicSocket end def resolved_successfully?(family) - resolved?(family) && !!@error_dict[family] + resolved?(family) && !@error_dict[family] end def resolved_all_families? diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 4d75caab50..27e60b3335 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -995,6 +995,28 @@ class TestSocket < Test::Unit::TestCase RUBY end + def test_tcp_socket_hostname_resolution_failed_after_connection_failure + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + server = TCPServer.new("127.0.0.1", 0) + port = server.connect_address.ip_port + + Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| + case family + when Socket::AF_INET6 then sleep(0.1); raise Socket::ResolutionError + when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)] + end + end + + server.close + + # SystemCallError is a workaround for Windows environment + assert_raise(Errno::ECONNREFUSED, SystemCallError) do + Socket.tcp("localhost", port) + end + RUBY + end + def test_tcp_socket_v6_address_passed opts = %w[-rsocket -W1] assert_separately opts, <<~RUBY diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index 4984a7e7bc..e6a41f5660 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -316,7 +316,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase port = server.connect_address.ip_port server.close - assert_raise(Socket::ResolutionError) do + assert_raise(Errno::ECONNREFUSED) do TCPSocket.new( "localhost", port,