summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-11-11 02:16:50 +0800
committerEric Wong <normalperson@yhbt.net>2010-11-11 07:18:20 +0800
commita89ccf321224f3248ddd00bb0edb320311604e4e (patch)
tree669d6af1eee6c70d72bdeba6f77d9a7114cf54ed
parent7d44b5384758aeddcb49d7606a9908308df7c698 (diff)
This allows users to override the current Rack spec and disable
the rewindable input requirement.  This can allow applications
to use less I/O to minimize the performance impact when
processing uploads.
-rw-r--r--TODO3
-rw-r--r--lib/unicorn/configurator.rb32
-rw-r--r--lib/unicorn/http_request.rb13
-rw-r--r--lib/unicorn/http_server.rb9
-rwxr-xr-xt/t0013-rewindable-input-false.sh24
-rwxr-xr-xt/t0014-rewindable-input-true.sh24
6 files changed, 94 insertions, 11 deletions
diff --git a/TODO b/TODO
index 971bd18..edbc8f8 100644
--- a/TODO
+++ b/TODO
@@ -7,6 +7,3 @@
 * scalability to >= 1024 worker processes for crazy NUMA systems
 
 * Rack 2.x support (when Rack 2.x exists)
-
-* allow disabling "rack.input" rewindability for performance
-  (but violate the Rack 1.x SPEC)
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb
index dd515a7..2a83dea 100644
--- a/lib/unicorn/configurator.rb
+++ b/lib/unicorn/configurator.rb
@@ -39,6 +39,7 @@ class Unicorn::Configurator
       },
     :pid => nil,
     :preload_app => false,
+    :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
   }
   #:startdoc:
 
@@ -373,12 +374,22 @@ class Unicorn::Configurator
   # cause the master process to exit with an error.
 
   def preload_app(bool)
-    case bool
-    when TrueClass, FalseClass
-      set[:preload_app] = bool
-    else
-      raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
-    end
+    set_bool(:preload_app, bool)
+  end
+
+  # Toggles making <code>env["rack.input"]</code> rewindable.
+  # Disabling rewindability can improve performance by lowering
+  # I/O and memory usage for applications that accept uploads.
+  # Keep in mind that the Rack 1.x spec requires
+  # <code>env["rack.input"]</code> to be rewindable, so this allows
+  # intentionally violating the current Rack 1.x spec.
+  #
+  # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
+  # Rack conformance.  When Rack 2.x is finalized, this will most
+  # likely default to +false+ while still conforming to the newer
+  # (less demanding) spec.
+  def rewindable_input(bool)
+    set_bool(:rewindable_input, bool)
   end
 
   # Allow redirecting $stderr to a given path.  Unlike doing this from
@@ -469,6 +480,15 @@ private
     end
   end
 
+  def set_bool(var, bool) #:nodoc:
+    case bool
+    when true, false
+      set[var] = bool
+    else
+      raise ArgumentError, "#{var}=#{bool.inspect} not a boolean"
+    end
+  end
+
   def set_hook(var, my_proc, req_arity = 2) #:nodoc:
     case my_proc
     when Proc
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index 2dcd839..1e3ac26 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -25,7 +25,15 @@ class Unicorn::HttpParser
   # A frozen format for this is about 15% faster
   REMOTE_ADDR = 'REMOTE_ADDR'.freeze
   RACK_INPUT = 'rack.input'.freeze
-  TeeInput = Unicorn::TeeInput
+  @@input_class = Unicorn::TeeInput
+
+  def self.input_class
+    @@input_class
+  end
+
+  def self.input_class=(klass)
+    @@input_class = klass
+  end
   # :startdoc:
 
   # Does the majority of the IO processing.  It has been written in
@@ -63,7 +71,8 @@ class Unicorn::HttpParser
         buf << socket.kgio_read!(16384)
       end while parse.nil?
     end
-    e[RACK_INPUT] = 0 == content_length ? NULL_IO : TeeInput.new(socket, self)
+    e[RACK_INPUT] = 0 == content_length ?
+                    NULL_IO : @@input_class.new(socket, self)
     e.merge!(DEFAULTS)
   end
 end
diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb
index 69b7cc8..f0dca7c 100644
--- a/lib/unicorn/http_server.rb
+++ b/lib/unicorn/http_server.rb
@@ -355,6 +355,15 @@ class Unicorn::HttpServer
     kill_each_worker(:KILL)
   end
 
+  def rewindable_input
+    Unicorn::HttpRequest.input_class.method_defined?(:rewind)
+  end
+
+  def rewindable_input=(bool)
+    Unicorn::HttpRequest.input_class = bool ?
+                                Unicorn::TeeInput : Unicorn::StreamInput
+  end
+
   private
 
   # wait for a signal hander to wake us up and then consume the pipe
diff --git a/t/t0013-rewindable-input-false.sh b/t/t0013-rewindable-input-false.sh
new file mode 100755
index 0000000..0e89631
--- /dev/null
+++ b/t/t0013-rewindable-input-false.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+. ./test-lib.sh
+t_plan 4 "rewindable_input toggled to false"
+
+t_begin "setup and start" && {
+        unicorn_setup
+        echo rewindable_input false >> $unicorn_config
+        unicorn -D -c $unicorn_config t0013.ru
+        unicorn_wait_start
+}
+
+t_begin "ensure worker is started" && {
+        test xOK = x$(curl -T t0013.ru -H Expect: -vsSf http://$listen/)
+}
+
+t_begin "killing succeeds" && {
+        kill $unicorn_pid
+}
+
+t_begin "check stderr" && {
+        check_stderr
+}
+
+t_done
diff --git a/t/t0014-rewindable-input-true.sh b/t/t0014-rewindable-input-true.sh
new file mode 100755
index 0000000..dd48bc6
--- /dev/null
+++ b/t/t0014-rewindable-input-true.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+. ./test-lib.sh
+t_plan 4 "rewindable_input toggled to true"
+
+t_begin "setup and start" && {
+        unicorn_setup
+        echo rewindable_input true >> $unicorn_config
+        unicorn -D -c $unicorn_config t0014.ru
+        unicorn_wait_start
+}
+
+t_begin "ensure worker is started" && {
+        test xOK = x$(curl -T t0014.ru -sSf http://$listen/)
+}
+
+t_begin "killing succeeds" && {
+        kill $unicorn_pid
+}
+
+t_begin "check stderr" && {
+        check_stderr
+}
+
+t_done