Feature #20646
closedImprove Socket.tcp
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.
- 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.
- 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
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, callingIO.select
one extra time.
- In this case,
- The consideration for
connect_timeout
was insufficient. After initiating one or more connections, it raises a 'user specified timeout' after theconnect_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.
- 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
- This prevents unnecessary repeated loops and calling
- 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 ofresolv_timeout
andconnect_timeout
.- This ensures that the
connect_timeout
is only detected after attempting to connect to all resolved addresses.
- This ensures that the
- 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.
- 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) 5 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)
Updated by Anonymous 5 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.
- 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.
- 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
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, callingIO.select
one extra time.
- In this case,
- The consideration for
connect_timeout
was insufficient. After initiating one or more connections, it raises a 'user specified timeout' after theconnect_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.
- 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
- This prevents unnecessary repeated loops and calling
- 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 ofresolv_timeout
andconnect_timeout
.- This ensures that the
connect_timeout
is only detected after attempting to connect to all resolved addresses.
- This ensures that the
- 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.
- 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)