Improve APIs for Globally Enabling/Disabling fast_fallback in Socket (#12257)
This change includes the following updates: - Added an environment variable `RUBY_TCP_NO_FAST_FALLBACK` to control enabling/disabling fast_fallback - Updated documentation and man pages - Revised the implementation of Socket.tcp_fast_fallback= and Socket.tcp_fast_fallback, which previously performed dynamic name resolution of constants and variables. As a result, the following performance improvements were achieved: (Case of 1000 executions of `TCPSocket.new` to the local host) Rehearsal ----------------------------------------- before 0.031462 0.147946 0.179408 ( 0.249279) after 0.031164 0.146839 0.178003 ( 0.346935) -------------------------------- total: 0.178003sec user system total real before 0.027584 0.138712 0.166296 ( 0.233356) after 0.025953 0.127608 0.153561 ( 0.237971)
This commit is contained in:
parent
77016a7b43
commit
9f924e2f13
Notes:
git
2024-12-14 06:51:36 +00:00
Merged-By: shioimm <shioi.mm@gmail.com>
@ -617,12 +617,6 @@ class Socket < BasicSocket
|
||||
IPV6_ADRESS_FORMAT = /\A(?i:(?:(?:[0-9A-F]{1,4}:){7}(?:[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){6}(?:[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){5}(?:(?::[0-9A-F]{1,4}){1,2}|:(?:[0-9A-F]{1,4}:){1,4}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){4}(?:(?::[0-9A-F]{1,4}){1,3}|:(?:[0-9A-F]{1,4}:){1,3}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){3}(?:(?::[0-9A-F]{1,4}){1,4}|:(?:[0-9A-F]{1,4}:){1,2}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){2}(?:(?::[0-9A-F]{1,4}){1,5}|:(?:[0-9A-F]{1,4}:)[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){1}(?:(?::[0-9A-F]{1,4}){1,6}|:(?:[0-9A-F]{1,4}:){0,5}[0-9A-F]{1,4}|:)|(?:::(?:[0-9A-F]{1,4}:){0,7}[0-9A-F]{1,4}|::)))(?:%.+)?\z/
|
||||
private_constant :IPV6_ADRESS_FORMAT
|
||||
|
||||
@tcp_fast_fallback = true
|
||||
|
||||
class << self
|
||||
attr_accessor :tcp_fast_fallback
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... }
|
||||
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
|
||||
@ -633,8 +627,13 @@ class Socket < BasicSocket
|
||||
# Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
# algorithm by default.
|
||||
#
|
||||
# For details on Happy Eyeballs Version 2,
|
||||
# see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket#tcp_fast_fallback=].
|
||||
#
|
||||
# To make it behave the same as in Ruby 3.3 and earlier,
|
||||
# explicitly specify the option +fast_fallback:false+.
|
||||
# explicitly specify the option fast_fallback:false.
|
||||
# Or, setting Socket.tcp_fast_fallback=false will disable
|
||||
# Happy Eyeballs Version 2 not only for this method but for all Socket globally.
|
||||
#
|
||||
# If local_host:local_port is given,
|
||||
# the socket is bound to it.
|
||||
@ -657,24 +656,6 @@ class Socket < BasicSocket
|
||||
# sock.close_write
|
||||
# puts sock.read
|
||||
# }
|
||||
#
|
||||
# === Happy Eyeballs Version 2
|
||||
# Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
# is an algorithm designed to improve client socket connectivity.<br>
|
||||
# It aims for more reliable and efficient connections by performing hostname resolution
|
||||
# and connection attempts in parallel, instead of serially.
|
||||
#
|
||||
# Starting from Ruby 3.4, this method operates as follows with this algorithm:
|
||||
#
|
||||
# 1. Start resolving both IPv6 and IPv4 addresses concurrently.
|
||||
# 2. Start connecting to the one of the addresses that are obtained first.<br>If IPv4 addresses are obtained first,
|
||||
# the method waits 50 ms for IPv6 name resolution to prioritize IPv6 connections.
|
||||
# 3. After starting a connection attempt, wait 250 ms for the connection to be established.<br>
|
||||
# If no connection is established within this time, a new connection is started every 250 ms<br>
|
||||
# until a connection is established or there are no more candidate addresses.<br>
|
||||
# (Although RFC 8305 strictly specifies sorting addresses,<br>
|
||||
# this method only alternates between IPv6 / IPv4 addresses due to the performance concerns)
|
||||
# 4. Once a connection is established, all remaining connection attempts are canceled.
|
||||
def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket
|
||||
sock = if fast_fallback && !(host && ip_address?(host))
|
||||
tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
|
||||
|
@ -501,6 +501,8 @@ void rsock_make_fd_nonblock(int fd);
|
||||
|
||||
int rsock_is_dgram(rb_io_t *fptr);
|
||||
|
||||
extern ID tcp_fast_fallback;
|
||||
|
||||
#if !defined HAVE_INET_NTOP && ! defined _WIN32
|
||||
const char *inet_ntop(int, const void *, char *, size_t);
|
||||
#elif defined __MINGW32__
|
||||
|
@ -14,6 +14,8 @@ static VALUE sym_wait_writable;
|
||||
|
||||
static VALUE sock_s_unpack_sockaddr_in(VALUE, VALUE);
|
||||
|
||||
ID tcp_fast_fallback;
|
||||
|
||||
void
|
||||
rsock_sys_fail_host_port(const char *mesg, VALUE host, VALUE port)
|
||||
{
|
||||
@ -1854,6 +1856,67 @@ socket_s_ip_address_list(VALUE self)
|
||||
#define socket_s_ip_address_list rb_f_notimplement
|
||||
#endif
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* Socket.tcp_fast_fallback -> true or false
|
||||
*
|
||||
* Returns whether Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]),
|
||||
* which is provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp,
|
||||
* is enabled or disabled.
|
||||
*
|
||||
* If true, it is enabled for TCPSocket.new and Socket.tcp.
|
||||
* (Note: Happy Eyeballs Version 2 is not provided when using TCPSocket.new on Windows.)
|
||||
*
|
||||
* If false, Happy Eyeballs Version 2 is disabled.
|
||||
*
|
||||
* For details on Happy Eyeballs Version 2,
|
||||
* see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket#tcp_fast_fallback=].
|
||||
*/
|
||||
VALUE socket_s_tcp_fast_fallback(VALUE self) {
|
||||
return rb_ivar_get(rb_cSocket, tcp_fast_fallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* Socket.tcp_fast_fallback= -> true or false
|
||||
*
|
||||
* Enable or disable Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
* globally, which is provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp.
|
||||
*
|
||||
* When set to true, the feature is enabled for both `TCPSocket.new` and `Socket.tcp`.
|
||||
* (Note: This feature is not available when using TCPSocket.new on Windows.)
|
||||
*
|
||||
* When set to false, the behavior reverts to that of Ruby 3.3 or earlier.
|
||||
*
|
||||
* The default value is true if no value is explicitly set by calling this method.
|
||||
* However, when the environment variable RUBY_TCP_NO_FAST_FALLBACK=1 is set,
|
||||
* the default is false.
|
||||
*
|
||||
* To control the setting on a per-method basis, use the fast_fallback keyword argument for each method.
|
||||
*
|
||||
* === Happy Eyeballs Version 2
|
||||
* Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
* is an algorithm designed to improve client socket connectivity.<br>
|
||||
* It aims for more reliable and efficient connections by performing hostname resolution
|
||||
* and connection attempts in parallel, instead of serially.
|
||||
*
|
||||
* Starting from Ruby 3.4, this method operates as follows with this algorithm:
|
||||
*
|
||||
* 1. Start resolving both IPv6 and IPv4 addresses concurrently.
|
||||
* 2. Start connecting to the one of the addresses that are obtained first.<br>If IPv4 addresses are obtained first,
|
||||
* the method waits 50 ms for IPv6 name resolution to prioritize IPv6 connections.
|
||||
* 3. After starting a connection attempt, wait 250 ms for the connection to be established.<br>
|
||||
* If no connection is established within this time, a new connection is started every 250 ms<br>
|
||||
* until a connection is established or there are no more candidate addresses.<br>
|
||||
* (Although RFC 8305 strictly specifies sorting addresses,<br>
|
||||
* this method only alternates between IPv6 / IPv4 addresses due to the performance concerns)
|
||||
* 4. Once a connection is established, all remaining connection attempts are canceled.
|
||||
*/
|
||||
VALUE socket_s_tcp_fast_fallback_set(VALUE self, VALUE value) {
|
||||
rb_ivar_set(rb_cSocket, tcp_fast_fallback, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
Init_socket(void)
|
||||
{
|
||||
@ -1982,6 +2045,16 @@ Init_socket(void)
|
||||
|
||||
rsock_init_socket_init();
|
||||
|
||||
const char *tcp_no_fast_fallback_config = getenv("RUBY_TCP_NO_FAST_FALLBACK");
|
||||
VALUE fast_fallback_default;
|
||||
if (tcp_no_fast_fallback_config == NULL || strcmp(tcp_no_fast_fallback_config, "0") == 0) {
|
||||
fast_fallback_default = Qtrue;
|
||||
} else {
|
||||
fast_fallback_default = Qfalse;
|
||||
}
|
||||
tcp_fast_fallback = rb_intern_const("tcp_fast_fallback");
|
||||
rb_ivar_set(rb_cSocket, tcp_fast_fallback, fast_fallback_default);
|
||||
|
||||
rb_define_method(rb_cSocket, "initialize", sock_initialize, -1);
|
||||
rb_define_method(rb_cSocket, "connect", sock_connect, 1);
|
||||
|
||||
@ -2025,6 +2098,9 @@ Init_socket(void)
|
||||
|
||||
rb_define_singleton_method(rb_cSocket, "ip_address_list", socket_s_ip_address_list, 0);
|
||||
|
||||
rb_define_singleton_method(rb_cSocket, "tcp_fast_fallback", socket_s_tcp_fast_fallback, 0);
|
||||
rb_define_singleton_method(rb_cSocket, "tcp_fast_fallback=", socket_s_tcp_fast_fallback_set, 1);
|
||||
|
||||
#undef rb_intern
|
||||
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
|
||||
}
|
||||
|
@ -22,33 +22,20 @@
|
||||
* Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
* algorithm by default, except on Windows.
|
||||
*
|
||||
* To make it behave the same as in Ruby 3.3 and earlier,
|
||||
* explicitly specify the option +fast_fallback:false+.
|
||||
* For details on Happy Eyeballs Version 2,
|
||||
* see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket#tcp_fast_fallback=].
|
||||
*
|
||||
* Happy Eyeballs Version 2 is not provided on Windows,
|
||||
* To make it behave the same as in Ruby 3.3 and earlier,
|
||||
* explicitly specify the option fast_fallback:false.
|
||||
* Or, setting Socket.tcp_fast_fallback=false will disable
|
||||
* Happy Eyeballs Version 2 not only for this method but for all Socket globally.
|
||||
*
|
||||
* When using TCPSocket.new on Windows, Happy Eyeballs Version 2 is not provided,
|
||||
* and it behaves the same as in Ruby 3.3 and earlier.
|
||||
*
|
||||
* [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts.
|
||||
* [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.<br>The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.<br>By default, all connection attempts continue until the timeout occurs.<br>When +fast_fallback:false+ is explicitly specified,<br>a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled.
|
||||
* [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default).
|
||||
*
|
||||
* === Happy Eyeballs Version 2
|
||||
* Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||
* is an algorithm designed to improve client socket connectivity.<br>
|
||||
* It aims for more reliable and efficient connections by performing hostname resolution
|
||||
* and connection attempts in parallel, instead of serially.
|
||||
*
|
||||
* Starting from Ruby 3.4, this method operates as follows with this algorithm except on Windows:
|
||||
*
|
||||
* 1. Start resolving both IPv6 and IPv4 addresses concurrently.
|
||||
* 2. Start connecting to the one of the addresses that are obtained first.<br>If IPv4 addresses are obtained first,
|
||||
* the method waits 50 ms for IPv6 name resolution to prioritize IPv6 connections.
|
||||
* 3. After starting a connection attempt, wait 250 ms for the connection to be established.<br>
|
||||
* If no connection is established within this time, a new connection is started every 250 ms<br>
|
||||
* until a connection is established or there are no more candidate addresses.<br>
|
||||
* (Although RFC 8305 strictly specifies sorting addresses,<br>
|
||||
* this method only alternates between IPv6 / IPv4 addresses due to the performance concerns)
|
||||
* 4. Once a connection is established, all remaining connection attempts are canceled.
|
||||
*/
|
||||
static VALUE
|
||||
tcp_init(int argc, VALUE *argv, VALUE sock)
|
||||
@ -82,9 +69,7 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
|
||||
}
|
||||
|
||||
if (fast_fallback == Qnil) {
|
||||
VALUE socket_class = rb_const_get(rb_cObject, rb_intern("Socket"));
|
||||
ID var_id = rb_intern("@tcp_fast_fallback");
|
||||
fast_fallback = rb_ivar_get(socket_class, var_id);
|
||||
fast_fallback = rb_ivar_get(rb_cSocket, tcp_fast_fallback);
|
||||
if (fast_fallback == Qnil) fast_fallback = Qtrue;
|
||||
}
|
||||
|
||||
|
11
man/ruby.1
11
man/ruby.1
@ -776,6 +776,17 @@ a program (or script) that is to be executed.
|
||||
.Pp
|
||||
The pipe template is split on spaces into an argument list before the
|
||||
template parameters are expanded.
|
||||
.Sh MISC ENVIRONMENT
|
||||
.Pp
|
||||
.Bl -hang -compact -width "RUBY_TCP_NO_FAST_FALLBACK"
|
||||
.It Ev RUBY_TCP_NO_FAST_FALLBACK
|
||||
If set to
|
||||
.Li 1 ,
|
||||
disables the fast fallback feature by default in TCPSocket.new and Socket.tcp.
|
||||
When set to
|
||||
.Li 0
|
||||
or left unset, the fast fallback feature is enabled.
|
||||
Introduced in Ruby 3.4, default: unset.
|
||||
.Sh SEE ALSO
|
||||
.Bl -hang -compact -width "https://www.ruby-toolbox.com/"
|
||||
.It Lk https://www.ruby-lang.org/
|
||||
|
@ -1017,4 +1017,17 @@ class TestSocket < Test::Unit::TestCase
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end
|
||||
|
||||
def test_tcp_fast_fallback
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, <<~RUBY
|
||||
assert_true(Socket.tcp_fast_fallback)
|
||||
|
||||
Socket.tcp_fast_fallback = false
|
||||
assert_false(Socket.tcp_fast_fallback)
|
||||
|
||||
Socket.tcp_fast_fallback = true
|
||||
assert_true(Socket.tcp_fast_fallback)
|
||||
RUBY
|
||||
end
|
||||
end if defined?(Socket)
|
||||
|
Loading…
x
Reference in New Issue
Block a user