about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--ext/sleepy_penguin/eventfd.c177
-rw-r--r--ext/sleepy_penguin/init.c7
-rw-r--r--test/test_eventfd.rb48
3 files changed, 232 insertions, 0 deletions
diff --git a/ext/sleepy_penguin/eventfd.c b/ext/sleepy_penguin/eventfd.c
new file mode 100644
index 0000000..e0a295c
--- /dev/null
+++ b/ext/sleepy_penguin/eventfd.c
@@ -0,0 +1,177 @@
+#ifdef HAVE_SYS_EVENTFD_H
+#include "sleepy_penguin.h"
+#include <sys/eventfd.h>
+#include "nonblock.h"
+static ID id_for_fd;
+
+static VALUE create(int argc, VALUE *argv, VALUE klass)
+{
+        VALUE _initval, _flags;
+        unsigned initval;
+        int flags = 0;
+        int fd;
+
+        rb_scan_args(argc, argv, "11", &_initval, &_flags);
+        initval = NUM2UINT(_initval);
+        flags = NIL_P(_flags) ? 0 : NUM2INT(_flags);
+
+        fd = eventfd(initval, flags);
+        if (fd == -1) {
+                if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
+                        rb_gc();
+                        fd = eventfd(initval, flags);
+                }
+                if (fd == -1)
+                        rb_sys_fail("eventfd");
+        }
+
+        return rb_funcall(klass, id_for_fd, 1, INT2NUM(fd));
+}
+
+#ifdef HAVE_RB_THREAD_BLOCKING_REGION
+struct efd_args {
+        int fd;
+        uint64_t val;
+};
+
+static VALUE efd_write(void *_args)
+{
+        struct efd_args *args = _args;
+        ssize_t w = write(args->fd, &args->buf, sizeof(uint64_t));
+
+        return (VALUE)w;
+}
+
+static VALUE efd_read(void *_args)
+{
+        struct efd_args *args = _args;
+        ssize_t r = read(args->fd, &args->buf, sizeof(uint64_t));
+
+        return (VALUE)r;
+}
+
+static VALUE incr(VALUE self, VALUE value)
+{
+        struct efd_args x;
+        ssize_t w;
+
+        x.fd = my_fileno(self);
+        x.val = (uint64_t)NUM2ULL(value);
+
+retry:
+        w = (ssize_t)rb_thread_blocking_region(efd_write, &x, RUBY_UBF_IO, 0);
+        if (w == -1) {
+                if (rb_io_wait_writable(x.fd))
+                        goto retry;
+                rb_sys_fail("write(eventfd)");
+        }
+
+        return Qnil;
+}
+
+static VALUE getvalue(VALUE self)
+{
+        struct efd_args x;
+
+        x.fd = my_fileno(self);
+
+retry:
+        w = (ssize_t)rb_thread_blocking_region(efd_read, &x, RUBY_UBF_IO, 0);
+        if (w == -1) {
+                if (rb_io_wait_readable(x.fd))
+                        goto retry;
+                rb_sys_fail("read(eventfd)");
+        }
+
+        return ULL2NUM(x.buf);
+}
+#else  /* !HAVE_RB_THREAD_BLOCKING_REGION */
+
+static VALUE incr(VALUE self, VALUE value)
+{
+        int fd = my_fileno(self);
+        uint64_t val = (uint64_t)NUM2ULL(value);
+        ssize_t w;
+
+        set_nonblock(fd);
+retry:
+        w = write(fd, &val, sizeof(uint64_t));
+        if (w == -1) {
+                if (rb_io_wait_writable(fd))
+                        goto retry;
+                rb_sys_fail("write(eventfd)");
+        }
+
+        return Qnil;
+}
+
+static VALUE getvalue(VALUE self)
+{
+        int fd = my_fileno(self);
+        uint64_t val;
+        ssize_t r;
+
+        set_nonblock(fd);
+retry:
+        r = read(fd, &val, sizeof(uint64_t));
+        if (r == -1) {
+                if (rb_io_wait_readable(fd))
+                        goto retry;
+                rb_sys_fail("read(eventfd)");
+        }
+
+        return ULL2NUM(val);
+}
+#endif /* !HAVE_RB_THREAD_BLOCKING_REGION */
+
+static VALUE getvalue_nonblock(VALUE self)
+{
+        int fd = my_fileno(self);
+        uint64_t val;
+        ssize_t r;
+
+        set_nonblock(fd);
+        r = read(fd, &val, sizeof(uint64_t));
+        if (r == -1)
+                rb_sys_fail("read(eventfd)");
+
+        return ULL2NUM(val);
+}
+
+static VALUE incr_nonblock(VALUE self, VALUE value)
+{
+        int fd = my_fileno(self);
+        uint64_t val = (uint64_t)NUM2ULL(value);
+        ssize_t w;
+
+        set_nonblock(fd);
+        w = write(fd, &val, sizeof(uint64_t));
+        if (w == -1)
+                rb_sys_fail("write(eventfd)");
+
+        return Qnil;
+}
+
+void sleepy_penguin_init_eventfd(void)
+{
+        VALUE mSleepyPenguin, cEventFD;
+
+        mSleepyPenguin = rb_const_get(rb_cObject, rb_intern("SleepyPenguin"));
+        cEventFD = rb_define_class_under(mSleepyPenguin, "EventFD", rb_cIO);
+        rb_define_singleton_method(cEventFD, "new", create, -1);
+#ifdef EFD_NONBLOCK
+        rb_define_const(cEventFD, "NONBLOCK", UINT2NUM(EFD_NONBLOCK));
+#endif
+#ifdef EFD_CLOEXEC
+        rb_define_const(cEventFD, "CLOEXEC", UINT2NUM(EFD_CLOEXEC));
+#endif
+#ifdef EFD_SEMAPHORE
+        rb_define_const(cEventFD, "SEMAPHORE", UINT2NUM(EFD_SEMAPHORE));
+#endif
+        rb_define_method(cEventFD, "value", getvalue, 0);
+        rb_define_method(cEventFD, "incr", incr, 1);
+        rb_define_method(cEventFD, "value_nonblock", getvalue_nonblock, 0);
+        rb_define_method(cEventFD, "incr_nonblock", incr_nonblock, 1);
+        id_for_fd = rb_intern("for_fd");
+}
+#endif /* HAVE_SYS_EVENTFD_H */
diff --git a/ext/sleepy_penguin/init.c b/ext/sleepy_penguin/init.c
index dd959af..3a95cbc 100644
--- a/ext/sleepy_penguin/init.c
+++ b/ext/sleepy_penguin/init.c
@@ -6,8 +6,15 @@ void sleepy_penguin_init_timerfd(void);
 #  define sleepy_penguin_init_timerfd() if(0)
 #endif
 
