Skip to content

Commit f0ce8fc

Browse files
committed
[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) ruby#13368
1 parent 830ab2c commit f0ce8fc

File tree

6 files changed

+119
-23
lines changed

6 files changed

+119
-23
lines changed

ext/socket/ipsocket.c

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct inetsock_arg
2323
int type;
2424
VALUE resolv_timeout;
2525
VALUE connect_timeout;
26+
VALUE open_timeout;
2627
};
2728

2829
static VALUE
@@ -44,6 +45,13 @@ inetsock_cleanup(VALUE v)
4445
return Qnil;
4546
}
4647

48+
static VALUE
49+
current_clocktime()
50+
{
51+
VALUE clock_monotnic_const = rb_const_get(rb_mProcess, rb_intern("CLOCK_MONOTONIC"));
52+
return rb_funcall(rb_mProcess, rb_intern("clock_gettime"), 1, clock_monotnic_const);
53+
}
54+
4755
static VALUE
4856
init_inetsock_internal(VALUE v)
4957
{
@@ -56,13 +64,18 @@ init_inetsock_internal(VALUE v)
5664
const char *syscall = 0;
5765
VALUE resolv_timeout = arg->resolv_timeout;
5866
VALUE connect_timeout = arg->connect_timeout;
67+
VALUE open_timeout = arg->open_timeout;
68+
VALUE timeout;
69+
VALUE starts_at;
70+
unsigned int timeout_msec;
5971

60-
unsigned int t = NIL_P(resolv_timeout) ? 0 : rsock_value_timeout_to_msec(resolv_timeout);
72+
timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout;
73+
timeout_msec = NIL_P(timeout) ? 0 : rsock_value_timeout_to_msec(timeout);
74+
starts_at = current_clocktime();
6175

6276
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
6377
family, SOCK_STREAM,
64-
(type == INET_SERVER) ? AI_PASSIVE : 0, t);
65-
78+
(type == INET_SERVER) ? AI_PASSIVE : 0, timeout_msec);
6679

6780
/*
6881
* Maybe also accept a local address
@@ -125,8 +138,21 @@ init_inetsock_internal(VALUE v)
125138
syscall = "bind(2)";
126139
}
127140

141+
if (NIL_P(open_timeout)) {
142+
timeout = connect_timeout;
143+
} else {
144+
VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at);
145+
timeout = rb_funcall(open_timeout, '-', 1, elapsed);
146+
147+
if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) {
148+
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
149+
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
150+
rb_raise(etimedout_error, "user specified timeout");
151+
}
152+
}
153+
128154
if (status >= 0) {
129-
status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout);
155+
status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), timeout);
130156
syscall = "connect(2)";
131157
}
132158
}
@@ -175,8 +201,12 @@ init_inetsock_internal(VALUE v)
175201
#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0
176202

177203
VALUE
178-
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)
179-
{
204+
rsock_init_inetsock(
205+
VALUE self, VALUE remote_host, VALUE remote_serv,
206+
VALUE local_host, VALUE local_serv, int type,
207+
VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout,
208+
VALUE _fast_fallback, VALUE _test_mode_settings
209+
) {
180210
struct inetsock_arg arg;
181211
arg.self = self;
182212
arg.io = Qnil;
@@ -189,6 +219,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
189219
arg.type = type;
190220
arg.resolv_timeout = resolv_timeout;
191221
arg.connect_timeout = connect_timeout;
222+
arg.open_timeout = open_timeout;
192223
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
193224
inetsock_cleanup, (VALUE)&arg);
194225
}
@@ -224,6 +255,7 @@ struct fast_fallback_inetsock_arg
224255
int type;
225256
VALUE resolv_timeout;
226257
VALUE connect_timeout;
258+
VALUE open_timeout;
227259

228260
const char *hostp, *portp;
229261
int *families;
@@ -383,12 +415,22 @@ select_expires_at(
383415
struct timeval *resolution_delay,
384416
struct timeval *connection_attempt_delay,
385417
struct timeval *user_specified_resolv_timeout_at,
386-
struct timeval *user_specified_connect_timeout_at
418+
struct timeval *user_specified_connect_timeout_at,
419+
struct timeval *user_specified_open_timeout_at
387420
) {
388421
if (any_addrinfos(resolution_store)) {
389-
return resolution_delay ? resolution_delay : connection_attempt_delay;
422+
struct timeval *delay;
423+
delay = resolution_delay ? resolution_delay : connection_attempt_delay;
424+
425+
if (user_specified_open_timeout_at &&
426+
timercmp(user_specified_open_timeout_at, delay, <)) {
427+
return user_specified_open_timeout_at;
428+
}
429+
return delay;
390430
}
391431

432+
if (user_specified_open_timeout_at) return user_specified_open_timeout_at;
433+
392434
struct timeval *timeout = NULL;
393435

394436
if (user_specified_resolv_timeout_at) {
@@ -506,6 +548,7 @@ init_fast_fallback_inetsock_internal(VALUE v)
506548
VALUE io = arg->io;
507549
VALUE resolv_timeout = arg->resolv_timeout;
508550
VALUE connect_timeout = arg->connect_timeout;
551+
VALUE open_timeout = arg->open_timeout;
509552
VALUE test_mode_settings = arg->test_mode_settings;
510553
struct addrinfo *remote_ai = NULL, *local_ai = NULL;
511554
int connected_fd = -1, status = 0, local_status = 0;
@@ -552,8 +595,16 @@ init_fast_fallback_inetsock_internal(VALUE v)
552595
struct timeval *user_specified_resolv_timeout_at = NULL;
553596
struct timeval user_specified_connect_timeout_storage;
554597
struct timeval *user_specified_connect_timeout_at = NULL;
598+
struct timeval user_specified_open_timeout_storage;
599+
struct timeval *user_specified_open_timeout_at = NULL;
555600
struct timespec now = current_clocktime_ts();
556601

602+
if (!NIL_P(open_timeout)) {
603+
struct timeval open_timeout_tv = rb_time_interval(open_timeout);
604+
user_specified_open_timeout_storage = add_ts_to_tv(open_timeout_tv, now);
605+
user_specified_open_timeout_at = &user_specified_open_timeout_storage;
606+
}
607+
557608
/* start of hostname resolution */
558609
if (arg->family_size == 1) {
559610
arg->wait = -1;
@@ -854,7 +905,8 @@ init_fast_fallback_inetsock_internal(VALUE v)
854905
resolution_delay_expires_at,
855906
connection_attempt_delay_expires_at,
856907
user_specified_resolv_timeout_at,
857-
user_specified_connect_timeout_at
908+
user_specified_connect_timeout_at,
909+
user_specified_open_timeout_at
858910
);
859911
if (ends_at) {
860912
delay = tv_to_timeout(ends_at, now);
@@ -1107,6 +1159,12 @@ init_fast_fallback_inetsock_internal(VALUE v)
11071159
}
11081160
}
11091161

