dumping ground for random patches and texts
 help / color / mirror / Atom feed
* [PATCH 0/5] avoid kwarg parsing in socket ext
@ 2015-11-13  4:10 Eric Wong
  2015-11-13  4:10 ` [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock Eric Wong
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

ref: [ruby-core:71439] [Feature #11339]

 benchmark/bm_accept_nonblock.rb  |  17 ++
 benchmark/bm_connect_nonblock.rb |  18 ++
 benchmark/bm_recvmsg_nonblock.rb |  16 ++
 benchmark/bm_sendmsg_nonblock.rb |  16 ++
 ext/socket/ancdata.c             | 179 +++------------
 ext/socket/basicsocket.c         |  73 ++----
 ext/socket/init.c                |  23 +-
 ext/socket/lib/socket.rb         | 479 +++++++++++++++++++++++++++++++++++++++
 ext/socket/rubysocket.h          |  26 +--
 ext/socket/socket.c              | 203 ++---------------
 ext/socket/tcpserver.c           |  48 +---
 ext/socket/udpsocket.c           |  63 +----
 ext/socket/unixserver.c          |  47 +---
 13 files changed, 659 insertions(+), 549 deletions(-)


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock
  2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
@ 2015-11-13  4:10 ` Eric Wong
  2015-11-13  4:10 ` [PATCH 2/5] socket: Socket#connect_nonblock avoids arg parsing with C API Eric Wong
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

* ext/socket/init.c (rsock_s_recvfrom_nonblock):
  avoid arg parsing with C API
  [ruby-core:71439] [Feature #11339]
* ext/socket/basicsocket.c (bsock_recv_nonblock):
  adjust for above change, make private
* ext/socket/socket.c (sock_recvfrom_nonblock): ditto
* ext/socket/udpsocket.c (udp_recvfrom_nonblock): ditto
* ext/socket/lib/socket.rb (BasicSocket#recv_nonblock):
  new wrapper for private method, move RDoc
  (Socket#recvfrom_nonblock): ditto
  (UDPSocket#recvfrom_nonblock): ditto

Note, not adding bm_recv_nonblock.rb to benchmark/ directory
since it is non-portable.  It is only in this commit message.

Benchmark results + code
target 0: a (ruby 2.3.0dev (2015-11-12 trunk 52540) [x86_64-linux])
target 1: b (ruby 2.3.0dev (2015-11-12 avoid-kwarg-capi 52540) [x86_64-linux]

-----------------------------------------------------------
recv_nonblock

require 'socket'
nr = 1000000
msg = 'hello world'
buf = ''
size = msg.bytesize
UNIXSocket.pair(:SEQPACKET) do |a, b|
  nr.times do
    a.sendmsg(msg)
    b.recv_nonblock(size, 0, buf, exception: false)
  end
end

-----------------------------------------------------------
raw data:

[["recv_nonblock",
  [[1.83511221408844,
    1.8703329525887966,
    1.8448856547474861,
    1.859263762831688,
    1.8331583738327026],
   [1.5637447573244572,
    1.4062932096421719,
    1.4247371144592762,
    1.4108827747404575,
    1.4802536629140377]]]]

Elapsed time: 16.530452496 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name          a       b
recv_nonblock   1.833   1.406

Speedup ratio: compare with the result of `a' (greater is better)
name          b
recv_nonblock   1.304
---
 ext/socket/basicsocket.c |  60 +++--------------
 ext/socket/init.c        |  13 ++--
 ext/socket/lib/socket.rb | 171 +++++++++++++++++++++++++++++++++++++++++++++++
 ext/socket/rubysocket.h  |   3 +-
 ext/socket/socket.c      |  72 ++------------------
 ext/socket/udpsocket.c   |  63 ++---------------
 6 files changed, 199 insertions(+), 183 deletions(-)

diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c
index e5597f0..6bf10e8 100644
--- a/ext/socket/basicsocket.c
+++ b/ext/socket/basicsocket.c
@@ -643,59 +643,11 @@ bsock_recv(int argc, VALUE *argv, VALUE sock)
     return rsock_s_recvfrom(sock, argc, argv, RECV_RECV);
 }
 
-/*
- * call-seq:
- * 	basicsocket.recv_nonblock(maxlen [, flags [, options ]) => mesg
- *
- * Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * _flags_ is zero or more of the +MSG_+ options.
- * The result, _mesg_, is the data received.
- *
- * When recvfrom(2) returns 0, Socket#recv_nonblock returns
- * an empty string as data.
- * The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
- *
- * === Parameters
- * * +maxlen+ - the number of bytes to receive from the socket
- * * +flags+ - zero or more of the +MSG_+ options
- * * +options+ - keyword hash, supporting `exception: false`
- *
- * === Example
- * 	serv = TCPServer.new("127.0.0.1", 0)
- * 	af, port, host, addr = serv.addr
- * 	c = TCPSocket.new(addr, port)
- * 	s = serv.accept
- * 	c.send "aaa", 0
- * 	begin # emulate blocking recv.
- * 	  p s.recv_nonblock(10) #=> "aaa"
- * 	rescue IO::WaitReadable
- * 	  IO.select([s])
- * 	  retry
- * 	end
- *
- * Refer to Socket#recvfrom for the exceptions that may be thrown if the call
- * to _recv_nonblock_ fails.
- *
- * BasicSocket#recv_nonblock may raise any error corresponding to recvfrom(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying recv_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that recv_nonblock should not raise an IO::WaitWritable exception, but
- * return the symbol :wait_writable instead.
- *
- * === See
- * * Socket#recvfrom
- */
-
+/* :nodoc: */
 static VALUE
-bsock_recv_nonblock(int argc, VALUE *argv, VALUE sock)
+bsock_recv_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, VALUE ex)
 {
-    return rsock_s_recvfrom_nonblock(sock, argc, argv, RECV_RECV);
+    return rsock_s_recvfrom_nonblock(sock, len, flg, str, ex, RECV_RECV);
 }
 
 /*
@@ -764,10 +716,14 @@ rsock_init_basicsocket(void)
     rb_define_method(rb_cBasicSocket, "remote_address", bsock_remote_address, 0);
     rb_define_method(rb_cBasicSocket, "send", rsock_bsock_send, -1);
     rb_define_method(rb_cBasicSocket, "recv", bsock_recv, -1);
-    rb_define_method(rb_cBasicSocket, "recv_nonblock", bsock_recv_nonblock, -1);
+
     rb_define_method(rb_cBasicSocket, "do_not_reverse_lookup", bsock_do_not_reverse_lookup, 0);
     rb_define_method(rb_cBasicSocket, "do_not_reverse_lookup=", bsock_do_not_reverse_lookup_set, 1);
 
+    /* for ext/socket/lib/socket.rb use only: */
+    rb_define_private_method(rb_cBasicSocket,
+			     "__recv_nonblock", bsock_recv_nonblock, 4);
+
     rb_define_method(rb_cBasicSocket, "sendmsg", rsock_bsock_sendmsg, -1); /* in ancdata.c */
     rb_define_method(rb_cBasicSocket, "sendmsg_nonblock", rsock_bsock_sendmsg_nonblock, -1); /* in ancdata.c */
     rb_define_method(rb_cBasicSocket, "recvmsg", rsock_bsock_recvmsg, -1); /* in ancdata.c */
diff --git a/ext/socket/init.c b/ext/socket/init.c
index 1710129..350ba65 100644
--- a/ext/socket/init.c
+++ b/ext/socket/init.c
@@ -200,24 +200,19 @@ rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from)
 }
 
 VALUE
