diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/unicorn.rb | 2 | ||||
-rw-r--r-- | lib/unicorn/configurator.rb | 13 | ||||
-rw-r--r-- | lib/unicorn/http_request.rb | 94 | ||||
-rw-r--r-- | lib/unicorn/http_server.rb | 1 | ||||
-rw-r--r-- | lib/unicorn/socket_helper.rb | 23 | ||||
-rw-r--r-- | lib/unicorn/stream_input.rb | 9 | ||||
-rw-r--r-- | lib/unicorn/tee_input.rb | 14 |
7 files changed, 128 insertions, 28 deletions
diff --git a/lib/unicorn.rb b/lib/unicorn.rb index f122563..4bd7bda 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -95,7 +95,7 @@ module Unicorn # returns an array of strings representing TCP listen socket addresses # and Unix domain socket paths. This is useful for use with - # Raindrops::Middleware under Linux: http://raindrops.bogomips.org/ + # Raindrops::Middleware under Linux: https://bogomips.org/raindrops/ def self.listener_names Unicorn::HttpServer::LISTENERS.map do |io| Unicorn::SocketHelper.sock_name(io) diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index f69f220..f404aea 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -56,7 +56,7 @@ class Unicorn::Configurator :worker_exec => false, :preload_app => false, :check_client_connection => false, - :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1), + :rewindable_input => true, :client_body_buffer_size => Unicorn::Const::MAX_BODY, } #:startdoc: @@ -515,13 +515,12 @@ class Unicorn::Configurator # 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. + # \env[\"rack.input\"] to be rewindable, + # but the Rack 2.x spec does not. # - # +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. + # +rewindable_input+ defaults to +true+ for compatibility. + # Setting it to +false+ may be safe for applications and + # frameworks developed for Rack 2.x and later. def rewindable_input(bool) set_bool(:rewindable_input, bool) end diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index c176083..7253497 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -2,6 +2,7 @@ # :enddoc: # no stable API here require 'unicorn_http' +require 'raindrops' # TODO: remove redundant names Unicorn.const_set(:HttpRequest, Unicorn::HttpParser) @@ -25,8 +26,10 @@ class Unicorn::HttpParser # :stopdoc: HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ] + EMPTY_ARRAY = [].freeze @@input_class = Unicorn::TeeInput @@check_client_connection = false + @@tcpi_inspect_ok = true def self.input_class @@input_class @@ -80,11 +83,7 @@ class Unicorn::HttpParser false until add_parse(socket.kgio_read!(16384)) end - # detect if the socket is valid by writing a partial response: - if @@check_client_connection && headers? - self.response_start_sent = true - HTTP_RESPONSE_START.each { |c| socket.write(c) } - end + check_client_connection(socket) if @@check_client_connection e['rack.input'] = 0 == content_length ? NULL_IO : @@input_class.new(socket, self) @@ -105,4 +104,89 @@ class Unicorn::HttpParser def hijacked? env.include?('rack.hijack_io'.freeze) end + + if Raindrops.const_defined?(:TCP_Info) + TCPI = Raindrops::TCP_Info.allocate + + def check_client_connection(socket) # :nodoc: + if Unicorn::TCPClient === socket + # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state) + raise Errno::EPIPE, "client closed connection".freeze, + EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state) + else + write_http_header(socket) + end + end + + if Raindrops.const_defined?(:TCP) + # raindrops 0.18.0+ supports FreeBSD + Linux using the same names + # Evaluate these hash lookups at load time so we can + # generate an opt_case_dispatch instruction + eval <<-EOS + def closed_state?(state) # :nodoc: + case state + when #{Raindrops::TCP[:ESTABLISHED]} + false + when #{Raindrops::TCP.values_at( + :CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')} + true + else + false + end + end + EOS + else + # raindrops before 0.18 only supported TCP_INFO under Linux + def closed_state?(state) # :nodoc: + case state + when 1 # ESTABLISHED + false + when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING + true + else + false + end + end + end + else + + # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect. + # Not that efficient, but probably still better than doing unnecessary + # work after a client gives up. + def check_client_connection(socket) # :nodoc: + if Unicorn::TCPClient === socket && @@tcpi_inspect_ok + opt = socket.getsockopt(:IPPROTO_TCP, :TCP_INFO).inspect + if opt =~ /\bstate=(\S+)/ + @@tcpi_inspect_ok = true + raise Errno::EPIPE, "client closed connection".freeze, + EMPTY_ARRAY if closed_state_str?($1) + else + @@tcpi_inspect_ok = false + write_http_header(socket) + end + opt.clear + else + write_http_header(socket) + end + end + + def closed_state_str?(state) + case state + when 'ESTABLISHED' + false + # not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D') + when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING' + true + else + false + end + end + end + + def write_http_header(socket) # :nodoc: + if headers? + self.response_start_sent = true + HTTP_RESPONSE_START.each { |c| socket.write(c) } + end + end end diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 40a154d..3827f2e 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -89,6 +89,7 @@ class Unicorn::HttpServer @self_pipe = [] @workers = {} # hash maps PIDs to Workers @sig_queue = [] # signal queue used for self-piping + @pid = nil # we try inheriting listeners first, so we bind them later. # we don't write the pid file until we've bound listeners in case diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb index df8315e..f52dde2 100644 --- a/lib/unicorn/socket_helper.rb +++ b/lib/unicorn/socket_helper.rb @@ -3,6 +3,18 @@ require 'socket' module Unicorn + + # Instead of using a generic Kgio::Socket for everything, + # tag TCP sockets so we can use TCP_INFO under Linux without + # incurring extra syscalls for Unix domain sockets. + # TODO: remove these when we remove kgio + TCPClient = Class.new(Kgio::Socket) # :nodoc: + class TCPSrv < Kgio::TCPServer # :nodoc: + def kgio_tryaccept # :nodoc: + super(TCPClient) + end + end + module SocketHelper # internal interface @@ -63,12 +75,15 @@ module Unicorn elsif respond_to?(:accf_arg) name = opt[:accept_filter] name = DEFAULTS[:accept_filter] if name.nil? + sock.listen(opt[:backlog]) + got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s + arg = accf_arg(name) begin - sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name)) + sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg) rescue => e logger.error("#{sock_name(sock)} " \ "failed to set accept_filter=#{name} (#{e.inspect})") - end + end if arg != got end end @@ -148,7 +163,7 @@ module Unicorn end sock.bind(Socket.pack_sockaddr_in(port, addr)) sock.autoclose = false - Kgio::TCPServer.for_fd(sock.fileno) + TCPSrv.for_fd(sock.fileno) end # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6 @@ -185,7 +200,7 @@ module Unicorn def server_cast(sock) begin Socket.unpack_sockaddr_in(sock.getsockname) - Kgio::TCPServer.for_fd(sock.fileno) + TCPSrv.for_fd(sock.fileno) rescue ArgumentError Kgio::UNIXServer.for_fd(sock.fileno) end diff --git a/lib/unicorn/stream_input.rb b/lib/unicorn/stream_input.rb index de5aeea..41d28a0 100644 --- a/lib/unicorn/stream_input.rb +++ b/lib/unicorn/stream_input.rb @@ -1,16 +1,17 @@ # -*- encoding: binary -*- -# When processing uploads, Unicorn may expose a StreamInput object under -# "rack.input" of the (future) Rack (2.x) environment. +# When processing uploads, unicorn may expose a StreamInput object under +# "rack.input" of the Rack environment when +# Unicorn::Configurator#rewindable_input is set to +false+ class Unicorn::StreamInput # The I/O chunk size (in +bytes+) for I/O operations where # the size cannot be user-specified when a method is called. # The default is 16 kilobytes. - @@io_chunk_size = Unicorn::Const::CHUNK_SIZE + @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc: # Initializes a new StreamInput object. You normally do not have to call # this unless you are writing an HTTP server. - def initialize(socket, request) + def initialize(socket, request) # :nodoc: @chunked = request.content_length.nil? @socket = socket @parser = request diff --git a/lib/unicorn/tee_input.rb b/lib/unicorn/tee_input.rb index 6f66162..2ccc2d9 100644 --- a/lib/unicorn/tee_input.rb +++ b/lib/unicorn/tee_input.rb @@ -1,6 +1,6 @@ # -*- encoding: binary -*- -# acts like tee(1) on an input input to provide a input-like stream +# Acts like tee(1) on an input input to provide a input-like stream # while providing rewindable semantics through a File/StringIO backing # store. On the first pass, the input is only read on demand so your # Rack application can use input notification (upload progress and @@ -9,22 +9,22 @@ # strict interpretation of Rack::Lint::InputWrapper functionality and # will not support any deviations from it. # -# When processing uploads, Unicorn exposes a TeeInput object under -# "rack.input" of the Rack environment. +# When processing uploads, unicorn exposes a TeeInput object under +# "rack.input" of the Rack environment by default. class Unicorn::TeeInput < Unicorn::StreamInput # The maximum size (in +bytes+) to buffer in memory before # resorting to a temporary file. Default is 112 kilobytes. - @@client_body_buffer_size = Unicorn::Const::MAX_BODY + @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc: # sets the maximum size of request bodies to buffer in memory, # amounts larger than this are buffered to the filesystem - def self.client_body_buffer_size=(bytes) + def self.client_body_buffer_size=(bytes) # :nodoc: @@client_body_buffer_size = bytes end # returns the maximum size of request bodies to buffer in memory, # amounts larger than this are buffered to the filesystem - def self.client_body_buffer_size + def self.client_body_buffer_size # :nodoc: @@client_body_buffer_size end @@ -37,7 +37,7 @@ class Unicorn::TeeInput < Unicorn::StreamInput # Initializes a new TeeInput object. You normally do not have to call # this unless you are writing an HTTP server. - def initialize(socket, request) + def initialize(socket, request) # :nodoc: @len = request.content_length super @tmp = @len && @len <= @@client_body_buffer_size ? |