Skip to content

Commit

Permalink
bind listeners after loading for preload_app users
Browse files Browse the repository at this point in the history
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
Eric Wong committed Aug 2, 2012
1 parent 91a3cde commit 53c375d
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lib/unicorn.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def self.builder(ru, op)
def self.listener_names def self.listener_names
Unicorn::HttpServer::LISTENERS.map do |io| Unicorn::HttpServer::LISTENERS.map do |io|
Unicorn::SocketHelper.sock_name(io) Unicorn::SocketHelper.sock_name(io)
end end + Unicorn::HttpServer::NEW_LISTENERS
end end


def self.log_error(logger, prefix, exc) def self.log_error(logger, prefix, exc)
Expand Down
13 changes: 12 additions & 1 deletion lib/unicorn/http_server.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class Unicorn::HttpServer
# all bound listener sockets # all bound listener sockets
LISTENERS = [] LISTENERS = []


# listeners we have yet to bind
NEW_LISTENERS = []

# This hash maps PIDs to Workers # This hash maps PIDs to Workers
WORKERS = {} WORKERS = {}


Expand Down Expand Up @@ -134,6 +137,7 @@ def start


self.master_pid = $$ self.master_pid = $$
build_app! if preload_app build_app! if preload_app
bind_new_listeners!
spawn_missing_workers spawn_missing_workers
self self
end end
Expand Down Expand Up @@ -738,7 +742,14 @@ def inherit_listeners!
@init_listeners << Unicorn::Const::DEFAULT_LISTEN @init_listeners << Unicorn::Const::DEFAULT_LISTEN
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}" START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
end 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? raise ArgumentError, "no listeners" if LISTENERS.empty?
NEW_LISTENERS.clear
end end
end end
4 changes: 4 additions & 0 deletions t/listener_names.ru
Original file line number Original file line Diff line number Diff line change
@@ -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 ] ] })
32 changes: 32 additions & 0 deletions t/t0022-listener_names-preload_app.sh
Original file line number Original file line Diff line number Diff line change
@@ -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

0 comments on commit 53c375d

Please sign in to comment.