mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
* [Feature #21347] Add `open_timeout` as an overall timeout option for `TCPSocket.new` With this change, `TCPSocket.new` now accepts the `open_timeout` option. This option raises an exception if the specified number of seconds has elapsed since the start of the method call, even if the operation is still in the middle of name resolution or connection attempts. The addition of this option follows the same intent as the previously merged change to `Socket.tcp`. [Feature #21347](https://bugs.ruby-lang.org/issues/21347) https://github.com/ruby/ruby/pull/13368 * Tidy up: Extract rsock_raise_user_specified_timeout() * Added a note to the documentation of `Socket.tcp` * Fix `rsock_init_inetsock` for `FAST_FALLBACK_INIT_INETSOCK_IMPL`
This commit is contained in:
parent
98aa2a6608
commit
ba490059b4
8 changed files with 126 additions and 32 deletions
|
@ -23,8 +23,17 @@ struct inetsock_arg
|
|||
int type;
|
||||
VALUE resolv_timeout;
|
||||
VALUE connect_timeout;
|
||||
VALUE open_timeout;
|
||||
};
|
||||
|
||||
void
|
||||
rsock_raise_user_specified_timeout()
|
||||
{
|
||||
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
|
||||
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
|
||||
rb_raise(etimedout_error, "user specified timeout");
|
||||
}
|
||||
|
||||
static VALUE
|
||||
inetsock_cleanup(VALUE v)
|
||||
{
|
||||
|
@ -44,6 +53,13 @@ inetsock_cleanup(VALUE v)
|
|||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
current_clocktime()
|
||||
{
|
||||
VALUE clock_monotnic_const = rb_const_get(rb_mProcess, rb_intern("CLOCK_MONOTONIC"));
|
||||
return rb_funcall(rb_mProcess, rb_intern("clock_gettime"), 1, clock_monotnic_const);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
init_inetsock_internal(VALUE v)
|
||||
{
|
||||
|
@ -56,13 +72,18 @@ init_inetsock_internal(VALUE v)
|
|||
const char *syscall = 0;
|
||||
VALUE resolv_timeout = arg->resolv_timeout;
|
||||
VALUE connect_timeout = arg->connect_timeout;
|
||||
VALUE open_timeout = arg->open_timeout;
|
||||
VALUE timeout;
|
||||
VALUE starts_at;
|
||||
unsigned int timeout_msec;
|
||||
|
||||
unsigned int t = NIL_P(resolv_timeout) ? 0 : rsock_value_timeout_to_msec(resolv_timeout);
|
||||
timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout;
|
||||
timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout);
|
||||
starts_at = current_clocktime();
|
||||
|
||||
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
|
||||
family, SOCK_STREAM,
|
||||
(type == INET_SERVER) ? AI_PASSIVE : 0, t);
|
||||
|
||||
(type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec);
|
||||
|
||||
/*
|
||||
* Maybe also accept a local address
|
||||
|
@ -125,8 +146,16 @@ init_inetsock_internal(VALUE v)
|
|||
syscall = "bind(2)";
|
||||
}
|
||||
|
||||
if (NIL_P(open_timeout)) {
|
||||
timeout = connect_timeout;
|
||||
} else {
|
||||
VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at);
|
||||
timeout = rb_funcall(open_timeout, '-', 1, elapsed);
|
||||
if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) rsock_raise_user_specified_timeout();
|
||||
}
|
||||
|
||||
if (status >= 0) {
|
||||
status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout);
|
||||
status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), timeout);
|
||||
syscall = "connect(2)";
|
||||
}
|
||||
}
|
||||
|
@ -175,8 +204,16 @@ init_inetsock_internal(VALUE v)
|
|||
#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0
|
||||
|
||||
VALUE
|
||||
rsock_init_inetsock(VALUE self, 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)
|
||||
{
|
||||
rsock_init_inetsock(
|
||||
VALUE self, VALUE remote_host, VALUE remote_serv,
|
||||
VALUE local_host, VALUE local_serv, int type,
|
||||
VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout,
|
||||
VALUE _fast_fallback, VALUE _test_mode_settings
|
||||
) {
|
||||
if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) {
|
||||
rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout");
|
||||
}
|
||||
|
||||
struct inetsock_arg arg;
|
||||
arg.self = self;
|
||||
arg.io = Qnil;
|
||||
|
@ -189,6 +226,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
|
|||
arg.type = type;
|
||||
arg.resolv_timeout = resolv_timeout;
|
||||
arg.connect_timeout = connect_timeout;
|
||||
arg.open_timeout = open_timeout;
|
||||
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
|
||||
inetsock_cleanup, (VALUE)&arg);
|
||||
}
|
||||
|
@ -224,6 +262,7 @@ struct fast_fallback_inetsock_arg
|
|||
int type;
|
||||
VALUE resolv_timeout;
|
||||
VALUE connect_timeout;
|
||||
VALUE open_timeout;
|
||||
|
||||
const char *hostp, *portp;
|
||||
int *families;
|
||||
|
@ -383,12 +422,22 @@ select_expires_at(
|
|||
struct timeval *resolution_delay,
|
||||
struct timeval *connection_attempt_delay,
|
||||
struct timeval *user_specified_resolv_timeout_at,
|
||||
struct timeval *user_specified_connect_timeout_at
|
||||
struct timeval *user_specified_connect_timeout_at,
|
||||
struct timeval *user_specified_open_timeout_at
|
||||
) {
|
||||
if (any_addrinfos(resolution_store)) {
|
||||
return resolution_delay ? resolution_delay : connection_attempt_delay;
|
||||
struct timeval *delay;
|
||||
delay = resolution_delay ? resolution_delay : connection_attempt_delay;
|
||||
|
||||
if (user_specified_open_timeout_at &&
|
||||
timercmp(user_specified_open_timeout_at, delay, <)) {
|
||||
return user_specified_open_timeout_at;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
if (user_specified_open_timeout_at) return user_specified_open_timeout_at;
|
||||
|
||||
struct timeval *timeout = NULL;
|
||||
|
||||
if (user_specified_resolv_timeout_at) {
|
||||
|
@ -506,6 +555,7 @@ init_fast_fallback_inetsock_internal(VALUE v)
|
|||
VALUE io = arg->io;
|
||||
VALUE resolv_timeout = arg->resolv_timeout;
|
||||
VALUE connect_timeout = arg->connect_timeout;
|
||||
VALUE open_timeout = arg->open_timeout;
|
||||
VALUE test_mode_settings = arg->test_mode_settings;
|
||||
struct addrinfo *remote_ai = NULL, *local_ai = NULL;
|
||||
int connected_fd = -1, status = 0, local_status = 0;
|
||||
|
@ -552,8 +602,16 @@ init_fast_fallback_inetsock_internal(VALUE v)
|
|||
struct timeval *user_specified_resolv_timeout_at = NULL;
|
||||
struct timeval user_specified_connect_timeout_storage;
|
||||
struct timeval *user_specified_connect_timeout_at = NULL;
|
||||
struct timeval user_specified_open_timeout_storage;
|
||||
struct timeval *user_specified_open_timeout_at = NULL;
|
||||
struct timespec now = current_clocktime_ts();
|
||||
|
||||
if (!NIL_P(open_timeout)) {
|
||||
struct timeval open_timeout_tv = rb_time_interval(open_timeout);
|
||||
user_specified_open_timeout_storage = add_ts_to_tv(open_timeout_tv, now);
|
||||
user_specified_open_timeout_at = &user_specified_open_timeout_storage;
|
||||
}
|
||||
|
||||
/* start of hostname resolution */
|
||||
if (arg->family_size == 1) {
|
||||
arg->wait = -1;
|
||||
|
@ -854,7 +912,8 @@ init_fast_fallback_inetsock_internal(VALUE v)
|
|||
resolution_delay_expires_at,
|
||||
connection_attempt_delay_expires_at,
|
||||
user_specified_resolv_timeout_at,
|
||||
user_specified_connect_timeout_at
|
||||
user_specified_connect_timeout_at,
|
||||
user_specified_open_timeout_at
|
||||
);
|
||||
if (ends_at) {
|
||||
delay = tv_to_timeout(ends_at, now);
|
||||
|
@ -1107,6 +1166,8 @@ init_fast_fallback_inetsock_internal(VALUE v)
|
|||
}
|
||||
}
|
||||
|
||||
if (is_timeout_tv(user_specified_open_timeout_at, now)) rsock_raise_user_specified_timeout();
|
||||
|
||||
if (!any_addrinfos(&resolution_store)) {
|
||||
if (!in_progress_fds(arg->connection_attempt_fds_size) &&
|
||||
resolution_store.is_all_finished) {
|
||||
|
@ -1128,9 +1189,7 @@ init_fast_fallback_inetsock_internal(VALUE v)
|
|||
resolution_store.is_all_finished) &&
|
||||
(is_timeout_tv(user_specified_connect_timeout_at, now) ||
|
||||
!in_progress_fds(arg->connection_attempt_fds_size))) {
|
||||
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
|
||||
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
|
||||
rb_raise(etimedout_error, "user specified timeout");
|
||||
rsock_raise_user_specified_timeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1220,8 +1279,16 @@ fast_fallback_inetsock_cleanup(VALUE v)
|
|||
}
|
||||
|
||||
VALUE
|
||||
rsock_init_inetsock(VALUE self, 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)
|
||||
{
|
||||
rsock_init_inetsock(
|
||||
VALUE self, VALUE remote_host, VALUE remote_serv,
|
||||
VALUE local_host, VALUE local_serv, int type,
|
||||
VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout,
|
||||
VALUE fast_fallback, VALUE test_mode_settings
|
||||
) {
|
||||
if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) {
|
||||
rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout");
|
||||
}
|
||||
|
||||
if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) {
|
||||
struct rb_addrinfo *local_res = NULL;
|
||||
char *hostp, *portp;
|
||||
|
@ -1278,6 +1345,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
|
|||
fast_fallback_arg.type = type;
|
||||
fast_fallback_arg.resolv_timeout = resolv_timeout;
|
||||
fast_fallback_arg.connect_timeout = connect_timeout;
|
||||
fast_fallback_arg.open_timeout = open_timeout;
|
||||
fast_fallback_arg.hostp = hostp;
|
||||
fast_fallback_arg.portp = portp;
|
||||
fast_fallback_arg.additional_flags = additional_flags;
|
||||
|
@ -1314,6 +1382,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
|
|||
arg.type = type;
|
||||
arg.resolv_timeout = resolv_timeout;
|
||||
arg.connect_timeout = connect_timeout;
|
||||
arg.open_timeout = open_timeout;
|
||||
|
||||
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
|
||||
inetsock_cleanup, (VALUE)&arg);
|
||||
|
|
|
@ -643,7 +643,7 @@ class Socket < BasicSocket
|
|||
#
|
||||
# [: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.
|
||||
# [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.
|
||||
# [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.<br>If this option is specified together with other timeout options, an +ArgumentError+ will be raised.
|
||||
# [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default).
|
||||
#
|
||||
# If a block is given, the block is called with the socket.
|
||||
|
|
|
@ -562,11 +562,7 @@ start:
|
|||
|
||||
if (need_free) free_getaddrinfo_arg(arg);
|
||||
|
||||
if (timedout) {
|
||||
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
|
||||
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
|
||||
rb_raise(etimedout_error, "user specified timeout");
|
||||
}
|
||||
if (timedout) rsock_raise_user_specified_timeout();
|
||||
|
||||
// If the current thread is interrupted by asynchronous exception, the following raises the exception.
|
||||
// But if the current thread is interrupted by timer thread, the following returns; we need to manually retry.
|
||||
|
|
|
@ -355,7 +355,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 fast_fallback, VALUE test_mode_settings);
|
||||
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 open_timeout, VALUE fast_fallback, VALUE test_mode_settings);
|
||||
VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server);
|
||||
|
||||
struct rsock_send_arg {
|
||||
|
@ -454,6 +454,7 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar
|
|||
#endif
|
||||
|
||||
unsigned int rsock_value_timeout_to_msec(VALUE);
|
||||
void rsock_raise_user_specified_timeout(void);
|
||||
|
||||
void rsock_init_basicsocket(void);
|
||||
void rsock_init_ipsocket(void);
|
||||
|
|
|
@ -35,7 +35,7 @@ socks_init(VALUE sock, VALUE host, VALUE port)
|
|||
init = 1;
|
||||
}
|
||||
|
||||
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qfalse, Qnil);
|
||||
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, 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, Qfalse, Qnil);
|
||||
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qnil, Qfalse, Qnil);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
*
|
||||
* [: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.
|
||||
* [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.<br>If this option is specified together with other timeout options, an +ArgumentError+ will be raised.
|
||||
* [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default).
|
||||
*/
|
||||
static VALUE
|
||||
|
@ -43,29 +44,32 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
|
|||
VALUE remote_host, remote_serv;
|
||||
VALUE local_host, local_serv;
|
||||
VALUE opt;
|
||||
static ID keyword_ids[4];
|
||||
VALUE kwargs[4];
|
||||
static ID keyword_ids[5];
|
||||
VALUE kwargs[5];
|
||||
VALUE resolv_timeout = Qnil;
|
||||
VALUE connect_timeout = Qnil;
|
||||
VALUE open_timeout = Qnil;
|
||||
VALUE fast_fallback = Qnil;
|
||||
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");
|
||||
CONST_ID(keyword_ids[2], "open_timeout");
|
||||
CONST_ID(keyword_ids[3], "fast_fallback");
|
||||
CONST_ID(keyword_ids[4], "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, 4, kwargs);
|
||||
rb_get_kwargs(opt, keyword_ids, 0, 5, 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]; }
|
||||
if (kwargs[2] != Qundef) { open_timeout = kwargs[2]; }
|
||||
if (kwargs[3] != Qundef) { fast_fallback = kwargs[3]; }
|
||||
if (kwargs[4] != Qundef) { test_mode_settings = kwargs[4]; }
|
||||
}
|
||||
|
||||
if (fast_fallback == Qnil) {
|
||||
|
@ -75,8 +79,8 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
|
|||
|
||||
return rsock_init_inetsock(sock, remote_host, remote_serv,
|
||||
local_host, local_serv, INET_CLIENT,
|
||||
resolv_timeout, connect_timeout, fast_fallback,
|
||||
test_mode_settings);
|
||||
resolv_timeout, connect_timeout, open_timeout,
|
||||
fast_fallback, test_mode_settings);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
|
|
|
@ -73,6 +73,30 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_tcp_initialize_open_timeout
|
||||
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
||||
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
port = server.connect_address.ip_port
|
||||
server.close
|
||||
|
||||
assert_raise(Errno::ETIMEDOUT) do
|
||||
TCPSocket.new(
|
||||
"localhost",
|
||||
port,
|
||||
open_timeout: 0.01,
|
||||
fast_fallback: true,
|
||||
test_mode_settings: { delay: { ipv4: 1000 } }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def test_initialize_open_timeout_with_other_timeouts
|
||||
assert_raise(ArgumentError) do
|
||||
TCPSocket.new("localhost", 12345, open_timeout: 0.01, resolv_timeout: 0.01)
|
||||
end
|
||||
end
|
||||
|
||||
def test_initialize_connect_timeout
|
||||
assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do
|
||||
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue