about summary refs log tree commit homepage
path: root/lib/rainbows/revactor/client
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rainbows/revactor/client')
-rw-r--r--lib/rainbows/revactor/client/methods.rb45
-rw-r--r--lib/rainbows/revactor/client/tee_socket.rb44
2 files changed, 89 insertions, 0 deletions
diff --git a/lib/rainbows/revactor/client/methods.rb b/lib/rainbows/revactor/client/methods.rb
new file mode 100644
index 0000000..e9b39a3
--- /dev/null
+++ b/lib/rainbows/revactor/client/methods.rb
@@ -0,0 +1,45 @@
+# -*- encoding: binary -*-
+# :enddoc:
+module Rainbows::Revactor::Client::Methods
+  if IO.method_defined?(:sendfile_nonblock)
+    def write_body_file(body, range)
+      body, client = body_to_io(body), @client
+      sock = @client.instance_variable_get(:@_io)
+      pfx = Revactor::TCP::Socket === client ? :tcp : :unix
+      write_complete = T[:"#{pfx}_write_complete", client]
+      closed = T[:"#{pfx}_closed", client]
+      offset, count = range ? range : [ 0, body.stat.size ]
+      begin
+        offset += (n = sock.sendfile_nonblock(body, offset, count))
+      rescue Errno::EAGAIN
+        # The @_write_buffer is empty at this point, trigger the
+        # on_readable method which in turn triggers on_write_complete
+        # even though nothing was written
+        client.controller = Actor.current
+        client.__send__(:enable_write_watcher)
+        Actor.receive do |filter|
+          filter.when(write_complete) {}
+          filter.when(closed) { raise Errno::EPIPE }
+        end
+        retry
+      rescue EOFError
+        break
+      end while (count -= n) > 0
+      ensure
+        close_if_private(body)
+    end
+  end
+
+  def handle_error(e)
+    Revactor::TCP::ReadError === e or super
+  end
+
+  def write_response(status, headers, body, alive)
+    super(status, headers, body, alive)
+    alive && @ts and @hp.buf << @ts.leftover
+  end
+
+  def self.included(klass)
+    klass.__send__ :alias_method, :write_body_stream, :write_body_each
+  end
+end
diff --git a/lib/rainbows/revactor/client/tee_socket.rb b/lib/rainbows/revactor/client/tee_socket.rb
new file mode 100644
index 0000000..2f9f52e
--- /dev/null
+++ b/lib/rainbows/revactor/client/tee_socket.rb
@@ -0,0 +1,44 @@
+# -*- encoding: binary -*-
+# :enddoc:
+#
+# Revactor Sockets do not implement readpartial, so we emulate just
+# enough to avoid mucking with TeeInput internals.  Fortunately
+# this code is not heavily used so we can usually avoid the overhead
+# of adding a userspace buffer.
+class Rainbows::Revactor::Client::TeeSocket
+  def initialize(socket)
+    # IO::Buffer is used internally by Rev which Revactor is based on
+    # so we'll always have it available
+    @socket, @rbuf = socket, IO::Buffer.new
+  end
+
+  def leftover
+    @rbuf.read
+  end
+
+  # Revactor socket reads always return an unspecified amount,
+  # sometimes too much
+  def kgio_read(length, dst = "")
+    return dst.replace("") if length == 0
+
+    # always check and return from the userspace buffer first
+    @rbuf.size > 0 and return dst.replace(@rbuf.read(length))
+
+    # read off the socket since there was nothing in rbuf
+    tmp = @socket.read
+
+    # we didn't read too much, good, just return it straight back
+    # to avoid needlessly wasting memory bandwidth
+    tmp.size <= length and return dst.replace(tmp)
+
+    # ugh, read returned too much
+    @rbuf << tmp[length, tmp.size]
+    dst.replace(tmp[0, length])
+    rescue EOFError
+  end
+
+  # just proxy any remaining methods TeeInput may use
+  def close
+    @socket.close
+  end
+end