about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2010-09-20 16:49:57 +0000
committerEric Wong <normalperson@yhbt.net>2010-09-26 03:52:39 +0000
commit2d5886698c3f7241ab23771c2876b985708ded40 (patch)
tree89d3b0b54f8927961b6cafee92d7ee4ff2581c59
parent4a9ce0319f6e0250c4a8e01284fd5684936bde21 (diff)
downloadsleepy_penguin-2d5886698c3f7241ab23771c2876b985708ded40.tar.gz
This wraps the timerfd_* interface in the Linux kernel.
It is not available on older distributions.
-rw-r--r--ext/sleepy_penguin/init.c7
-rw-r--r--ext/sleepy_penguin/nonblock.h19
-rw-r--r--ext/sleepy_penguin/timerfd.c128
-rw-r--r--ext/sleepy_penguin/value2timespec.h64
-rw-r--r--test/test_timerfd.rb38
5 files changed, 256 insertions, 0 deletions
diff --git a/ext/sleepy_penguin/init.c b/ext/sleepy_penguin/init.c
index 5dcfb01..dd959af 100644
--- a/ext/sleepy_penguin/init.c
+++ b/ext/sleepy_penguin/init.c
@@ -1,6 +1,13 @@
 void sleepy_penguin_init_epoll(void);
 
+#ifdef HAVE_SYS_TIMERFD_H
+void sleepy_penguin_init_timerfd(void);
+#else
+#  define sleepy_penguin_init_timerfd() if(0)
+#endif
+
 void Init_sleepy_penguin_ext(void)
 {
         sleepy_penguin_init_epoll();
+        sleepy_penguin_init_timerfd();
 }
diff --git a/ext/sleepy_penguin/nonblock.h b/ext/sleepy_penguin/nonblock.h
new file mode 100644
index 0000000..7198114
--- /dev/null
+++ b/ext/sleepy_penguin/nonblock.h
@@ -0,0 +1,19 @@
+#ifndef SLEEPY_PENGUIN_NONBLOCK_H
+#define SLEEPY_PENGUIN_NONBLOCK_H
+#include <unistd.h>
+#include <fcntl.h>
+#include <ruby.h>
+static void set_nonblock(int fd)
+{
+        int flags = fcntl(fd, F_GETFL);
+
+        if (flags == -1)
+                rb_sys_fail("fcntl(F_GETFL)");
+        if ((flags & O_NONBLOCK) == O_NONBLOCK)
+                return;
+        flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+        if (flags == -1)
+                rb_sys_fail("fcntl(F_SETFL)");
+}
+
+#endif /* SLEEPY_PENGUIN_NONBLOCK_H */
diff --git a/ext/sleepy_penguin/timerfd.c b/ext/sleepy_penguin/timerfd.c
new file mode 100644
index 0000000..89ee774
--- /dev/null
+++ b/ext/sleepy_penguin/timerfd.c
@@ -0,0 +1,128 @@
+#ifdef HAVE_SYS_TIMERFD_H
+#include "sleepy_penguin.h"
+#include <sys/timerfd.h>
+#include "value2timespec.h"
+static ID id_for_fd;
+
+static VALUE create(int argc, VALUE *argv, VALUE klass)
+{
+        VALUE cid, fl;
+        int clockid, flags = 0;
+        int fd;
+
+        rb_scan_args(argc, argv, "02", &cid, &fl);
+        clockid = NIL_P(cid) ? CLOCK_MONOTONIC : NUM2INT(cid);
+        flags = NIL_P(fl) ? 0 : NUM2INT(fl);
+
+        fd = timerfd_create(clockid, flags);
+        if (fd == -1) {
+                if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
+                        rb_gc();
+                        fd = timerfd_create(clockid, flags);
+                }
+                if (fd == -1)
+                        rb_sys_fail("timerfd_create");
+        }
+
+        return rb_funcall(klass, id_for_fd, 1, INT2NUM(fd));
+}
+
+static VALUE itimerspec2ary(struct itimerspec *its)
+{
+        VALUE interval = timespec2num(&its->it_interval);
+        VALUE value = timespec2num(&its->it_value);
+
+        return rb_ary_new3(2, interval, value);
+}
+
+static VALUE settime(VALUE self, VALUE fl, VALUE interval, VALUE value)
+{
+        int fd = my_fileno(self);
+        int flags = NUM2INT(fl);
+        struct itimerspec old, new;
+
+        value2timespec(&new.it_interval, interval);
+        value2timespec(&new.it_value, value);
+
+        if (timerfd_settime(fd, flags, &new, &old) == -1)
+                rb_sys_fail("timerfd_settime");
+
+        return itimerspec2ary(&old);
+}
+
+static VALUE gettime(VALUE self)
+{
+        int fd = my_fileno(self);
+        struct itimerspec curr;
+
+        if (timerfd_gettime(fd, &curr) == -1)
+                rb_sys_fail("timerfd_gettime");
+
+        return itimerspec2ary(&curr);
+}
+
+#ifdef HAVE_RB_THREAD_BLOCKING_REGION
+static VALUE tfd_read(void *args)
+{
+        uint64_t *buf = args;
+        int fd = (int)(*buf);
+        ssize_t r = read(fd, buf, sizeof(uint64_t));
+
+        return (VALUE)r;
+}
+
+static VALUE expirations(VALUE self)
+{
+        ssize_t r;
+        uint64_t buf = (int)my_fileno(self);
+
+        r = (VALUE)rb_thread_blocking_region(tfd_read, &buf, RUBY_UBF_IO, 0);
+        if (r == -1)
+                rb_sys_fail("read(timerfd)");
+
+        return ULL2NUM(buf);
+}
+#else /* ! HAVE_RB_THREAD_BLOCKING_REGION */
+#include "nonblock.h"
+static VALUE expirations(VALUE self)
+{
+        int fd = my_fileno(self);
+        uint64_t buf;
+        ssize_t r;
+
+        set_nonblock(fd);
+retry:
+        r = read(fd, &buf, sizeof(uint64_t));
+        if (r == -1) {
+                if (rb_io_wait_readable(fd))
+                        goto retry;
+                rb_sys_fail("read(timerfd)");
+        }
+
+        return ULL2NUM(buf);
+}
+#endif
+
+void sleepy_penguin_init_timerfd(void)
+{
+        VALUE mSleepyPenguin, cTimerFD;
+
+        mSleepyPenguin = rb_const_get(rb_cObject, rb_intern("SleepyPenguin"));
+        cTimerFD = rb_define_class_under(mSleepyPenguin, "TimerFD", rb_cIO);
+        rb_define_singleton_method(cTimerFD, "create", create, -1);
+        rb_define_singleton_method(cTimerFD, "new", create, -1);
+        rb_define_const(cTimerFD, "REALTIME", UINT2NUM(CLOCK_REALTIME));
+        rb_define_const(cTimerFD, "MONOTONIC", UINT2NUM(CLOCK_MONOTONIC));
+        rb_define_const(cTimerFD, "ABSTIME", UINT2NUM(TFD_TIMER_ABSTIME));
+#ifdef TFD_NONBLOCK
+        rb_define_const(cTimerFD, "NONBLOCK", UINT2NUM(TFD_NONBLOCK));
+#endif
+#ifdef TFD_CLOEXEC
+        rb_define_const(cTimerFD, "CLOEXEC", UINT2NUM(TFD_CLOEXEC));
+#endif
+
+        rb_define_method(cTimerFD, "settime", settime, 3);
+        rb_define_method(cTimerFD, "expirations", expirations, 0);
+        id_for_fd = rb_intern("for_fd");
+}
+#endif /* HAVE_SYS_TIMERFD_H */
diff --git a/ext/sleepy_penguin/value2timespec.h b/ext/sleepy_penguin/value2timespec.h
new file mode 100644
index 0000000..d1bb7af
--- /dev/null
+++ b/ext/sleepy_penguin/value2timespec.h
@@ -0,0 +1,64 @@
+#ifndef VALUE2TIMESPEC_H
+#define VALUE2TIMESPEC_H
+
+#include <ruby.h>
+#include <math.h>
+#include <time.h>
+
+#ifndef NUM2TIMET
+#  define NUM2TIMET(n) NUM2LONG(n)
+#endif
+
+#ifndef RFLOAT_VALUE
+#  define RFLOAT_VALUE(v) (RFLOAT(v)->value)
+#endif
+
+static struct timespec *value2timespec(struct timespec *ts, VALUE num)
+{
+        switch (TYPE(num)) {
+        case T_FIXNUM:
+        case T_BIGNUM:
+                ts->tv_sec = NUM2TIMET(num);
+                ts->tv_nsec = 0;
+                return ts;
+        case T_FLOAT: {
+                double orig = RFLOAT_VALUE(num);
+                double f, d;
+
+                d = modf(orig, &f);
+                if (d >= 0) {
+                        ts->tv_nsec = (long)(d * 1e9 + 0.5);
+                } else {
+                        ts->tv_nsec = (long)(-d * 1e9 + 0.5);
+                        if (ts->tv_nsec > 0) {
+                                ts->tv_nsec = (long)1e9 - ts->tv_nsec;
+                                f -= 1;
+                        }
+                }
+                ts->tv_sec = (time_t)f;
+                if (f != ts->tv_sec)
+                        rb_raise(rb_eRangeError, "%f out of range", orig);
+                return ts;
+        }}
+        {
+                VALUE tmp = rb_inspect(num);
+                rb_raise(rb_eTypeError, "can't convert %s into timespec",
+                         StringValuePtr(tmp));
+        }
+        rb_bug("rb_raise() failed, timespec failed");
+        return NULL;
+}
+
+#ifndef TIMET2NUM
+#  define TIMET2NUM(n) LONG2NUM(n)
+#endif
+
+static VALUE timespec2num(struct timespec *ts)
+{
+        if (ts->tv_nsec == 0)
+                return TIMET2NUM(ts->tv_sec);
+
+        return rb_float_new(ts->tv_sec + ((double)ts->tv_nsec / 1e9));
+}
+
+#endif /* VALUE2TIMESPEC_H */
diff --git a/test/test_timerfd.rb b/test/test_timerfd.rb
new file mode 100644
index 0000000..0b6afae
--- /dev/null
+++ b/test/test_timerfd.rb
@@ -0,0 +1,38 @@
+require 'test/unit'
+require 'fcntl'
+$-w = true
+
+require 'sleepy_penguin'
+
+class TestTimerFD < Test::Unit::TestCase
+  include SleepyPenguin
+
+  def test_constants
+    assert_kind_of Integer, TimerFD::REALTIME
+    assert_kind_of Integer, TimerFD::MONOTONIC
+  end
+
+  def test_create
+    tfd = TimerFD.create
+    assert_kind_of(IO, tfd)
+  end
+
+  def test_create_nonblock
+    tfd = TimerFD.create(TimerFD::REALTIME, TimerFD::NONBLOCK)
+    flags = tfd.fcntl(Fcntl::F_GETFL) & Fcntl::O_NONBLOCK
+    assert_equal(Fcntl::O_NONBLOCK, flags)
+  end if defined?(TimerFD::NONBLOCK)
+
+  def test_create_cloexec
+    tfd = TimerFD.create(TimerFD::REALTIME, TimerFD::CLOEXEC)
+    flags = tfd.fcntl(Fcntl::F_GETFD) & Fcntl::FD_CLOEXEC
+    assert_equal(Fcntl::FD_CLOEXEC, flags)
+  end if defined?(TimerFD::CLOEXEC)
+
+  def test_settime
+    tfd = TimerFD.create(TimerFD::REALTIME)
+    assert_equal([0, 0], tfd.settime(TimerFD::ABSTIME, 0, 0.01))
+    sleep 0.01
+    assert_equal 1, tfd.expirations
+  end
+end if defined?(SleepyPenguin::TimerFD)