+#ifdef HAVE_SYS_EVENTFD_H
+void sleepy_penguin_init_eventfd(void);
+#else
+#  define sleepy_penguin_init_eventfd() if(0)
+#endif
+
 void Init_sleepy_penguin_ext(void)
 {
         sleepy_penguin_init_epoll();
         sleepy_penguin_init_timerfd();
+        sleepy_penguin_init_eventfd();
 }
diff --git a/test/test_eventfd.rb b/test/test_eventfd.rb
new file mode 100644
index 0000000..4780c48
--- /dev/null
+++ b/test/test_eventfd.rb
@@ -0,0 +1,48 @@
+require 'test/unit'
+require 'fcntl'
+$-w = true
+
+require 'sleepy_penguin'
+
+class TestEventFD < Test::Unit::TestCase
+  include SleepyPenguin
+
+  def test_constants
+    defined?(EventFD::NONBLOCK) and
+      assert_kind_of Integer, EventFD::NONBLOCK
+    defined?(EventFD::CLOEXEC) and
+      assert_kind_of Integer, EventFD::CLOEXEC
+    defined?(EventFD::SEMAPHORE) and
+      assert_kind_of Integer, EventFD::SEMAPHORE
+  end
+
+  def test_new
+    efd = EventFD.new 0
+    assert_kind_of(IO, efd)
+  end
+
+  def test_new_nonblock
+    efd = EventFD.new(0, EventFD::NONBLOCK)
+    flags = efd.fcntl(Fcntl::F_GETFL) & Fcntl::O_NONBLOCK
+    assert_equal(Fcntl::O_NONBLOCK, flags)
+  end if defined?(EventFD::NONBLOCK)
+
+  def test_new_cloexec
+    efd = EventFD.new(0, EventFD::CLOEXEC)
+    flags = efd.fcntl(Fcntl::F_GETFD) & Fcntl::FD_CLOEXEC
+    assert_equal(Fcntl::FD_CLOEXEC, flags)
+  end if defined?(EventFD::CLOEXEC)
+
+  def test_incr_value
+    efd = EventFD.new(0)
+    assert_nil efd.incr(1)
+    assert_equal 1, efd.value
+
+    assert_raises(Errno::EAGAIN) { efd.value_nonblock }
+    assert_nil efd.incr(9)
+    assert_equal 9, efd.value_nonblock
+
+    assert_nil efd.incr(0xfffffffffffffffe)
+    assert_raises(Errno::EAGAIN) { efd.incr_nonblock 1 }
+  end
+end if defined?(SleepyPenguin::EventFD)