about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-04-12 23:54:13 +0000
committerEric Wong <normalperson@yhbt.net>2013-04-12 23:59:37 +0000
commit78450556becf67a3eb8b0870b38b2c75d99ad96e (patch)
tree24e44929a587af55dbdd892a2da86353c7f32e0d
parent4c4a6ae523bc7bac4be0fd4dc104b055161ad260 (diff)
downloadsleepy_penguin-78450556becf67a3eb8b0870b38b2c75d99ad96e.tar.gz
rb_thread_blocking_region is deprecated in Ruby 2.0, but
rb_thread_io_blocking region is not (and superior for I/O).  So we will
favor rb_thread_io_blocking_region for now.

While we're at it, reimplement green-thread-safe (Ruby 1.8) epoll_wait
in Ruby instead of C.  The extra #ifdefs for 1.8.7 were more prone to
bitrot and Ruby code should be easier to follow for Rubyists who care
about 1.8.
-rw-r--r--ext/sleepy_penguin/epoll.c13
-rw-r--r--ext/sleepy_penguin/epoll_green.h95
-rw-r--r--ext/sleepy_penguin/extconf.rb1
-rw-r--r--ext/sleepy_penguin/inotify.c2
-rw-r--r--ext/sleepy_penguin/sleepy_penguin.h45
-rw-r--r--ext/sleepy_penguin/util.c14
-rw-r--r--lib/sleepy_penguin/epoll/io.rb27
7 files changed, 71 insertions, 126 deletions
diff --git a/ext/sleepy_penguin/epoll.c b/ext/sleepy_penguin/epoll.c
index 7ce5dcb..46183ca 100644
--- a/ext/sleepy_penguin/epoll.c
+++ b/ext/sleepy_penguin/epoll.c
@@ -176,7 +176,6 @@ static int epoll_resume_p(uint64_t expire_at, struct ep_per_thread *ept)
         return 1;
 }
 
