about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-09-27 18:09:28 -0700
committerEric Wong <normalperson@yhbt.net>2009-09-27 19:02:21 -0700
commitaa83722ab57fe05ac88164cfac949bb13c4aa7a5 (patch)
tree671a0141557e2682014851ed0527b2bf6c9bab06
parent7774d770f0021e25a644e85ad404e8acb7b81afc (diff)
downloadunicorn-aa83722ab57fe05ac88164cfac949bb13c4aa7a5.tar.gz
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.
-rw-r--r--lib/unicorn.rb12
-rw-r--r--lib/unicorn/configurator.rb29
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