Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

bind listeners after loading for preload_app users

In the case where preload_app is true, delay binding new
listeners until after loading the application.

Some applications have very long load times (especially Rails
apps with Ruby 1.9.2).  Binding listeners early may cause a load
balancer to incorrectly believe the unicorn workers are ready to
serve traffic even while the app is being loaded.

Once a listener is bound, connect() requests from the load
balancer succeed until the listen backlog is filled.  This
allows requests to pile up for a bit (depending on backlog size)
before getting rejected by the kernel.  By the time the
application is loaded and ready-to-run, requests in the
listen backlog are likely stale and not useful to process.

Processes inheriting listeners do not suffer this effect, as the
old process should still be capable of serving new requests.

This change does not improve the situation for the
preload_app=false (default) use case.  There may not be a
solution for preload_app=false users using large applications.

Fortunately Ruby 1.9.3+ improves load times of large
applications significantly over 1.9.2 so this should be less of
a problem in the future.

Reported via private email sent on 2012-06-29T22:59:10Z
  • Loading branch information...
commit 53c375dc933b62b24df2c54d3938b03fa9da1f06 1 parent 91a3cde
Eric Wong authored
View
2  lib/unicorn.rb
@@ -82,7 +82,7 @@ def self.builder(ru, op)
def self.listener_names
Unicorn::HttpServer::LISTENERS.map do |io|
Unicorn::SocketHelper.sock_name(io)
- end
+ end + Unicorn::HttpServer::NEW_LISTENERS
end
def self.log_error(logger, prefix, exc)
View
13 lib/unicorn/http_server.rb
@@ -28,6 +28,9 @@ class Unicorn::HttpServer
# all bound listener sockets
LISTENERS = []
+ # listeners we have yet to bind
+ NEW_LISTENERS = []
+
# This hash maps PIDs to Workers
WORKERS = {}
@@ -134,6 +137,7 @@ def start
self.master_pid = $$
build_app! if preload_app
+ bind_new_listeners!
spawn_missing_workers
self
end
@@ -738,7 +742,14 @@ def inherit_listeners!
@init_listeners << Unicorn::Const::DEFAULT_LISTEN
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
end
- config_listeners.each { |addr| listen(addr) }
+ NEW_LISTENERS.replace(config_listeners)
+ end
+
+ # call only after calling inherit_listeners!
+ # This binds any listeners we did NOT inherit from the parent
+ def bind_new_listeners!
+ NEW_LISTENERS.each { |addr| listen(addr) }
raise ArgumentError, "no listeners" if LISTENERS.empty?
+ NEW_LISTENERS.clear
end
end
View
4 t/listener_names.ru
@@ -0,0 +1,4 @@
+use Rack::ContentLength
+use Rack::ContentType, "text/plain"
+names = Unicorn.listener_names.inspect # rely on preload_app=true
+run(lambda { |_| [ 200, {}, [ names ] ] })
View
32 t/t0022-listener_names-preload_app.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+. ./test-lib.sh
+
+# Raindrops::Middleware depends on Unicorn.listener_names,
+# ensure we don't break Raindrops::Middleware when preload_app is true
+
+t_plan 4 "Unicorn.listener_names available with preload_app=true"
+
+t_begin "setup and startup" && {
+ unicorn_setup
+ echo preload_app true >> $unicorn_config
+ unicorn -E none -D listener_names.ru -c $unicorn_config
+ unicorn_wait_start
+}
+
+t_begin "read listener names includes listener" && {
+ resp=$(curl -sSf http://$listen/)
+ ok=false
+ t_info "resp=$resp"
+ case $resp in
+ *\"$listen\"*) ok=true ;;
+ esac
+ $ok
+}
+
+t_begin "killing succeeds" && {
+ kill $unicorn_pid
+}
+
+t_begin "check stderr" && check_stderr
+
+t_done
Please sign in to comment.
Something went wrong with that request. Please try again.