about summary refs log tree commit homepage
path: root/lib/unicorn/oob_gc.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2011-04-29 15:48:35 -0700
committerEric Wong <normalperson@yhbt.net>2011-04-29 16:01:14 -0700
commitfaeb3223636c39ea8df4017dc9a9d39ac649b26d (patch)
treefce6b3ee71fc84e41ed3227ff2136b167b2d59b6 /lib/unicorn/oob_gc.rb
parentce4995a4daf1e4da7034dc87fd218a283c405410 (diff)
downloadunicorn-faeb3223636c39ea8df4017dc9a9d39ac649b26d.tar.gz
This was broken since v3.3.1[1] since nginx relies on a closed
socket (and not Content-Length/Transfer-Encoding) to detect
a response completion.  We have to close the client socket
before invoking GC to ensure the client sees the response
in a timely manner.

[1] - commit b72a86f66c722d56a6d77ed1d2779ace6ad103ed
Diffstat (limited to 'lib/unicorn/oob_gc.rb')
-rw-r--r--lib/unicorn/oob_gc.rb111
1 files changed, 61 insertions, 50 deletions
diff --git a/lib/unicorn/oob_gc.rb b/lib/unicorn/oob_gc.rb
index 402d4ed..312b44c 100644
--- a/lib/unicorn/oob_gc.rb
+++ b/lib/unicorn/oob_gc.rb
@@ -1,58 +1,69 @@
 # -*- encoding: binary -*-
-module Unicorn
 
-  # Run GC after every request, after closing the client socket and
-  # before attempting to accept more connections.
-  #
-  # This shouldn't hurt overall performance as long as the server cluster
-  # is at <50% CPU capacity, and improves the performance of most memory
-  # intensive requests.  This serves to improve _client-visible_
-  # performance (possibly at the cost of overall performance).
-  #
-  # We'll call GC after each request is been written out to the socket, so
-  # the client never sees the extra GC hit it.
-  #
-  # This middleware is _only_ effective for applications that use a lot
-  # of memory, and will hurt simpler apps/endpoints that can process
-  # multiple requests before incurring GC.
-  #
-  # This middleware is only designed to work with Unicorn, as it harms
-  # keepalive performance.
-  #
-  # Example (in config.ru):
-  #
-  #     require 'unicorn/oob_gc'
-  #
-  #     # GC ever two requests that hit /expensive/foo or /more_expensive/foo
-  #     # in your app.  By default, this will GC once every 5 requests
-  #     # for all endpoints in your app
-  #     use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
-  class OobGC < Struct.new(:app, :interval, :path, :nr, :env, :body)
-
-    def initialize(app, interval = 5, path = %r{\A/})
-      super(app, interval, path, interval)
-    end
-
-    def call(env)
-      status, headers, self.body = app.call(self.env = env)
-      [ status, headers, self ]
-    end
+# Runs GC after requests, after closing the client socket and
+# before attempting to accept more connections.
+#
+# This shouldn't hurt overall performance as long as the server cluster
+# is at <50% CPU capacity, and improves the performance of most memory
+# intensive requests.  This serves to improve _client-visible_
+# performance (possibly at the cost of overall performance).
+#
+# Increasing the number of +worker_processes+ may be necessary to
+# improve average client response times because some of your workers
+# will be busy doing GC and unable to service clients.  Think of
+# using more workers with this module as a poor man's concurrent GC.
+#
+# We'll call GC after each request is been written out to the socket, so
+# the client never sees the extra GC hit it.
+#
+# This middleware is _only_ effective for applications that use a lot
+# of memory, and will hurt simpler apps/endpoints that can process
+# multiple requests before incurring GC.
+#
+# This middleware is only designed to work with unicorn, as it harms
+# performance with keepalive-enabled servers.
+#
+# Example (in config.ru):
+#
+#     require 'unicorn/oob_gc'
+#
+#     # GC ever two requests that hit /expensive/foo or /more_expensive/foo
+#     # in your app.  By default, this will GC once every 5 requests
+#     # for all endpoints in your app
+#     use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
+#
+# Feedback from users of early implementations of this module:
+# * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
+# * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
+module Unicorn::OobGC
 
-    def each
-      body.each { |x| yield x }
+  # this pretends to be Rack middleware because it used to be
+  # But we need to hook into unicorn internals so we need to close
+  # the socket before clearing the request env.
+  #
+  # +interval+ is the number of requests matching the +path+ regular
+  # expression before invoking GC.
+  def self.new(app, interval = 5, path = %r{\A/})
+    @@nr = interval
+    self.const_set :OOBGC_PATH, path
+    self.const_set :OOBGC_INTERVAL, interval
+    ObjectSpace.each_object(Unicorn::HttpServer) do |s|
+      s.extend(self)
+      self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
     end
+    app # pretend to be Rack middleware since it was in the past
+  end
 
-    # in Unicorn, this is closed _after_ the client socket
-    def close
-      body.close if body.respond_to?(:close)
-
-      if path =~ env['PATH_INFO'] && ((self.nr -= 1) <= 0)
-        self.nr = interval
-        self.body = nil
-        env.clear
-        GC.start
-      end
+  #:stopdoc:
+  PATH_INFO = "PATH_INFO"
+  def process_client(client)
+    super(client) # Unicorn::HttpServer#process_client
+    if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
+      @@nr = OOBGC_INTERVAL
+      OOBGC_ENV.clear
+      GC.start
     end
-
   end
+
+  # :startdoc:
 end