From a89ccf321224f3248ddd00bb0edb320311604e4e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Nov 2010 02:16:50 +0800 Subject: configurator: enable "rewindable_input" directive 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. --- TODO | 3 --- lib/unicorn/configurator.rb | 32 ++++++++++++++++++++++++++------ lib/unicorn/http_request.rb | 13 +++++++++++-- lib/unicorn/http_server.rb | 9 +++++++++ t/t0013-rewindable-input-false.sh | 24 ++++++++++++++++++++++++ t/t0014-rewindable-input-true.sh | 24 ++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100755 t/t0013-rewindable-input-false.sh create mode 100755 t/t0014-rewindable-input-true.sh 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 env["rack.input"] 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 + # env["rack.input"] 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 -- cgit v1.2.3-24-ge0c7