about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2014-02-08 03:30:28 -0500
committerEric Wong <e@80x24.org>2014-02-08 08:39:16 +0000
commitf46aea4aa26d3c3c5613d6800b2832cddb14b754 (patch)
tree4acb585e193f3add8b47e30b694c59b16e6df7ec
parent922e7141e30fc0ef5fc973716fab20a272531300 (diff)
downloadyahns-f46aea4aa26d3c3c5613d6800b2832cddb14b754.tar.gz
Tested on Debian GNU/kFreeBSD (sid):

	make check SENDFILE_BROKEN=1
-rw-r--r--INSTALL7
-rw-r--r--lib/yahns.rb2
-rw-r--r--lib/yahns/queue_kqueue.rb82
-rw-r--r--lib/yahns/sendfile_compat.rb29
-rw-r--r--lib/yahns/wbuf_common.rb7
5 files changed, 125 insertions, 2 deletions
diff --git a/INSTALL b/INSTALL
index bd269f2..87acc53 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,3 +6,10 @@ No tarballs are currently provided.
 
 You may also install yahns from the git source, see the HACKING document for
 more details.
+
+Debian GNU/kFreeBSD:
+
+For now, you will need to define SENDFILE_BROKEN=1 in the env before
+running yahns, and also install the "io-extra" RubyGem (tested on 1.2.7)
+
+        gem install -v 1.2.7 io-extra
diff --git a/lib/yahns.rb b/lib/yahns.rb
index 5ffb845..5e5875a 100644
--- a/lib/yahns.rb
+++ b/lib/yahns.rb
@@ -57,7 +57,7 @@ end
 
 # FIXME: require lazily
 require_relative 'yahns/log'
-require_relative 'yahns/queue_epoll'
+require_relative 'yahns/queue'
 require_relative 'yahns/stream_input'
 require_relative 'yahns/tee_input'
 require_relative 'yahns/queue_egg'
diff --git a/lib/yahns/queue_kqueue.rb b/lib/yahns/queue_kqueue.rb
new file mode 100644
index 0000000..c502de0
--- /dev/null
+++ b/lib/yahns/queue_kqueue.rb
@@ -0,0 +1,82 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2014, Eric Wong <normalperson@yhbt.net> and all contributors
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# This is the dangerous, low-level kqueue interface for sleepy_penguin
+# It is safe as long as you're aware of all potential concurrency
+# issues given multithreading, GC, and kqueue itself.
+class Yahns::Queue < SleepyPenguin::Kqueue::IO # :nodoc:
+  include SleepyPenguin
+  attr_accessor :fdmap # Yahns::Fdmap
+
+  # public
+  QEV_QUIT = nil # Level Trigger for QueueQuitter
+  QEV_RD = EvFilt::READ
+  QEV_WR = EvFilt::WRITE
+
+  ADD_ONESHOT = Ev::ADD | Ev::ONESHOT # private
+
+  def self.new
+    rv = super
+    rv.close_on_exec = true
+    rv
+  end
+
+  # for HTTP and HTTPS servers, we rely on the io writing to us, first
+  # flags: QEV_RD/QEV_WR (usually QEV_RD)
+  def queue_add(io, flags)
+    # order is very important here, this thread cannot do anything with
+    # io once we've issued epoll_ctl() because another thread may use it
+    @fdmap.add(io)
+    fflags = ADD_ONESHOT
+    if flags == QEV_QUIT
+      fflags = Ev::ADD
+      flags = QEV_WR
+    end
+    kevent(Kevent[io.fileno, flags, fflags, 0, 0, io])
+  end
+
+  def thr_init
+    Thread.current[:yahns_rbuf] = ""
+    Thread.current[:yahns_fdmap] = @fdmap
+  end
+
+  def queue_del(io)
+    # do not bother with kevent EV_DELETE, it may be tricky to get right,
+    # we only did it in epoll since Eric knows the epoll internals well.
+    @fdmap.forget(io)
+  end
+
+  # returns an array of infinitely running threads
+  def worker_thread(logger, max_events)
+    Thread.new do
+      thr_init
+      begin
+        kevent(nil, max_events) do |_,_,_,_,_,io| # don't care for flags for now
+          # Note: we absolutely must not do anything with io after
+          # we've called epoll_ctl on it, io is exclusive to this
+          # thread only until epoll_ctl is called on it.
+          case rv = io.yahns_step
+          when :wait_readable
+            kevent(Kevent[io.fileno, QEV_RD, ADD_ONESHOT, 0, 0, io])
+          when :wait_writable
+            kevent(Kevent[io.fileno, QEV_WR, ADD_ONESHOT, 0, 0, io])
+          when :ignore # only used by rack.hijack
+            # we cannot EV_DELETE 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 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
+        end
+      rescue => e
+        break if closed? # can still happen due to shutdown_timeout
+        Yahns::Log.exception(logger, 'queue loop', e)
+      end while true
+    end
+  end
+end
diff --git a/lib/yahns/sendfile_compat.rb b/lib/yahns/sendfile_compat.rb
new file mode 100644
index 0000000..e3f53d1
--- /dev/null
+++ b/lib/yahns/sendfile_compat.rb
@@ -0,0 +1,29 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2009-2014, Eric Wong <normalperson@yhbt.net> et. al.
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+require 'io/extra' # gem install io-extra
+
+module Yahns::SendfileCompat
+  def trysendfile(io, offset, count)
+    return 0 if count == 0
+    count = 0x4000 if count > 0x4000
+    str = IO.pread(io.fileno, count, offset)
+    if count > str.bytesize
+      raise EOFError, "end of file reached"
+    end
+    n = 0
+    case rv = kgio_trywrite(str)
+    when String # partial write, keep trying
+      n += (str.bytesize - rv.bytesize)
+      str = rv
+    when :wait_writable, :wait_readable
+      return n > 0 ? n : rv
+    when nil
+      return n + str.bytesize # yay!
+    end while true
+  end
+end
+
+class IO
+  include Yahns::SendfileCompat
+end
diff --git a/lib/yahns/wbuf_common.rb b/lib/yahns/wbuf_common.rb
index a3e737b..01e20bb 100644
--- a/lib/yahns/wbuf_common.rb
+++ b/lib/yahns/wbuf_common.rb
@@ -1,7 +1,12 @@
 # -*- encoding: binary -*-
 # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
 # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
-require 'sendfile'
+if ENV["SENDFILE_BROKEN"]
+  require_relative 'sendfile_compat'
+else
+  require 'sendfile'
+end
+
 module Yahns::WbufCommon # :nodoc:
   # returns nil on success, :wait_*able when blocked
   # currently, we rely on each thread having exclusive access to the