From 2c05e083c89b1429b4df19ca8f48f20dada1a957 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Sat, 10 Sep 2011 16:50:55 -0700
Subject: [PATCH] thread.c (rb_thread_select): mark original fd_sets properly

This fixes false-positives whenever select() returns.
A more-efficient implementation can avoid any memcpy()
between fd_sets, but this is a deprecated function.
---
 ext/-test-/old_thread_select/old_thread_select.c   |   19 +++++++++++++++++
 .../old_thread_select/test_old_thread_select.rb    |   14 ++++++++++++
 thread.c                                           |   22 +++++++++++++++++--
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/ext/-test-/old_thread_select/old_thread_select.c b/ext/-test-/old_thread_select/old_thread_select.c
index 881cb7d..e374f02 100644
--- a/ext/-test-/old_thread_select/old_thread_select.c
+++ b/ext/-test-/old_thread_select/old_thread_select.c
@@ -25,6 +25,18 @@ static fd_set * array2fdset(fd_set *fds, VALUE ary, int *max)
     return fds;
 }
 
+static void fdset2array(VALUE dst, fd_set *fds, int max)
+{
+    int i;
+
+    rb_ary_clear(dst);
+
+    for (i = 0; i < max; i++) {
+	if (FD_ISSET(i, fds))
+	    rb_ary_push(dst, INT2NUM(i));
+    }
+}
+
 static VALUE
 old_thread_select(VALUE klass, VALUE r, VALUE w, VALUE e, VALUE timeout)
 {
@@ -45,6 +57,13 @@ old_thread_select(VALUE klass, VALUE r, VALUE w, VALUE e, VALUE timeout)
     rc = rb_thread_select(max, rp, wp, ep, tvp);
     if (rc == -1)
 	rb_sys_fail("rb_wait_for_single_fd");
+
+    if (rp)
+	fdset2array(r, &rfds, max);
+    if (wp)
+	fdset2array(w, &wfds, max);
+    if (ep)
+	fdset2array(e, &efds, max);
     return INT2NUM(rc);
 }
 
diff --git a/test/-ext-/old_thread_select/test_old_thread_select.rb b/test/-ext-/old_thread_select/test_old_thread_select.rb
index b40f3a4..b70e1d3 100644
--- a/test/-ext-/old_thread_select/test_old_thread_select.rb
+++ b/test/-ext-/old_thread_select/test_old_thread_select.rb
@@ -34,6 +34,20 @@ class TestOldThreadSelect < Test::Unit::TestCase
     end
   end
 
+  def test_old_select_false_positive
+    bug5306 = '[ruby-core:39435]'
+    with_pipe do |r2, w2|
+      with_pipe do |r, w|
+        t0 = Time.now
+        w.syswrite '.'
+        rfds = [ r.fileno, r2.fileno ]
+        rc = IO.old_thread_select(rfds, nil, nil, nil)
+        assert_equal [ r.fileno ], rfds, bug5306
+        assert_equal 1, rc, bug5306
+      end
+    end
+  end
+
   def test_old_select_read_write_check
     with_pipe do |r, w|
       w.syswrite('.')
diff --git a/thread.c b/thread.c
index 0466381..8a403db 100644
--- a/thread.c
+++ b/thread.c
@@ -2682,6 +2682,16 @@ rb_thread_fd_writable(int fd)
     return TRUE;
 }
 
+static void rb_fd_rcopy(fd_set *dst, rb_fdset_t *src)
+{
+    size_t size = howmany(rb_fd_max(src), NFDBITS) * sizeof(fd_mask);
+
+    if (size < sizeof(fd_set))
+	size = sizeof(fd_set);
+    memcpy(dst, rb_fd_ptr(src), size);
+    rb_fd_term(src);
+}
+
 int
 rb_thread_select(int max, fd_set * read, fd_set * write, fd_set * except,
 		 struct timeval *timeout)
@@ -2710,12 +2720,18 @@ rb_thread_select(int max, fd_set * read, fd_set * write, fd_set * except,
 
     retval = rb_thread_fd_select(max, rfds, wfds, efds, timeout);
 
-    if (rfds)
+    if (rfds) {
+	rb_fd_rcopy(read, rfds);
 	rb_fd_term(rfds);
-    if (wfds)
+    }
+    if (wfds) {
+	rb_fd_rcopy(write, wfds);
 	rb_fd_term(wfds);
-    if (efds)
+    }
+    if (efds) {
+	rb_fd_rcopy(except, efds);
 	rb_fd_term(efds);
+    }
 
     return retval;
 }
