diff options
author | Eric Wong <e@yhbt.net> | 2011-01-15 11:46:13 +0000 |
---|---|---|
committer | Eric Wong <e@yhbt.net> | 2011-01-15 11:46:13 +0000 |
commit | 681c7b02f1e1d9ca70a5748ef986361840746c3d (patch) | |
tree | 122d12e7217518689f2d556c15ddec1ca738432c | |
parent | ab4f1a27e5d2c1688a33870b6d070aaa510ccdbc (diff) | |
download | sleepy_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.c | 8 | ||||
-rw-r--r-- | test/test_epoll_gc.rb | 47 |
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 |