about summary refs log tree commit homepage
path: root/lib/yahns/queue_epoll.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-10-31 04:49:22 +0000
committerEric Wong <normalperson@yhbt.net>2013-10-31 04:49:22 +0000
commitbfe699496ced8f73f659da0f0857fda614fb40b6 (patch)
treecc10c8afcdc4d3baab142a4b5181b7c9ae75087e /lib/yahns/queue_epoll.rb
parentb4e8651a50733f256c065e576c2d9198c3911532 (diff)
downloadyahns-bfe699496ced8f73f659da0f0857fda614fb40b6.tar.gz
With a GVL-free Ruby implementation, the following situation
may occur as FDs are recycled frequently with 3 threads
running:

expiry                 | normal    | acceptor
-----------------------+-----------+----------------------
yahns_expire(fd).enter |           |
                       | close(fd) |
                       |           | accept().return => fd
shutdown(fd) - WRONG   |           |
-----------------------+-----------+----------------------

So we must prevent expiry from running while another thread
is closing, otherwise there is a small chance a lack of
lock inside the Ruby implementation itself can lead to a
mis-issued close() to a fast-recycled FD.
Diffstat (limited to 'lib/yahns/queue_epoll.rb')
-rw-r--r--lib/yahns/queue_epoll.rb13
1 files changed, 8 insertions, 5 deletions
diff --git a/lib/yahns/queue_epoll.rb b/lib/yahns/queue_epoll.rb
index 3d2e33f..665b87c 100644
--- a/lib/yahns/queue_epoll.rb
+++ b/lib/yahns/queue_epoll.rb
@@ -27,7 +27,9 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
     Thread.current[:yahns_fdmap] = @fdmap
   end
 
+  # use only before hijacking, once hijacked, io may be unusable to us
   def queue_del(io)
+    @fdmap.forget(io)
     epoll_ctl(Epoll::CTL_DEL, io, 0)
   end
 
@@ -45,12 +47,13 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
           when :wait_readwrite
             epoll_ctl(Epoll::CTL_MOD, io, QEV_RDWR)
           when :ignore # only used by rack.hijack
-            @fdmap.decr
+            # we cannot call Epoll::CTL_DEL after hijacking, the hijacker
+            # may have already closed it  Likewise, io.fileno is not
+            # expected to work, so we had to erase it from fdmap before hijack
           when nil, :close
-            # this is be the ONLY place where we call IO#close on
-            # things inside the queue
-            io.close
-            @fdmap.decr
+            # this must be the ONLY place where we call IO#close on
+            # things that got inside the queue
+            @fdmap.sync_close(io)
           else
             raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
           end