summaryrefslogtreecommitdiff
path: root/ext/socket
diff options
context:
space:
mode:
authorakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2014-01-28 14:37:34 +0000
committerakr <akr@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2014-01-28 14:37:34 +0000
commit965b947fff6453464acfc9726db09db359db920b (patch)
tree431763219edb8e8be63cf5f7978e90acff0e604e /ext/socket
parentfbf4850cabbb7dc50be08660fe39889d03a03b96 (diff)
* ext/socket: Avoid redundant fcntl/fstat syscalls for cloexec
sockets. Patch by Eric Wong. [ruby-core:59429] [Feature #9330] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@44728 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/socket')
-rw-r--r--ext/socket/ancdata.c10
-rw-r--r--ext/socket/init.c48
-rw-r--r--ext/socket/rubysocket.h2
-rw-r--r--ext/socket/socket.c35
-rw-r--r--ext/socket/unixsocket.c8
5 files changed, 83 insertions, 20 deletions
diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c
index 9a68a0c..7054d65 100644
--- a/ext/socket/ancdata.c
+++ b/ext/socket/ancdata.c
@@ -2,6 +2,8 @@
#include <time.h>
+int rsock_cmsg_cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
+
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
static VALUE rb_cAncillaryData;
@@ -1416,7 +1418,7 @@ discard_cmsg(struct cmsghdr *cmh, char *msg_end, int msg_peek_p)
int *end = (int *)((char *)cmh + cmh->cmsg_len);
while ((char *)fdp + sizeof(int) <= (char *)end &&
(char *)fdp + sizeof(int) <= msg_end) {
- rb_fd_fix_cloexec(*fdp);
+ rb_update_max_fd(*fdp);
close(*fdp);
fdp++;
}
@@ -1459,7 +1461,11 @@ make_io_for_unix_rights(VALUE ctl, struct cmsghdr *cmh, char *msg_end)
VALUE io;
if (fstat(fd, &stbuf) == -1)
rb_raise(rb_eSocket, "invalid fd in SCM_RIGHTS");
- rb_fd_fix_cloexec(fd);
+ rb_update_max_fd(fd);
+ if (rsock_cmsg_cloexec_state < 0)
+ rsock_cmsg_cloexec_state = rsock_detect_cloexec(fd);
+ if (rsock_cmsg_cloexec_state == 0 || fd <= 2)
+ rb_maygvl_fd_fix_cloexec(fd);
if (S_ISSOCK(stbuf.st_mode))
io = rsock_init_sock(rb_obj_alloc(rb_cSocket), fd);
else
diff --git a/ext/socket/init.c b/ext/socket/init.c
index a12800b..cc44f94 100644
--- a/ext/socket/init.c
+++ b/ext/socket/init.c
@@ -249,24 +249,55 @@ rsock_s_recvfrom_nonblock(VALUE sock, int argc, VALUE *argv, enum sock_recv_type
return rb_assoc_new(str, addr);
}
+/* returns true if SOCK_CLOEXEC is supported */
+int rsock_detect_cloexec(int fd)
+{
+#ifdef SOCK_CLOEXEC
+ int flags = fcntl(fd, F_GETFD);
+
+ if (flags == -1)
+ rb_bug("rsock_detect_cloexec: fcntl(%d, F_GETFD) failed: %s", fd, strerror(errno));
+
+ if (flags & FD_CLOEXEC)
+ return 1;
+#endif
+ return 0;
+}
+
static int
rsock_socket0(int domain, int type, int proto)
{
int ret;
#ifdef SOCK_CLOEXEC
- static int try_sock_cloexec = 1;
- if (try_sock_cloexec) {
+ static int cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
+
+ if (cloexec_state > 0) { /* common path, if SOCK_CLOEXEC is defined */
ret = socket(domain, type|SOCK_CLOEXEC, proto);
- if (ret == -1 && errno == EINVAL) {
+ if (ret >= 0) {
+ if (ret <= 2)
+ goto fix_cloexec;
+ goto update_max_fd;
+ }
+ }
+ else if (cloexec_state < 0) { /* usually runs once only for detection */
+ ret = socket(domain, type|SOCK_CLOEXEC, proto);
+ if (ret >= 0) {
+ cloexec_state = rsock_detect_cloexec(ret);
+ if (cloexec_state == 0 || ret <= 2)
+ goto fix_cloexec;
+ goto update_max_fd;
+ }
+ else if (ret == -1 && errno == EINVAL) {
/* SOCK_CLOEXEC is available since Linux 2.6.27. Linux 2.6.18 fails with EINVAL */
ret = socket(domain, type, proto);
if (ret != -1) {
- try_sock_cloexec = 0;
+ cloexec_state = 0;
+ /* fall through to fix_cloexec */
}
}
}
- else {
+ else { /* cloexec_state == 0 */
ret = socket(domain, type, proto);
}
#else
@@ -274,11 +305,12 @@ rsock_socket0(int domain, int type, int proto)
#endif
if (ret == -1)
return -1;
-
- rb_fd_fix_cloexec(ret);
+fix_cloexec:
+ rb_maygvl_fd_fix_cloexec(ret);
+update_max_fd:
+ rb_update_max_fd(ret);
return ret;
-
}
int
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index 2f8ce9d..d619959 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -226,6 +226,7 @@ typedef union {
#define INET_SOCKS 2
extern int rsock_do_not_reverse_lookup;
+extern int rsock_cmsg_cloexec_state;
#define FMODE_NOREVLOOKUP 0x100
extern VALUE rb_cBasicSocket;
@@ -304,6 +305,7 @@ socklen_t rsock_unix_sockaddr_len(VALUE path);
#endif
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);
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index 0cddd23..bac2425 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -174,10 +174,24 @@ rsock_socketpair0(int domain, int type, int protocol, int sv[2])
int ret;
#ifdef SOCK_CLOEXEC
- static int try_sock_cloexec = 1;
- if (try_sock_cloexec) {
+ static int cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
+
+ if (cloexec_state > 0) { /* common path, if SOCK_CLOEXEC is defined */
+ ret = socketpair(domain, type|SOCK_CLOEXEC, protocol, sv);
+ if (ret == 0 && (sv[0] <= 2 || sv[1] <= 2)) {
+ goto fix_cloexec; /* highly unlikely */
+ }
+ goto update_max_fd;
+ }
+ else if (cloexec_state < 0) { /* usually runs once only for detection */
ret = socketpair(domain, type|SOCK_CLOEXEC, protocol, sv);
- if (ret == -1 && errno == EINVAL) {
+ if (ret == 0) {
+ cloexec_state = rsock_detect_cloexec(sv[0]);
+ if ((cloexec_state == 0) || (sv[0] <= 2 || sv[1] <= 2))
+ goto fix_cloexec;
+ goto update_max_fd;
+ }
+ else if (ret == -1 && errno == EINVAL) {
/* SOCK_CLOEXEC is available since Linux 2.6.27. Linux 2.6.18 fails with EINVAL */
ret = socketpair(domain, type, protocol, sv);
if (ret != -1) {
@@ -185,11 +199,11 @@ rsock_socketpair0(int domain, int type, int protocol, int sv[2])
* So disable SOCK_CLOEXEC only if socketpair() succeeds without SOCK_CLOEXEC.
* Ex. Socket.pair(:UNIX, 0xff) fails with EINVAL.
*/
- try_sock_cloexec = 0;
+ cloexec_state = 0;
}
}
}
- else {
+ else { /* cloexec_state == 0 */
ret = socketpair(domain, type, protocol, sv);
}
#else
@@ -200,8 +214,13 @@ rsock_socketpair0(int domain, int type, int protocol, int sv[2])
return -1;
}
- rb_fd_fix_cloexec(sv[0]);
- rb_fd_fix_cloexec(sv[1]);
+fix_cloexec:
+ rb_maygvl_fd_fix_cloexec(sv[0]);
+ rb_maygvl_fd_fix_cloexec(sv[1]);
+
+update_max_fd:
+ rb_update_max_fd(sv[0]);
+ rb_update_max_fd(sv[1]);
return ret;
}
@@ -267,8 +286,6 @@ rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass)
if (ret < 0) {
rb_sys_fail("socketpair(2)");
}
- rb_fd_fix_cloexec(sp[0]);
- rb_fd_fix_cloexec(sp[1]);
s1 = rsock_init_sock(rb_obj_alloc(klass), sp[0]);
s2 = rsock_init_sock(rb_obj_alloc(klass), sp[1]);
diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c
index 1742496..519343b 100644
--- a/ext/socket/unixsocket.c
+++ b/ext/socket/unixsocket.c
@@ -389,7 +389,13 @@ unix_recv_io(int argc, VALUE *argv, VALUE sock)
#if FD_PASSING_BY_MSG_CONTROL
memcpy(&fd, CMSG_DATA(&cmsg.hdr), sizeof(int));
#endif
- rb_fd_fix_cloexec(fd);
+
+ rb_update_max_fd(fd);
+
+ if (rsock_cmsg_cloexec_state < 0)
+ rsock_cmsg_cloexec_state = rsock_detect_cloexec(fd);
+ if (rsock_cmsg_cloexec_state == 0 || fd <= 2)
+ rb_maygvl_fd_fix_cloexec(fd);
if (klass == Qnil)
return INT2FIX(fd);