From 855881614182179714d60722ee1d85ef98cc1ebe Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Mon, 20 Jul 2015 20:07:58 +0000
Subject: [PATCH] io.c: IO.copy_stream uses poll on Linux

poll and ppoll have a superior API which doesn't require the
kernel to scan a potentially large bitmap to find a high-numbered
FD [ruby-core:35572].  So favor using poll in case IO.copy_stream
encounters a non-blocking FD.

We cannot reliably use poll on most OSes, because file types (e.g.
FIFOs) which work with select may not work with poll.  Fortunately,
Linux uses a common notification mechanism between all
select/poll/epoll variants, so all file types are equally supported
between the notification mechanisms.

Verified by watching strace on the following scripts:

*** maygvl_copy_stream_wait_read ***
require 'io/nonblock'
r, w = IO.pipe
r.nonblock = true
IO.copy_stream(r, "/dev/null")

*** nogvl_copy_stream_wait_write ***
require 'io/nonblock'
r, w = IO.pipe
w.nonblock = true
IO.copy_stream("/dev/zero", w)

* io.c (nogvl_wait_for_single_fd): new function for Linux
  (maygvl_copy_stream_wait_read): Linux-specific version
  (nogvl_copy_stream_wait_write): use nogvl_wait_for_single_fd
---
 ChangeLog |  6 ++++++
 io.c      | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index c156d8f..866c136 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Tue Jul 21 05:20:21 2015  Eric Wong  <e@80x24.org>
+
+	* io.c (nogvl_wait_for_single_fd): new function for Linux
+	  (maygvl_copy_stream_wait_read): Linux-specific version
+	  (nogvl_copy_stream_wait_write): use nogvl_wait_for_single_fd
+
 Mon Jul 20 15:04:30 2015  Eric Wong  <e@80x24.org>
 
 	* parse.y (parser_initialize): avoid redundant zero-ing
diff --git a/io.c b/io.c
index 3616d0a..24fe983 100644
--- a/io.c
+++ b/io.c
@@ -10087,6 +10087,49 @@ maygvl_copy_stream_continue_p(int has_gvl, struct copy_stream_struct *stp)
     return FALSE;
 }
 
+/* non-Linux poll may not work on all FDs */
+#if defined(HAVE_POLL) && defined(__linux__)
+#  define USE_POLL 1
+#  define IOWAIT_SYSCALL "poll"
+#else
+#  define IOWAIT_SYSCALL "select"
+#  define USE_POLL 0
+#endif
+
+#if USE_POLL
+static int
+nogvl_wait_for_single_fd(int fd, short events)
+{
+    struct pollfd fds;
+
+    fds.fd = fd;
+    fds.events = events;
+
+    return poll(&fds, 1, 0);
+}
+
+static int
+maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp)
+{
+    int ret;
+
+    do {
+	if (has_gvl) {
+	    ret = rb_wait_for_single_fd(stp->src_fd, RB_WAITFD_IN, NULL);
+	}
+	else {
+	    ret = nogvl_wait_for_single_fd(stp->src_fd, POLLIN);
+	}
+    } while (ret == -1 && maygvl_copy_stream_continue_p(has_gvl, stp));
+
+    if (ret == -1) {
+        stp->syserr = "poll";
+        stp->error_no = errno;
+        return -1;
+    }
+    return 0;
+}
+#else /* !USE_POLL */
 static int
 maygvl_select(int has_gvl, int n, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, struct timeval *timeout)
 {
@@ -10114,6 +10157,7 @@ maygvl_copy_stream_wait_read(int has_gvl, struct copy_stream_struct *stp)
     }
     return 0;
 }
+#endif /* !USE_POLL */
 
 static int
 nogvl_copy_stream_wait_write(struct copy_stream_struct *stp)
@@ -10121,13 +10165,17 @@ nogvl_copy_stream_wait_write(struct copy_stream_struct *stp)
     int ret;
 
     do {
+#if USE_POLL
+	ret = nogvl_wait_for_single_fd(stp->dst_fd, POLLOUT);
+#else
 	rb_fd_zero(&stp->fds);
 	rb_fd_set(stp->dst_fd, &stp->fds);
         ret = rb_fd_select(rb_fd_max(&stp->fds), NULL, &stp->fds, NULL, NULL);
+#endif
     } while (ret == -1 && maygvl_copy_stream_continue_p(0, stp));
 
     if (ret == -1) {
-        stp->syserr = "select";
+        stp->syserr = IOWAIT_SYSCALL;
         stp->error_no = errno;
         return -1;
     }
-- 
EW

