From aa83722ab57fe05ac88164cfac949bb13c4aa7a5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 27 Sep 2009 18:09:28 -0700 Subject: HttpServer#listen accepts :tries and :delay parameters This allows per-worker listeners to be configured to retry and and not continue until the equivalent worker belonging to a previous master (or even another server) has released the socket. In the Configurator RDoc, include better examples for per-worker server.listen calls using these :tries == -1. Inspired by an example by Chris Wanstrath. --- lib/unicorn.rb | 12 ++++++++++-- lib/unicorn/configurator.rb | 29 +++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index 0e46261..bc5ba23 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -175,10 +175,17 @@ module Unicorn # add a given address to the +listeners+ set, idempotently # Allows workers to add a private, per-process listener via the # after_fork hook. Very useful for debugging and testing. + # +:tries+ may be specified as an option for the number of times + # to retry, and +:delay+ may be specified as the time in seconds + # to delay between retries. + # A negative value for +:tries+ indicates the listen will be + # retried indefinitely, this is useful when workers belonging to + # different masters are spawned during a transparent upgrade. def listen(address, opt = {}.merge(listener_opts[address] || {})) return if String === address && listener_names.include?(address) - delay, tries = 0.5, 5 + delay = opt[:delay] || 0.5 + tries = opt[:tries] || 5 begin io = bind_listen(address, opt) unless TCPServer === io || UNIXServer === io @@ -192,7 +199,8 @@ module Unicorn logger.error "adding listener failed addr=#{address} (in use)" raise err if tries == 0 tries -= 1 - logger.error "retrying in #{delay} seconds (#{tries} tries left)" + logger.error "retrying in #{delay} seconds " \ + "(#{tries < 0 ? 'infinite' : tries} tries left)" sleep(delay) retry end diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index 947ea18..523765a 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -89,11 +89,13 @@ module Unicorn # # after_fork do |server,worker| # # per-process listener ports for debugging/admin: - # # "rescue nil" statement is needed because USR2 will - # # cause the master process to reexecute itself and the - # # per-worker ports can be taken, necessitating another - # # HUP after QUIT-ing the original master: - # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil + # addr = "127.0.0.1:#{9293 + worker.nr}" + # + # # the negative :tries parameter indicates we will retry forever + # # waiting on the existing process to exit with a 5 second :delay + # # Existing options for Unicorn::Configurator#listen such as + # # :backlog, :rcvbuf, :sndbuf are available here as well. + # server.listen(addr, :tries => -1, :delay => 5, :backlog => 128) # # # drop permissions to "www-data" in the worker # # generally there's no reason to start Unicorn as a priviledged user @@ -215,12 +217,23 @@ module Unicorn # # This has no effect on UNIX sockets. # + # +tries+: times to retry binding a socket if it is already in use + # + # A negative number indicates we will retry indefinitely, this is + # useful for migrations and upgrades when individual workers + # are binding to different ports. + # + # Default: 5 + # + # +delay+: seconds to wait between successive +tries+ + # + # Default: 0.5 seconds def listen(address, opt = {}) address = expand_addr(address) if String === address Hash === set[:listener_opts] or set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} } - [ :backlog, :sndbuf, :rcvbuf ].each do |key| + [ :backlog, :sndbuf, :rcvbuf, :tries ].each do |key| value = opt[key] or next Integer === value or raise ArgumentError, "not an integer: #{key}=#{value.inspect}" @@ -230,6 +243,10 @@ module Unicorn TrueClass === value || FalseClass === value or raise ArgumentError, "not boolean: #{key}=#{value.inspect}" end + unless (value = opt[:tries]).nil? + Numeric === value or + raise ArgumentError, "not numeric: #{key}=#{value.inspect}" + end set[:listener_opts][address].merge!(opt) end -- cgit v1.2.3-24-ge0c7