-rsock_s_recvfrom_nonblock(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from)
+rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str,
+			  VALUE ex, enum sock_recv_type from)
 {
     rb_io_t *fptr;
-    VALUE str;
     union_sockaddr buf;
     socklen_t alen = (socklen_t)sizeof buf;
-    VALUE len, flg;
     long buflen;
     long slen;
     int fd, flags;
     VALUE addr = Qnil;
-    VALUE opts = Qnil;
     socklen_t len0;
 
-    rb_scan_args(argc, argv, "12:", &len, &flg, &str, &opts);
-
-    if (flg == Qnil) flags = 0;
-    else             flags = NUM2INT(flg);
+    flags = NUM2INT(flg);
     buflen = NUM2INT(len);
     str = rsock_strbuf(str, buflen);
 
@@ -249,7 +244,7 @@ rsock_s_recvfrom_nonblock(VALUE sock, int argc, VALUE *argv, enum sock_recv_type
 #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
 	  case EWOULDBLOCK:
 #endif
-            if (rsock_opt_false_p(opts, sym_exception))
+            if (ex == Qfalse)
 		return sym_wait_readable;
             rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvfrom(2) would block");
 	}
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index 7c66e8f..915eb96 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -272,6 +272,56 @@ class BasicSocket < IO
     end
     addr
   end
+
+  # call-seq:
+  # 	basicsocket.recv_nonblock(maxlen [, flags [, buf [, options ]]]) => mesg
+  #
+  # Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # _flags_ is zero or more of the +MSG_+ options.
+  # The result, _mesg_, is the data received.
+  #
+  # When recvfrom(2) returns 0, Socket#recv_nonblock returns
+  # an empty string as data.
+  # The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
+  #
+  # === Parameters
+  # * +maxlen+ - the number of bytes to receive from the socket
+  # * +flags+ - zero or more of the +MSG_+ options
+  # * +options+ - keyword hash, supporting `exception: false`
+  #
+  # === Example
+  # 	serv = TCPServer.new("127.0.0.1", 0)
+  # 	af, port, host, addr = serv.addr
+  # 	c = TCPSocket.new(addr, port)
+  # 	s = serv.accept
+  # 	c.send "aaa", 0
+  # 	begin # emulate blocking recv.
+  # 	  p s.recv_nonblock(10) #=> "aaa"
+  # 	rescue IO::WaitReadable
+  # 	  IO.select([s])
+  # 	  retry
+  # 	end
+  #
+  # Refer to Socket#recvfrom for the exceptions that may be thrown if the call
+  # to _recv_nonblock_ fails.
+  #
+  # BasicSocket#recv_nonblock may raise any error corresponding to recvfrom(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying recv_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that recv_nonblock should not raise an IO::WaitWritable exception, but
+  # return the symbol :wait_writable instead.
+  #
+  # === See
+  # * Socket#recvfrom
+  def recv_nonblock(len, flag = 0, str = nil, exception: true)
+    __recv_nonblock(len, flag, str, exception)
+  end
 end
 
 class Socket < BasicSocket
@@ -282,6 +332,70 @@ class Socket < BasicSocket
     end
   end
 
+  # call-seq:
+  #   socket.recvfrom_nonblock(maxlen) => [mesg, sender_addrinfo]
+  #   socket.recvfrom_nonblock(maxlen, flags) => [mesg, sender_addrinfo]
+  #
+  # Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # _flags_ is zero or more of the +MSG_+ options.
+  # The first element of the results, _mesg_, is the data received.
+  # The second element, _sender_addrinfo_, contains protocol-specific address
+  # information of the sender.
+  #
+  # When recvfrom(2) returns 0, Socket#recvfrom_nonblock returns
+  # an empty string as data.
+  # The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
+  #
+  # === Parameters
+  # * +maxlen+ - the maximum number of bytes to receive from the socket
+  # * +flags+ - zero or more of the +MSG_+ options
+  #
+  # === Example
+  #   # In one file, 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)
+  #   client, client_addrinfo = socket.accept
+  #   begin # emulate blocking recvfrom
+  #     pair = client.recvfrom_nonblock(20)
+  #   rescue IO::WaitReadable
+  #     IO.select([client])
+  #     retry
+  #   end
+  #   data = pair[0].chomp
+  #   puts "I only received 20 bytes '#{data}'"
+  #   sleep 1
+  #   socket.close
+  #
+  #   # In another file, 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 "Watch this get cut short!"
+  #   socket.close
+  #
+  # Refer to Socket#recvfrom for the exceptions that may be thrown if the call
+  # to _recvfrom_nonblock_ fails.
+  #
+  # Socket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
+  #
+  # === See
+  # * Socket#recvfrom
+  def recvfrom_nonblock(len, flag = 0, str = nil, exception: true)
+    __recvfrom_nonblock(len, flag, str, 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])
@@ -866,3 +980,60 @@ class Socket < BasicSocket
 
 end
 
+class UDPSocket < IPSocket
+
+  # call-seq:
+  #   udpsocket.recvfrom_nonblock(maxlen [, flags [, options]]) => [mesg, sender_inet_addr]
+  #
+  # Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # If _maxlen_ is omitted, its default value is 65536.
+  # _flags_ is zero or more of the +MSG_+ options.
+  # The first element of the results, _mesg_, is the data received.
+  # The second element, _sender_inet_addr_, is an array to represent the sender address.
+  #
+  # When recvfrom(2) returns 0,
+  # Socket#recvfrom_nonblock returns an empty string as data.
+  # It means an empty packet.
+  #
+  # === Parameters
+  # * +maxlen+ - the number of bytes to receive from the socket
+  # * +flags+ - zero or more of the +MSG_+ options
+  # * +options+ - keyword hash, supporting `exception: false`
+  #
+  # === Example
+  # 	require 'socket'
+  # 	s1 = UDPSocket.new
+  # 	s1.bind("127.0.0.1", 0)
+  # 	s2 = UDPSocket.new
+  # 	s2.bind("127.0.0.1", 0)
+  # 	s2.connect(*s1.addr.values_at(3,1))
+  # 	s1.connect(*s2.addr.values_at(3,1))
+  # 	s1.send "aaa", 0
+  # 	begin # emulate blocking recvfrom
+  # 	  p s2.recvfrom_nonblock(10)  #=> ["aaa", ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]]
+  # 	rescue IO::WaitReadable
+  # 	  IO.select([s2])
+  # 	  retry
+  # 	end
+  #
+  # Refer to Socket#recvfrom for the exceptions that may be thrown if the call
+  # to _recvfrom_nonblock_ fails.
+  #
+  # UDPSocket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
+  # including Errno::EWOULDBLOCK.
+  #
+  # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
+  # it is extended by IO::WaitReadable.
+  # So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
+  # return the symbol :wait_writable instead.
+  #
+  # === See
+  # * Socket#recvfrom
+  def recvfrom_nonblock(len, flag = 0, str = nil, exception: true)
+    __recvfrom_nonblock(len, flag, str, exception)
+  end
+end
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index 601667d..9fa16ec 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -347,7 +347,8 @@ enum sock_recv_type {
     RECV_SOCKET                 /* Socket#recvfrom */
 };
 
-VALUE rsock_s_recvfrom_nonblock(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from);
+VALUE rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str,
+			        VALUE ex, enum sock_recv_type from);
 VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from);
 
 int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks);
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index 779398c..1a3320d 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -813,72 +813,11 @@ sock_recvfrom(int argc, VALUE *argv, VALUE sock)
     return rsock_s_recvfrom(sock, argc, argv, RECV_SOCKET);
 }
 
