From bee5b49aece1499309b7e7bb6e8e373a1d67e4ce Mon Sep 17 00:00:00 2001 From: normal Date: Mon, 16 Nov 2015 23:40:15 +0000 Subject: socket: avoid arg parsing in rsock_s_accept_nonblock * ext/socket/init.c (rsock_s_accept_nonblock): avoid parsing args [ruby-core:71439] [Feature #11339] * ext/socket/rubysocket.h: adjust prototype * ext/socket/socket.c (sock_accept_nonblock): make private * ext/socket/tcpserver.c (tcp_accept_nonblock): ditto * ext/socket/unixserver.c (unix_accept_nonblock): ditto * ext/socket/lib/socket.rb (Socket#accept_nonblock): implement as wrapper, move RDoc (TCPServer#accept_nonblock): ditto (UNIXServer#accept_nonblock): ditto target 0: a (ruby 2.3.0dev (2015-11-12 trunk 52550) [x86_64-linux]) target 1: b (ruby 2.3.0dev (2015-11-12 avoid-kwarg-capi 52550) [x86_64-linux] ----------------------------------------------------------- accept_nonblock require 'tempfile' require 'socket' require 'io/wait' nr = 500000 Tempfile.create(%w(accept_nonblock .sock)) do |tmp| path = tmp.path File.unlink(path) s = UNIXServer.new(path) addr = Socket.sockaddr_un(path).freeze nr.times do s.accept_nonblock(exception: false) c = UNIXSocket.new(path) s.wait_readable s.accept_nonblock(exception: false).close c.close end end ----------------------------------------------------------- raw data: [["accept_nonblock", [[4.807877402752638, 4.930681671947241, 4.738454818725586, 4.69268161803484, 4.684675686061382], [4.253904823213816, 4.255124930292368, 4.295955188572407, 4.248479191213846, 4.213303029537201]]]] Elapsed time: 45.123040065 (sec) ----------------------------------------------------------- benchmark results: minimum results in each 5 measurements. Execution time (sec) name a b accept_nonblock 4.685 4.213 Speedup ratio: compare with the result of `a' (greater is better) name b accept_nonblock 1.112 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52601 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/socket/init.c | 10 +--- ext/socket/lib/socket.rb | 140 +++++++++++++++++++++++++++++++++++++++++++++++ ext/socket/rubysocket.h | 3 +- ext/socket/socket.c | 66 +++------------------- ext/socket/tcpserver.c | 48 ++-------------- ext/socket/unixserver.c | 47 +++------------- 6 files changed, 166 insertions(+), 148 deletions(-) (limited to 'ext/socket') diff --git a/ext/socket/init.c b/ext/socket/init.c index 350ba652e7..45070d5d12 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -29,7 +29,7 @@ VALUE rb_cSOCKSSocket; #endif int rsock_do_not_reverse_lookup = 1; -static VALUE sym_exception, sym_wait_readable; +static VALUE sym_wait_readable; void rsock_raise_socket_error(const char *reason, int error) @@ -544,13 +544,10 @@ cloexec_accept(int socket, struct sockaddr *address, socklen_t *address_len, } VALUE -rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr, +rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr, struct sockaddr *sockaddr, socklen_t *len) { int fd2; - VALUE opts = Qnil; - - rb_scan_args(argc, argv, "0:", &opts); rb_io_set_nonblock(fptr); fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len, 1); @@ -564,7 +561,7 @@ rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr, #if defined EPROTO case EPROTO: #endif - if (rsock_opt_false_p(opts, sym_exception)) + if (ex == Qfalse) return sym_wait_readable; rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "accept(2) would block"); } @@ -673,6 +670,5 @@ rsock_init_socket_init(void) rsock_init_socket_constants(); #undef rb_intern - sym_exception = ID2SYM(rb_intern("exception")); sym_wait_readable = ID2SYM(rb_intern("wait_readable")); } diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 86da09e997..fed1d43e34 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -398,6 +398,63 @@ class Socket < BasicSocket __recvfrom_nonblock(len, flag, str, exception) end + # call-seq: + # socket.accept_nonblock([options]) => [client_socket, client_addrinfo] + # + # Accepts an incoming connection using accept(2) after + # O_NONBLOCK is set for the underlying file descriptor. + # It returns an array containing the accepted socket + # for the incoming connection, _client_socket_, + # and an Addrinfo, _client_addrinfo_. + # + # === Example + # # In one script, start this first + # require 'socket' + # include Socket::Constants + # socket = Socket.new(AF_INET, SOCK_STREAM, 0) + # sockaddr = Socket.sockaddr_in(2200, 'localhost') + # socket.bind(sockaddr) + # socket.listen(5) + # begin # emulate blocking accept + # client_socket, client_addrinfo = socket.accept_nonblock + # rescue IO::WaitReadable, Errno::EINTR + # IO.select([socket]) + # retry + # end + # puts "The client said, '#{client_socket.readline.chomp}'" + # client_socket.puts "Hello from script one!" + # socket.close + # + # # In another script, start this second + # require 'socket' + # include Socket::Constants + # socket = Socket.new(AF_INET, SOCK_STREAM, 0) + # sockaddr = Socket.sockaddr_in(2200, 'localhost') + # socket.connect(sockaddr) + # socket.puts "Hello from script 2." + # puts "The server said, '#{socket.readline.chomp}'" + # socket.close + # + # Refer to Socket#accept for the exceptions that may be thrown if the call + # to _accept_nonblock_ fails. + # + # Socket#accept_nonblock may raise any error corresponding to accept(2) failure, + # including Errno::EWOULDBLOCK. + # + # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO, + # it is extended by IO::WaitReadable. + # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. + # + # By specifying `exception: false`, the options hash allows you to indicate + # that accept_nonblock should not raise an IO::WaitReadable exception, but + # return the symbol :wait_readable instead. + # + # === See + # * Socket#accept + def accept_nonblock(exception: true) + __accept_nonblock(exception) + end + # :call-seq: # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) @@ -1086,3 +1143,86 @@ class UDPSocket < IPSocket __recvfrom_nonblock(len, flag, str, exception) end end + +class TCPServer < TCPSocket + + # call-seq: + # tcpserver.accept_nonblock([options]) => tcpsocket + # + # Accepts an incoming connection using accept(2) after + # O_NONBLOCK is set for the underlying file descriptor. + # It returns an accepted TCPSocket for the incoming connection. + # + # === Example + # require 'socket' + # serv = TCPServer.new(2202) + # begin # emulate blocking accept + # sock = serv.accept_nonblock + # rescue IO::WaitReadable, Errno::EINTR + # IO.select([serv]) + # retry + # end + # # sock is an accepted socket. + # + # Refer to Socket#accept for the exceptions that may be thrown if the call + # to TCPServer#accept_nonblock fails. + # + # TCPServer#accept_nonblock may raise any error corresponding to accept(2) failure, + # including Errno::EWOULDBLOCK. + # + # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, + # it is extended by IO::WaitReadable. + # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. + # + # By specifying `exception: false`, the options hash allows you to indicate + # that accept_nonblock should not raise an IO::WaitReadable exception, but + # return the symbol :wait_readable instead. + # + # === See + # * TCPServer#accept + # * Socket#accept + def accept_nonblock(exception: true) + __accept_nonblock(exception) + end +end + +class UNIXServer < UNIXSocket + # call-seq: + # unixserver.accept_nonblock([options]) => unixsocket + # + # Accepts an incoming connection using accept(2) after + # O_NONBLOCK is set for the underlying file descriptor. + # It returns an accepted UNIXSocket for the incoming connection. + # + # === Example + # require 'socket' + # serv = UNIXServer.new("/tmp/sock") + # begin # emulate blocking accept + # sock = serv.accept_nonblock + # rescue IO::WaitReadable, Errno::EINTR + # IO.select([serv]) + # retry + # end + # # sock is an accepted socket. + # + # Refer to Socket#accept for the exceptions that may be thrown if the call + # to UNIXServer#accept_nonblock fails. + # + # UNIXServer#accept_nonblock may raise any error corresponding to accept(2) failure, + # including Errno::EWOULDBLOCK. + # + # If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO, + # it is extended by IO::WaitReadable. + # So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. + # + # By specifying `exception: false`, the options hash allows you to indicate + # that accept_nonblock should not raise an IO::WaitReadable exception, but + # return the symbol :wait_readable instead. + # + # === See + # * UNIXServer#accept + # * Socket#accept + def accept_nonblock(exception: true) + __accept_nonblock(exception) + end +end diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 9fa16ec9dc..d39de0ab1a 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -354,7 +354,8 @@ VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type fr int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks); VALUE rsock_s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len); -VALUE rsock_s_accept_nonblock(int argc, VALUE *argv, VALUE klass, rb_io_t *fptr, struct sockaddr *sockaddr, socklen_t *len); +VALUE rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr, + struct sockaddr *sockaddr, socklen_t *len); VALUE rsock_sock_listen(VALUE sock, VALUE log); VALUE rsock_sockopt_new(int family, int level, int optname, VALUE data); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index a410625537..cc5cbe10d5 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -800,63 +800,9 @@ sock_accept(VALUE sock) return rb_assoc_new(sock2, rsock_io_socket_addrinfo(sock2, &buf.addr, len)); } -/* - * call-seq: - * socket.accept_nonblock([options]) => [client_socket, client_addrinfo] - * - * Accepts an incoming connection using accept(2) after - * O_NONBLOCK is set for the underlying file descriptor. - * It returns an array containing the accepted socket - * for the incoming connection, _client_socket_, - * and an Addrinfo, _client_addrinfo_. - * - * === Example - * # In one script, start this first - * require 'socket' - * include Socket::Constants - * socket = Socket.new(AF_INET, SOCK_STREAM, 0) - * sockaddr = Socket.sockaddr_in(2200, 'localhost') - * socket.bind(sockaddr) - * socket.listen(5) - * begin # emulate blocking accept - * client_socket, client_addrinfo = socket.accept_nonblock - * rescue IO::WaitReadable, Errno::EINTR - * IO.select([socket]) - * retry - * end - * puts "The client said, '#{client_socket.readline.chomp}'" - * client_socket.puts "Hello from script one!" - * socket.close - * - * # In another script, start this second - * require 'socket' - * include Socket::Constants - * socket = Socket.new(AF_INET, SOCK_STREAM, 0) - * sockaddr = Socket.sockaddr_in(2200, 'localhost') - * socket.connect(sockaddr) - * socket.puts "Hello from script 2." - * puts "The server said, '#{socket.readline.chomp}'" - * socket.close - * - * Refer to Socket#accept for the exceptions that may be thrown if the call - * to _accept_nonblock_ fails. - * - * Socket#accept_nonblock may raise any error corresponding to accept(2) failure, - * including Errno::EWOULDBLOCK. - * - * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO, - * it is extended by IO::WaitReadable. - * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. - * - * By specifying `exception: false`, the options hash allows you to indicate - * that accept_nonblock should not raise an IO::WaitReadable exception, but - * return the symbol :wait_readable instead. - * - * === See - * * Socket#accept - */ +/* :nodoc: */ static VALUE -sock_accept_nonblock(int argc, VALUE *argv, VALUE sock) +sock_accept_nonblock(VALUE sock, VALUE ex) { rb_io_t *fptr; VALUE sock2; @@ -865,7 +811,7 @@ sock_accept_nonblock(int argc, VALUE *argv, VALUE sock) socklen_t len = (socklen_t)sizeof buf; GetOpenFile(sock, fptr); - sock2 = rsock_s_accept_nonblock(argc, argv, rb_cSocket, fptr, addr, &len); + sock2 = rsock_s_accept_nonblock(rb_cSocket, ex, fptr, addr, &len); if (SYMBOL_P(sock2)) /* :wait_readable */ return sock2; @@ -2073,7 +2019,11 @@ Init_socket(void) rb_define_method(rb_cSocket, "bind", sock_bind, 1); rb_define_method(rb_cSocket, "listen", rsock_sock_listen, 1); rb_define_method(rb_cSocket, "accept", sock_accept, 0); - rb_define_method(rb_cSocket, "accept_nonblock", sock_accept_nonblock, -1); + + /* for ext/socket/lib/socket.rb use only: */ + rb_define_private_method(rb_cSocket, + "__accept_nonblock", sock_accept_nonblock, 1); + rb_define_method(rb_cSocket, "sysaccept", sock_sysaccept, 0); rb_define_method(rb_cSocket, "recvfrom", sock_recvfrom, -1); diff --git a/ext/socket/tcpserver.c b/ext/socket/tcpserver.c index a38faf8368..1bbb31adcf 100644 --- a/ext/socket/tcpserver.c +++ b/ext/socket/tcpserver.c @@ -64,53 +64,16 @@ tcp_accept(VALUE sock) return rsock_s_accept(rb_cTCPSocket, fptr->fd, &from.addr, &fromlen); } -/* - * call-seq: - * tcpserver.accept_nonblock([options]) => tcpsocket - * - * Accepts an incoming connection using accept(2) after - * O_NONBLOCK is set for the underlying file descriptor. - * It returns an accepted TCPSocket for the incoming connection. - * - * === Example - * require 'socket' - * serv = TCPServer.new(2202) - * begin # emulate blocking accept - * sock = serv.accept_nonblock - * rescue IO::WaitReadable, Errno::EINTR - * IO.select([serv]) - * retry - * end - * # sock is an accepted socket. - * - * Refer to Socket#accept for the exceptions that may be thrown if the call - * to TCPServer#accept_nonblock fails. - * - * TCPServer#accept_nonblock may raise any error corresponding to accept(2) failure, - * including Errno::EWOULDBLOCK. - * - * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, - * it is extended by IO::WaitReadable. - * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. - * - * By specifying `exception: false`, the options hash allows you to indicate - * that accept_nonblock should not raise an IO::WaitReadable exception, but - * return the symbol :wait_readable instead. - * - * === See - * * TCPServer#accept - * * Socket#accept - */ +/* :nodoc: */ static VALUE -tcp_accept_nonblock(int argc, VALUE *argv, VALUE sock) +tcp_accept_nonblock(VALUE sock, VALUE ex) { rb_io_t *fptr; union_sockaddr from; - socklen_t fromlen; + socklen_t len = (socklen_t)sizeof(from); GetOpenFile(sock, fptr); - fromlen = (socklen_t)sizeof(from); - return rsock_s_accept_nonblock(argc, argv, rb_cTCPSocket, fptr, &from.addr, &fromlen); + return rsock_s_accept_nonblock(rb_cTCPSocket, ex, fptr, &from.addr, &len); } /* @@ -175,7 +138,8 @@ rsock_init_tcpserver(void) */ rb_cTCPServer = rb_define_class("TCPServer", rb_cTCPSocket); rb_define_method(rb_cTCPServer, "accept", tcp_accept, 0); - rb_define_method(rb_cTCPServer, "accept_nonblock", tcp_accept_nonblock, -1); + rb_define_private_method(rb_cTCPServer, + "__accept_nonblock", tcp_accept_nonblock, 1); rb_define_method(rb_cTCPServer, "sysaccept", tcp_sysaccept, 0); rb_define_method(rb_cTCPServer, "initialize", tcp_svr_init, -1); rb_define_method(rb_cTCPServer, "listen", rsock_sock_listen, 1); /* in socket.c */ diff --git a/ext/socket/unixserver.c b/ext/socket/unixserver.c index 532d951c93..799dcffb00 100644 --- a/ext/socket/unixserver.c +++ b/ext/socket/unixserver.c @@ -57,45 +57,9 @@ unix_accept(VALUE sock) (struct sockaddr*)&from, &fromlen); } -/* - * call-seq: - * unixserver.accept_nonblock([options]) => unixsocket - * - * Accepts an incoming connection using accept(2) after - * O_NONBLOCK is set for the underlying file descriptor. - * It returns an accepted UNIXSocket for the incoming connection. - * - * === Example - * require 'socket' - * serv = UNIXServer.new("/tmp/sock") - * begin # emulate blocking accept - * sock = serv.accept_nonblock - * rescue IO::WaitReadable, Errno::EINTR - * IO.select([serv]) - * retry - * end - * # sock is an accepted socket. - * - * Refer to Socket#accept for the exceptions that may be thrown if the call - * to UNIXServer#accept_nonblock fails. - * - * UNIXServer#accept_nonblock may raise any error corresponding to accept(2) failure, - * including Errno::EWOULDBLOCK. - * - * If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO, - * it is extended by IO::WaitReadable. - * So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock. - * - * By specifying `exception: false`, the options hash allows you to indicate - * that accept_nonblock should not raise an IO::WaitReadable exception, but - * return the symbol :wait_readable instead. - * - * === See - * * UNIXServer#accept - * * Socket#accept - */ +/* :nodoc: */ static VALUE -unix_accept_nonblock(int argc, VALUE *argv, VALUE sock) +unix_accept_nonblock(VALUE sock, VALUE ex) { rb_io_t *fptr; struct sockaddr_un from; @@ -103,7 +67,7 @@ unix_accept_nonblock(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, fptr); fromlen = (socklen_t)sizeof(from); - return rsock_s_accept_nonblock(argc, argv, rb_cUNIXSocket, fptr, + return rsock_s_accept_nonblock(rb_cUNIXSocket, ex, fptr, (struct sockaddr *)&from, &fromlen); } @@ -152,7 +116,10 @@ rsock_init_unixserver(void) rb_cUNIXServer = rb_define_class("UNIXServer", rb_cUNIXSocket); rb_define_method(rb_cUNIXServer, "initialize", unix_svr_init, 1); rb_define_method(rb_cUNIXServer, "accept", unix_accept, 0); - rb_define_method(rb_cUNIXServer, "accept_nonblock", unix_accept_nonblock, -1); + + rb_define_private_method(rb_cUNIXServer, + "__accept_nonblock", unix_accept_nonblock, 1); + rb_define_method(rb_cUNIXServer, "sysaccept", unix_sysaccept, 0); rb_define_method(rb_cUNIXServer, "listen", rsock_sock_listen, 1); /* in socket.c */ #endif -- cgit v1.2.3