Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An adventure of Happy Eyeballs

An adventure of Happy Eyeballs

RubyKaigi 2024
https://rubykaigi.org/2024/presentations/coe401_.html

Introduction of Happy Eyeballs Version 2 (RFC8305) in Socket.tcp #9374
https://github.com/ruby/ruby/pull/9374

More Decks by Misaki Shioi(塩井美咲/しおい)

Other Decks in Programming

Transcript

  1. $VSSFOUMZ UIFBUUFNQUTBSFNBEFTFRVFOUJBMMZGPSFBDI*1 BEESFTT*GBDPOOFDUJPOBUUFNQUUPPOFBEESFTTGBJMT  JUXBJUTVOUJMUIFGBJMVSFJTDPO fi SNFECFGPSFBUUFNQUJOH XJUIBOPUIFSBEESFTT 4FSWFS $MJFOU

    4FSWFS *1WSFTPMWFE *1WSFTPMWFE *TTVFTJO3VCZTTPDLFUMJCSBSZ  $POOFDUJPO0, 5PBOPUIFSSFTPMWFE*1BEESFTT $POOFDUJPO/050, 5PBSFTPMWFE*1BEESFTT
  2. *GBDMJFOUBUUFNQUTUPDPOOFDUUPBO*1WBEESFTT fi STU  JUFOETVQXBJUJOHBMPOHUJNFGPSUIFDPOOFDUJPOUPCF FTUBCMJTIFE *TTVFTJO3VCZTTPDLFUMJCSBSZ  $BOOPUSFTQPOTFJNNFEJBUFMZ 4FSWFS

    $MJFOU 4FSWFS %FTQJUFUIFDMJFOULOPXJOHBOBWBJMBCMF*1WBEESFTT *1WSFTPMWFE *1WSFTPMWFE 5PBSFTPMWFE*1WBEESFTT *1WDBOOPUDPOOFDUJNNFEJBUFMZ *1WDBODPOOFDU
  3. )PX)BQQZ&ZFCBMMT7FSTJPO )&W XPSLT /PX MFUTTFFIPX)&WXPSLT"DDPSEJOHUP3'$  UIJTBMHPSJUINJODMVEFTGPVSQIBTFT *OJUJBUJPOPGBTZODISPOPVT%/4RVFSJFT 4PSUJOHPGSFTPMWFEEFTUJOBUJPOBEESFTTFT *OJUJBUJPOPGBTZODISPOPVTDPOOFDUJPOBUUFNQUT

    &TUBCMJTINFOUPGPOFDPOOFDUJPO XIJDIDBODFMT BMMPUIFSBUUFNQUT )BQQZ&ZFCBMMT7FSTJPO#FUUFS$POOFDUJWJUZ6TJOH$PODVSSFODZ0WFSWJFX IUUQTEBUBUSBDLFSJFUGPSHEPDIUNMSGDTFDUJPO
  4. 8IFORVFSZJOHBEPNBJOOBNFPGBUBSHFUTFSWFS JUJT PGUFOUIFDBTFUIBUNVMUJQMF*1WBOE*1WBEESFTTFTBSF PCUBJOFE 4PSUJOHPGSFTPMWFEEFTUJOBUJPOBEESFTTFT %/44FSWFS $MJFOU "OTXFS*1WBEESFTTFT "OTXFS*1WBEESFTTFT *1WBEESFTTFTBSF

    D D *1WBEESFTTFTBSF   2VFSJFT*1WBEESFTT 2VFSJFT*1WBEESFTT
  5. class Socket def self.tcp(host, port) # Do something end end

    /PX MFUTJNQMFNFOU4PDLFUUDQUIBUTVQQPSUT)&W *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  6. class Socket def self.tcp(host, port) threads = [Socket::AF_INET6, Socket::AF_INET].map {

    |family| Thread.new(...) { |...| addresses = Addrinfo.getaddrinfo(...) shared_list.concat(addresses); cond.signal } } connected_socket = loop do cond.wait(mutex) while addresses.empty? address = addresses.shift; socket = Socket.new(...) socket.connect_nonblock(address); sockets.push(socket) _, writables, = IO.select(nil, sockets, nil, 0.25) connected_socket = pick_connected_socket(...) break connected_socket if connected_socket end connected_socket end end "OEJUJTDPNQMFUFE *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  7. class Socket def self.tcp(host, port) # ... threads = [Socket::AF_INET6,

    Socket::AF_INET].map { |family| args = { Thread.new(args) { |args| addresses = Addrinfo.getaddrinfo(...) sleep 0.05 if addresses.last == Socket::AF_INET args[:mutex].synchronize do args[:shared_list].concat(addresses) args[:cond].signal end } } # ... end end 5IF fi STUQBSUJTUPIBOEMFOBNFSFTPMVUJPO *UTUBSUTCZDSFBUJOHUISFBETGPSFBDIBEESFTTGBNJMZ BOETUBSUIPTUOBNFSFTPMVUJPOBTZODISPOPVTMZ XJUIJOFBDIUISFBE )PTUOBNFSFTPMVUJPO $SFBUFBUISFBE *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W { host:, port:, family:, resolved_addresses:, mutex:, cond: }
  8. class Socket def self.tcp(host, port) # ... threads = [Socket::AF_INET6,

    Socket::AF_INET].map { |family| args = { Thread.new(args) { |args| addresses = Addrinfo.getaddrinfo(...) sleep 0.05 if addresses.last == Socket::AF_INET args[:mutex].synchronize do args[:resolved_addresses].concat(addresses) args[:cond].signal end } } # ... end end 0ODFOBNFSFTPMVUJPOJT fi OJTIFE  BEEUIFOFXBEESFTTFTUPUIFMJTUPGSFTPMWFE *1BEESFTTFT "EEBEESFTTFTUPUIFMJTU *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W { host:, port:, family:, resolved_addresses:, mutex:, cond: }
  9. class Socket def self.tcp(host, port) # ... threads = [Socket::AF_INET6,

    Socket::AF_INET].map { |family| # ... } # Do "obtaining an address", "starting a connection attempt", # and "waiting for connections" # ... end end 5IFOFYUQBSUJTDPOOFDUJPOQSPDFTTJOH 5IFQSPDFTTPGPCUBJOJOHBOBEESFTT TUBSUJOHB DPOOFDUJPOBUUFNQUBOEXBJUJOHGPSDPOOFDUJPOTJT SFQFBUFEFWFSZNT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  10. class Socket def self.tcp(host, port) # ... threads = [Socket::AF_INET6,

    Socket::AF_INET].map { |family| # ... } loop do # Do "obtaining an address", "starting a connection # and "waiting for connections" end # ... end end 6TF,FSOFMMPPQUPSFQFBUUIFTFTUFQT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W n attempt",
  11. class Socket def self.tcp(host, port) # ... loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = Socket.new(...) # ... end # ... end end 6QPO fi STUFOUFSJOHUIFMPPQ UIFMJTUPGSFTPMWFE*1 BEESFTTFTJTFNQUZ8BJUVOUJMBOZDIJMEUISFBEBEET BEESFTTFT UIFOCSFBLUIFXBJUBOEPCUBJOPOFPGUIFN 8BJUGPSBEESFTTFT 0CUBJOBOBEESFTT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  12. class Socket def self.tcp(host, port) # ... loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) # ... end # ... end end 5IFOTUBSUBDPOOFDUJPOBUUFNQU UPUIFPCUBJOFEBEESFTT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W 4UBSUBDPOOFDUJPOBUUFNQU
  13. class Socket def self.tcp(host, port) # ... loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # cleanup connected end end "GUFSTUBSUJOHUIFDPOOFDUJPO XBJUGPSNT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W 8BJUGPSDPOOFDUJPOT
  14. class Socket def self.tcp(host, port) # ... loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end do_cleanup connected end end *GBDPOOFDUJPOJTFTUBCMJTIFEXJUIJOUIFUJNF FYJUUIF MPPQ"GUFSQPTUQSPDFTTJOH SFUVSOUIFDPOOFDUFE TPDLFU BOEUIFNFUIPEDPODMVEFT 0CUBJODPOOFDUFETPDLFU 3FUVSOJU *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  15. class Socket def self.tcp(host, port) # ... connected = loop

    do mutex.synchronize do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end do_cleanup connected end end 0UIFSXJTF JUHPFTUPUIFUPQPGUIFMPPQBOESFQFBUT UIFQSPDFTTPGPCUBJOJOHBOBEESFTT TUBSUJOH BDPOOFDUJPOBUUFNQU BOEXBJUJOHGPSDPOOFDUJPOT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W &OUFSUIFOFYUMPPQ
  16. class Socket def self.tcp(host, port) # ... connected = loop

    do mutex.synchronize do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end do_cleanup connected end end 4PDLFUUDQXJUI)&WTVQQPSUFEJTOPXDPNQMFUF *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  17. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end *TTVFTXJUIUIJTJNQMFNFOUBUJPO  -FUTDPOTJEFSUIFDBTFXIFSF BGUFSTUBSUJOHB DPOOFDUJPOBUUFNQU JUIBTOPUCFFOFTUBCMJTIFE XJUIJONT BOEJUFOUFSTUIFOFYUMPPQ &OUFSUIFOFYUMPPQ
  18. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end 4VQQPTFUIFMJTUPGSFTPMWFE*1BEESFTTFTJTFNQUZ BUUIJTUJNF *GTP JUXJMMXBJUIFSFVOUJMOFXBEESFTTFTBSFBEEFE 8BJUIFSF *TTVFTXJUIUIJTJNQMFNFOUBUJPO  &NQUZ
  19. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end 8IBUTIPVMEJUEPJGUIFDPOOFDUJPOJTFTUBCMJTIFEJO UIFCBDLHSPVOEEVSJOHUIJTUJNF "DPOOFDUJPOJTFTUBCMJTIFE JOUIFCBDLHSPVOE *TTVFTXJUIUIJTJNQMFNFOUBUJPO  8BJUIFSF &NQUZ
  20. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end *OTVDIBDBTF UIFNFUIPETIPVMESFUVSOUIFDPOOFDUFE TPDLFUJNNFEJBUFMZ)PXFWFS JUTJNQPTTJCMFUPEFUFDUB FTUBCMJTIFEDPOOFDUJPOXIJMFBXBJUJOHBEESFTTFTDVSSFOUMZ *TTVFTXJUIUIJTJNQMFNFOUBUJPO  $BOUTUPQUPXBJU &NQUZ
  21. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end 0SXIBUXPVMEIBQQFOJGUIFMJTUPGSFTPMWFE*1 BEESFTTFTJTFNQUZBOEBMMBEESFTTGBNJMJFTIBWFCFFO OBNFSFTPMWFE *TTVFTXJUIUIJTJNQMFNFOUBUJPO  8BJUIFSF &NQUZ "MMOBNFSFTPMVUJPOTIBWF BMSFBEZCFFO fi OJTIFE
  22. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end *OTVDIBDBTF OPOFXBEESFTTFTXJMMCFBEEFEUP UIFMJTU BOEUIVT UIFQSPDFTTXPVMECFQFSNBOFOUMZ TUPQQFE *TTVFTXJUIUIJTJNQMFNFOUBUJPO  8BJUIFSFGPSFWFS &NQUZ
  23. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end "MTP DPOOFDUJPOBUUFNQUTDBOGBJMXJUIJONTPGTUBSUJOH *OTVDIBDBTF JUTIPVMEXBJUGPSUIFSFNBJOEFSPGUIFNT CFGPSFFOUFSJOHUIFOFYUMPPQBOETUBSUBOFXBUUFNQU *UUVSOFEPVUUIBUBDPOOFDUJPOBUUFNQUJTGBJMFE *TTVFTXJUIUIJTJNQMFNFOUBUJPO 
  24. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) break connected if connected end # ... end #VUXJUIUIFJNQMFNFOUBUJPO JUQSPDFFETUPUIFOFYU MPPQXJUIPVUXBJUJOHGPSUIFSFNBJOEFSPGUIFUJNF  JNNFEJBUFMZTUBSUJOHBOFXDPOOFDUJPOBUUFNQU &OUFSUIFOFYUMPPQXJUIPVU XBJUJOHGPSSFNBJOJOHUJNF *TTVFTXJUIUIJTJNQMFNFOUBUJPO  4UBSUBOFXDPOOFDUJPOBUUFNQU
  25. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end 5PBWPJEUIJT TIPVMEJUTMFFQGPSUIFSFNBJOJOHUJNF JGBOZDPOOFDUJPOTGBJM 4IPVMETMFFQ  *TTVFTXJUIUIJTJNQMFNFOUBUJPO 
  26. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end *OUIBUDBTF JGPUIFSDPOOFDUJPOTBSFJOQSPHSFTTBOE POFJTFTUBCMJTIFEXIJMFTMFFQJOHJOUIFCBDLHSPVOE  JUDBOUEFUFDUUIFFTUBCMJTIFEDPOOFDUJPO $BOUEFUFDUFTUBCMJTIFEDPOOFDUJPOT *TTVFTXJUIUIJTJNQMFNFOUBUJPO  "DPOOFDUJPOJTFTUBCMJTIFE JOUIFCBDLHSPVOE
  27. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end 0S JGUIFMJTUPGSFTPMWFE*1BEESFTTFTJTFNQUZJOUIF OFYUMPPQBGUFSXBLJOHGSPNTMFFQ JUXPVMETUBSUXBJUJOH GPSBEESFTTFTBHBJO"OETPPOBOETPGPSUI &OUFSUIFOFYUMPPQ *TTVFTXJUIUIJTJNQMFNFOUBUJPO  4UBSUUPXBJUIFSFBHBJO JGUIFMJTUJTFNQUZ &NQUZ
  28. 8IZEPFTUIFNIBQQFO def self.tcp(host, port) # ... connected = loop do

    mutex.synchronize do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end -FUTSFWJTJUUIFDVSSFOUJNQMFNFOUBUJPO UPDMBSJGZUIFVOEFSMZJOHQSPCMFN
  29. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end 8JUIJOUIJTMPPQ GSPNUIFUPQEPXO JUTXBJUJOHGPS OBNFSFTPMVUJPOTBOEPCUBJOJOHBOBEESFTT 8IZEPFTUIFNIBQQFO 8BJUGPSOBNFSFTPMVUJPOT "DPOUJOVPVTTFRVFODFJOUIFMPPQ 0CUBJOBOBEESFTT
  30. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end TUBSUJOHBDPOOFDUJPOBUUFNQU 8IZEPFTUIFNIBQQFO "DPOUJOVPVTTFRVFODFJOUIFMPPQ 4UBSUBDPOOFDUJPOBUUFNQU
  31. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end BOEXBJUJOHGPSUIPTFDPOOFDUJPOT BTBDPOUJOVPVTTFRVFODF 8IZEPFTUIFNIBQQFO 8BJUGPSDPOOFDUJPOT "DPOUJOVPVTTFRVFODFJOUIFMPPQ
  32. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end 8IZEPFTUIFNIBQQFO )PXFWFS EFQFOEJOHPOUIFTUBUVTPGUIFMJTUPG SFTPMWFE*1BEESFTTFTBOEUIFDPOOFDUJOHTPDLFUT  VOOFDFTTBSZEFMBZTDBOPDDVSXJUIJOUIFMPPQ 6OOFDFTTBSZEFMBZTDBOPDDVS "DPOUJOVPVTTFRVFODFJOUIFMPPQ
  33. def self.tcp(host, port) # ... connected = loop do mutex.synchronize

    do cond.wait(mutex) while resolved_addresses.empty? address = resolved_addresses.shift end socket = sockets.push(Socket.new(...)).last socket.connect_nonblock(address, exception: false) _, writable_sockets, = IO.select(nil, sockets, nil, 0.25) connected = pick_connected_socket(writable_sockets, sockets) sleep(remaining) if connection_failed break connected if connected end # ... end 8IZEPFTUIFNIBQQFO 5IFQSPDFTTUIBUTIPVMECFFYFDVUFEJOUIFMPPQTFFNT UPCFEJ ff FSFOUFBDIUJNF EFQFOEJOHPOUIFTUBUVT PGUIFMJTUPGBEESFTTFTBOEUIFDPOOFDUJOHTPDLFUT "DPOUJOVPVTTFRVFODFJOUIFMPPQ
  34. )&WTTUBUFUSBOTJUJPO 4P JOUIFBDUVBMEFWFMPQNFOUUIJTUJNF UPVOEFSTUBOEUIF fl PXPG)&W`TCFIBWJPS *TUBSUFECZDSFBUJOHQTFVEPDPEF MJLFUIJT SFGFSSJOHUIFFBSMJFSEJBHSBNBTBIJOU case

    start to - v6c - v4w - failure - timeout do Thread.new { IPv6 Addrinfo.getaddrinfo } Thread.new { IPv4 Addrinfo.getaddrinfo } Until the next state is determined select([Hostname resolution rpipe], Resolv timeout expires at) case IPv6 resolved Selectable addrinfo = Hostname resolution queue.pop Selectable addrinfos.push Selectable addrinfo -> v6c "OFYBNQMFPGUIFQTFVEPDPEF
  35. )&WTTUBUFUSBOTJUJPO case start to - v6c - v4w - failure

    - timeout do Local addrinfos = Addrinfo.getaddrinfo case ϦϞʔτϗετ·ͨ͸ϩʔΧϧϗετ͕IPΞυϨε addrinfo = Addrinfo.getaddrinfo Selectable addrinfos.push Selectable addrinfo case IPv6 addrinfo -> v6c case IPv4 addrinfo -> v4c case ϦϞʔτϗετͱϩʔΧϧϗετ͕υϝΠϯ໊ Thread.new { IPv6 Addrinfo.getaddrinfo } Thread.new { IPv4 Addrinfo.getaddrinfo } while ࣍ͷstate͕ܾఆ͢Δ·Ͱ select([Hostname resolution rpipe], Resolv timeout expires at) case Resolv timeout expires at ௒ա -> timeout case v6໊લղܾ Selectable addrinfo = Hostname resolution queue.pop Selectable addrinfos.push Selectable addrinfo -> v6c case v4໊લղܾ Selectable addrinfo = Hostname resolution queue.pop Selectable addrinfos.push Selectable addrinfo -> v4w case ໊લղܾΤϥʔ case retryable: true retry case retryable: false Last error = Τϥʔ -> failure case v4w from - start condition - selectͷฦΓ஋: - Resolution delay expires at ௒ա - v6໊લղܾ - ໊લղܾΤϥʔ to - v4c - v46c do select([Hostname resolution rpipe], Resolution delay expires at) case Resolution delay expires at ௒ա -> v4c case v6ΞυϨεղܾ Selectable addrinfo = Hostname resolution queue.pop Selectable addrinfos.push Selectable addrinfo -> v46c case ໊લղܾΤϥʔ -> v46c case v6c, v4c, v46c from - start - v4w - v46w to - v46c - v46w - success - failure do Connecting addrinfo = Selectable addrinfos.pop Connecting socket = Socket.new(Connecting addrinfo) case Local addrinfos: any case Connecting addrinfoͱಉ͡ΞυϨεϑΝϛϦͷLocal addrinfo͕͋Δ Local addrinfo = Local addrinfos.pick Connecting socket.bind Local addrinfo ҎԼ΁ਐΉ case Connecting addrinfoͱಉ͡ΞυϨεϑΝϛϦͷLocal addrinfo͕ͳ͍ case Selectable addrinfos: any && Connecting sockets: any && Hostname resolution queue: opened # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting sockets: any && Hostname resolution queue: closed # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: opened # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: closed # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: opened # ࣍ͷϧʔϓͰ઀ଓ໊͔લղܾΛ଴ͭ # Connecting sockets͕͋Δ͏ͪ͸Resolv timeout expires at ͸ߟྀ͠ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: closed # ࣍ͷϧʔϓͰ઀ଓΛ଴ͭ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: opened case Resolv timeout expires at: ઃఆ͋Γ && Resolv timeout expires at ௒ա -> timeout case ͦΕҎ֎ # ࣍ͷϧʔϓͰ໊લղܾΛ଴ͭ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: closed -> failure case Local addrinfos: empty ҎԼ΁ਐΉ Connection attempt delay expires at = ݱࡏͷΫϩοΫ࣌ؒ + Connection Attempt Delay case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: closed connect case ͦΕҎ֎ connect_nonblock ҎԼ΁ਐΉ case ઀ଓʹ੒ޭ -> success case ઀ଓத Connecting sockets.add Connecting socket -> v46w case SystemCallError Last error = SystemCallError case Selectable addrinfos: any && Connecting Sockets: any && Hostname resolution queue: opened # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting Sockets: any && Hostname resolution queue: closed # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting Sockets: empty && Hostname resolution queue: opened # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: any && Connecting Sockets: empty && Hostname resolution queue: closed # ࣍ͷϧʔϓͰผͷSelectable addrinfoΛࢼ͢ -> v46c case Selectable addrinfos: empty && Connecting Sockets: any && Hostname resolution queue: opened # ࣍ͷϧʔϓͰ໊લղܾ͔઀ଓΛ଴ͭɻ໊લղܾͨ͠৔߹͸͞Βʹ࣍ͷϧʔϓͰv46c΁ɺ઀ଓͨ͠৔߹͸͞Βʹ࣍ͷϧʔϓͰsuccess΁ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting Sockets: any && Hostname resolution queue: closed # ࣍ͷϧʔϓͰ઀ଓΛ଴ͭ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting Sockets: empty && Hostname resolution queue: opened case Resolv timeout expires at: ઃఆ͋Γ && Resolv timeout expires at ௒ա -> timeout case ͦΕҎ֎ # ࣍ͷϧʔϓͰ໊લղܾΛ଴ͭ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting Sockets: empty && Hostname resolution queue: closed -> failure case v46w from - v6c - v4c - v46c - v46w to - v46c - v46w - success - timeout do case Connect timeout expires at ௒ա -> timeoeut case Connect timeout expires at ௒աͰ͸ͳ͍ ҎԼ΁ਐΉ select([Hostname resolution rpipe], Connecting sockets, Connection attempt delay expires at) case Connectable sockets: any while (Connectable socket = Connectable sockets.pop) Connecting sockets.remove Connectable socket getsockopt(<઀ଓΤϥʔͷ֬ೝ>) case Τϥʔͳ͠ Connected socket = Connectable socket -> success case Τϥʔ͋Γ case Connectable sockets: any retry case Connectable sockets: empty ҎԼʹਐΉ case Selectable addrinfos: any && Connecting sockets: any && Hostname resolution queue: opened # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ઀ଓ໊͔લղܾΛ଴ͭ # ઀ଓ͠ͳ͔ͬͨ৔߹ɺ໊લղܾͯ͠΋͠ͳͯ͘΋͞Βʹ࣍ͷϧʔϓͰv46c΁ -> v46w case Selectable addrinfos: any && Connecting sockets: any && Hostname resolution queue: closed # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ઀ଓΛ଴ͭ # ઀ଓ͠ͳ͔ͬͨ৔߹͸͞Βʹ࣍ͷϧʔϓͰv46c΁ -> v46w case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: opened # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ໊લղܾΛ଴ͭ # ໊લղܾͯ͠΋͠ͳͯ͘΋͞Βʹ࣍ͷϧʔϓͰv46c΁ -> v46w case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: closed # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ໊લղܾΛ଴ͭ # ͞Βʹ࣍ͷϧʔϓͰv46c΁ -> v46w case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: opened # ͋ͱ͸઀ଓ໊͔લղܾΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ # Connecting sockets͕͋Δ͏ͪ͸Resolv timeout expires at ͸ߟྀ͠ͳ͍ -> v46w case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: closed # ͋ͱ͸઀ଓΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: opened case Resolv timeout expires at: ઃఆ͋Γ && Resolv timeout expires at ௒ա -> timeout case ͦΕҎ֎ # ͋ͱ͸໊લղܾΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: closed -> failure case ໊લղܾ Selectable addrinfo = Hostname resolution queue.pop Selectable addrinfos.push Selectable addrinfo # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ଴ͪɺ͞Βʹ࣍ͷϧʔϓͰv46c΁ -> v46w case ໊લղܾΤϥʔ # ࣍ͷϧʔϓͰConnection attempt delay expires atΛ௒ա͢Δ·Ͱ଴ͪɺ͞Βʹ࣍ͷϧʔϓͰϦιʔεͷঢ়ଶʹΑͬͯ෼ذ -> v46w case Connection attempt delay expires at ௒ա case Selectable addrinfos: any && Connecting sockets: any && Hostname resolution queue: opened -> v46c case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: opened -> v46c case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: opened -> v46c case Selectable addrinfos: any && Connecting sockets: empty && Hostname resolution queue: closed -> v46c case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: opened # ͋ͱ͸઀ଓ໊͔લղܾΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ # Connecting sockets͕͋Δ͏ͪ͸Resolv timeout expires at ͸ߟྀ͠ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: opened case Resolv timeout expires at: ઃఆ͋Γ && Resolv timeout expires at ௒ա -> timeout case ͦΕҎ֎ # ͋ͱ͸໊લղܾΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: any && Hostname resolution queue: closed # ͋ͱ͸઀ଓΛͻͨ͢Β଴ͭ͜ͱ͔͠Ͱ͖ͳ͍ Connection attempt delay expires at = nil -> v46w case Selectable addrinfos: empty && Connecting sockets: empty && Hostname resolution queue: closed -> failure case success from - v46w do cleanup return Connected socket case failure from - start do cleanup raise Last error case timeout from - start do cleanup raise Errno::ETIMEDOUT, 'user specified timeout' UVSOFEPVUMJLFUIJT 8SJUUFOJO+BQBOFTF 5IFBDUVBMQTFVEPDPEFDSFBUFE
  36. )&WTTUBUFUSBOTJUJPOWX ➡︎ WD "OTXFS*1WBEESFTTFT WX 3FTPVSDFT $POOFDUJOHTPDLFUT 3FTPMWFE*1BEESFTTFT WD *G*1WBEESFTTFTBSFSFTPMWFEXJUIJONT

     BEEUIFTF*1WBEESFTTFTUPUIFMJTUPGSFTPMWFE*1 BEESFTTFT5IFOUSBOTJUJPOUPBTUBUFDBMMFEWD %/44FSWFS "EE ➡︎
  37. )&WTTUBUFUSBOTJUJPOWX ➡︎ WD WX 0S *GUIFOBNFSFTPMVUJPOPG*1WBEESFTTFTEPFTOPU  fi OJTIXJUIJONT FOEUIFXBJUBOEUSBOTJUJPO

    UPBTUBUFDBMMFEWD WD 3FTPVSDFT $POOFDUJOHTPDLFUT 3FTPMWFE*1BEESFTTFT %/44FSWFS /PUIJOHXBTBOTXFSFE
  38. )&WTTUBUFUSBOTJUJPOWDWDWD 8BJUGPS*1W*1WBOTXFS "EEJUJPOBMMZ JGUIFDVSSFOUTUBUFJTFJUIFSWDPSWD  JUNFBOTUIFOBNFSFTPMVUJPOGPSBOBEESFTTGBNJMZ IBTOPUZFU fi OJTIFE *OUIJTTJUVBUJPO

    OFFEUPXBJUGPSCPUIUIFDPOOFDUJPOT BOEUIFOBNFSFTPMVUJPOTJNVMUBOFPVTMZ WD WD ➡︎ *1WOBNFSFTPMVUJPOJOQSPHSFTT ➡︎ *1WOBNFSFTPMVUJPOJOQSPHSFTT %/44FSWFS 4UJMMSFTPMWJOHOBNF
  39. def self.tcp(host, port, ...) # Do something ensure # ...

    end /PX MFUNFCSJF fl ZJOUSPEVDF UIFBDUVBMDPEFGPS4PDLFUUDQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W
  40. def self.tcp(host, port, ...) # ... loop do case state

    when :start # ... end end ensure # ... end *OUIJTJNQMFNFOUBUJPO  VTF,FSOFMMPPQBOEDBTFTUBUFNFOUTUPQFSGPSN BQQSPQSJBUFBDUJPOTGPSFBDITUBUF *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W ,FSOFMMPPQ DBTFTUBUFNFOUT
  41. def self.tcp(host, port, ...) # ... state = :start loop

    do case state when :start # ... end end ensure # ... end 'JSTU TFUDVSSFOUTUBUFUPTUBSUBTBOJOJUJBMTUBUF CFGPSFTUBSUJOHUIFMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&W *OJUJBMJ[FTUBUF
  42. case state when :start # ... h hostname_resolution_threads.concat( resolving_family_names.map {

    |family| thread_args = [family, *hostname_resolution_args] thread = Thread.new(*thread_args) { |*thread_args| hostname_resolution(*thread_args) } Thread.pass thread } ) # ... 5IJTJTTUBSU $SFBUFUISFBETGPSFBDIBEESFTTGBNJMZBOE TUBSUOBNFSFTPMVUJPOXJUIJOFBDIUISFBE *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTUBSU hostname_resolution_args = [host, port, hostname_resolution_queue] $SFBUFUISFBETGPSFBDIBEESFTTGBNJMZ &YFDVUFOBNFSFTPMVUJPONFUIPE
  43. case state when :start # ... hostname_resolution_queue = HostnameResolutionQueue.new(...) hostname_resolution_waiting

    = h # ... remaining = resolv_timeout ? second_to_timeout(...) : nil hostname_resolved, _, = IO.select( # ... 5IFO XBJUGPSFJUIFS*1WPS*1WOBNFSFTPMVUJPO UP fi OJTI)FSF VTF*0TFMFDUUPXBJUGPSOBNF SFTPMVUJPO5IFSFBTPOGPSUIJTXJMMCFFYQMBJOFEMBUFS *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTUBSU (hostname_resolution_waiting, nil, nil, remaining) hostname_resolution_queue.waiting_pipe 8BJUGPSOBNFSFTPMVUJPOUP fi OJTI
  44. case state when :start # ... family_name, res = hostname_resolution_queue.get

    if res.is_a? Exception # ... else state = case family_name when :ipv6 then :v6c when :ipv4 then <resolution completed?> ? :v4c : :v4w end selectable_addrinfos.add(family_name, res) # ... end # ... next # ... 0ODFOBNFSFTPMVUJPOJT fi OJTIFE BEEUIFPCUBJOFE BEESFTTFTUPUIFMJTUPGSFTPMWFE*1BEESFTTFT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTUBSU "EEUIFPCUBJOFEBEESFTTFT 5IFMJTUPGSFTPMWFE*1BEESFTTFT
  45. case state when :start # ... family_name, res = hostname_resolution_queue.get

    if res.is_a? Exception # ... else state = case family_name when :ipv6 then :v6c when :ipv4 then < end selectable_addrinfos.add(family_name, res) # ... end # ... next # ... %FUFSNJOFUIFOFYUTUBUFCBTFEPOUIFPCUBJOFE BEESFTT BOEUIFOFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTUBSU %FUFSNJOFUIFOFYUTUBUF ⬅︎ All name resolutions are finished?> ? :v4c : :v4w
  46. case state when :v4w ipv6_resolved, _, = IO.select( # ...

    5IJTJTWX *0TFMFDUJTVTFEBHBJOUP XBJUGPS*1WOBNFSFTPMVUJPOGPSNT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX (hostname_resolution_waiting, nil, nil, RESOLUTION_DELAY) 8BJUGPSOBNFSFTPMVUJPOUP fi OJTI 3&40-65*0/@%&-":NT
  47. case state when :v4w ipv6_resolved, _, = IO.select( if ipv6_resolved

    family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E # ... else # ... end # ... *GUIFOBNFSFTPMVUJPOJT fi OJTIFEXJUIJONT  BEEUIFOFXMZPCUBJOFEBEESFTTFTUPUIFMJTUPG SFTPMWFE*1BEESFTTFT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX Exception (hostname_resolution_waiting, nil, nil, RESOLUTION_DELAY) "EEUIFPCUBJOFEBEESFTTFT 5IFMJTUPGSFTPMWFE*1BEESFTTFT
  48. case state when :v4w ipv6_resolved, _, = IO.select( if ipv6_resolved

    family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E state = :v46c else state = :v4c end next # ... %FUFSNJOFUIFOFYUCBTFEPOXIFUIFSUIFOBNF SFTPMVUJPOXBT fi OJTIFEXJUIJOUIFBMMPUUFEUJNF  BOEUIFOFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX Exception (hostname_resolution_waiting, nil, nil, RESOLUTION_DELAY) %FUFSNJOFUIFOFYUTUBUF ⬅︎
  49. case state when :v6c, :v4c, v46c # ... WD WD

    BOEWDBSFEJ ff FSFOUTUBUFT CVUTJODF UIFDPEFTGPSFBDIJTFYBDUMZUIFTBNF  UIFZBSFHSPVQFEUPHFUIFSBTBTJOHMFDBTF *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWDWDWD
  50. case state when :v6c, :v4c, v46c # ... addrinfo =

    selectable_addrinfos.get # ... connection_attempt_delay_expires_at = current_clocktime + CONNECTION_ATTEMPT_DELAY # ... socket = Socket.new( socket.bind(local_addrinfo) if local_addrinfo result = socket.connect_nonblock(addrinfo, exception: false) # ... )FSF  fi STUPCUBJOBOBEESFTTGSPNUIFMJTUPGSFTPMWFE *1BEESFTTFTBOEUIFOTUBSUBDPOOFDUJPOBUUFNQU *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWDWDWD (addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol) 0CUBJOBOBEESFTT 4UBSUBDPOOFDUJPOBUUFNQU 5IFMJTUPGSFTPMWFE*1BEESFTTFT 0CUBJOUJNFPVUQFSJPE
  51. case state when :v6c, :v4c, v46c # ... addrinfo =

    selectable_addrinfos.get # ... 1FSUIF3'$ BEESFTTFTTIPVMECFTPSUFECFGPSF DPOOFDUJOH5IJTJNQMFNFOUBUJPOTJNQMJ fi FTUIFQSPDFTT CZBMUFSOBUJOHCFUXFFO*1WBOE*1WBEESFTTFT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWDWDWD precedences = case @last_family when :ipv4, nil then [:ipv6, :ipv4] when :ipv6 then [:ipv4, :ipv6] end precedences.each do |family_name| addrinfo = @addrinfo_dict[family_name]&.shift next unless addrinfo @last_family = family_name return addrinfo end 0CUBJOBOBEESFTT GSPNBEJ ff FSFOUBEESFTTGBNJMZ UIBOQSFWJPVTMZTFMFDUFE  QSJPSJUJ[JOH*1WJOJUJBMMZ
  52. case state when :v6c, :v4c, v46c # ... case result

    when 0 connected_socket = socket state = :success when Socket connected_socket = result state = :success when :wait_writable connecting_sockets.add(socket, addrinfo) state = :v46w end # ... next "GUFSTUBSUJOHUIFDPOOFDUJPOBUUFNQU EFUFSNJOFUIF OFYUTUBUFCBTFEPOXIFUIFSUIFDPOOFDUJPOXBTNBEF JNNFEJBUFMZ BOEUIFOFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWDWDWD %FUFSNJOFUIFOFYUTUBUF ⬅︎
  53. case state when :v6c, :v4c, v46c # ... case result

    when 0 connected_socket = socket state = :success when Socket connected_socket = result state = :success when :wait_writable connecting_sockets.add(socket, addrinfo) state = :v46w end # ... next *GUIFDPOOFDUJPODBOUCFFTUBCMJTIFEJNNFEJBUFMZ  BEEUIFTPDLFUVTFEGPSUIFBUUFNQUUPUIFMJTUPG DPOOFDUJOHTPDLFUTCFGPSFEFUFSNJOJOHUIFOFYUTUBUF *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWDWDWD "EEUIFTPDLFUVTFE 5IFMJTUPGDPOOFDUJOHTPDLFUT
  54. case state when :v46w # ... ...tname_resolved, ...nectable_sockets, = IO.select(

    # ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX *0TFMFDUJTVTFEIFSFBHBJOGPSUIJTQVSQPTF (...tname_resolution_waiting, ...necting_sockets.all, ..., ...aining) *0TFMFDUJTDBMMFE
  55. case state when :v46w # ... ...tname_resolved, ...nectable_sockets, = IO.select(

    # ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 4JODFUIF4PDLFUGBNJMZPGDMBTTFTJOIFSJUTGSPN*0 DMBTT *0TFMFDUDBOBMTPXBJUGPS4PDLFUPCKFDUT 6UJMJ[JOHUIJT JOWX UIFDPOOFDUJOHTPDLFUTBSF QBTTFEUPUIJTNFUIPEBT*0PCKFDUTXBJUJOHGPSXSJUJOH (...tname_resolution_waiting, connecting_sockets.all, nil, ...aining) $POOFDUJOHTPDLFUTBTXSJUJOH*0PCKFDUT Socket.ancestors => [Socket, BasicSocket, IO, # ... BasicObject]
  56. case state when :v46w # ... ...tname_resolved, connectable_sockets, = IO.select(

    # ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 8IFOBDPOOFDUJPOJTFTUBCMJTIFEPSGBJMTPOBOZPG UIFTPDLFUTQBTTFEBTBSHVNFOUT *0TFMFDUDPOTJEFST UIFTFUPCFXSJUBCMF*0PCKFDUT 5IFO*0TFMFDUSFMFBTFTUIFXBJU BOESFUVSOTBOBSSBZ PGUIFSFMFWBOUTPDLFUTBTXSJUBCMF*0PCKFDUT (...tname_resolution_waiting, connecting_sockets.all, nil, ...aining) 4PDLFUTBTXSJUBCMF*0PCKFDUT
  57. case state when :v46w # ... ...tname_resolved, connectable_sockets, = IO.select(

    # ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 5IJTBMMPXTVTUPXBJUGPSUIFFTUBCMJTINFOUPG DPOOFDUJPOT4P IPXTIPVMEXFXBJUGPSUIF  fi OJTIPGUIFOBNFSFTPMVUJPO (...tname_resolution_waiting, connecting_sockets.all, nil, ...aining)
  58. case state when :v46w # ... ...tname_resolved, connectable_sockets, = IO.select(

    # ... (<IO objectsca?n be passed >, connecting_sockets.all, nil, ...aining) *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 5IFEJ ff i DVMUZJTUIBU UIFOBNFSFTPMVUJPOUISFBETBSFOPU*0PCKFDUTBOE DBOOPUCFXBJUFEGPSJO*0TFMFDU $BOUQBTTOBNFSFTPMVUJPOUISFBET
  59. case state when :v46w # ... ...tname_resolved, connectable_sockets, = IO.select(

    # ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 5PSFTPMWFUIJT VTF*0QJQF (...tname_resolution_waiting, connecting_sockets.all, nil, ...aining)
  60. *0QJQF 8IFOZPVXSJUFUPUIFXSJUJOH*0PCKFDU  ZPVDBOSFBEUIBUGSPNUIFSFBEJOH*0PCKFDU rpipe, wpipe = IO.pipe Thread.new(wpipe) do

    |w| w.puts "foo" w.close end p rpipe.gets # => "foo" 8SJUFUIFXSJUJOH*0PCKFDU 5IFOUIFSFBEJOH*0PCKFDUDBOCFSFBE
  61. case state when :start # ... hostname_resolution_queue = HostnameResolutionQueue.new(...) #

    ... *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX *OUIJTJNQMFNFOUBUJPO *0QJQFXBTDBMMFEBUTUBSUUP DSFBUFBQBJSPG*0PCKFDUT class HostnameResolutionQueue def initialize(size) # ... @rpipe, @wpipe = IO.pipe end # ... $SFBUFBQBJSPG*0PCKFDUT
  62. case state when :start # ... hostname_resolution_queue = HostnameResolutionQueue.new(...) #

    ... *0QJQF 0GUIFTFUXP*0PCKFDUT UIFPOFGPSXSJUJOHJT class HostnameResolutionQueue # ... def waiting_pipe [@wpipe] end # ... hostname_resolution_waiting = hostname_resolution_queue.waiting_pipe hostname_resolution_args = [host, port, hostname_resolution_queue] 5IFXSJUJOH*0PCKFDU 5IFXSJUJOH*0PCKFDU BOEPUIFSBSHVNFOUT
  63. case state when :start # ... hostname_resolution_queue = HostnameResolutionQueue.new(...) hostname_resolution_threads.concat(

    resolving_family_names.map { |family| thread_args = [family, *hostname_resolution_args] thread = Thread.new(*thread_args) { |*thread_args| hostname_resolution(*thread_args) } Thread.pass thread } ) # ... *0QJQF QBTTFEBTBOBSHVNFOUUPUIFOBNFSFTPMVUJPO UISFBET hostname_resolution_waiting = hostname_resolution_queue.waiting_pipe hostname_resolution_args = [host, port, hostname_resolution_queue] 1BTTUIFBSHTJODMVEJOHUIFXSJUJOH*0PCKFDU
  64. case state when :start # ... thread = Thread.new(*thread_args) {

    |*thread_args| hostname_resolution(*thread_args) } *0QJQF 0ODFOBNFSFTPMVUJPOJT fi OJTIFEJOUIFUISFBE  JUXSJUFTUPUIFXSJUJOH*0PCKFDUUPOPUJGZDPNQMFUJPO def self.hostname_resolution(family, ..., hostname_resolution_queue) resolved_addrinfos = Addrinfo.getaddrinfo(...) hostname_resolution_queue.add_resolved(family, resolved_addrinfos) # ... def add_resolved(family, resolved_addrinfos) @mutex.synchronize do @queue.push [family, resolved_addrinfos] @wpipe.putc HOSTNAME_RESOLUTION_QUEUE_UPDATED # ... 8SJUFUPUIFXSJUJOH*0PCKFDU
  65. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX case state when :v46w # ... ...tname_resolved, connectable_sockets, =

    IO.select( # ... .FBOXIJMF UIFSFBEJOH*0PCKFDUDSFBUFEXJUI*0QJQF JTQBTTFEUP*0TFMFDUJONBJOUISFBE (hostname_resolution_waiting, connecting_sockets.all, nil, ...aining) 5IFSFBEJOH*0PCKFDU
  66. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX case state when :v46w # ... hostname_resolved, connectable_sockets, =

    IO.select( # ... 5IJT*0PCKFDUXJMMCFSFBEBCMFPODFOBNFSFTPMVUJPO JT fi OJTIFEJOUIFDIJMEUISFBET 5IFO*0TFMFDUXJMMSFMFBTFUIFXBJUBOESFUVSOUIJT SFBEJOH*0PCKFDU (hostname_resolution_waiting, connecting_sockets.all, nil, ...aining) 5IFSFBEJOH*0PCKFDU 3FBEBCMF*0PCKFDUT
  67. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX case state when :v46w # ... hostname_resolved, connectable_sockets, =

    IO.select( # ... 5IJTJTUIFUSJDLUPBMMPXGPSXBJUJOHGPSCPUI UIFDPNQMFUJPOPGDPOOFDUJPOTBOEOBNFSFTPMVUJPO (hostname_resolution_waiting, connecting_sockets.all, nil, ...aining) #PUIDPOOFDUJPOTBOEOBNFSFTPMVUJPODBOCFXBJU
  68. case state when :start # ... hostname_resolution_queue = HostnameResolutionQueue.new(...) hostname_resolution_waiting

    = h # ... hostname_resolved, _, = IO.select( # ... when :v4w ipv6_resolved, _, = IO.select( # ... (hostname_resolution_waiting, nil, nil, remaining) hostname_resolution_queue.waiting_pipe *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTUBSUWX "OEJUJTUIFSFBTPOXIZTUBSUBOEWXVTFE*0TFMFDU UPXBJUGPSOBNFSFTPMVUJPO (hostname_resolution_waiting, nil, nil, RESOLUTION_DELAY) 8BJUXJUIUIFSFBEJOH*0PCKFDU 8BJUXJUIUIFSFBEJOH*0PCKFDU
  69. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX case state when :v46w # ... remaining = s

    hostname_resolved, connectable_sockets, = IO.select( # ... -FUTSFUVSOUPWX 4FUUIFUJNFPVUCZQBTTJOH*0TFMFDUUIFUJNFUPXBJU GPSDPOOFDUJPOT (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) second_to_timeout(connection_attempt_delay_expires_at) 4FUUJNFPVU $BMDVMBUFSFNBJOJOHUJNFGSPNUIFUJNFPVUQFSJPE
  70. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? # ... else # ... end # ... next (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) *G*0TFMFDUSFUVSOTBXSJUBCMF*0PCKFDUXJUIJOUIF XBJUJOHQFSJPE JUNFBOTUIBUBTJUVBUJPOIBTBSJTFO XIFSFTPDLFUTIBWFFJUIFSTVDDFTTGVMMZDPOOFDUFEPS GBJMFEUPDPOOFDU *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX 8IFO*0TFMFDUSFUVSOTXSJUBCMF*0PCKFDUT 3FBDIUIJTCMPDL
  71. case state when :v46w # ... if connectable_sockets&.any? while (connectable_socket

    = connectable_sockets.pop) # ... sockopt = c is_connected = sockopt.int.zero? # ... if is_connected # ... else # ... end # ... next *OUIJTDBTF EFUFSNJOFUIFOFYUTUBUFCBTFEPO XIFUIFSUIFTPDLFUTXFSFDPOOFDUFEPSOPU  BOEUIFOFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX connectable_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) %FUFSNJOFUIFOFYUTUBUF $IFDLUIFDPOOFDUJPOTUBUVT ⬅︎
  72. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? # ... else # ... end # ... next 0SJG*0TFMFDUSFUVSOTSFBEBCMF*0PCKFDUTXJUIJOUIF XBJUJOHQFSJPE JUNFBOTUIBUOBNFSFTPMVUJPOJT  fi OJTIFE *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) 8IFO*0TFMFDUSFUVSOTSFBEBCMF*0PCKFDUT 3FBDIUIJTCMPDL
  73. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E else # ... end # ... next *OUIJTDBTF BEEUIFPCUBJOFEBEESFTTFTUPUIFMJTUPG SFTPMWFE*1BEESFTTFT *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX Exception (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) "EEUIFPCUBJOFEBEESFTTFT 5IFMJTUPGSFTPMWFE*1BEESFTTFT
  74. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E else # ... end # ... next 5PXBJUGPSUIFSFNBJOJOHUJNF LFFQUIFDVSSFOUTUBUF BUWX BOEFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) &OUFSUIFOFYUMPPQXJUIWXTUBUF Exception
  75. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E else # ... end # ... next 0SJG*0TFMFDUUJNFTPVUBOESFUVSOTOJM JUNFBOTUIBU OFJUIFSBDPOOFDUJPOOPSUIFOBNFSFTPMVUJPOXFSF  fi OJTIFEXJUIJOUIFBMMPUUFEUJNF *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) 8IFO*0TFMFDUSFUVSOTOJM 3FBDIUIJTCMPDL Exception
  76. case state when :v46w # ... hostname_resolved, connectable_sockets, = IO.select(

    # ... if connectable_sockets&.any? # ... elsif hostname_resolved&.any? family_name, res = hostname_resolution_queue.get selectable_addrinfos.add(family_name, res) unless res.is_a? E else # ... end # ... next *OUIJTDBTF EFUFSNJOFUIFOFYUTUBUFCBTFEPOUIF DVSSFOUSFTPVSDFTTUBUVT BOEUIFOFOUFSUIFOFYUMPPQ *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WWX (hostname_resolution_waiting, connecting_sockets.all, nil, remaining) %FUFSNJOFUIFOFYUTUBUF ⬅︎ Exception
  77. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WGBJMVSF ret = loop do case state # ... when

    :success # ... when :failure raise last_error when :timeout # ... end end 5IFTUBUFTJOUSPEVDFEIFSFBGUFSBMMSFQSFTFOUUIF DPNQMFUJPOPGTUBUFUSBOTJUJPOT GBJMVSFXIJDIJTSFBDIFEJGBMMOBNFSFTPMVUJPOTPS BMMDPOOFDUJPOBUUFNQUTGBJM SBJTFBOFYDFQUJPO XJUIUIFMBTUDBQUVSFEFSSPS 3BJTFBOFYDFQUJPO
  78. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WUJNFPVU ret = loop do case state # ... when

    :success # ... when :failure raise last_error when :timeout raise Errno::ETIMEDOUT, "user specified timeout" end end *GBVTFSTQFDJ fi FEUJNFPVUPDDVST SFBDIUJNFPVU BOESBJTFBOFYDFQUJPOJOEJDBUJOHJU 3BJTFBOFYDFQUJPO
  79. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WTVDDFTT ret = loop do case state # ... when

    :success break connected_socket when :failure raise last_error when :timeout raise Errno::ETIMEDOUT, "user specified timeout" end end TVDDFTTXIJDIJTSFBDIFEXIFOPOFPGUIFDPOOFDUJPOT JTFTUBCMJTIFE JTIFSF #SFBLUIFMPPQXJUIUIFDPOOFDUFETPDLFU #SFBLUIFMPPQ
  80. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WDMFBOVQ def self.tcp(host, port, ...) # ... ret = loop

    do # ... end # ... ensure # ... hostname_resolution_threads.each do |thread| thread&.exit end hostname_resolution_queue&.close_all connecting_sockets.each do |connecting_socket| connecting_socket.close unless connecting_socket.closed? end end "GUFSCSFBLJOHPVUPGUIFMPPQ DMFBOVQUISFBET QJQFT  BOEPUIFSTPDLFUTJOFBDIDPOOFDUJPOBUUFNQUT $MFBOVQ
  81. *NQMFNFOUBUJPOPG4PDLFUUDQXJUI)&WFYJU def self.tcp(host, port, ...) # ... ret = loop

    do # ... end if block_given? begin yield ret ensure ret.close end else ret end ensure # ... end SFUVSOUIFDPOOFDUFETPDLFUBOEUIFNFUIPEFYJUT &YFDVUFCMPDLXJUIUIFDPOOFDUFETPDLFU 3FUVSOUIFDPOOFDUFETPDLFU
  82. "EEJUJPOBMDPOTJEFSBUJPOT  WX "O*1WBEESFTTSFTPMWFE 4FSWFS %/44FSWFS -FUTDPOTJEFSUIFTJUVBUJPOMJLFUIJT &JUIFS*1WPS*1WOBNFSFTPMVUJPOIBTCFFO  fi

    OJTIFE POFPSUIFPUIFS 3FTPVSDFT $POOFDUJOHTPDLFUT 3FTPMWFE*1BEESFTTFT *1WJTTUJMMSFTPMWJOH &NQUZ "EE ➡︎
  83. "EEJUJPOBMDPOTJEFSBUJPOT  5IFTFFYFDVUJPODPTUTEJSFDUMZJNQBDUSVOUJNF  BOEBTBSFTVMU UIFFYFDVUJPOUJNFIBTSPVHIMZEPVCMFE DPNQBSFEUPCFGPSFUIFDIBOHFT Benchmark.bmbm do |x|

    x.report do 100.times { Socket.tcp("localhost", 9292).close } end end #FGPSF 3VCZ "GUFS *OMPDBMFOWJSPONFOU "QQMF..BY
  84. "TBDPNQSPNJTFCFUXFFOUIFTFUXPQFSTQFDUJWFT BOPQUJPO UPFOBCMFPSEJTBCMF)&WXBTJOUSPEVDFEBTBGFBUVSF 5IJTDBOCFDPOUSPMMFECZQBTTJOHUSVFPSGBMTFUPUIF GBTU@GBMMCBDLLFZXPSEBSHVNFOUXIFODBMMJOH4PDLFUUDQ "EEJUJPOBMDPOTJEFSBUJPOT  @tcp_fast_fallback = true

    class << self attr_accessor :tcp_fast_fallback end def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &block) &OBCMFPSEJTBCMF)&W
  85. "EEJUJPOBMDPOTJEFSBUJPOT  #ZEFGBVMU UIFGBTU@GBMMCBDLPQUJPOJTTFUUPUSVF  NFBOJOH)&WJTFOBCMFE *GGBMTFJTFYQMJDJUMZQBTTFE JUXJMMJOWPLFUIFDPEFGPS 4PDLFUUDQBTJUXBTCFGPSF)&WXBTJOUSPEVDFE *EFBMMZ

    UIFQFSGPSNBODFXJMMFWFOUVBMMZJNQSPWFUPUIFQPJOU XIFSFUIJTPQUJPOJTOPMPOHFSOFFEFE CVUGPSOPX JUSFNBJOT BGVUVSFDIBMMFOHF Socket.tcp(HOSTNAME, PORT, fast_fallback: false) do |socket| socket.write "GET / HTTP/1.0\r\n\r\n" print socket.read end
  86. $PEFTGPSEFNP $MJFOU require "socket" HOSTNAME = "localhost" PORT = 4567

    Socket.tcp(HOSTNAME, PORT) do |socket| socket.write "Hi\r\n" print socket.read end 5IJTCMPDLXPVMECFFYFDVUFE PODFBDPOOFDUJPOJTFTUBCMJTIFE 4UBOEBSEPVUQVUUIFSFTQPOTFSFUVSOFEGSPNUIFUBSHFUTFSWFS &YFDVUF4PDLFUUDQ
  87. $PEFTGPSEFNP 4FSWFS require "socket" require "optparse" class Server ADDRESS_FAMILIES =

    { IPv6: [Socket::AF_INET6, "::1"], IPv4: [Socket::AF_INET, "127.0.0.1"], } #... def accept_loop # ... end end if child_pid = fork Server.new(:IPv6).accept_loop Process.waitpid(child_pid) else Server.new(:IPv4).accept_loop end 4UBSUUXPQSPDFTTFTUPIBOEMFSFRVFTUT UPCPUI*1WBOE*1WBEESFTTFT )BOEMFSFRVFTUTXJUIUIJTNFUIPE
  88. $PEFTGPSEFNP 4FSWFS class Server # ... def accept_loop target_family =

    ARGV.getopts("", "sleep:")["sleep"]&.to_sym sleep if target_family == @version @socket.listen(5) loop do conn, ai = @socket.accept puts "Received #{@version} request" conn.readpartial(128) conn.write("Connection OK: #{ai.ip_address} (#{@version})\n") conn.close # ... end end end 8BJUGPSSFRVFTUT 3FUVSOBSFTQPOTF "DDFQUBSFRVFTU
  89. $PEFTGPSEFNP 4FSWFS class Server # ... def accept_loop target_family =

    ARGV.getopts("", "sleep:")["sleep"]&.to_sym sleep if target_family == @version @socket.listen(5) loop do conn, ai = @socket.accept puts "Received #{@version} request" conn.readpartial(128) conn.write("Connection OK: #{ai.ip_address} (#{@version})\n") conn.close # ... end end end 8BJUGPSSFRVFTUT 8IFOFYFDVUFEXJUIUIFBEESFTTGBNJMZ *1W*1W  BT"3(7 JUPCUBJOTUIFBEESFTTGBNJMZGSPN"3(7 4MFFQUIFTFSWFSQSPDFTTCFGPSF XBJUJOHGPSSFRVFTUT 5IJTTJNVMBUFTBTJUVBUJPOXIFSF  BGUFSTUBSUJOHBDPOOFDUJPOBUUFNQUUPBDFSUBJO BEESFTT UIFDPOOFDUJPOJTOPUFTUBCMJTIFETPPO