From: "hsbt (Hiroshi SHIBATA) via ruby-core" Date: 2024-02-26T05:55:30+00:00 Subject: [ruby-core:116945] [Ruby master Feature#20108] Introduction of Happy Eyeballs Version 2 (RFC8305) in Socket.tcp Issue #20108 has been updated by hsbt (Hiroshi SHIBATA). Status changed from Open to Closed This proposal has been merged at https://github.com/ruby/ruby/commit/9ec342e07df6aa5e2c2e9003517753a2f1b508fd ---------------------------------------- Feature #20108: Introduction of Happy Eyeballs Version 2 (RFC8305) in Socket.tcp https://bugs.ruby-lang.org/issues/20108#change-106986 * Author: shioimm (Misaki Shioi) * Status: Closed ---------------------------------------- This is an implementation of Happy Eyeballs version 2 (RFC 8305) in Socket.tcp. ### Background Currently, `Socket.tcp` synchronously resolves names and makes connection attempts with `Addrinfo::foreach.` This implementation has the following two problems. 1. In hostname resolution, the program stops until the DNS server responds to all DNS queries. 2. In a connection attempt, while an IP address is trying to connect to the destination host and is taking time, the program stops, and other resolved IP addresses cannot try to connect. ### Proposal "Happy Eyeballs" ([RFC 8305](https://datatracker.ietf.org/doc/html/rfc8305)) is an algorithm to solve this kind of problem. It avoids delays to the user whenever possible and also uses IPv6 preferentially. I implemented it into `Socket.tcp` by using `Addrinfo.getaddrinfo` in each thread spawned per address family to resolve the hostname asynchronously, and using `Socket::connect_nonblock` to try to connect with multiple addrinfo in parallel. See https://github.com/ruby/ruby/pull/9374 ### Outcome This change eliminates a fatal defect in the following cases. #### Case 1. One of the A or AAAA DNS queries does not return ```ruby require 'socket' class Addrinfo class << self # Current Socket.tcp depends on foreach def foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, timeout: nil, &block) getaddrinfo(nodename, service, Socket::AF_INET6, socktype, protocol, flags, timeout: timeout) .concat(getaddrinfo(nodename, service, Socket::AF_INET, socktype, protocol, flags, timeout: timeout)) .each(&block) end def getaddrinfo(_, _, family, *_) case family when Socket::AF_INET6 then sleep when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", 4567)] end end end end Socket.tcp("localhost", 4567) ``` Because the current `Socket.tcp` cannot resolve IPv6 names, the program stops in this case. It cannot start to connect with IPv4 address. Though `Socket.tcp` with HEv2 can promptly start a connection attempt with IPv4 address in this case. #### Case 2. Server does not promptly return ack for syn of either IPv4 / IPv6 address family ```ruby require 'socket' fork do socket = Socket.new(Socket::AF_INET6, :STREAM) socket.setsockopt(:SOCKET, :REUSEADDR, true) socket.bind(Socket.pack_sockaddr_in(4567, '::1')) sleep socket.listen(1) connection, _ = socket.accept connection.close socket.close end fork do socket = Socket.new(Socket::AF_INET, :STREAM) socket.setsockopt(:SOCKET, :REUSEADDR, true) socket.bind(Socket.pack_sockaddr_in(4567, '127.0.0.1')) socket.listen(1) connection, _ = socket.accept connection.close socket.close end Socket.tcp("localhost", 4567) ``` The current `Socket.tcp` tries to connect serially, so when its first name resolves an IPv6 address and initiates a connection to an IPv6 server, this server does not return an ACK, and the program stops. Though `Socket.tcp` with HEv2 starts to connect sequentially and in parallel so a connection can be established promptly at the socket that attempted to connect to the IPv4 server. In exchange, the performance of `Socket.tcp` with HEv2 will be degraded. ``` 100.times { Socket.tcp("www.ruby-lang.org", 80) } # Socket.tcp (Before) 0.123809 # Socket.tcp (After) 0.224684 ``` This is due to the addition of the creation of IO objects, Thread objects, etc., and calls to `IO::select` in the implementation. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/