diff --git a/ChangeLog b/ChangeLog index 6be23f0807..0a56e032df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Tue Jan 28 23:36:01 2014 Tanaka Akira + + * ext/socket: Avoid redundant fcntl/fstat syscalls for cloexec + sockets. + Patch by Eric Wong. [ruby-core:59429] [Feature #9330] + Tue Jan 28 20:51:07 2014 Tanaka Akira * process.c (READ_FROM_CHILD): Apply the last hunk of diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c index 9a68a0c289..7054d654ea 100644 --- a/ext/socket/ancdata.c +++ b/ext/socket/ancdata.c @@ -2,6 +2,8 @@ #include +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 a12800b7b6..cc44f94571 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 2f8ce9d084..d619959247 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 0cddd236c4..bac24259a5 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 == -1 && errno == EINVAL) { + 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 == 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 1742496e84..519343b558 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);