about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2011-01-15 11:46:13 +0000
committerEric Wong <e@yhbt.net>2011-01-15 11:46:13 +0000
commit681c7b02f1e1d9ca70a5748ef986361840746c3d (patch)
tree122d12e7217518689f2d556c15ddec1ca738432c
parentab4f1a27e5d2c1688a33870b6d070aaa510ccdbc (diff)
downloadsleepy_penguin-681c7b02f1e1d9ca70a5748ef986361840746c3d.tar.gz
Users of our code may forget to keep references for their IO
objects at all, and since it's not possible for GC to mark
kernel memory, we just hold on to the IO objects for them.

We can't unmark close()d file descriptors, ever, so we don't
bother with the EPOLL_CTL_DEL case, either.  Just storing IO
objects in an array using the raw descriptor as a key will allow
bounded space usage just like the in-kernel FD tables as long
as the user remembers to close descriptors themselves.
-rw-r--r--ext/sleepy_penguin/epoll.c8
-rw-r--r--test/test_epoll_gc.rb47
2 files changed, 55 insertions, 0 deletions
diff --git a/ext/sleepy_penguin/epoll.c b/ext/sleepy_penguin/epoll.c
index d06ece1..26995de 100644
--- a/ext/sleepy_penguin/epoll.c
+++ b/ext/sleepy_penguin/epoll.c
@@ -50,6 +50,7 @@ struct rb_epoll {
         int capa;
         struct epoll_event *events;
         VALUE io;
+        VALUE marks;
         int flags;
 };
 
@@ -93,6 +94,7 @@ static void gcmark(void *ptr)
         struct rb_epoll *ep = ptr;
 
         rb_gc_mark(ep->io);
+        rb_gc_mark(ep->marks);
 }
 
 static void gcfree(void *ptr)
@@ -122,6 +124,7 @@ static VALUE alloc(VALUE klass)
         self = Data_Make_Struct(klass, struct rb_epoll, gcmark, gcfree, ep);
         ep->fd = -1;
         ep->io = Qnil;
+        ep->marks = rb_ary_new();
         ep->capa = step;
         ep->flags = EPOLL_CLOEXEC;
         ep->events = xmalloc(sizeof(struct epoll_event) * ep->capa);
@@ -201,6 +204,8 @@ static VALUE ctl(VALUE self, VALUE io, VALUE flags, int op)
                 if (rv == -1)
                         rb_sys_fail("epoll_ctl");
         }
+        if (op == EPOLL_CTL_ADD)
+                rb_ary_store(ep->marks, fd, io);
 
         return INT2NUM(rv);
 }
@@ -226,6 +231,8 @@ static VALUE set(VALUE self, VALUE io, VALUE flags)
                         rv = epoll_ctl(ep->fd, EPOLL_CTL_ADD, fd, &event);
                         if (rv == -1)
                                 rb_sys_fail("epoll_ctl - add");
+
+                        rb_ary_store(ep->marks, fd, io);
                         return INT2NUM(rv);
                 }
                 rb_sys_fail("epoll_ctl - mod");
@@ -514,6 +521,7 @@ static VALUE init_copy(VALUE copy, VALUE orig)
                NIL_P(b->io) && "Ruby broken?");
 
         ep_check(a);
+        b->marks = a->marks;
         b->flags = a->flags;
         b->fd = cloexec_dup(a);
         if (b->fd == -1) {
diff --git a/test/test_epoll_gc.rb b/test/test_epoll_gc.rb
new file mode 100644
index 0000000..4a7c115
--- /dev/null
+++ b/test/test_epoll_gc.rb
@@ -0,0 +1,47 @@
+require 'test/unit'
+$-w = true
+
+require 'sleepy_penguin'
+
+class TestEpollGC < Test::Unit::TestCase
+  include SleepyPenguin
+
+  def setup
+    GC.stress = true if GC.respond_to?(:stress=)
+    @ep = Epoll.new
+  end
+
+  def teardown
+    GC.stress = false if GC.respond_to?(:stress=)
+  end
+
+  def add_pipe_no_tailcall(m, depth)
+    add_pipe(m, depth += 1)
+  end
+
+  def add_pipe(m, depth = 0)
+    if depth > 6000
+      rd, wr = IO.pipe
+      warn "wr: #{wr.fileno}"
+      @ep.__send__(m, wr, Epoll::OUT)
+    else
+      add_pipe_no_tailcall(m, depth + 1)
+    end
+  end
+
+  def test_gc_safety
+    done = false
+    begin
+      if done
+        x = nil
+        @ep.wait(nil, 10) { |flags, obj| p [  flags, x = obj ] }
+        assert x, "#{x.inspect}"
+        break
+      else
+        add_pipe(:add)
+        2048.times { IO.pipe; File.open(__FILE__)}
+        done = true
+      end
+    end while true
+  end
+end