From bb7b4147ca5025380792d2650325e645c929028f Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Fri, 10 Apr 2015 21:41:37 +0000
Subject: [PATCH] lib/net/*: use io/wait methods instead of IO.select

io/wait is expected to work on any platform where sockets are
supported.  io/wait methods uses fewer allocations and uses
ppoll internally under Linux for better performance on
high-numbered FDs.

[ruby-core:35572] describes the performance advantage of ppoll
on high-numbered FDs.

EXPERIMENTAL: add IO#wait_readable(..., wait_only: true) support

* lib/net/protocol.rb (rbuf_fill): use IO#wait_*able
* lib/net/http/generic_request.rb (wait_for_continue): ditto
---
 ext/io/wait/wait.c              | 23 ++++++++++++++++++-----
 lib/net/http/generic_request.rb |  2 +-
 lib/net/protocol.rb             |  6 ++++--
 test/io/wait/test_io_wait.rb    | 11 +++++++++++
 4 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/ext/io/wait/wait.c b/ext/io/wait/wait.c
index fd32be7..921ea09 100644
--- a/ext/io/wait/wait.c
+++ b/ext/io/wait/wait.c
@@ -39,6 +39,7 @@
 #define FIONREAD_POSSIBLE_P(fd) ((void)(fd),Qtrue)
 #endif
 
+static VALUE sym_wait_only;
 static VALUE io_ready_p _((VALUE io));
 static VALUE io_wait_readable _((int argc, VALUE *argv, VALUE io));
 static VALUE io_wait_writable _((int argc, VALUE *argv, VALUE io));
@@ -107,15 +108,19 @@ static VALUE
 io_wait_readable(int argc, VALUE *argv, VALUE io)
 {
     rb_io_t *fptr;
+    VALUE opts = Qnil;
     int i;
-    ioctl_arg n;
     VALUE timeout;
     struct timeval timerec;
     struct timeval *tv;
+    int check_nread = 1;
 
     GetOpenFile(io, fptr);
     rb_io_check_readable(fptr);
-    rb_scan_args(argc, argv, "01", &timeout);
+    rb_scan_args(argc, argv, "01:", &timeout, &opts);
+    if (!NIL_P(opts) && Qtrue == rb_hash_lookup2(opts, sym_wait_only, Qundef))
+	check_nread = 0;
+
     if (NIL_P(timeout)) {
 	tv = NULL;
     }
@@ -125,13 +130,20 @@ io_wait_readable(int argc, VALUE *argv, VALUE io)
     }
 
     if (rb_io_read_pending(fptr)) return Qtrue;
-    if (!FIONREAD_POSSIBLE_P(fptr->fd)) return Qfalse;
+    if (check_nread && !FIONREAD_POSSIBLE_P(fptr->fd)) return Qfalse;
     i = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, tv);
     if (i < 0)
 	rb_sys_fail(0);
     rb_io_check_closed(fptr);
-    if (ioctl(fptr->fd, FIONREAD, &n)) rb_sys_fail(0);
-    if (n > 0) return io;
+    if (check_nread) {
+	ioctl_arg n;
+
+	if (ioctl(fptr->fd, FIONREAD, &n)) rb_sys_fail(0);
+	if (n > 0) return io;
+    } else {
+	if (i & RB_WAITFD_IN)
+	    return io;
+    }
     return Qnil;
 }
 
@@ -184,4 +196,5 @@ Init_wait(void)
     rb_define_method(rb_cIO, "wait", io_wait_readable, -1);
     rb_define_method(rb_cIO, "wait_readable", io_wait_readable, -1);
     rb_define_method(rb_cIO, "wait_writable", io_wait_writable, -1);
+    sym_wait_only = ID2SYM(rb_intern("wait_only"));
 }
diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb
index 00ff434..0232e28 100644
--- a/lib/net/http/generic_request.rb
+++ b/lib/net/http/generic_request.rb
@@ -309,7 +309,7 @@ class Net::HTTPGenericRequest
   def wait_for_continue(sock, ver)
     if ver >= '1.1' and @header['expect'] and
         @header['expect'].include?('100-continue')
-      if IO.select([sock.io], nil, nil, sock.continue_timeout)
+      if sock.io.to_io.wait_readable(sock.continue_timeout, wait_only: true)
         res = Net::HTTPResponse.read_new(sock)
         unless res.kind_of?(Net::HTTPContinue)
           res.decode_content = @decode_content
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index 5a20c5d..a8f0626 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -20,6 +20,7 @@
 
 require 'socket'
 require 'timeout'
+require 'io/wait'
 
 module Net # :nodoc:
 
@@ -153,12 +154,13 @@ module Net # :nodoc:
       when String
         return @rbuf << rv
       when :wait_readable
-        IO.select([@io], nil, nil, @read_timeout) or raise Net::ReadTimeout
+        @io.to_io.wait_readable(@read_timeout, wait_only: true) or
+          raise Net::ReadTimeout
         # continue looping
       when :wait_writable
         # OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
         # http://www.openssl.org/support/faq.html#PROG10
-        IO.select(nil, [@io], nil, @read_timeout) or raise Net::ReadTimeout
+        @io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
         # continue looping
       when nil
         # callers do not care about backtrace, so avoid allocating for it
diff --git a/test/io/wait/test_io_wait.rb b/test/io/wait/test_io_wait.rb
index 7729d45..55a10cf 100644
--- a/test/io/wait/test_io_wait.rb
+++ b/test/io/wait/test_io_wait.rb
@@ -98,6 +98,17 @@ class TestIOWait < Test::Unit::TestCase
     assert_raises(IOError) { @w.wait_writable }
   end
 
+  def test_wait_readable_wait_only
+    host = '127.0.0.1'
+    s = TCPServer.new(host, 0)
+    assert_nil s.wait_readable(0.01, wait_only: true)
+    c = TCPSocket.new(host, s.addr[1])
+    assert_equal s, s.wait_readable(wait_only: true)
+  ensure
+    s.close if s
+    c.close if c
+  end
+
 private
 
   def fill_pipe
-- 
EW