-/*
- * call-seq:
- *   socket.recvfrom_nonblock(maxlen) => [mesg, sender_addrinfo]
- *   socket.recvfrom_nonblock(maxlen, flags) => [mesg, sender_addrinfo]
- *
- * Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * _flags_ is zero or more of the +MSG_+ options.
- * The first element of the results, _mesg_, is the data received.
- * The second element, _sender_addrinfo_, contains protocol-specific address
- * information of the sender.
- *
- * When recvfrom(2) returns 0, Socket#recvfrom_nonblock returns
- * an empty string as data.
- * The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
- *
- * === Parameters
- * * +maxlen+ - the maximum number of bytes to receive from the socket
- * * +flags+ - zero or more of the +MSG_+ options
- *
- * === Example
- *   # In one file, 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)
- *   client, client_addrinfo = socket.accept
- *   begin # emulate blocking recvfrom
- *     pair = client.recvfrom_nonblock(20)
- *   rescue IO::WaitReadable
- *     IO.select([client])
- *     retry
- *   end
- *   data = pair[0].chomp
- *   puts "I only received 20 bytes '#{data}'"
- *   sleep 1
- *   socket.close
- *
- *   # In another file, 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 "Watch this get cut short!"
- *   socket.close
- *
- * Refer to Socket#recvfrom for the exceptions that may be thrown if the call
- * to _recvfrom_nonblock_ fails.
- *
- * Socket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
- *
- * === See
- * * Socket#recvfrom
- */
+/* :nodoc: */
 static VALUE
-sock_recvfrom_nonblock(int argc, VALUE *argv, VALUE sock)
+sock_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, VALUE ex)
 {
-    return rsock_s_recvfrom_nonblock(sock, argc, argv, RECV_SOCKET);
+    return rsock_s_recvfrom_nonblock(sock, len, flg, str, ex, RECV_SOCKET);
 }
 
 /*
@@ -2182,7 +2121,10 @@ Init_socket(void)
     rb_define_method(rb_cSocket, "sysaccept", sock_sysaccept, 0);
 
     rb_define_method(rb_cSocket, "recvfrom", sock_recvfrom, -1);
-    rb_define_method(rb_cSocket, "recvfrom_nonblock", sock_recvfrom_nonblock, -1);
+
+    /* for ext/socket/lib/socket.rb use only: */
+    rb_define_private_method(rb_cSocket,
+			     "__recvfrom_nonblock", sock_recvfrom_nonblock, 4);
 
     rb_define_singleton_method(rb_cSocket, "socketpair", rsock_sock_s_socketpair, -1);
     rb_define_singleton_method(rb_cSocket, "pair", rsock_sock_s_socketpair, -1);
diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c
index d55ddfe..7b7b34f 100644
--- a/ext/socket/udpsocket.c
+++ b/ext/socket/udpsocket.c
@@ -214,63 +214,11 @@ udp_send(int argc, VALUE *argv, VALUE sock)
     return ret;
 }
 
-/*
- * call-seq:
- *   udpsocket.recvfrom_nonblock(maxlen [, flags [, options]]) => [mesg, sender_inet_addr]
- *
- * Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
- * O_NONBLOCK is set for the underlying file descriptor.
- * If _maxlen_ is omitted, its default value is 65536.
- * _flags_ is zero or more of the +MSG_+ options.
- * The first element of the results, _mesg_, is the data received.
- * The second element, _sender_inet_addr_, is an array to represent the sender address.
- *
- * When recvfrom(2) returns 0,
- * Socket#recvfrom_nonblock returns an empty string as data.
- * It means an empty packet.
- *
- * === Parameters
- * * +maxlen+ - the number of bytes to receive from the socket
- * * +flags+ - zero or more of the +MSG_+ options
- * * +options+ - keyword hash, supporting `exception: false`
- *
- * === Example
- * 	require 'socket'
- * 	s1 = UDPSocket.new
- * 	s1.bind("127.0.0.1", 0)
- * 	s2 = UDPSocket.new
- * 	s2.bind("127.0.0.1", 0)
- * 	s2.connect(*s1.addr.values_at(3,1))
- * 	s1.connect(*s2.addr.values_at(3,1))
- * 	s1.send "aaa", 0
- * 	begin # emulate blocking recvfrom
- * 	  p s2.recvfrom_nonblock(10)  #=> ["aaa", ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]]
- * 	rescue IO::WaitReadable
- * 	  IO.select([s2])
- * 	  retry
- * 	end
- *
- * Refer to Socket#recvfrom for the exceptions that may be thrown if the call
- * to _recvfrom_nonblock_ fails.
- *
- * UDPSocket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
- * including Errno::EWOULDBLOCK.
- *
- * If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
- * it is extended by IO::WaitReadable.
- * So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
- * return the symbol :wait_writable instead.
- *
- * === See
- * * Socket#recvfrom
- */
+/* :nodoc: */
 static VALUE
-udp_recvfrom_nonblock(int argc, VALUE *argv, VALUE sock)
+udp_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, VALUE ex)
 {
-    return rsock_s_recvfrom_nonblock(sock, argc, argv, RECV_IP);
+    return rsock_s_recvfrom_nonblock(sock, len, flg, str, ex, RECV_IP);
 }
 
 void
@@ -287,5 +235,8 @@ rsock_init_udpsocket(void)
     rb_define_method(rb_cUDPSocket, "connect", udp_connect, 2);
     rb_define_method(rb_cUDPSocket, "bind", udp_bind, 2);
     rb_define_method(rb_cUDPSocket, "send", udp_send, -1);
-    rb_define_method(rb_cUDPSocket, "recvfrom_nonblock", udp_recvfrom_nonblock, -1);
+
+    /* for ext/socket/lib/socket.rb use only: */
+    rb_define_private_method(rb_cUDPSocket,
+			     "__recvfrom_nonblock", udp_recvfrom_nonblock, 4);
 }
-- 
EW


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/5] socket: Socket#connect_nonblock avoids arg parsing with C API
  2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
  2015-11-13  4:10 ` [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock Eric Wong
@ 2015-11-13  4:10 ` Eric Wong
  2015-11-13  4:10 ` [PATCH 3/5] socket: avoid arg parsing in rsock_s_accept_nonblock Eric Wong
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

* ext/socket/socket.c (sock_connect_nonblock):
  avoid argument parsing in C.
  [ruby-core:71439] [Feature #11339]
* ext/socket/lib/socket.rb (Socket#connect_nonblock):
  new wrapper for private method, move RDoc

target 0: a (ruby 2.3.0dev (2015-11-12 trunk 52540) [x86_64-linux])
target 1: b (ruby 2.3.0dev (2015-11-12 avoid-kwarg-capi 52540) [x86_64-linux]

-----------------------------------------------------------
connect_nonblock

require 'tempfile'
require 'socket'
require 'io/wait'
nr = 500000
Tempfile.create(%w(connect_nonblock .sock)) do |tmp|
  path = tmp.path
  File.unlink(path)
  s = UNIXServer.new(path)
  addr = Socket.sockaddr_un(path).freeze
  nr.times do
    c = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM)
    while c.connect_nonblock(addr, exception: false) == :wait_writable
      c.wait_writable
    end
    s.accept.close
    c.close
  end
end

-----------------------------------------------------------
raw data:

[["connect_nonblock",
  [[4.014209181070328,
    3.8479955345392227,
    3.981342639774084,
    4.471840236335993,
    3.7867715656757355],
   [3.639054525643587,
    3.58337214961648,
    3.525284394621849,
    3.52646067738533,
    3.511393066495657]]]]

Elapsed time: 37.889623996 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name             a       b
connect_nonblock   3.787   3.511

Speedup ratio: compare with the result of `a' (greater is better)
name             b
connect_nonblock   1.078
---
 benchmark/bm_connect_nonblock.rb | 18 +++++++++++
 ext/socket/lib/socket.rb         | 47 +++++++++++++++++++++++++++++
 ext/socket/socket.c              | 65 +++++++---------------------------------
 3 files changed, 75 insertions(+), 55 deletions(-)
 create mode 100644 benchmark/bm_connect_nonblock.rb

