Skip to content

Commit 78f1885

Browse files
committed
Add connect_timeout to TCPSocket
Add connect_timeout to TCPSocket.new in the same way as Socket.tcp. Closes [Feature #17187]
1 parent 658b4ff commit 78f1885

File tree

10 files changed

+48
-20
lines changed

10 files changed

+48
-20
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,10 @@ Outstanding ones only.
348348

349349
* Update to IRB 1.2.6
350350

351+
* Socket
352+
353+
* Add :connect_timeout to TCPSocket.new [[Feature #17187]]
354+
351355
* Net::HTTP
352356

353357
* Net::HTTP#verify_hostname= and Net::HTTP#verify_hostname have been

ext/socket/init.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ rsock_socket(int domain, int type, int proto)
451451

452452
/* emulate blocking connect behavior on EINTR or non-blocking socket */
453453
static int
454-
wait_connectable(int fd)
454+
wait_connectable(int fd, struct timeval *timeout)
455455
{
456456
int sockerr, revents;
457457
socklen_t sockerrlen;
@@ -488,7 +488,7 @@ wait_connectable(int fd)
488488
*
489489
* Note: rb_wait_for_single_fd already retries on EINTR/ERESTART
490490
*/
491-
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, NULL);
491+
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, timeout);
492492

493493
if (revents < 0)
494494
return -1;
@@ -503,6 +503,12 @@ wait_connectable(int fd)
503503
* be defensive in case some platforms set SO_ERROR on the original,
504504
* interrupted connect()
505505
*/
506+
507+
/* when the connection timed out, no errno is set and revents is 0. */
508+
if (timeout && revents == 0) {
509+
errno = ETIMEDOUT;
510+
return -1;
511+
}
506512
case EINTR:
507513
#ifdef ERESTART
508514
case ERESTART:
@@ -550,7 +556,7 @@ socks_connect_blocking(void *data)
550556
#endif
551557

552558
int
553-
rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks)
559+
rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout)
554560
{
555561
int status;
556562
rb_blocking_function_t *func = connect_blocking;
@@ -574,7 +580,7 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks)
574580
#ifdef EINPROGRESS
575581
case EINPROGRESS:
576582
#endif
577-
return wait_connectable(fd);
583+
return wait_connectable(fd, timeout);
578584
}
579585
}
580586
return status;

ext/socket/ipsocket.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct inetsock_arg
2020
int type;
2121
int fd;
2222
VALUE resolv_timeout;
23+
VALUE connect_timeout;
2324
};
2425

2526
static VALUE
@@ -50,6 +51,14 @@ init_inetsock_internal(VALUE v)
5051
int fd, status = 0, local = 0;
5152
int family = AF_UNSPEC;
5253
const char *syscall = 0;
54+
VALUE connect_timeout = arg->connect_timeout;
55+
struct timeval tv_storage;
56+
struct timeval *tv = NULL;
57+
58+
if (!NIL_P(connect_timeout)) {
59+
tv_storage = rb_time_interval(connect_timeout);
60+
tv = &tv_storage;
61+
}
5362

5463
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
5564
family, SOCK_STREAM,
@@ -116,7 +125,7 @@ init_inetsock_internal(VALUE v)
116125

117126
if (status >= 0) {
118127
status = rsock_connect(fd, res->ai_addr, res->ai_addrlen,
119-
(type == INET_SOCKS));
128+
(type == INET_SOCKS), tv);
120129
syscall = "connect(2)";
121130
}
122131
}
@@ -161,7 +170,7 @@ init_inetsock_internal(VALUE v)
161170
VALUE
162171
rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
163172
VALUE local_host, VALUE local_serv, int type,
164-
VALUE resolv_timeout)
173+
VALUE resolv_timeout, VALUE connect_timeout)
165174
{
166175
struct inetsock_arg arg;
167176
arg.sock = sock;
@@ -174,6 +183,7 @@ rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
174183
arg.type = type;
175184
arg.fd = -1;
176185
arg.resolv_timeout = resolv_timeout;
186+
arg.connect_timeout = connect_timeout;
177187
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
178188
inetsock_cleanup, (VALUE)&arg);
179189
}

ext/socket/rubysocket.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ int rsock_socket(int domain, int type, int proto);
346346
int rsock_detect_cloexec(int fd);
347347
VALUE rsock_init_sock(VALUE sock, int fd);
348348
VALUE rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass);
349-
VALUE rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout);
349+
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);
350350
VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server);
351351