-#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
 static VALUE nogvl_wait(void *args)
 {
         struct ep_per_thread *ept = args;
@@ -187,18 +186,15 @@ static VALUE nogvl_wait(void *args)
 
 static VALUE real_epwait(struct ep_per_thread *ept)
 {
-        int n;
+        long n;
         uint64_t expire_at = ept->timeout > 0 ? now_ms() + ept->timeout : 0;
 
         do {
-                n = (int)rb_sp_fd_region(nogvl_wait, ept, ept->fd);
+                n = (long)rb_sp_fd_region(nogvl_wait, ept, ept->fd);
         } while (n == -1 && epoll_resume_p(expire_at, ept));
 
-        return epwait_result(ept, n);
+        return epwait_result(ept, (int)n);
 }
-#else /* 1.8 Green thread compatible code */
-#  include "epoll_green.h"
-#endif /* 1.8 Green thread compatibility code */
 
 /*
  * call-seq:
@@ -342,4 +338,7 @@ void sleepy_penguin_init_epoll(void)
         rb_define_const(cEpoll, "ONESHOT", UINT2NUM(EPOLLONESHOT));
 
         id_for_fd = rb_intern("for_fd");
+
+        if (RB_SP_GREEN_THREAD)
+                rb_require("sleepy_penguin/epoll/io");
 }
diff --git a/ext/sleepy_penguin/epoll_green.h b/ext/sleepy_penguin/epoll_green.h
deleted file mode 100644
index e3414eb..0000000
--- a/ext/sleepy_penguin/epoll_green.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/* this file is only used by Matz Ruby 1.8 which used green threads */
-
-/*
- * we have to worry about green threads and always pass zero
- * as the timeout for epoll_wait :(
- */
-#include <rubysig.h>
-#include <sys/time.h>
-
-/* in case _BSD_SOURCE doesn't give us this macro */
-#ifndef timersub
-#  define timersub(a, b, result) \
-do { \
-        (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
-        (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
-        if ((result)->tv_usec < 0) { \
-                --(result)->tv_sec; \
-                (result)->tv_usec += 1000000; \
-        } \
-} while (0)
-#endif
-
-static int safe_epoll_wait(struct ep_per_thread *ept)
-{
-        int n;
-
-        do {
-                TRAP_BEG;
-                n = epoll_wait(ept->fd, ept->events, ept->maxevents, 0);
-                TRAP_END;
-        } while (n == -1 && ep_fd_check(ept) && errno == EINTR);
-
-        return n;
-}
-
-static int epwait_forever(struct ep_per_thread *ept)
-{
-        int n;
-
-        do {
-                (void)rb_io_wait_readable(ept->fd);
-                n = safe_epoll_wait(ept);
-        } while (n == 0);
-
-        return n;
-}
-
-static int epwait_timed(struct ep_per_thread *ept)
-{
-        struct timeval tv;
-
-        tv.tv_sec = ept->timeout / 1000;
-        tv.tv_usec = (ept->timeout % 1000) * 1000;
-
-        for (;;) {
-                struct timeval t0, now, diff;
-                int n;
-                int fd = ept->fd;
-                fd_set rfds;
-
-                FD_ZERO(&rfds);
-                FD_SET(fd, &rfds);
-
-                gettimeofday(&t0, NULL);
-                (void)rb_thread_select(fd + 1, &rfds, NULL, NULL, &tv);
-                n = safe_epoll_wait(ept);
-                if (n != 0)
-                        return n;
-
-                /* XXX use CLOCK_MONOTONIC if people care about 1.8... */
-                gettimeofday(&now, NULL);
-                timersub(&now, &t0, &diff);
-                timersub(&tv, &diff, &tv);
-
-                if (tv.tv_usec < 0 || tv.tv_sec < 0)
-                        return (n == -1) ? 0 : n;
-        }
-
-        assert("should never get here (epwait_timed)");
-        return -1;
-}
-
-static VALUE real_epwait(struct ep_per_thread *ept)
-{
-        int n;
-
-        if (ept->timeout == -1)
-                n = epwait_forever(ept);
-        else if (ept->timeout == 0)
-                n = safe_epoll_wait(ept);
-        else
-                n = epwait_timed(ept);
-
-        return epwait_result(ept, n);
-}
diff --git a/ext/sleepy_penguin/extconf.rb b/ext/sleepy_penguin/extconf.rb
index fe8e1ac..5e2c223 100644
--- a/ext/sleepy_penguin/extconf.rb
+++ b/ext/sleepy_penguin/extconf.rb
@@ -11,6 +11,7 @@ have_header('sys/timerfd.h')
 have_header('sys/inotify.h')
 have_header('ruby/io.h') and have_struct_member('rb_io_t', 'fd', 'ruby/io.h')
 have_func('epoll_create1', %w(sys/epoll.h))
+have_func('rb_thread_call_without_gvl')
 have_func('rb_thread_blocking_region')
 have_func('rb_thread_io_blocking_region')
 have_func('rb_thread_fd_close')
diff --git a/ext/sleepy_penguin/inotify.c b/ext/sleepy_penguin/inotify.c
index 344145c..01862ae 100644
--- a/ext/sleepy_penguin/inotify.c
+++ b/ext/sleepy_penguin/inotify.c
@@ -215,7 +215,7 @@ static VALUE take(int argc, VALUE *argv, VALUE self)
         else
                 blocking_io_prepare(args.fd);
         do {
-                r = rb_sp_fd_region(inread, &args, args.fd);
+                r = (ssize_t)rb_sp_fd_region(inread, &args, args.fd);
                 if (r == 0 /* Linux < 2.6.21 */
                     ||
                     (r < 0 && errno == EINVAL) /* Linux >= 2.6.21 */
diff --git a/ext/sleepy_penguin/sleepy_penguin.h b/ext/sleepy_penguin/sleepy_penguin.h
index 599b319..86c6e5f 100644
--- a/ext/sleepy_penguin/sleepy_penguin.h
+++ b/ext/sleepy_penguin/sleepy_penguin.h
@@ -19,24 +19,51 @@ int rb_sp_io_closed(VALUE io);
 int rb_sp_fileno(VALUE io);
 void rb_sp_set_nonblock(int fd);
 
-#ifdef HAVE_RB_THREAD_BLOCKING_REGION
-static inline VALUE rb_sp_io_region(rb_blocking_function_t *func, void *data)
-{
-        return rb_thread_blocking_region(func, data, RUBY_UBF_IO, 0);
-}
+#if defined(HAVE_RB_THREAD_BLOCKING_REGION) || \
+    defined(HAVE_RB_THREAD_IO_BLOCKING_REGION) || \
+    defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
+#  define RB_SP_GREEN_THREAD 0
 #  define blocking_io_prepare(fd) ((void)(fd))
 #else
-typedef VALUE rb_blocking_function_t(void *);
-VALUE rb_sp_io_region(rb_blocking_function_t *func, void *data);
+#  define RB_SP_GREEN_THREAD 1
 #  define blocking_io_prepare(fd) rb_sp_set_nonblock((fd))
 #endif
 
 #ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
+/* Ruby 1.9.3 and 2.0.0 */
 VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int);
 #  define rb_sp_fd_region(fn,data,fd) \
-          rb_thread_io_blocking_region((fn),(data),(fd))
+        rb_thread_io_blocking_region((fn),(data),(fd))
+#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && \
+        defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H
+/* in case Ruby 2.0+ ever drops rb_thread_io_blocking_region: */
+#  include <ruby/thread.h>
+#  define COMPAT_FN (void *(*)(void *))
+#  define rb_sp_fd_region(fn,data,fd) \
+        rb_thread_call_without_gvl(COMPAT_FN(fn),(data),RUBY_UBF_IO,NULL)
+#elif defined(HAVE_RB_THREAD_BLOCKING_REGION)
+/* Ruby 1.9.2 */
+#  define rb_sp_fd_region(fn,data,fd) \
+        rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,NULL)
 #else
-#  define rb_sp_fd_region(fn,data,fd) rb_sp_io_region(fn,data)
+/*
+ * Ruby 1.8 does not have a GVL, we'll just enable signal interrupts
+ * here in case we make interruptible syscalls.
+ *
+ * Note: epoll_wait with timeout=0 was interruptible until Linux 2.6.39
+ */
+#  include <rubysig.h>
+static inline VALUE fake_blocking_region(VALUE (*fn)(void *), void *data)
+{
+        VALUE rv;
+
+        TRAP_BEG;
+        rv = fn(data);
+        TRAP_END;
+
+        return rv;
+}
+#  define rb_sp_fd_region(fn,data,fd) fake_blocking_region((fn),(data))
 #endif
 
 #define NODOC_CONST(klass,name,value) \
diff --git a/ext/sleepy_penguin/util.c b/ext/sleepy_penguin/util.c
index 5cf84bf..4ae702d 100644
--- a/ext/sleepy_penguin/util.c
+++ b/ext/sleepy_penguin/util.c
@@ -135,20 +135,6 @@ void rb_sp_set_nonblock(int fd)
                 rb_sys_fail("fcntl(F_SETFL)");
 }
 
-#ifndef HAVE_RB_THREAD_BLOCKING_REGION
-#include <rubysig.h>
-VALUE rb_sp_io_region(rb_blocking_function_t *func, void *data)
-{
-        VALUE rv;
-
-        TRAP_BEG;
-        rv = func(data);
-        TRAP_END;
-
-        return rv;
-}
-#endif
-
 int rb_sp_wait(rb_sp_waitfn waiter, VALUE obj, int *fd)
 {
         /*
diff --git a/lib/sleepy_penguin/epoll/io.rb b/lib/sleepy_penguin/epoll/io.rb
new file mode 100644
index 0000000..caeb376
--- /dev/null
+++ b/lib/sleepy_penguin/epoll/io.rb
@@ -0,0 +1,27 @@
+# :stopdoc:
+class SleepyPenguin::Epoll::IO
+  alias __epoll_wait epoll_wait
+  undef_method :epoll_wait
+  def epoll_wait(maxevents = 64, timeout = nil)
+    begin
+      if timeout == nil || timeout < 0 # wait forever
+        begin
+          IO.select([self])
+          n = __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
+        end while n == 0
+      elsif timeout == 0
+        return __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
+      else
+        done = Time.now + (timeout / 1000.0)
+        begin
+          tout = done - Time.now
+          IO.select([self], nil, nil, tout) if tout > 0
+          n = __epoll_wait(maxevents, 0) { |events,io| yield(events, io) }
+        end while n == 0 && tout > 0
+      end
+      n
+    rescue Errno::EINTR
+      retry
+    end
+  end
+end