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/
|
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
|
private_constant :IPV6_ADRESS_FORMAT
|
||||||
|
|
||||||
@tcp_fast_fallback = true
|
|
||||||
|
|
||||||
class << self
|
|
||||||
attr_accessor :tcp_fast_fallback
|
|
||||||
end
|
|
||||||
|
|
||||||
# :call-seq:
|
# :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]) {|socket| ... }
|
||||||
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
|
# 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])
|
# Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||||
# algorithm by default.
|
# 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,
|
# 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,
|
# If local_host:local_port is given,
|
||||||
# the socket is bound to it.
|
# the socket is bound to it.
|
||||||
@ -657,24 +656,6 @@ class Socket < BasicSocket
|
|||||||
# sock.close_write
|
# sock.close_write
|
||||||
# puts sock.read
|
# 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
|
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))
|
sock = if fast_fallback && !(host && ip_address?(host))
|
||||||
tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:)
|
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);
|
int rsock_is_dgram(rb_io_t *fptr);
|
||||||
|
|
||||||
|
extern ID tcp_fast_fallback;
|
||||||
|
|
||||||
#if !defined HAVE_INET_NTOP && ! defined _WIN32
|
#if !defined HAVE_INET_NTOP && ! defined _WIN32
|
||||||
const char *inet_ntop(int, const void *, char *, size_t);
|
const char *inet_ntop(int, const void *, char *, size_t);
|
||||||
#elif defined __MINGW32__
|
#elif defined __MINGW32__
|
||||||
|
@ -14,6 +14,8 @@ static VALUE sym_wait_writable;
|
|||||||
|
|
||||||
static VALUE sock_s_unpack_sockaddr_in(VALUE, VALUE);
|
static VALUE sock_s_unpack_sockaddr_in(VALUE, VALUE);
|
||||||
|
|
||||||
|
ID tcp_fast_fallback;
|
||||||
|
|
||||||
void
|
void
|
||||||
rsock_sys_fail_host_port(const char *mesg, VALUE host, VALUE port)
|
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
|
#define socket_s_ip_address_list rb_f_notimplement
|
||||||
#endif
|
#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
|
void
|
||||||
Init_socket(void)
|
Init_socket(void)
|
||||||
{
|
{
|
||||||
@ -1982,6 +2045,16 @@ Init_socket(void)
|
|||||||
|
|
||||||
rsock_init_socket_init();
|
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, "initialize", sock_initialize, -1);
|
||||||
rb_define_method(rb_cSocket, "connect", sock_connect, 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, "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
|
#undef rb_intern
|
||||||
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
|
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])
|
* Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305])
|
||||||
* algorithm by default, except on Windows.
|
* algorithm by default, except on Windows.
|
||||||
*
|
*
|
||||||
* To make it behave the same as in Ruby 3.3 and earlier,
|
* For details on Happy Eyeballs Version 2,
|
||||||
* explicitly specify the option +fast_fallback:false+.
|
* 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.
|
* 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.
|
* [: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.
|
* [: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).
|
* [: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
|
static VALUE
|
||||||
tcp_init(int argc, VALUE *argv, VALUE sock)
|
tcp_init(int argc, VALUE *argv, VALUE sock)
|
||||||
@ -82,9 +69,7 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fast_fallback == Qnil) {
|
if (fast_fallback == Qnil) {
|
||||||
VALUE socket_class = rb_const_get(rb_cObject, rb_intern("Socket"));
|
fast_fallback = rb_ivar_get(rb_cSocket, tcp_fast_fallback);
|
||||||
ID var_id = rb_intern("@tcp_fast_fallback");
|
|
||||||
fast_fallback = rb_ivar_get(socket_class, var_id);
|
|
||||||
if (fast_fallback == Qnil) fast_fallback = Qtrue;
|
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
|
.Pp
|
||||||
The pipe template is split on spaces into an argument list before the
|
The pipe template is split on spaces into an argument list before the
|
||||||
template parameters are expanded.
|
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
|
.Sh SEE ALSO
|
||||||
.Bl -hang -compact -width "https://www.ruby-toolbox.com/"
|
.Bl -hang -compact -width "https://www.ruby-toolbox.com/"
|
||||||
.It Lk https://www.ruby-lang.org/
|
.It Lk https://www.ruby-lang.org/
|
||||||
|
@ -1017,4 +1017,17 @@ class TestSocket < Test::Unit::TestCase
|
|||||||
server.close
|
server.close
|
||||||
socket.close if socket && !socket.closed?
|
socket.close if socket && !socket.closed?
|
||||||
end
|
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)
|
end if defined?(Socket)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user