diff --git a/benchmark/bm_connect_nonblock.rb b/benchmark/bm_connect_nonblock.rb
new file mode 100644
index 0000000..8a4428c
--- /dev/null
+++ b/benchmark/bm_connect_nonblock.rb
@@ -0,0 +1,18 @@
+require 'tempfile'
+require 'socket'
+require 'io/wait'
+nr = 500000
+Tempfile.create(%w(connect_nonblock .sock)) do |tmp|
+  path = tmp.path
+  File.unlink(path)
+  s = UNIXServer.new(path)
+  addr = Socket.sockaddr_un(path).freeze
+  nr.times do
+    c = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM)
+    while c.connect_nonblock(addr, exception: false) == :wait_writable
+      c.wait_writable
+    end
+    s.accept.close
+    c.close
+  end
+end
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index 915eb96..1f55323 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -978,6 +978,53 @@ class Socket < BasicSocket
     }
   end
 
+  # call-seq:
+  #   socket.connect_nonblock(remote_sockaddr, [options]) => 0
+  #
+  # Requests a connection to be made on the given +remote_sockaddr+ after
+  # O_NONBLOCK is set for the underlying file descriptor.
+  # Returns 0 if successful, otherwise an exception is raised.
+  #
+  # === Parameter
+  #  # +remote_sockaddr+ - the +struct+ sockaddr contained in a string or Addrinfo object
+  #
+  # === Example:
+  #   # Pull down Google's web page
+  #   require 'socket'
+  #   include Socket::Constants
+  #   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
+  #   sockaddr = Socket.sockaddr_in(80, 'www.google.com')
+  #   begin # emulate blocking connect
+  #     socket.connect_nonblock(sockaddr)
+  #   rescue IO::WaitWritable
+  #     IO.select(nil, [socket]) # wait 3-way handshake completion
+  #     begin
+  #       socket.connect_nonblock(sockaddr) # check connection failure
+  #     rescue Errno::EISCONN
+  #     end
+  #   end
+  #   socket.write("GET / HTTP/1.0\r\n\r\n")
+  #   results = socket.read
+  #
+  # Refer to Socket#connect for the exceptions that may be thrown if the call
+  # to _connect_nonblock_ fails.
+  #
+  # Socket#connect_nonblock may raise any error corresponding to connect(2) failure,
+  # including Errno::EINPROGRESS.
+  #
+  # If the exception is Errno::EINPROGRESS,
+  # it is extended by IO::WaitWritable.
+  # So IO::WaitWritable can be used to rescue the exceptions for retrying connect_nonblock.
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that connect_nonblock should not raise an IO::WaitWritable exception, but
+  # return the symbol :wait_writable instead.
+  #
+  # === See
+  #  # Socket#connect
+  def connect_nonblock(addr, exception: true)
+    __connect_nonblock(addr, exception)
+  end
 end
 
 class UDPSocket < IPSocket
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index 1a3320d..a410625 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -10,7 +10,7 @@
 
 #include "rubysocket.h"
 
-static VALUE sym_exception, sym_wait_writable;
+static VALUE sym_wait_writable;
 
 static VALUE sock_s_unpack_sockaddr_in(VALUE, VALUE);
 
@@ -439,62 +439,14 @@ sock_connect(VALUE sock, VALUE addr)
     return INT2FIX(n);
 }
 
-/*
- * call-seq:
- *   socket.connect_nonblock(remote_sockaddr, [options]) => 0
- *
- * Requests a connection to be made on the given +remote_sockaddr+ after
- * O_NONBLOCK is set for the underlying file descriptor.
- * Returns 0 if successful, otherwise an exception is raised.
- *
- * === Parameter
- * * +remote_sockaddr+ - the +struct+ sockaddr contained in a string or Addrinfo object
- *
- * === Example:
- *   # Pull down Google's web page
- *   require 'socket'
- *   include Socket::Constants
- *   socket = Socket.new(AF_INET, SOCK_STREAM, 0)
- *   sockaddr = Socket.sockaddr_in(80, 'www.google.com')
- *   begin # emulate blocking connect
- *     socket.connect_nonblock(sockaddr)
- *   rescue IO::WaitWritable
- *     IO.select(nil, [socket]) # wait 3-way handshake completion
- *     begin
- *       socket.connect_nonblock(sockaddr) # check connection failure
- *     rescue Errno::EISCONN
- *     end
- *   end
- *   socket.write("GET / HTTP/1.0\r\n\r\n")
- *   results = socket.read
- *
- * Refer to Socket#connect for the exceptions that may be thrown if the call
- * to _connect_nonblock_ fails.
- *
- * Socket#connect_nonblock may raise any error corresponding to connect(2) failure,
- * including Errno::EINPROGRESS.
- *
- * If the exception is Errno::EINPROGRESS,
- * it is extended by IO::WaitWritable.
- * So IO::WaitWritable can be used to rescue the exceptions for retrying connect_nonblock.
- *
- * By specifying `exception: false`, the options hash allows you to indicate
- * that connect_nonblock should not raise an IO::WaitWritable exception, but
- * return the symbol :wait_writable instead.
- *
- * === See
- * * Socket#connect
- */
+/* :nodoc: */
 static VALUE
-sock_connect_nonblock(int argc, VALUE *argv, VALUE sock)
+sock_connect_nonblock(VALUE sock, VALUE addr, VALUE ex)
 {
-    VALUE addr;
-    VALUE opts = Qnil;
     VALUE rai;
     rb_io_t *fptr;
     int n;
 
-    rb_scan_args(argc, argv, "1:", &addr, &opts);
     SockAddrStringValueWithAddrinfo(addr, rai);
     addr = rb_str_new4(addr);
     GetOpenFile(sock, fptr);
@@ -502,13 +454,13 @@ sock_connect_nonblock(int argc, VALUE *argv, VALUE sock)
     n = connect(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr));
     if (n < 0) {
         if (errno == EINPROGRESS) {
-           if (rsock_opt_false_p(opts, sym_exception)) {
+            if (ex == Qfalse) {
                 return sym_wait_writable;
             }
             rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "connect(2) would block");
 	}
 	if (errno == EISCONN) {
-           if (rsock_opt_false_p(opts, sym_exception)) {
+            if (ex == Qfalse) {
                 return INT2FIX(0);
             }
 	}
@@ -2113,7 +2065,11 @@ Init_socket(void)
 
     rb_define_method(rb_cSocket, "initialize", sock_initialize, -1);
     rb_define_method(rb_cSocket, "connect", sock_connect, 1);
-    rb_define_method(rb_cSocket, "connect_nonblock", sock_connect_nonblock, -1);
+
+    /* for ext/socket/lib/socket.rb use only: */
+    rb_define_private_method(rb_cSocket,
+			     "__connect_nonblock", sock_connect_nonblock, 2);
+
     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);
@@ -2147,6 +2103,5 @@ Init_socket(void)
     rb_define_singleton_method(rb_cSocket, "ip_address_list", socket_s_ip_address_list, 0);
 
 #undef rb_intern
-    sym_exception = ID2SYM(rb_intern("exception"));
     sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
 }
