about summary refs log tree commit homepage
path: root/lib/unicorn/http_request.rb
diff options
context:
space:
mode:
authorSimon Eskildsen <simon.eskildsen@shopify.com>2017-03-06 16:32:02 -0500
committerEric Wong <e@80x24.org>2017-03-07 22:43:29 +0000
commitbeaee769c6553bf4e0260be2507b8235f0aa764f (patch)
tree56b3d1904ce56bb1ddf8180cd44f076a1f0bc866 /lib/unicorn/http_request.rb
parent73e1ce827faad59bfcaff0bc758c8255a5e4f747 (diff)
downloadunicorn-ccc-tcp.tar.gz
* Use a frozen empty array and a class variable for TCP_Info to avoid
  garbage. As far as I can tell, this shouldn't result in any garbage on
  any requests (other than on the first request).
* Pass listener socket to #read to only check the client connection on
  a TCP server.
* Short circuit CLOSE_WAIT after ESTABLISHED since in my testing it's
  the most common state after ESTABLISHED, it makes the numbers
  un-ordered, though. But comment should make it OK.
* Definition of of `check_client_connection` based on whether
  Raindrops::TCP_Info is defined, instead of the class variable
  approach.
* Changed the unit tests to pass a `nil` listener.

Tested on our staging environment, and still works like a dream.

I should note that I got the idea between this patch into Puma as well!

https://github.com/puma/puma/pull/1227

[ew: squashed in temporary change for oob_gc.rb, but we'll come
 up with a different change to avoid breaking gctools
 <https://github.com/tmm1/gctools>]

Acked-by: Eric Wong <e@80x24.org>
Diffstat (limited to 'lib/unicorn/http_request.rb')
-rw-r--r--lib/unicorn/http_request.rb44
1 files changed, 38 insertions, 6 deletions
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index c176083..7d9a103 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)
@@ -59,7 +60,7 @@ class Unicorn::HttpParser
   # returns an environment hash suitable for Rack if successful
   # This does minimal exception trapping and it is up to the caller
   # to handle any socket errors (e.g. user aborted upload).
-  def read(socket)
+  def read(socket, listener)
     clear
     e = env
 
@@ -80,11 +81,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, listener) if @@check_client_connection
 
     e['rack.input'] = 0 == content_length ?
                       NULL_IO : @@input_class.new(socket, self)
@@ -105,4 +102,39 @@ class Unicorn::HttpParser
   def hijacked?
     env.include?('rack.hijack_io'.freeze)
   end
+
+  if defined?(Raindrops::TCP_Info)
+    def check_client_connection(socket, listener) # :nodoc:
+      if Kgio::TCPServer === listener
+        @@tcp_info ||= Raindrops::TCP_Info.new(socket)
+        @@tcp_info.get!(socket)
+        raise Errno::EPIPE, "client closed connection".freeze,
+              EMPTY_ARRAY if closed_state?(@@tcp_info.state)
+      else
+        write_http_header(socket)
+      end
+    end
+
+    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
+  else
+    def check_client_connection(socket, listener) # :nodoc:
+      write_http_header(socket)
+    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