352352
struct rsock_send_arg {
@@ -371,7 +371,7 @@ VALUE rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str,
371371
VALUE ex, enum sock_recv_type from);
372372
VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from);
373373

374-
int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks);
374+
int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout);
375375

376376
VALUE rsock_s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len);
377377
VALUE rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr,

ext/socket/socket.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ sock_connect(VALUE sock, VALUE addr)
393393
addr = rb_str_new4(addr);
394394
GetOpenFile(sock, fptr);
395395
fd = fptr->fd;
396-
n = rsock_connect(fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0);
396+
n = rsock_connect(fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, NULL);
397397
if (n < 0) {
398398
rsock_sys_fail_raddrinfo_or_sockaddr("connect(2)", addr, rai);
399399
}

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);
39+
return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil);
4040
}
4141

4242
/*

ext/socket/tcpsocket.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,43 @@
1212

1313
/*
1414
* call-seq:
15-
* TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil)
15+
* TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil)
1616
*
1717
* Opens a TCP connection to +remote_host+ on +remote_port+. If +local_host+
1818
* and +local_port+ are specified, then those parameters are used on the local
1919
* end to establish the connection.
2020
*
2121
* [:resolv_timeout] specify the name resolution timeout in seconds.
22+
* [:connect_timeout] specify the timeout in seconds.
2223
*/
2324
static VALUE
2425
tcp_init(int argc, VALUE *argv, VALUE sock)
2526
{
2627
VALUE remote_host, remote_serv;
2728
VALUE local_host, local_serv;
2829
VALUE opt;
29-
static ID keyword_ids[1];
30-
VALUE kwargs[1];
30+
static ID keyword_ids[2];
31+
VALUE kwargs[2];
3132
VALUE resolv_timeout = Qnil;
33+
VALUE connect_timeout = Qnil;
3234

3335
if (!keyword_ids[0]) {
3436
CONST_ID(keyword_ids[0], "resolv_timeout");
37+
CONST_ID(keyword_ids[1], "connect_timeout");
3538
}
3639

3740
rb_scan_args(argc, argv, "22:", &remote_host, &remote_serv,
3841
&local_host, &local_serv, &opt);
3942

4043
if (!NIL_P(opt)) {
41-
rb_get_kwargs(opt, keyword_ids, 0, 1, kwargs);
42-
if (kwargs[0] != Qundef) {
43-
resolv_timeout = kwargs[0];
44-
}
44+
rb_get_kwargs(opt, keyword_ids, 0, 2, kwargs);
45+
if (kwargs[0] != Qundef) { resolv_timeout = kwargs[0]; }
46+
if (kwargs[1] != Qundef) { connect_timeout = kwargs[1]; }
4547
}
4648

4749
return rsock_init_inetsock(sock, remote_host, remote_serv,
4850
local_host, local_serv, INET_CLIENT,
49-
resolv_timeout);
51+
resolv_timeout, connect_timeout);
5052
}
5153

5254
static VALUE

ext/socket/udpsocket.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ udp_connect_internal(VALUE v)
6060
rb_io_check_closed(fptr = arg->fptr);
6161
fd = fptr->fd;
6262
for (res = arg->res->ai; res; res = res->ai_next) {
63-
if (rsock_connect(fd, res->ai_addr, res->ai_addrlen, 0) >= 0) {
63+
if (rsock_connect(fd, res->ai_addr, res->ai_addrlen, 0, NULL) >= 0) {
6464
return Qtrue;
6565
}
6666
}

ext/socket/unixsocket.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ unixsock_connect_internal(VALUE a)
2222
{
2323
struct unixsock_arg *arg = (struct unixsock_arg *)a;
2424
return (VALUE)rsock_connect(arg->fd, (struct sockaddr*)arg->sockaddr,
25-
arg->sockaddrlen, 0);
25+
arg->sockaddrlen, 0, NULL);
2626
}
2727

2828
static VALUE

test/socket/test_tcp.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ def test_initialize_resolv_timeout
6969
end
7070
end
7171

72+
def test_initialize_connect_timeout
73+
assert_raise(Errno::ETIMEDOUT) do
74+
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)
75+
end
76+
end
77+
7278
def test_recvfrom
7379
TCPServer.open("localhost", 0) {|svr|
7480
th = Thread.new {

0 commit comments

Comments
 (0)