-- 
EW


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/5] socket: avoid arg parsing in rsock_s_accept_nonblock
  2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
  2015-11-13  4:10 ` [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock Eric Wong
  2015-11-13  4:10 ` [PATCH 2/5] socket: Socket#connect_nonblock avoids arg parsing with C API Eric Wong
@ 2015-11-13  4:10 ` Eric Wong
  2015-11-13  4:10 ` [PATCH 4/5] socket (bsock_recvmsg_internal): avoid arg parsing Eric Wong
  2015-11-13  4:10 ` [PATCH 5/5] socket: avoid arg parsing in bsock_sendmsg_internal Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

* 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
---
 benchmark/bm_accept_nonblock.rb |  17 +++++
 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 ++------------
 7 files changed, 183 insertions(+), 148 deletions(-)
 create mode 100644 benchmark/bm_accept_nonblock.rb

diff --git a/benchmark/bm_accept_nonblock.rb b/benchmark/bm_accept_nonblock.rb
new file mode 100644
index 0000000..8e537f9
--- /dev/null
+++ b/benchmark/bm_accept_nonblock.rb
@@ -0,0 +1,17 @@
+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
diff --git a/ext/socket/init.c b/ext/socket/init.c
index 350ba65..45070d5 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 1f55323..edee69a 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -396,6 +396,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])
@@ -1084,3 +1141,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 9fa16ec..d39de0a 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 a410625..cc5cbe1 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 a38faf8..1bbb31a 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 532d951..799dcff 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
-- 
EW


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/5] socket (bsock_recvmsg_internal): avoid arg parsing
  2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
                   ` (2 preceding siblings ...)
  2015-11-13  4:10 ` [PATCH 3/5] socket: avoid arg parsing in rsock_s_accept_nonblock Eric Wong
@ 2015-11-13  4:10 ` Eric Wong
  2015-11-13  4:10 ` [PATCH 5/5] socket: avoid arg parsing in bsock_sendmsg_internal Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

* ext/socket/ancdata.c (bsock_recvmsg_internal): avoid arg parsing
  (rsock_bsock_recvmsg): adjust for above change
  (rsock_bsock_recvmsg_nonblock): ditto
  [ruby-core:71439] [Feature #11339]
* ext/socket/rubysocket.h: adjust prototypes for above
* ext/socket/basicsocket.c (rsock_init_basicsocket):
  adjust private methods
* ext/socket/lib/socket.rb (BasicSocket#recvmsg): wrapper method
  (BasicSocket#recvmsg_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]

-----------------------------------------------------------
recvmsg_nonblock

require 'socket'
nr = 1_000_000
i = 0
msg = '.'
buf = '.'
begin
  r, w = UNIXSocket.pair(:SEQPACKET)
  while i < nr
    i += 1
    w.sendmsg(msg)
    r.recvmsg_nonblock(1, exception: false)
  end
ensure
  r.close
  w.close
end

-----------------------------------------------------------
raw data:

[["recvmsg_nonblock",
  [[3.721687912940979,
    3.6072621569037437,
    3.580637402832508,
    3.614185404032469,
    3.6029579415917397],
   [2.4694008752703667,
    2.4908322244882584,
    2.5051278844475746,
    2.5037173740565777,
    2.548359278589487]]]]

Elapsed time: 30.646087052 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name             a       b
recvmsg_nonblock   3.581   2.469

Speedup ratio: compare with the result of `a' (greater is better)
name             b
recvmsg_nonblock   1.450
---
 benchmark/bm_recvmsg_nonblock.rb | 16 +++++++
 ext/socket/ancdata.c             | 97 +++++++---------------------------------
 ext/socket/basicsocket.c         |  8 +++-
 ext/socket/lib/socket.rb         | 71 +++++++++++++++++++++++++++++
 ext/socket/rubysocket.h          |  6 ++-
 5 files changed, 112 insertions(+), 86 deletions(-)
 create mode 100644 benchmark/bm_recvmsg_nonblock.rb

diff --git a/benchmark/bm_recvmsg_nonblock.rb b/benchmark/bm_recvmsg_nonblock.rb
new file mode 100644
index 0000000..3c1056b
--- /dev/null
+++ b/benchmark/bm_recvmsg_nonblock.rb
@@ -0,0 +1,16 @@
+require 'socket'
+nr = 1_000_000
+i = 0
+msg = '.'
+buf = '.'
+begin
+  r, w = UNIXSocket.pair(:SEQPACKET)
+  while i < nr
+    i += 1
+    w.sendmsg(msg)
+    r.recvmsg_nonblock(1, exception: false)
+  end
+ensure
+  r.close
+  w.close
+end
diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c
index afa75ef..877975e 100644
--- a/ext/socket/ancdata.c
+++ b/ext/socket/ancdata.c
@@ -1487,11 +1487,11 @@ make_io_for_unix_rights(VALUE ctl, struct cmsghdr *cmh, char *msg_end)
 #endif
 
 static VALUE
-bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+bsock_recvmsg_internal(VALUE sock,
+		VALUE vmaxdatlen, VALUE vflags, VALUE vmaxctllen,
+		VALUE scm_rights, VALUE ex, int nonblock)
 {
     rb_io_t *fptr;
-    VALUE vmaxdatlen, vmaxctllen, vflags;
-    VALUE vopts;
     int grow_buffer;
     size_t maxdatlen;
     int flags, orig_flags;
@@ -1512,17 +1512,14 @@ bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
     int gc_done = 0;
 #endif
 
-
-    rb_scan_args(argc, argv, "03:", &vmaxdatlen, &vflags, &vmaxctllen, &vopts);
-
-    maxdatlen = NIL_P(vmaxdatlen) ? 4096 : NUM2SIZET(vmaxdatlen);
+    maxdatlen = NUM2SIZET(vmaxdatlen);
 #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
-    maxctllen = NIL_P(vmaxctllen) ? 4096 : NUM2SIZET(vmaxctllen);
+    maxctllen = NUM2SIZET(vmaxctllen);
 #else
     if (!NIL_P(vmaxctllen))
         rb_raise(rb_eArgError, "control message not supported");
 #endif
-    flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
+    flags = NUM2INT(vflags);
 #ifdef MSG_DONTWAIT
     if (nonblock)
         flags |= MSG_DONTWAIT;
@@ -1532,7 +1529,7 @@ bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
     grow_buffer = NIL_P(vmaxdatlen) || NIL_P(vmaxctllen);
 
     request_scm_rights = 0;
-    if (!NIL_P(vopts) && RTEST(rb_hash_aref(vopts, ID2SYM(rb_intern("scm_rights")))))
+    if (RTEST(scm_rights))
         request_scm_rights = 1;
 #if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
     if (request_scm_rights)
@@ -1602,7 +1599,7 @@ bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
             goto retry;
         }
         if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