1162+
if (is_timeout_tv(user_specified_open_timeout_at, now)) {
1163+
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
1164+
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
1165+
rb_raise(etimedout_error, "user specified timeout");
1166+
}
1167+
11101168
if (!any_addrinfos(&resolution_store)) {
11111169
if (!in_progress_fds(arg->connection_attempt_fds_size) &&
11121170
resolution_store.is_all_finished) {
@@ -1220,8 +1278,16 @@ fast_fallback_inetsock_cleanup(VALUE v)
12201278
}
12211279

12221280
VALUE
1223-
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)
1224-
{
1281+
rsock_init_inetsock(
1282+
VALUE self, VALUE remote_host, VALUE remote_serv,
1283+
VALUE local_host, VALUE local_serv, int type,
1284+
VALUE resolv_timeout, VALUE connect_timeout, VALUE open_timeout,
1285+
VALUE fast_fallback, VALUE test_mode_settings
1286+
) {
1287+
if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) {
1288+
rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout");
1289+
}
1290+
12251291
if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) {
12261292
struct rb_addrinfo *local_res = NULL;
12271293
char *hostp, *portp;
@@ -1278,6 +1344,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
12781344
fast_fallback_arg.type = type;
12791345
fast_fallback_arg.resolv_timeout = resolv_timeout;
12801346
fast_fallback_arg.connect_timeout = connect_timeout;
1347+
fast_fallback_arg.open_timeout = open_timeout;
12811348
fast_fallback_arg.hostp = hostp;
12821349
fast_fallback_arg.portp = portp;
12831350
fast_fallback_arg.additional_flags = additional_flags;
@@ -1314,6 +1381,7 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca
13141381
arg.type = type;
13151382
arg.resolv_timeout = resolv_timeout;
13161383
arg.connect_timeout = connect_timeout;
1384+
arg.open_timeout = open_timeout;
13171385

13181386
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
13191387
inetsock_cleanup, (VALUE)&arg);

ext/socket/rubysocket.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ int rsock_socket(int domain, int type, int proto);
355355
int rsock_detect_cloexec(int fd);
356356
VALUE rsock_init_sock(VALUE sock, int fd);
357357
VALUE rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass);
358-
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);
358+
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);
359359
VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server);
360360

