diff options
-rw-r--r-- | SIGNALS | 51 | ||||
-rw-r--r-- | lib/unicorn.rb | 17 |
2 files changed, 66 insertions, 2 deletions
@@ -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 |