about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--SIGNALS51
-rw-r--r--lib/unicorn.rb17
2 files changed, 66 insertions, 2 deletions
diff --git a/SIGNALS b/SIGNALS
index c7c3833..b1a3141 100644
--- a/SIGNALS
+++ b/SIGNALS
@@ -22,6 +22,9 @@ processes are documented here as well.
    should be sent to the original process once the child is verified to
    be up and running.
 
+ * WINCH - gracefully stops workers but keep the master running.
+   This will only work for daemonized processes.
+
 === Worker Processes
 
 Sending signals directly to the worker processes should not normally be
@@ -34,3 +37,51 @@ automatically respawned.
 
  * USR1 - reopen all logs owned by the worker process
    See Unicorn::Util.reopen_logs for what is considered a log.
+
+=== Procedure to replace a running unicorn executable
+
+You may replace a running instance of unicorn with a new one without
+losing any incoming connections.  Doing so will reload all of your
+application code, Unicorn config, Ruby executable, and all libraries.
+The only things that will not change (due to OS limitations) are:
+
+1. The listener backlog size of already-bound sockets
+
+2. The path to the unicorn executable script.  If you want to change to
+   a different installation of Ruby, you can modify the shebang
+   line to point to your alternative interpreter.
+
+The procedure is exactly like that of nginx:
+
+1. Send USR2 to the master process
+
+2. Check your process manager or pid files to see if a new master spawned
+   successfully.  If you're using a pid file, the old process will have
+   ".oldbin" appended to its path.  You should have two master instances
+   of unicorn running now, both of which will have workers servicing
+   requests.  Your process tree should look something like this:
+
+   unicorn master (old)
+   \_ unicorn worker[0]
+   \_ unicorn worker[1]
+   \_ unicorn worker[2]
+   \_ unicorn worker[3]
+   \_ unicorn master
+      \_ unicorn worker[0]
+      \_ unicorn worker[1]
+      \_ unicorn worker[2]
+      \_ unicorn worker[3]
+
+4. You can now send WINCH to the old master process so only the new workers
+   serve requests.  If your unicorn process is bound to an interactive
+   terminal, you can skip this step.  Step 5 will be more difficult but
+   you can also skip it if your process is not daemonized.
+
+5. You should now ensure that everything is running correctly with the
+   new workers as the old workers die off.
+
+6a. If everything seems ok, then send QUIT to the old master.  You're done!
+
+6b. If something is broken, then send HUP to the old master to reload
+    the config and restart its workers.  Then send QUIT to the new master
+    process.
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 7b7c4bb..9245f53 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -161,6 +161,7 @@ module Unicorn
       @rd_sig, @wr_sig = IO.pipe unless (@rd_sig && @wr_sig)
       @rd_sig.nonblock = @wr_sig.nonblock = true
       ready = mode = nil
+      respawn = true
 
       QUEUE_SIGS.each { |sig| trap_deferred(sig) }
       trap('CHLD') { |sig_nr| awaken_master }
@@ -172,7 +173,7 @@ module Unicorn
           case (mode = @sig_queue.shift)
           when nil
             murder_lazy_workers
-            spawn_missing_workers
+            spawn_missing_workers if respawn
           when 'QUIT' # graceful shutdown
             break
           when 'TERM', 'INT' # immediate shutdown
@@ -183,7 +184,16 @@ module Unicorn
             Unicorn::Util.reopen_logs
           when 'USR2' # exec binary, stay alive in case something went wrong
             reexec
+          when 'WINCH'
+            if ppid == 1 || getpgrp != $$
+              respawn = false
+              logger.info "gracefully stopping all workers"
+              kill_each_worker('QUIT')
+            else
+              logger.info "SIGWINCH ignored because we're not daemonized"
+            end
           when 'HUP'
+            respawn = true
             if @config.config_file
               load_config!
               redo # immediate reaping since we may have QUIT workers
@@ -239,7 +249,8 @@ module Unicorn
     private
 
     # list of signals we care about and trap in master.
-    QUEUE_SIGS = %w(QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
+    QUEUE_SIGS =
+      %w(WINCH QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
 
     # defer a signal for later processing in #join (master process)
     def trap_deferred(signal)
@@ -271,6 +282,7 @@ module Unicorn
             logger.error "reaped exec()-ed PID:#{pid} status=#{$?.exitstatus}"
             @reexec_pid = 0
             self.pid = @pid.chomp('.oldbin') if @pid
+            $0 = "unicorn master"
           else
             worker = @workers.delete(pid)
             worker.tempfile.close rescue nil
@@ -324,6 +336,7 @@ module Unicorn
         @before_exec.call(self) if @before_exec
         exec(*cmd)
       end
+      $0 = "unicorn master (old)"
     end
 
     # forcibly terminate all workers that haven't checked in in @timeout