* Introduction of Happy Eyeballs Version 2 (RFC8305) in TCPSocket.new This is an implementation of Happy Eyeballs version 2 (RFC 8305) in `TCPSocket.new`. See https://github.com/ruby/ruby/pull/11653 1. Background Prior to this implementation, I implemented Happy Eyeballs Version 2 (HEv2) for `Socket.tcp` in https://github.com/ruby/ruby/pull/9374. HEv2 is an algorithm defined in [RFC 8305](https://datatracker.ietf.org/doc/html/rfc8305), aimed at improving network connectivity. For more details on the specific cases that HEv2 helps, please refer to https://bugs.ruby-lang.org/issues/20108. 2. Proposal & Outcome This proposal implements the same HEv2 algorithm in `TCPSocket.new`. Since `TCPSocket.new` is used more widely than `Socket.tcp`, this change is expected to broaden the impact of HEv2's benefits. Like `Socket.tcp`, I have also added `fast_fallback` keyword argument to `TCPSocket.new`. This option is set to true by default, enabling the HEv2 functionality. However, users can explicitly set it to false to disable HEv2 and use the previous behavior of `TCPSocket.new`. It should be noted that HEv2 is enabled only in environments where pthreads are available. This specification follows the approach taken in https://bugs.ruby-lang.org/issues/19965 , where name resolution can be interrupted. (In environments where pthreads are not available, the `fast_fallback` option is ignored.) 3. Performance Below is the benchmark of 100 requests to `www.ruby-lang.org` with the fast_fallback option set to true and false, respectively. While there is a slight performance degradation when HEv2 is enabled, the degradation is smaller compared to that seen in `Socket.tcp`. ``` ~/s/build ❯❯❯ ../install/bin/ruby ../ruby/test.rb Rehearsal -------------------------------------------------------- fast_fallback: true 0.017588 0.097045 0.114633 ( 1.460664) fast_fallback: false 0.014033 0.078984 0.093017 ( 1.413951) ----------------------------------------------- total: 0.207650sec user system total real fast_fallback: true 0.020891 0.124054 0.144945 ( 1.473816) fast_fallback: false 0.018392 0.110852 0.129244 ( 1.466014) ``` * Update debug prints Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com> * Remove debug prints * misc * Disable HEv2 in Win * Raise resolution error with hostname resolution * Fix to handle errors * Remove warnings * Errors that do not need to be handled * misc * Improve doc * Fix bug on cancellation * Avoid EAI_ADDRFAMILY for resolving IPv6 * Follow upstream * misc * Refactor connection_attempt_fds management - Introduced allocate_connection_attempt_fds and reallocate_connection_attempt_fds for improved memory allocation of connection_attempt_fds - Added remove_connection_attempt_fd to resize connection_attempt_fds dynamically. - Simplified the in_progress_fds function to only check the size of connection_attempt_fds. * Rename do_pthread_create to raddrinfo_pthread_create to avoid conflicting --------- Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
This commit is contained in:
parent
821a5b966f
commit
4c270200db
Notes:
git
2024-11-12 01:07:04 +00:00
Merged-By: shioimm <shioi.mm@gmail.com>
File diff suppressed because it is too large
Load Diff
@ -469,8 +469,8 @@ cancel_getaddrinfo(void *ptr)
|
||||
rb_nativethread_lock_unlock(&arg->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
do_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg)
|
||||
int
|
||||
raddrinfo_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg)
|
||||
{
|
||||
int limit = 3, ret;
|
||||
do {
|
||||
@ -505,7 +505,7 @@ start:
|
||||
}
|
||||
|
||||
pthread_t th;
|
||||
if (do_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) {
|
||||
if (raddrinfo_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) {
|
||||
int err = errno;
|
||||
free_getaddrinfo_arg(arg);
|
||||
errno = err;
|
||||
@ -726,7 +726,7 @@ start:
|
||||
}
|
||||
|
||||
pthread_t th;
|
||||
if (do_pthread_create(&th, do_getnameinfo, arg) != 0) {
|
||||
if (raddrinfo_pthread_create(&th, do_getnameinfo, arg) != 0) {
|
||||
int err = errno;
|
||||
free_getnameinfo_arg(arg);
|
||||
errno = err;
|
||||
@ -822,7 +822,7 @@ str_is_number(const char *p)
|
||||
((ptr)[0] == name[0] && \
|
||||
rb_strlen_lit(name) == (len) && memcmp(ptr, name, len) == 0)
|
||||
|
||||
static char*
|
||||
char*
|
||||
host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr)
|
||||
{
|
||||
if (NIL_P(host)) {
|
||||
@ -861,7 +861,7 @@ host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
static char*
|
||||
char*
|
||||
port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr)
|
||||
{
|
||||
if (NIL_P(port)) {
|
||||
@ -3024,6 +3024,106 @@ rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len)
|
||||
UNREACHABLE_RETURN(Qnil);
|
||||
}
|
||||
|
||||
#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 1
|
||||
|
||||
void
|
||||
free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shared **shared)
|
||||
{
|
||||
free((*shared)->node);
|
||||
(*shared)->node = NULL;
|
||||
free((*shared)->service);
|
||||
(*shared)->service = NULL;
|
||||
close((*shared)->notify);
|
||||
close((*shared)->wait);
|
||||
rb_nativethread_lock_destroy((*shared)->lock);
|
||||
free(*shared);
|
||||
*shared = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
free_fast_fallback_getaddrinfo_entry(struct fast_fallback_getaddrinfo_entry **entry)
|
||||
{
|
||||
if ((*entry)->ai) {
|
||||
freeaddrinfo((*entry)->ai);
|
||||
(*entry)->ai = NULL;
|
||||
}
|
||||
free(*entry);
|
||||
*entry = NULL;
|
||||
}
|
||||
|
||||
void *
|
||||
do_fast_fallback_getaddrinfo(void *ptr)
|
||||
{
|
||||
struct fast_fallback_getaddrinfo_entry *entry = (struct fast_fallback_getaddrinfo_entry *)ptr;
|
||||
struct fast_fallback_getaddrinfo_shared *shared = entry->shared;
|
||||
int err = 0, need_free = 0, shared_need_free = 0;
|
||||
|
||||
err = numeric_getaddrinfo(shared->node, shared->service, &entry->hints, &entry->ai);
|
||||
|
||||
if (err != 0) {
|
||||
err = getaddrinfo(shared->node, shared->service, &entry->hints, &entry->ai);
|
||||
#ifdef __linux__
|
||||
/* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and
|
||||
* it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420]
|
||||
*/
|
||||
if (err == EAI_SYSTEM && errno == ENOENT)
|
||||
err = EAI_NONAME;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* for testing HEv2 */
|
||||
if (entry->test_sleep_ms > 0) {
|
||||
struct timespec sleep_ts;
|
||||
sleep_ts.tv_sec = entry->test_sleep_ms / 1000;
|
||||
sleep_ts.tv_nsec = (entry->test_sleep_ms % 1000) * 1000000L;
|
||||
if (sleep_ts.tv_nsec >= 1000000000L) {
|
||||
sleep_ts.tv_sec += sleep_ts.tv_nsec / 1000000000L;
|
||||
sleep_ts.tv_nsec = sleep_ts.tv_nsec % 1000000000L;
|
||||
}
|
||||
nanosleep(&sleep_ts, NULL);
|
||||
}
|
||||
if (entry->test_ecode != 0) {
|
||||
err = entry->test_ecode;
|
||||
if (entry->ai) {
|
||||
freeaddrinfo(entry->ai);
|
||||
entry->ai = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
rb_nativethread_lock_lock(shared->lock);
|
||||
{
|
||||
entry->err = err;
|
||||
if (*shared->cancelled) {
|
||||
if (entry->ai) {
|
||||
freeaddrinfo(entry->ai);
|
||||
entry->ai = NULL;
|
||||
}
|
||||
} else {
|
||||
const char notification = entry->family == AF_INET6 ?
|
||||
IPV6_HOSTNAME_RESOLVED : IPV4_HOSTNAME_RESOLVED;
|
||||
|
||||
if ((write(shared->notify, ¬ification, strlen(¬ification))) < 0) {
|
||||
entry->err = errno;
|
||||
entry->has_syserr = true;
|
||||
}
|
||||
}
|
||||
if (--(entry->refcount) == 0) need_free = 1;
|
||||
if (--(shared->refcount) == 0) shared_need_free = 1;
|
||||
}
|
||||
rb_nativethread_lock_unlock(shared->lock);
|
||||
|
||||
if (need_free && entry) {
|
||||
free_fast_fallback_getaddrinfo_entry(&entry);
|
||||
}
|
||||
if (shared_need_free && shared) {
|
||||
free_fast_fallback_getaddrinfo_shared(&shared);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Addrinfo class
|
||||
*/
|
||||
|
@ -354,7 +354,7 @@ int rsock_socket(int domain, int type, int proto);
|
||||
int rsock_detect_cloexec(int fd);
|
||||
VALUE rsock_init_sock(VALUE sock, int fd);
|
||||
VALUE rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass);
|
||||
VALUE rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout);
|
||||
VALUE rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE fast_fallback, VALUE test_mode_settings);
|
||||
VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server);
|
||||
|
||||
struct rsock_send_arg {
|
||||
@ -413,6 +413,45 @@ ssize_t rsock_recvmsg(int socket, struct msghdr *message, int flags);
|
||||
void rsock_discard_cmsg_resource(struct msghdr *mh, int msg_peek_p);
|
||||
#endif
|
||||
|
||||
char *host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr);
|
||||
char *port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr);
|
||||
|
||||
#ifndef FAST_FALLBACK_INIT_INETSOCK_IMPL
|
||||
# if !defined(HAVE_PTHREAD_CREATE) || !defined(HAVE_PTHREAD_DETACH) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
# define FAST_FALLBACK_INIT_INETSOCK_IMPL 0
|
||||
# else
|
||||
# include "ruby/thread_native.h"
|
||||
# define FAST_FALLBACK_INIT_INETSOCK_IMPL 1
|
||||
# define IPV6_HOSTNAME_RESOLVED '1'
|
||||
# define IPV4_HOSTNAME_RESOLVED '2'
|
||||
# define SELECT_CANCELLED '3'
|
||||
|
||||
struct fast_fallback_getaddrinfo_shared
|
||||
{
|
||||
int wait, notify, refcount, connection_attempt_fds_size;
|
||||
int *connection_attempt_fds, *cancelled;
|
||||
char *node, *service;
|
||||
rb_nativethread_lock_t *lock;
|
||||
};
|
||||
|
||||
struct fast_fallback_getaddrinfo_entry
|
||||
{
|
||||
int family, err, refcount;
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *ai;
|
||||
struct fast_fallback_getaddrinfo_shared *shared;
|
||||
int has_syserr;
|
||||
long test_sleep_ms;
|
||||
int test_ecode;
|
||||
};
|
||||
|
||||
int raddrinfo_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg);
|
||||
void *do_fast_fallback_getaddrinfo(void *ptr);
|
||||
void free_fast_fallback_getaddrinfo_entry(struct fast_fallback_getaddrinfo_entry **entry);
|
||||
void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shared **shared);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
void rsock_init_basicsocket(void);
|
||||
void rsock_init_ipsocket(void);
|
||||
void rsock_init_tcpsocket(void);
|
||||
|
@ -34,7 +34,7 @@ socks_init(VALUE sock, VALUE host, VALUE port)
|
||||
init = 1;
|
||||
}
|
||||
|
||||
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil);
|
||||
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qfalse, Qnil);
|
||||
}
|
||||
|
||||
#ifdef SOCKS5
|
||||
|
@ -36,7 +36,7 @@ tcp_svr_init(int argc, VALUE *argv, VALUE sock)
|
||||
VALUE hostname, port;
|
||||
|
||||
rb_scan_args(argc, argv, "011", &hostname, &port);
|
||||
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil);
|
||||
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qfalse, Qnil);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -12,13 +12,43 @@
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, connect_timeout: nil)
|
||||
* TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, fast_fallback: true)
|
||||
*
|
||||
* Opens a TCP connection to +remote_host+ on +remote_port+. If +local_host+
|
||||
* and +local_port+ are specified, then those parameters are used on the local
|
||||
* end to establish the connection.
|
||||
*
|
||||
* [:connect_timeout] specify the timeout in seconds.
|
||||
* Starting from Ruby 3.4, this method operates according to the
|
||||
* 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+.
|
||||
*
|
||||
* Happy Eyeballs Version 2 is not provided on Windows,
|
||||
* 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)
|
||||
@ -26,28 +56,35 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
|
||||
VALUE remote_host, remote_serv;
|
||||
VALUE local_host, local_serv;
|
||||
VALUE opt;
|
||||
static ID keyword_ids[2];
|
||||
VALUE kwargs[2];
|
||||
static ID keyword_ids[4];
|
||||
VALUE kwargs[4];
|
||||
VALUE resolv_timeout = Qnil;
|
||||
VALUE connect_timeout = Qnil;
|
||||
VALUE fast_fallback = Qtrue;
|
||||
VALUE test_mode_settings = Qnil;
|
||||
|
||||
if (!keyword_ids[0]) {
|
||||
CONST_ID(keyword_ids[0], "resolv_timeout");
|
||||
CONST_ID(keyword_ids[1], "connect_timeout");
|
||||
CONST_ID(keyword_ids[2], "fast_fallback");
|
||||
CONST_ID(keyword_ids[3], "test_mode_settings");
|
||||
}
|
||||
|
||||
rb_scan_args(argc, argv, "22:", &remote_host, &remote_serv,
|
||||
&local_host, &local_serv, &opt);
|
||||
|
||||
if (!NIL_P(opt)) {
|
||||
rb_get_kwargs(opt, keyword_ids, 0, 2, kwargs);
|
||||
rb_get_kwargs(opt, keyword_ids, 0, 4, kwargs);
|
||||
if (kwargs[0] != Qundef) { resolv_timeout = kwargs[0]; }
|
||||
if (kwargs[1] != Qundef) { connect_timeout = kwargs[1]; }
|
||||
if (kwargs[2] != Qundef) { fast_fallback = kwargs[2]; }
|
||||
if (kwargs[3] != Qundef) { test_mode_settings = kwargs[3]; }
|
||||
}
|
||||
|
||||
return rsock_init_inetsock(sock, remote_host, remote_serv,
|
||||
local_host, local_serv, INET_CLIENT,
|
||||
resolv_timeout, connect_timeout);
|
||||
resolv_timeout, connect_timeout, fast_fallback,
|
||||
test_mode_settings);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
|
@ -140,4 +140,238 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
||||
server_threads.each(&:join)
|
||||
end
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_earlier
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
||||
exit
|
||||
end
|
||||
|
||||
server_thread = Thread.new { server.accept }
|
||||
port = server.addr[1]
|
||||
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv4: 1000 } })
|
||||
assert_true(socket.remote_address.ipv6?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v4_hostname_resolved_earlier
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.addr[1]
|
||||
|
||||
server_thread = Thread.new { server.accept }
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv6: 1000 } })
|
||||
|
||||
assert_true(socket.remote_address.ipv4?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_in_resolution_delay
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
||||
exit
|
||||
end
|
||||
|
||||
port = server.addr[1]
|
||||
delay_time = 25 # Socket::RESOLUTION_DELAY (private) is 50ms
|
||||
|
||||
server_thread = Thread.new { server.accept }
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv6: delay_time } })
|
||||
assert_true(socket.remote_address.ipv6?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_earlier_and_v6_server_is_not_listening
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
ipv4_address = "127.0.0.1"
|
||||
ipv4_server = Socket.new(Socket::AF_INET, :STREAM)
|
||||
ipv4_server.bind(Socket.pack_sockaddr_in(0, ipv4_address))
|
||||
port = ipv4_server.connect_address.ip_port
|
||||
|
||||
ipv4_server_thread = Thread.new { ipv4_server.listen(1); ipv4_server.accept }
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv4: 10 } })
|
||||
assert_equal(ipv4_address, socket.remote_address.ip_address)
|
||||
|
||||
accepted, _ = ipv4_server_thread.value
|
||||
accepted.close
|
||||
ipv4_server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolved_later_and_v6_server_is_not_listening
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
ipv4_server = Socket.new(Socket::AF_INET, :STREAM)
|
||||
ipv4_server.bind(Socket.pack_sockaddr_in(0, "127.0.0.1"))
|
||||
port = ipv4_server.connect_address.ip_port
|
||||
|
||||
ipv4_server_thread = Thread.new { ipv4_server.listen(1); ipv4_server.accept }
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv6: 25 } })
|
||||
|
||||
assert_equal(
|
||||
socket.remote_address.ipv4?,
|
||||
true
|
||||
)
|
||||
accepted, _ = ipv4_server_thread.value
|
||||
accepted.close
|
||||
ipv4_server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v6_hostname_resolution_failed_and_v4_hostname_resolution_is_success
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.addr[1]
|
||||
|
||||
server_thread = Thread.new { server.accept }
|
||||
socket = TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv4: 10 }, error: { ipv6: Socket::EAI_FAIL } })
|
||||
|
||||
assert_true(socket.remote_address.ipv4?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_resolv_timeout_with_connection_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
server = TCPServer.new("::1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
server.close
|
||||
|
||||
begin;
|
||||
assert_raise(Errno::ETIMEDOUT) do
|
||||
TCPSocket.new("localhost", port, resolv_timeout: 0.01, test_mode_settings: { delay: { ipv4: 1000 } })
|
||||
end
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_with_hostname_resolution_failure_after_connection_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
||||
exit
|
||||
end
|
||||
port = server.connect_address.ip_port
|
||||
server.close
|
||||
|
||||
begin;
|
||||
assert_raise(Socket::ResolutionError) do
|
||||
TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv4: 100 }, error: { ipv4: Socket::EAI_FAIL } })
|
||||
end
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_with_connection_failure_after_hostname_resolution_failure
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
server.close
|
||||
|
||||
begin;
|
||||
assert_raise(Errno::ECONNREFUSED) do
|
||||
TCPSocket.new("localhost", port, test_mode_settings: { delay: { ipv4: 100 }, error: { ipv6: Socket::EAI_FAIL } })
|
||||
end
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v6_connected_socket_with_v6_address
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
begin
|
||||
server = TCPServer.new("::1", 0)
|
||||
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
||||
exit
|
||||
end
|
||||
|
||||
server_thread = Thread.new { server.accept }
|
||||
port = server.addr[1]
|
||||
|
||||
socket = TCPSocket.new("::1", port)
|
||||
assert_true(socket.remote_address.ipv6?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_v4_connected_socket_with_v4_address
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
opts = %w[-rsocket -W1]
|
||||
assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
|
||||
|
||||
begin;
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
server_thread = Thread.new { server.accept }
|
||||
port = server.addr[1]
|
||||
|
||||
socket = TCPSocket.new("127.0.0.1", port)
|
||||
assert_true(socket.remote_address.ipv4?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end;
|
||||
end
|
||||
|
||||
def test_initialize_fast_fallback_is_false
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
_, port, = server.addr
|
||||
server_thread = Thread.new { server.accept }
|
||||
socket = TCPSocket.new("127.0.0.1", port, fast_fallback: false)
|
||||
|
||||
assert_true(socket.remote_address.ipv4?)
|
||||
server_thread.value.close
|
||||
server.close
|
||||
socket.close if socket && !socket.closed?
|
||||
end
|
||||
end if defined?(TCPSocket)
|
||||
|
Loading…
x
Reference in New Issue
Block a user