361361
struct rsock_send_arg {

ext/socket/sockssocket.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ socks_init(VALUE sock, VALUE host, VALUE port)
3535
init = 1;
3636
}
3737

38-
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qfalse, Qnil);
38+
return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qnil, Qfalse, Qnil);
3939
}
4040

4141
#ifdef SOCKS5

ext/socket/tcpserver.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ tcp_svr_init(int argc, VALUE *argv, VALUE sock)
3636
VALUE hostname, port;
3737

3838
rb_scan_args(argc, argv, "011", &hostname, &port);
39-
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qfalse, Qnil);
39+
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qnil, Qfalse, Qnil);
4040
}
4141

4242
/*

ext/socket/tcpsocket.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
*
3636
* [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts.
3737
* [: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.
38+
* [: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.
3839
* [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default).
3940
*/
4041
static VALUE
@@ -43,29 +44,32 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
4344
VALUE remote_host, remote_serv;
4445
VALUE local_host, local_serv;
4546
VALUE opt;
46-
static ID keyword_ids[4];
47-
VALUE kwargs[4];
47+
static ID keyword_ids[5];
48+
VALUE kwargs[5];
4849
VALUE resolv_timeout = Qnil;
4950
VALUE connect_timeout = Qnil;
51+
VALUE open_timeout = Qnil;
5052
VALUE fast_fallback = Qnil;
5153
VALUE test_mode_settings = Qnil;
5254

5355
if (!keyword_ids[0]) {
5456
CONST_ID(keyword_ids[0], "resolv_timeout");
5557
CONST_ID(keyword_ids[1], "connect_timeout");
56-
CONST_ID(keyword_ids[2], "fast_fallback");
57-
CONST_ID(keyword_ids[3], "test_mode_settings");
58+
CONST_ID(keyword_ids[2], "open_timeout");
59+
CONST_ID(keyword_ids[3], "fast_fallback");
60+
CONST_ID(keyword_ids[4], "test_mode_settings");
5861
}
5962

6063
rb_scan_args(argc, argv, "22:", &remote_host, &remote_serv,
6164
&local_host, &local_serv, &opt);
6265

6366
if (!NIL_P(opt)) {
64-
rb_get_kwargs(opt, keyword_ids, 0, 4, kwargs);
67+
rb_get_kwargs(opt, keyword_ids, 0, 5, kwargs);
6568
if (kwargs[0] != Qundef) { resolv_timeout = kwargs[0]; }
6669
if (kwargs[1] != Qundef) { connect_timeout = kwargs[1]; }
67-
if (kwargs[2] != Qundef) { fast_fallback = kwargs[2]; }
68-
if (kwargs[3] != Qundef) { test_mode_settings = kwargs[3]; }
70+
if (kwargs[2] != Qundef) { open_timeout = kwargs[2]; }
71+
if (kwargs[3] != Qundef) { fast_fallback = kwargs[3]; }
72+
if (kwargs[4] != Qundef) { test_mode_settings = kwargs[4]; }
6973
}
7074

7175
if (fast_fallback == Qnil) {
@@ -75,8 +79,8 @@ tcp_init(int argc, VALUE *argv, VALUE sock)
7579

7680
return rsock_init_inetsock(sock, remote_host, remote_serv,
7781
local_host, local_serv, INET_CLIENT,
78-
resolv_timeout, connect_timeout, fast_fallback,
79-
test_mode_settings);
82+
resolv_timeout, connect_timeout, open_timeout,
83+
fast_fallback, test_mode_settings);
8084
}
8185

8286
static VALUE

test/socket/test_tcp.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ def test_initialize_resolv_timeout
7373
end
7474
end
7575

76+
def test_tcp_initialize_open_timeout
77+
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
78+
79+
server = TCPServer.new("127.0.0.1", 0)
80+
port = server.connect_address.ip_port
81+
server.close
82+
83+
assert_raise(Errno::ETIMEDOUT) do
84+
TCPSocket.new(
85+
"localhost",
86+
port,
87+
open_timeout: 0.01,
88+
fast_fallback: true,
89+
test_mode_settings: { delay: { ipv4: 1000 } }
90+
)
91+
end
92+
end
93+
94+
def test_initialize_open_timeout_with_other_timeouts
95+
assert_raise(ArgumentError) do
96+
TCPSocket.new("localhost", 12345, open_timeout: 0.01, resolv_timeout: 0.01)
97+
end
98+
end
99+
76100
def test_initialize_connect_timeout
77101
assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do
78102
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy