diff options
author | Eric Wong <e@yhbt.net> | 2010-09-20 16:49:57 +0000 |
---|---|---|
committer | Eric Wong <normalperson@yhbt.net> | 2010-09-26 03:52:39 +0000 |
commit | 2d5886698c3f7241ab23771c2876b985708ded40 (patch) | |
tree | 89d3b0b54f8927961b6cafee92d7ee4ff2581c59 | |
parent | 4a9ce0319f6e0250c4a8e01284fd5684936bde21 (diff) | |
download | sleepy_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.c | 7 | ||||
-rw-r--r-- | ext/sleepy_penguin/nonblock.h | 19 | ||||
-rw-r--r-- | ext/sleepy_penguin/timerfd.c | 128 | ||||
-rw-r--r-- | ext/sleepy_penguin/value2timespec.h | 64 | ||||
-rw-r--r-- | test/test_timerfd.rb | 38 |
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) |