-            if (rsock_opt_false_p(vopts, sym_exception)) {
+            if (ex == Qfalse) {
                 return sym_wait_readable;
             }
             rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvmsg(2) would block");
@@ -1720,85 +1717,21 @@ bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
 #endif
 
 #if defined(HAVE_RECVMSG)
-/*
- * call-seq:
- *    basicsocket.recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) => [mesg, sender_addrinfo, rflags, *controls]
- *
- * recvmsg receives a message using recvmsg(2) system call in blocking manner.
- *
- * _maxmesglen_ is the maximum length of mesg to receive.
- *
- * _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_PEEK.
- *
- * _maxcontrollen_ is the maximum length of controls (ancillary data) to receive.
- *
- * _opts_ is option hash.
- * Currently :scm_rights=>bool is the only option.
- *
- * :scm_rights option specifies that application expects SCM_RIGHTS control message.
- * If the value is nil or false, application don't expects SCM_RIGHTS control message.
- * In this case, recvmsg closes the passed file descriptors immediately.
- * This is the default behavior.
- *
- * If :scm_rights value is neither nil nor false, application expects SCM_RIGHTS control message.
- * In this case, recvmsg creates IO objects for each file descriptors for
- * Socket::AncillaryData#unix_rights method.
- *
- * The return value is 4-elements array.
- *
- * _mesg_ is a string of the received message.
- *
- * _sender_addrinfo_ is a sender socket address for connection-less socket.
- * It is an Addrinfo object.
- * For connection-oriented socket such as TCP, sender_addrinfo is platform dependent.
- *
- * _rflags_ is a flags on the received message which is bitwise OR of MSG_* constants such as Socket::MSG_TRUNC.
- * It will be nil if the system uses 4.3BSD style old recvmsg system call.
- *
- * _controls_ is ancillary data which is an array of Socket::AncillaryData objects such as:
- *
- *   #<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>
- *
- * _maxmesglen_ and _maxcontrollen_ can be nil.
- * In that case, the buffer will be grown until the message is not truncated.
- * Internally, MSG_PEEK is used and MSG_TRUNC/MSG_CTRUNC are checked.
- *
- * recvmsg can be used to implement recv_io as follows:
- *
- *   mesg, sender_sockaddr, rflags, *controls = sock.recvmsg(:scm_rights=>true)
- *   controls.each {|ancdata|
- *     if ancdata.cmsg_is?(:SOCKET, :RIGHTS)
- *       return ancdata.unix_rights[0]
- *     end
- *   }
- *
- */
 VALUE
-rsock_bsock_recvmsg(int argc, VALUE *argv, VALUE sock)
+rsock_bsock_recvmsg(VALUE sock, VALUE dlen, VALUE flags, VALUE clen,
+		    VALUE scm_rights)
 {
-    return bsock_recvmsg_internal(argc, argv, sock, 0);
+    VALUE ex = Qtrue;
+    return bsock_recvmsg_internal(sock, dlen, flags, clen, scm_rights, ex, 0);
 }
 #endif
 
 #if defined(HAVE_RECVMSG)
-/*
- * call-seq:
- *    basicsocket.recvmsg_nonblock(maxdatalen=nil, flags=0, maxcontrollen=nil, opts={}) => [data, sender_addrinfo, rflags, *controls]
- *
- * recvmsg receives a message using recvmsg(2) system call in non-blocking manner.
- *
- * It is similar to BasicSocket#recvmsg
- * but non-blocking flag is set before the system call
- * and it doesn't retry the system call.
- *
- * By specifying `exception: false`, the _opts_ hash allows you to indicate
- * that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
- * return the symbol :wait_writable instead.
- */
 VALUE
-rsock_bsock_recvmsg_nonblock(int argc, VALUE *argv, VALUE sock)
+rsock_bsock_recvmsg_nonblock(VALUE sock, VALUE dlen, VALUE flags, VALUE clen,
+			     VALUE scm_rights, VALUE ex)
 {
-    return bsock_recvmsg_internal(argc, argv, sock, 1);
+    return bsock_recvmsg_internal(sock, dlen, flags, clen, scm_rights, ex, 1);
 }
 #endif
 
diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c
index 6bf10e8..1a0120c 100644
--- a/ext/socket/basicsocket.c
+++ b/ext/socket/basicsocket.c
@@ -726,7 +726,11 @@ rsock_init_basicsocket(void)
 
     rb_define_method(rb_cBasicSocket, "sendmsg", rsock_bsock_sendmsg, -1); /* in ancdata.c */
     rb_define_method(rb_cBasicSocket, "sendmsg_nonblock", rsock_bsock_sendmsg_nonblock, -1); /* in ancdata.c */
-    rb_define_method(rb_cBasicSocket, "recvmsg", rsock_bsock_recvmsg, -1); /* in ancdata.c */
-    rb_define_method(rb_cBasicSocket, "recvmsg_nonblock", rsock_bsock_recvmsg_nonblock, -1); /* in ancdata.c */
+
+    /* in ancdata.c */
+    rb_define_private_method(rb_cBasicSocket, "__recvmsg",
+			     rsock_bsock_recvmsg, 4);
+    rb_define_private_method(rb_cBasicSocket, "__recvmsg_nonblock",
+			    rsock_bsock_recvmsg_nonblock, 5);
 
 }
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index edee69a..65e3d02 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -322,6 +322,77 @@ class BasicSocket < IO
   def recv_nonblock(len, flag = 0, str = nil, exception: true)
     __recv_nonblock(len, flag, str, exception)
   end
+
+  # call-seq:
+  #    basicsocket.recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) => [mesg, sender_addrinfo, rflags, *controls]
+  #
+  # recvmsg receives a message using recvmsg(2) system call in blocking manner.
+  #
+  # _maxmesglen_ is the maximum length of mesg to receive.
+  #
+  # _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_PEEK.
+  #
+  # _maxcontrollen_ is the maximum length of controls (ancillary data) to receive.
+  #
+  # _opts_ is option hash.
+  # Currently :scm_rights=>bool is the only option.
+  #
+  # :scm_rights option specifies that application expects SCM_RIGHTS control message.
+  # If the value is nil or false, application don't expects SCM_RIGHTS control message.
+  # In this case, recvmsg closes the passed file descriptors immediately.
+  # This is the default behavior.
+  #
+  # If :scm_rights value is neither nil nor false, application expects SCM_RIGHTS control message.
+  # In this case, recvmsg creates IO objects for each file descriptors for
+  # Socket::AncillaryData#unix_rights method.
+  #
+  # The return value is 4-elements array.
+  #
+  # _mesg_ is a string of the received message.
+  #
+  # _sender_addrinfo_ is a sender socket address for connection-less socket.
+  # It is an Addrinfo object.
+  # For connection-oriented socket such as TCP, sender_addrinfo is platform dependent.
+  #
+  # _rflags_ is a flags on the received message which is bitwise OR of MSG_* constants such as Socket::MSG_TRUNC.
+  # It will be nil if the system uses 4.3BSD style old recvmsg system call.
+  #
+  # _controls_ is ancillary data which is an array of Socket::AncillaryData objects such as:
+  #
+  #   #<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>
+  #
+  # _maxmesglen_ and _maxcontrollen_ can be nil.
+  # In that case, the buffer will be grown until the message is not truncated.
+  # Internally, MSG_PEEK is used and MSG_TRUNC/MSG_CTRUNC are checked.
+  #
+  # recvmsg can be used to implement recv_io as follows:
+  #
+  #   mesg, sender_sockaddr, rflags, *controls = sock.recvmsg(:scm_rights=>true)
+  #   controls.each {|ancdata|
+  #     if ancdata.cmsg_is?(:SOCKET, :RIGHTS)
+  #       return ancdata.unix_rights[0]
+  #     end
+  #   }
+  def recvmsg(dlen = 4096, flags = 0, clen = 4096, scm_rights: false)
+    __recvmsg(dlen, flags, clen, scm_rights)
+  end
+
+  # call-seq:
+  #    basicsocket.recvmsg_nonblock(maxdatalen=nil, flags=0, maxcontrollen=nil, opts={}) => [data, sender_addrinfo, rflags, *controls]
+  #
+  # recvmsg receives a message using recvmsg(2) system call in non-blocking manner.
+  #
+  # It is similar to BasicSocket#recvmsg
+  # but non-blocking flag is set before the system call
+  # and it doesn't retry the system call.
+  #
+  # By specifying `exception: false`, the _opts_ hash allows you to indicate
+  # that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
+  # return the symbol :wait_writable instead.
+  def recvmsg_nonblock(dlen = 4096, flags = 0, clen = 4096,
+                       scm_rights: false, exception: true)
+    __recvmsg_nonblock(dlen, flags, clen, scm_rights, exception)
+  end
 end
 
 class Socket < BasicSocket
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index d39de0a..652876c 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -369,8 +369,10 @@ VALUE rsock_bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock);
 #endif
 
 #if defined(HAVE_RECVMSG)
