Project

General

Profile

Actions

Feature #20646

closed

Improve Socket.tcp

Added by shioimm (Misaki Shioi) 4 months ago. Updated 3 months ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:118660]

Description

Patch: https://github.com/ruby/ruby/pull/11187

This is a proposed improvement to Socket.tcp, which has implemented Happy Eyeballs version 2 (RFC8305) in PR9374.

  1. Background
    I implemented Happy Eyeballs version 2 (HEv2) for Socket.tcp in PR9374, but several issues have been identified:
  • IO.select waits for name resolution or connection establishment in v46w, but it does not consider the case where both events occur simultaneously when it returns a value.
    • In this case, Socket.tcp can only capture one event and needs to execute an unnecessary loop to capture the other one, calling IO.select one extra time.
  • IO.select waits for both IPv6/IPv4 name resolution (in start), but when it returns a value, it doesn't consider the case where name resolution for both address families is complete.
    • In this case, Socket.tcp can only obtain the addresses of one address family and needs to execute an unnecessary loop obtain the other addresses, calling IO.select one extra time.
  • The consideration for connect_timeout was insufficient. After initiating one or more connections, it raises a 'user specified timeout' after the connect_timeout period even if there were addresses that have been resolved and have not yet tried to connect.
  • It does not retry with another address in case of a connection failure.
  • It executes unnecessary state transitions even when an IP address is passed as the host argument.
  • The regex for IP addresses did not correctly specify the start and end.
  1. Proposal & Outcome
    To overcome the aforementioned issues, this PR introduces the following changes:
  • Previously, each loop iteration represented a single state transition. This has been changed to execute all processes that meet the execution conditions within a single loop iteration.
    • This prevents unnecessary repeated loops and calling IO.select
  • Introduced logic to determine the timeout value set for IO.select. During the Resolution Delay and Connection Attempt Delay, the user-specified timeout is ignored. Otherwise, the timeout value is set to the larger of resolv_timeout and connect_timeout.
    • This ensures that the connect_timeout is only detected after attempting to connect to all resolved addresses.
  • Retry with another address in case of a connection failure.
    • This prevents unnecessary repeated loops upon connection failure.
  • Call tcp_without_fast_fallback when an IP address is passed as the host argument.
    • This prevents unnecessary state transitions when an IP address is passed.
  • Fixed regex for IP addresses.

Additionally, the code has been reduced by over 100 lines, and redundancy has been minimized, which is expected to improve readability.

  1. Performance
    No significant performance changes were observed in the happy case before and after the improvement.
    However, improvements in state transition deficiencies are expected to enhance performance in edge cases.
require 'socket'
require 'benchmark'

Benchmark.bmbm do |x|
  x.report('fast_fallback: true') do
    30.times { Socket.tcp("www.ruby-lang.org", 80) }
  end

  x.report('fast_fallback: false') do # Ruby3.3時点と同じ
    30.times { Socket.tcp("www.ruby-lang.org", 80, fast_fallback: false) }
  end
end

Before:

~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb

                           user     system      total        real
fast_fallback: true    0.021315   0.040723   0.062038 (  0.504866)
fast_fallback: false   0.007553   0.026248   0.033801 (  0.533211)

After:

~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb

                           user     system      total        real
fast_fallback: true    0.023081   0.040525   0.063606 (  0.406219)
fast_fallback: false   0.007302   0.025515   0.032817 (  0.418680)

Updated by shioimm (Misaki Shioi) 4 months ago · Edited

Additional note

The PR for this issue is Improve Socket.tcp https://github.com/ruby/ruby/pull/11187 .

Also, as the number of times the performance measurement has been run is low, the results of sending 3000 requests to localhost are attached for reference.

Before

                          user     system      total        real
fast_fallback: true   1.612526   4.072099   5.684625 ( 29.892578)

After

                          user     system      total        real
fast_fallback: true   1.631585   4.012447   5.644032 ( 31.351084)
Actions #2

Updated by alanwu (Alan Wu) 3 months ago

  • Description updated (diff)
Actions #3

Updated by Anonymous 3 months ago

  • Status changed from Open to Closed

Applied in changeset git|b3baa11ee90c3fc0a3c43f1ac3293558fbfe76cb.


Improve Socket.tcp (#11187)

[Feature #20646]Improve Socket.tcp

This is a proposed improvement to Socket.tcp, which has implemented Happy Eyeballs version 2 (RFC8305) in PR9374.

  1. Background
    I implemented Happy Eyeballs version 2 (HEv2) for Socket.tcp in PR9374, but several issues have been identified:
  • IO.select waits for name resolution or connection establishment in v46w, but it does not consider the case where both events occur simultaneously when it returns a value.
    • In this case, Socket.tcp can only capture one event and needs to execute an unnecessary loop to capture the other one, calling IO.select one extra time.
  • IO.select waits for both IPv6/IPv4 name resolution (in start), but when it returns a value, it doesn't consider the case where name resolution for both address families is complete.
    • In this case, Socket.tcp can only obtain the addresses of one address family and needs to execute an unnecessary loop obtain the other addresses, calling IO.select one extra time.
  • The consideration for connect_timeout was insufficient. After initiating one or more connections, it raises a 'user specified timeout' after the connect_timeout period even if there were addresses that have been resolved and have not yet tried to connect.
  • It does not retry with another address in case of a connection failure.
  • It executes unnecessary state transitions even when an IP address is passed as the host argument.
  • The regex for IP addresses did not correctly specify the start and end.
  1. Proposal & Outcome
    To overcome the aforementioned issues, this PR introduces the following changes:
  • Previously, each loop iteration represented a single state transition. This has been changed to execute all processes that meet the execution conditions within a single loop iteration.
    • This prevents unnecessary repeated loops and calling IO.select
  • Introduced logic to determine the timeout value set for IO.select. During the Resolution Delay and Connection Attempt Delay, the user-specified timeout is ignored. Otherwise, the timeout value is set to the larger of resolv_timeout and connect_timeout.
    • This ensures that the connect_timeout is only detected after attempting to connect to all resolved addresses.
  • Retry with another address in case of a connection failure.
    • This prevents unnecessary repeated loops upon connection failure.
  • Call tcp_without_fast_fallback when an IP address is passed as the host argument.
    • This prevents unnecessary state transitions when an IP address is passed.
  • Fixed regex for IP addresses.

Additionally, the code has been reduced by over 100 lines, and redundancy has been minimized, which is expected to improve readability.

  1. Performance
    No significant performance changes were observed in the happy case before and after the improvement.
    However, improvements in state transition deficiencies are expected to enhance performance in edge cases.
require 'socket'
require 'benchmark'

Benchmark.bmbm do |x|
  x.report('fast_fallback: true') do
    30.times { Socket.tcp("www.ruby-lang.org", 80) }
  end

  x.report('fast_fallback: false') do # Ruby3.3時点と同じ
    30.times { Socket.tcp("www.ruby-lang.org", 80, fast_fallback: false) }
  end
end

Before:

~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb

                           user     system      total        real
fast_fallback: true    0.021315   0.040723   0.062038 (  0.504866)
fast_fallback: false   0.007553   0.026248   0.033801 (  0.533211)

After:

~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb

                           user     system      total        real
fast_fallback: true    0.023081   0.040525   0.063606 (  0.406219)
fast_fallback: false   0.007302   0.025515   0.032817 (  0.418680)
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0