connect with timeout is too verbose: addr = Socket.pack_sockaddr_in(9418, "127.0.0.1") s = Socket.new(:AF_INET, :SOCK_STREAM, 0) begin s.connect_nonblock(addr) rescue Errno::EINPROGRESS IO.select(nil, [s], nil, 0.5) or raise Timeout::Error end 4UBSUBOPOCMPDLJOHDPOOFDUJPO 5JNFPVUJOTFD GSPNOPSNBMQFSTPO &SJD8POH IUUQTCVHTSVCZMBOHPSHJTTVFT "UJNFPVUGPSDPOOFDUJPOBUUFNQUT
about Socket class? You can implement timeout on Socket. I think you need full power of socket API, so you should use low level bindings for socket API i.e. Socket class. GSPNBLS "LJSB5BOBLB IUUQTCVHTSVCZMBOHPSHJTTVFTOPUF
standard libraries. lib/net/http.rb 945 s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { 946 begin 947 TCPSocket.open(conn_address, conn_port, @local_host, @local_port) 948 rescue => e 949 raise e, "Failed to open TCP connection to " + 950 "#{conn_address}:#{conn_port} (#{e.message})" 951 end 952 } GSPN(MBTT@TBHB .BTBLJ.BUTVTIJUB IUUQTCVHTSVCZMBOHPSHJTTVFT #BDLHSPVOE5JNFPVU)BOEMJOHJOOFUIUUQ 5IFUJNFPVUMJCSBSZTNFUIPE
library that forces an operation to be interrupted by raising an exception once a speci fi ed time has elapsed. Currently, we use Timeout in Net::HTTP and other standard libraries. lib/net/http.rb 945 s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { 946 begin 947 TCPSocket.open(conn_address, conn_port, @local_host, @local_port) 948 rescue => e 949 raise e, "Failed to open TCP connection to " + 950 "#{conn_address}:#{conn_port} (#{e.message})" 951 end 952 } 5IFUJNFPVUMJCSBSZTNFUIPE #BDLHSPVOE5JNFPVU)BOEMJOHJOOFUIUUQ
other standard libraries. lib/net/http.rb 945 s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { 946 begin 947 TCPSocket.open(conn_address, conn_port, @local_host, @local_port) 948 rescue => e 949 raise e, "Failed to open TCP connection to " + 950 "#{conn_address}:#{conn_port} (#{e.message})" 951 end 952 } GSPN(MBTT@TBHB .BTBLJ.BUTVTIJUB IUUQTCVHTSVCZMBOHPSHJTTVFT 5IFPWFSBMMUJNF #BDLHSPVOE5JNFPVU)BOEMJOHJOOFUIUUQ
getaddrinfo_a(3) if available, otherwise it uses Timeout. We can avoid thread creation to make a TCP connection if getaddrinfo_a(3) is available. GSPN(MBTT@TBHB .BTBLJ.BUTVTIJUB IUUQTCVHTSVCZMBOHPSHJTTVFT )PXUPJOUFSSVQUUIFOBNFSFTPMVUJPOJOQSPHSFTT
IUUQTCVHTSVCZMBOHPSHJTTVFT Socket.tcp supports connect_timeout, but Addrinfo.getaddrinfo doesn't support timeout. We need to use Timeout to wait name resolution. In this patch, Addrinfo.getaddrinfo support timeout and Socket.tcp accepts resolv_timeout. It uses getaddrinfo_a(3) if available, otherwise it uses Timeout. We can avoid thread creation to make a TCP connection if getaddrinfo_a(3) is available. ˞IUUQTHJUIVCDPNSVCZSVCZDPNNJUGDDBDFCDEBEB
supports connect_timeout, but Addrinfo.getaddrinfo doesn't support timeout. We need to use Timeout to wait name resolution. In this patch, Addrinfo.getaddrinfo support timeout and Socket.tcp accepts resolv_timeout. It uses getaddrinfo_a(3) if available, otherwise it uses Timeout. We can avoid thread creation to make a TCP connection if getaddrinfo_a(3) is available.
※ 2020 Grant Accomplishment Report Happy Eyeballs Version 2 (RFC8305) support for Ruby’s socket library https://www.ruby.or.jp/grant/2020/matsushita_ fi nal_report.pdf
4PDLFUHFUBEESJOGPXBTDBMMFEBHBJOJOUIFDIJMEQSPDFTT Here's a small script that reproduces the problem: require "socket" Socket.getaddrinfo("localhost", nil) pid = fork do Socket.getaddrinfo("localhost", nil) end Process.wait pid GSPNFVHFOFJVT &VHFOF,FOOZ IUUQTCVHTSVCZMBOHPSHJTTVFTOPUF 3BJMT"DUJWF+PCJOUFHSBUJPOUFTUTGBJMFT
= now + RESOLUTION_DELAY "GUFSFJUIFSBEESFTTGBNJMZJTSFTPMWFE 4FOE%/4RVFSZGPS*1W 4FOE%/4RVFSZGPS*1W 3FUVSO*1W%/4SFTQPOTF fi STU 4UJMMXBJUJOHGPSUIF*1W%/4SFTQPOTF
end # When the resolved addresses are IPv4 addresses resolution_delay_expires_at = now + RESOLUTION_DELAY *GSFTPMVUJPO@EFMBZ@FYQJSFT@BUIBTBWBMVF UIBUWBMVFJTSFUVSOFE "GUFSFJUIFSBEESFTTGBNJMZJTSFTPMWFE 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC
= now + CONNECTION_ATTEMPT_DELAY 4FSWFS 8BJUGPSUIFDPOOFDUJPOUPCFFTUBCMJTIFE 4UBSUBDPOOFDUJPOBUUFNQUUP*1W 4FOE%/4RVFSZGPS*1W 4FOE%/4RVFSZGPS*1W 3FUVSO*1W%/4SFTQPOTF fi STU 4UJMMXBJUJOHGPSUIF*1W%/4SFTQPOTF
Float::INFINITY end 0ODFBDPOOFDUJPOBUUFNQUIBTCFFOTUBSUFEGPS UIFMBTUEFTUJOBUJPOBEESFTT BWBMVFJTBTTJHOFEUP VTFS@TQFDJ fi FE@DPOOFDU@UJNFPVU@BU 8BJUGPSOBNFSFTPMVUJPOPSDPOOFDUJPO 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC
Float::INFINITY end *GDPOOFDU@UJNFPVUJTTQFDJ fi FE VTFS@TQFDJ fi FE@DPOOFDU@UJNFPVU@BUJTTFUUP JUTFYQJSBUJPOUJNF 8BJUGPSOBNFSFTPMVUJPOPSDPOOFDUJPO 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC 5IFVTFSTQFDJ fi FEFYQJSBUJPOUJNF
Float::INFINITY end 0UIFSXJTF JUJTTFUUP'MPBU*/'*/*5: UPNFBOXBJUJOEF fi OJUFMZ 8BJUGPSOBNFSFTPMVUJPOPSDPOOFDUJPO 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC NFBOTJOEF fi OJUFMZ
: Float::INFINITY end 0ODFBDPOOFDUJPOBUUFNQUIBTCFFOTUBSUFEGPS UIFMBTUBEESFTT VTFS@TQFDJ fi FE@DPOOFDU@UJNFPVU@BUJTTFUBHBJO 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC
the method Socket.tcp(host, port, fast_fallback: false) TCPSocket.new(host, port, fast_fallback: false) # An accessor method on the Socket class Socket.tcp_fast_fallback = false # An environment variable TCP_NO_FAST_FALLBACK = 1
Timeout in Net::HTTP and other standard libraries. lib/net/http.rb 945 s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { 946 begin 947 TCPSocket.open(conn_address, conn_port, @local_host, @local_port) 948 rescue => e 949 raise e, "Failed to open TCP connection to " + 950 "#{conn_address}:#{conn_port} (#{e.message})" 951 end 952 } 5IFUJNFPVUMJCSBSZTNFUIPE
sleep 2 end end r.take )PXFWFS BTPG3VCZ UIFUJNFPVUMJCSBSZEJEOPUXPSLXFMM XJUI3BDUPS 6TJOHJUJOTJEFBOPONBJO3BDUPSXPVMESBJTFBOFSSPS # => Ractor::IsolationError can not access non-shareable objects in constant Timeout::State::GLOBAL_STATE by non-main ractor.
open_timeout: nil, ..., & ) if open_timeout && (connect_timeout || resolv_timeout) raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" end # ... end *GPQFO@UJNFPVUJTTQFDJ fi FEUPHFUIFSXJUI BOPUIFSUJNFPVUBSHVNFOU JUJTUSFBUFEBTJOWBMJEBOE BO"SHVNFOU&SSPSJTSBJTFE 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC .VTUCFSBJTFE "EEFE
end if expired?(now, user_specified_open_timeout_at) raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") end *GUIFPQFO@UJNFPVUFYQJSBUJPOUJNFIBT BMSFBEZQBTTFEXIFOUIFXBJUFOET BUJNFPVUFSSPSJTSBJTFE PQFO@UJNFPVUXJUIGBTU@GBMMCBDL 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC .VTUCFSBJTFE *O3VCZ
JGPQFO@UJNFPVUJTTQFDJ fi FE JUJTUIFPOMZUJNFPVUUIBUDBODBVTFBUJNFPVUFSSPS PQFO@UJNFPVUXJUIGBTU@GBMMCBDL 'PS4PDLFUUDQIUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUMJCTPDLFUSC *O3VCZ
family, VALUE socktype, VALUE protocol, VALUE flags, int socktype_hack, VALUE timeout ) { // ... } 5IFDPEFUIBUDBMMTSTPDL@HFUBEESJOGP 8IBUJTIBQQFOJOHXJUIUIFOBNFSFTPMVUJPOUJNFPVUT IUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUSBEESJOGPD *O3VCZ
family, VALUE socktype, VALUE protocol, VALUE flags, int socktype_hack, VALUE timeout ) { // ... } 5IFDPEFUIBUDBMMTSTPDL@HFUBEESJOGP BMSFBEZUBLFTUIFVTFSTQFDJ fi FEUJNFPVUWBMVF BTBOBSHVNFOU 8IBUJTIBQQFOJOHXJUIUIFOBNFSFTPMVUJPOUJNFPVUT 5IFVTFSTQFDJ fi FEUJNFPVUWBMVF IUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUSBEESJOGPD *O3VCZ
family, VALUE socktype, VALUE protocol, VALUE flags, int socktype_hack, VALUE timeout ) { // ... res = rsock_getaddrinfo(node, service, &hints, socktype_hack); // ... } #VUJOGBDU JUXBTOPUCFJOHQBTTFEUPSTPDL@HFUBEESJOGP 8IBUJTIBQQFOJOHXJUIUIFOBNFSFTPMVUJPOUJNFPVUT 5IFWBMVFJTOPUQBTTFE 5IFVTFSTQFDJ fi FEUJNFPVUWBMVF IUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUSBEESJOGPD *O3VCZ
int socktype_hack ) { // ... } "OEPOUIFSTPDL@HFUBEESJOGP TJEF UIFSFXBTOP QBSBNFUFSGPSSFDFJWJOHUIFWBMVF5IFVTFSTQFDJ fi FE UJNFPVUWBMVFTJNQMZWBOJTIFEBUUIJTQPJOU 5IFVTFSTQFDJ fi FEUJNFPVUWBMVFDBOOPUCFQBTTFE 8IBUJTIBQQFOJOHXJUIUIFOBNFSFTPMVUJPOUJNFPVUT IUUQTHJUIVCDPNSVCZSVCZCMPCSVCZ@@FYUTPDLFUSBEESJOGPD *O3VCZ
in Net::HTTP and other standard libraries. lib/net/http.rb 945 s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { 946 begin 947 TCPSocket.open(conn_address, conn_port, @local_host, @local_port) 948 rescue => e 949 raise e, "Failed to open TCP connection to " + 950 "#{conn_address}:#{conn_port} (#{e.message})" 951 end 952 } 5IFUJNFPVUMJCSBSZTNFUIPE
sleep 2 end end r.take 'PSUIFSFDPSE UIFJTTVFCFUXFFO3BDUPSBOEUIFUJNFPVUMJCSBSZ UIBUMFEUPUIFJOUSPEVDUJPOPGPQFO@UJNFPVUXBTSFTPMWFE JO3VCZUISPVHIJNQSPWFNFOUTUP3BDUPS 5PEBZ UIFUJNFPVUMJCSBSZDBOCFVTFEJOTJEFBOPONBJO3BDUPS # => no exception in Ruby 4.0!