-VALUE rsock_bsock_recvmsg(int argc, VALUE *argv, VALUE sock);
-VALUE rsock_bsock_recvmsg_nonblock(int argc, VALUE *argv, VALUE sock);
+VALUE rsock_bsock_recvmsg(VALUE sock, VALUE dlen, VALUE clen, VALUE flags,
+			  VALUE scm_rights);
+VALUE rsock_bsock_recvmsg_nonblock(VALUE sock, VALUE dlen, VALUE clen,
+				   VALUE flags, VALUE scm_rights, VALUE ex);
 ssize_t rsock_recvmsg(int socket, struct msghdr *message, int flags);
 #else
 #define rsock_bsock_recvmsg rb_f_notimplement
-- 
EW


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 5/5] socket: avoid arg parsing in bsock_sendmsg_internal
  2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
                   ` (3 preceding siblings ...)
  2015-11-13  4:10 ` [PATCH 4/5] socket (bsock_recvmsg_internal): avoid arg parsing Eric Wong
@ 2015-11-13  4:10 ` Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2015-11-13  4:10 UTC (permalink / raw)
  To: spew

* ext/socket/ancdata.c (bsock_sendmsg_internal): avoid arg parsing
  [ruby-core:71439] [Feature #11339]
  (rsock_bsock_sendmsg): make private, adjust for above
  (rsock_bsock_sendmsg_nonblock): ditto
* ext/socket/rubysocket.h: adjust prototypes
  (rsock_opt_false_p): remove
* ext/socket/basicsocket.c (rsock_init_basicsocket):
  define private methods
* ext/socket/lib/socket.rb (BasicSocket#sendmsg): new wrapper
  (BasicSocket#sendmsg_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]

-----------------------------------------------------------
sendmsg_nonblock

require 'socket'
nr = 1_000_000
i = 0
msg = '.'
buf = '.'
begin
  r, w = UNIXSocket.pair(:SEQPACKET)
  while i < nr
    i += 1
    w.sendmsg_nonblock(msg, exception: false)
    r.recv(1, 0, buf)
  end
ensure
  r.close
  w.close
end

-----------------------------------------------------------
raw data:

[["sendmsg_nonblock",
  [[1.875997293740511,
    1.8452614955604076,
    1.8449317328631878,
    1.8418389447033405,
    1.869386937469244],
   [1.5175109766423702,
    1.4987873211503029,
    1.4989623799920082,
    1.47918451577425,
    1.5017359890043736]]]]

Elapsed time: 16.775453245 (sec)
-----------------------------------------------------------
benchmark results:
minimum results in each 5 measurements.
Execution time (sec)
name             a       b
sendmsg_nonblock   1.842   1.479

Speedup ratio: compare with the result of `a' (greater is better)
name             b
sendmsg_nonblock   1.245
---
 benchmark/bm_sendmsg_nonblock.rb | 16 ++++++++
 ext/socket/ancdata.c             | 82 +++++++++-------------------------------
 ext/socket/basicsocket.c         |  7 ++--
 ext/socket/lib/socket.rb         | 50 ++++++++++++++++++++++++
 ext/socket/rubysocket.h          | 14 ++-----
 5 files changed, 91 insertions(+), 78 deletions(-)
 create mode 100644 benchmark/bm_sendmsg_nonblock.rb

diff --git a/benchmark/bm_sendmsg_nonblock.rb b/benchmark/bm_sendmsg_nonblock.rb
new file mode 100644
index 0000000..a6d3b47
--- /dev/null
+++ b/benchmark/bm_sendmsg_nonblock.rb
@@ -0,0 +1,16 @@
+require 'socket'
+nr = 1_000_000
+i = 0
+msg = '.'
+buf = '.'
+begin
+  r, w = UNIXSocket.pair(:SEQPACKET)
+  while i < nr
+    i += 1
+    w.sendmsg_nonblock(msg, exception: false)
+    r.recv(1, 0, buf)
+  end
+ensure
+  r.close
+  w.close
+end
diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c
index 877975e..7e0441c 100644
--- a/ext/socket/ancdata.c
+++ b/ext/socket/ancdata.c
@@ -3,7 +3,7 @@
 #include <time.h>
 
 int rsock_cmsg_cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
-static VALUE sym_exception, sym_wait_readable, sym_wait_writable;
+static VALUE sym_wait_readable, sym_wait_writable;
 
 #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
 static VALUE rb_cAncillaryData;
@@ -1128,14 +1128,13 @@ rb_sendmsg(int fd, const struct msghdr *msg, int flags)
 }
 
 static VALUE
-bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+bsock_sendmsg_internal(VALUE sock, VALUE data, VALUE vflags,
+		       VALUE dest_sockaddr, VALUE controls, VALUE ex,
+		       int nonblock)
 {
     rb_io_t *fptr;
-    VALUE data, vflags, dest_sockaddr;
     struct msghdr mh;
     struct iovec iov;
-    VALUE opts = Qnil;
-    VALUE controls = Qnil;
     int controls_num;
 #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
     VALUE controls_str = 0;
@@ -1149,15 +1148,11 @@ bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
     family = rsock_getfamily(fptr);
 #endif
 
-    data = vflags = dest_sockaddr = Qnil;
-
-    if (argc == 0)
-        rb_raise(rb_eArgError, "mesg argument required");
-
-    rb_scan_args(argc, argv, "12*:", &data, &vflags, &dest_sockaddr, &controls,
-                 &opts);
-
     StringValue(data);
+
+    if (!RB_TYPE_P(controls, T_ARRAY)) {
+	controls = rb_ary_new();
+    }
     controls_num = RARRAY_LENINT(controls);
 
     if (controls_num) {
@@ -1285,7 +1280,7 @@ bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
             goto retry;
         }
         if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
-	    if (rsock_opt_false_p(opts, sym_exception)) {
+	    if (ex == Qfalse) {
 		return sym_wait_writable;
 	    }
 	    rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE,
@@ -1302,64 +1297,22 @@ bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
 #endif
 
 #if defined(HAVE_SENDMSG)
-/*
- * call-seq:
- *    basicsocket.sendmsg(mesg, flags=0, dest_sockaddr=nil, *controls) => numbytes_sent
- *
- * sendmsg sends a message using sendmsg(2) system call in blocking manner.
- *
- * _mesg_ is a string to send.
- *
- * _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_OOB.
- *
- * _dest_sockaddr_ is a destination socket address for connection-less socket.
- * It should be a sockaddr such as a result of Socket.sockaddr_in.
- * An Addrinfo object can be used too.
- *
- * _controls_ is a list of ancillary data.
- * The element of _controls_ should be Socket::AncillaryData or
- * 3-elements array.
- * The 3-element array should contains cmsg_level, cmsg_type and data.
- *
- * The return value, _numbytes_sent_ is an integer which is the number of bytes sent.
- *
- * sendmsg can be used to implement send_io as follows:
- *
- *   # use Socket::AncillaryData.
- *   ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, io.fileno)
- *   sock.sendmsg("a", 0, nil, ancdata)
- *
- *   # use 3-element array.
- *   ancdata = [:SOCKET, :RIGHTS, [io.fileno].pack("i!")]
- *   sock.sendmsg("\0", 0, nil, ancdata)
- *
- */
 VALUE
-rsock_bsock_sendmsg(int argc, VALUE *argv, VALUE sock)
+rsock_bsock_sendmsg(VALUE sock, VALUE data, VALUE flags, VALUE dest_sockaddr,
+		    VALUE controls)
 {
-    return bsock_sendmsg_internal(argc, argv, sock, 0);
+    return bsock_sendmsg_internal(sock, data, flags, dest_sockaddr, controls,
+				  Qtrue, 0);
 }
 #endif
 
 #if defined(HAVE_SENDMSG)
-/*
- * call-seq:
- *    basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) => numbytes_sent
- *
- * sendmsg_nonblock sends a message using sendmsg(2) system call in non-blocking manner.
- *
- * It is similar to BasicSocket#sendmsg
- * but the non-blocking flag is set before the system call
- * and it doesn't retry the system call.
- *
- * By specifying `exception: false`, the _opts_ hash allows you to indicate
- * that sendmsg_nonblock should not raise an IO::WaitWritable exception, but
- * return the symbol :wait_writable instead.
- */
 VALUE
-rsock_bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock)
+rsock_bsock_sendmsg_nonblock(VALUE sock, VALUE data, VALUE flags,
+			     VALUE dest_sockaddr, VALUE controls, VALUE ex)
 {
-    return bsock_sendmsg_internal(argc, argv, sock, 1);
+    return bsock_sendmsg_internal(sock, data, flags, dest_sockaddr,
+				  controls, ex, 1);
 }
 #endif
 
@@ -1773,7 +1726,6 @@ rsock_init_ancdata(void)
     rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_ifindex", ancillary_ipv6_pktinfo_ifindex, 0);
 #endif
 #undef rb_intern
-    sym_exception = ID2SYM(rb_intern("exception"));
     sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
     sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
 }
diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c
index 1a0120c..eef66d4 100644
--- a/ext/socket/basicsocket.c
+++ b/ext/socket/basicsocket.c
@@ -724,10 +724,11 @@ rsock_init_basicsocket(void)
     rb_define_private_method(rb_cBasicSocket,
 			     "__recv_nonblock", bsock_recv_nonblock, 4);
 
-    rb_define_method(rb_cBasicSocket, "sendmsg", rsock_bsock_sendmsg, -1); /* in ancdata.c */
-    rb_define_method(rb_cBasicSocket, "sendmsg_nonblock", rsock_bsock_sendmsg_nonblock, -1); /* in ancdata.c */
-
     /* in ancdata.c */
+    rb_define_private_method(rb_cBasicSocket, "__sendmsg",
+			     rsock_bsock_sendmsg, 4);
+    rb_define_private_method(rb_cBasicSocket, "__sendmsg_nonblock",
+			     rsock_bsock_sendmsg_nonblock, 5);
     rb_define_private_method(rb_cBasicSocket, "__recvmsg",
 			     rsock_bsock_recvmsg, 4);
     rb_define_private_method(rb_cBasicSocket, "__recvmsg_nonblock",
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index 65e3d02..bc3d38a 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -274,6 +274,56 @@ class BasicSocket < IO
   end
 
   # call-seq:
+  #    basicsocket.sendmsg(mesg, flags=0, dest_sockaddr=nil, *controls) => numbytes_sent
+  #
+  # sendmsg sends a message using sendmsg(2) system call in blocking manner.
+  #
+  # _mesg_ is a string to send.
+  #
+  # _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_OOB.
+  #
+  # _dest_sockaddr_ is a destination socket address for connection-less socket.
+  # It should be a sockaddr such as a result of Socket.sockaddr_in.
+  # An Addrinfo object can be used too.
+  #
+  # _controls_ is a list of ancillary data.
+  # The element of _controls_ should be Socket::AncillaryData or
+  # 3-elements array.
+  # The 3-element array should contains cmsg_level, cmsg_type and data.
+  #
+  # The return value, _numbytes_sent_ is an integer which is the number of bytes sent.
+  #
+  # sendmsg can be used to implement send_io as follows:
+  #
+  #   # use Socket::AncillaryData.
+  #   ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, io.fileno)
+  #   sock.sendmsg("a", 0, nil, ancdata)
+  #
+  #   # use 3-element array.
+  #   ancdata = [:SOCKET, :RIGHTS, [io.fileno].pack("i!")]
+  #   sock.sendmsg("\0", 0, nil, ancdata)
+  def sendmsg(mesg, flags = 0, dest_sockaddr = nil, *controls)
+    __sendmsg(mesg, flags, dest_sockaddr, controls)
+  end
+
+  # call-seq:
+  #    basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) => numbytes_sent
+  #
+  # sendmsg_nonblock sends a message using sendmsg(2) system call in non-blocking manner.
+  #
+  # It is similar to BasicSocket#sendmsg
+  # but the non-blocking flag is set before the system call
+  # and it doesn't retry the system call.
+  #
+  # By specifying `exception: false`, the _opts_ hash allows you to indicate
+  # that sendmsg_nonblock should not raise an IO::WaitWritable exception, but
+  # return the symbol :wait_writable instead.
+  def sendmsg_nonblock(mesg, flags = 0, dest_sockaddr = nil, *controls,
+                       exception: true)
+    __sendmsg_nonblock(mesg, flags, dest_sockaddr, controls, exception)
+  end
+
+  # call-seq:
   # 	basicsocket.recv_nonblock(maxlen [, flags [, buf [, options ]]]) => mesg
   #
   # Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index 652876c..2135ea4 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -361,8 +361,10 @@ VALUE rsock_sock_listen(VALUE sock, VALUE log);
 VALUE rsock_sockopt_new(int family, int level, int optname, VALUE data);
 
 #if defined(HAVE_SENDMSG)
-VALUE rsock_bsock_sendmsg(int argc, VALUE *argv, VALUE sock);
-VALUE rsock_bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock);
+VALUE rsock_bsock_sendmsg(VALUE sock, VALUE data, VALUE flags,
+			  VALUE dest_sockaddr, VALUE controls);
+VALUE rsock_bsock_sendmsg_nonblock(VALUE sock, VALUE data, VALUE flags,
+			     VALUE dest_sockaddr, VALUE controls, VALUE ex);
 #else
 #define rsock_bsock_sendmsg rb_f_notimplement
 #define rsock_bsock_sendmsg_nonblock rb_f_notimplement
@@ -437,12 +439,4 @@ static inline void rsock_maybe_wait_fd(int fd) { }
 #  define MSG_DONTWAIT_RELIABLE 0
 #endif
 
-static inline int
-rsock_opt_false_p(VALUE opt, VALUE sym)
-{
-    if (!NIL_P(opt) && Qfalse == rb_hash_lookup2(opt, sym, Qundef))
-	return 1;
-    return 0;
-}
-
 #endif
-- 
EW


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-11-13  4:10 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-13  4:10 [PATCH 0/5] avoid kwarg parsing in socket ext Eric Wong
2015-11-13  4:10 ` [PATCH 1/5] socket: avoid arg parsing in rsock_s_recvfrom_nonblock Eric Wong
2015-11-13  4:10 ` [PATCH 2/5] socket: Socket#connect_nonblock avoids arg parsing with C API Eric Wong
2015-11-13  4:10 ` [PATCH 3/5] socket: avoid arg parsing in rsock_s_accept_nonblock Eric Wong
2015-11-13  4:10 ` [PATCH 4/5] socket (bsock_recvmsg_internal): avoid arg parsing Eric Wong
2015-11-13  4:10 ` [PATCH 5/5] socket: avoid arg parsing in bsock_sendmsg_internal Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).