From e9be891dc851015eaf592d3c64caff77398f1246 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 19 Mar 2009 18:50:39 -0700 Subject: Trap WINCH to QUIT children without respawning This will only be enabled if we're daemonized and "real" WINCH signals cannot be generated by resizing the terminal. This is to avoid confusing developers who run in the foreground of a terminal. Additionally document procedures for reexecuting a running binary. --- SIGNALS | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/unicorn.rb | 17 +++++++++++++++-- 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 -- cgit v1.2.3-24-ge0c7