From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS47066 71.19.144.0/20 X-Spam-Status: No, score=-1.9 required=3.0 tests=AWL,BAYES_00 shortcircuit=no autolearn=unavailable version=3.3.2 X-Original-To: normalperson@yhbt.net Received: from zedshaw2.xen.prgmr.com (zedshaw2.xen.prgmr.com [71.19.156.177]) by dcvr.yhbt.net (Postfix) with ESMTP id EA47E1F435 for ; Sat, 13 Apr 2013 00:22:36 +0000 (UTC) Received: from zedshaw2.xen.prgmr.com (unknown [IPv6:::1]) by zedshaw2.xen.prgmr.com (Postfix) with ESMTP id 17FC473DC9 for ; Sat, 13 Apr 2013 00:23:15 +0000 (UTC) MIME-Version: 1.0 Date: Sat, 13 Apr 2013 00:22:20 +0000 From: Eric Wong In-Reply-To: <20130413002220.GA18434@dcvr.yhbt.net> List-Archive: List-Help: List-Id: List-Post: List-Subscribe: List-Unsubscribe: Message-Id: <20130413002220.GA18434@dcvr.yhbt.net> Precedence: list References: <20130413002220.GA18434@dcvr.yhbt.net> Sender: sleepy.penguin@librelist.org Subject: [sleepy.penguin] [PATCH] epoll: cleanup GVL-release code for Ruby 2.0.0 To: sleepy.penguin@librelist.org Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit 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. --- ext/sleepy_penguin/epoll.c | 13 +++-- ext/sleepy_penguin/epoll_green.h | 95 ------------------------------------- ext/sleepy_penguin/extconf.rb | 1 + ext/sleepy_penguin/inotify.c | 2 +- ext/sleepy_penguin/sleepy_penguin.h | 45 ++++++++++++++---- ext/sleepy_penguin/util.c | 14 ------ lib/sleepy_penguin/epoll/io.rb | 27 +++++++++++ 7 files changed, 71 insertions(+), 126 deletions(-) delete mode 100644 ext/sleepy_penguin/epoll_green.h create mode 100644 lib/sleepy_penguin/epoll/io.rb 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 -#include - -/* 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/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 +# 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 +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 -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 -- Eric Wong