Skip to content

Instantly share code, notes, and snippets.

@macks
Created December 29, 2011 12:57
Show Gist options
  • Save macks/1533954 to your computer and use it in GitHub Desktop.
Save macks/1533954 to your computer and use it in GitHub Desktop.
Graceful restart script for Unicorn
#!/usr/bin/ruby
# restart-unicorn: Graceful restart for Unicorn
# depends on Linux's proc(5)
#
# MIT License: Copyright(c)2011 MATSUYAMA Kengo
require 'scanf'
require 'timeout'
class ProcFS
PROC_DIR = '/proc'
# cf. proc(5)
PROC_STAT_FORMAT = [
%w[pid %d],
%w[comm %s],
%w[state %c],
%w[ppid %d],
%w[pgrp %d],
%w[session %d],
]
STAT_PARAMS = PROC_STAT_FORMAT.map(&:first)
STAT_FORMAT = PROC_STAT_FORMAT.map(&:last).join(' ')
Stat = Struct.new(*STAT_PARAMS.map(&:to_sym))
attr_reader :pid
STAT_PARAMS.each do |name|
next if method_defined?(name)
define_method(name) { stat && stat.send(name) }
end
def self.all
Dir.entries(PROC_DIR).grep(/\A\d+\z/).map {|pid| ProcFS.new(pid)}
end
def self.detect(options = {}, &block)
if timeout = options[:timeout]
Timeout.timeout(timeout) do
all.detect(&block) or (sleep 0.2 and redo)
end
else
all.detect(&block)
end
end
def initialize(pid)
@pid = pid.to_i
end
def stat
@stat ||= Stat.new(*read('stat').scanf(STAT_FORMAT))
rescue
nil
end
def cmdline
@cmdline ||= read('cmdline').gsub(/\0/, ' ').strip
rescue
nil
end
private
def read(name)
File.read(File.join(PROC_DIR, @pid.to_s, name))
end
end
if ARGV.size != 1
puts "USAGE: #{File.basename($0)} PIDFILE"
exit
end
pid_file = ARGV.shift
shutdown_old_master_process = proc do
old_pid_file = pid_file + '.oldbin'
if File.exist?(old_pid_file)
Process.kill(:QUIT, File.read(old_pid_file).to_i)
File.unlink(old_pid_file)
end
end
# shutdown an old master process. (if exists)
shutdown_old_master_process.call
# find a current master process.
pid = File.read(pid_file).to_i
master_proccess = ProcFS.new(pid)
unless master_proccess.stat
STDERR.puts "Master process #{pid} is missing"
exit 1
end
# send SIGUSR2 to the master.
Process.kill(:USR2, pid)
begin
# find a new master.
new_master_process = ProcFS.detect(:timeout => 5) do |process|
process.ppid == pid && process.cmdline =~ /\Aunicorn(?:_rails)? master/
end
rescue Exception
# can't find a new master process.
STDERR.puts "#{$!.class}: #{$!.to_s}"
STDERR.puts "Can't find a new master process"
exit 1
end
begin
# find a new worker process.
new_worker_process = ProcFS.detect(:timeout => 5) do |process|
process.ppid == new_master_process.pid && process.cmdline =~ /\Aunicorn(?:_rails)? worker/
end
# wait for the worker to sleep.
timeout(60) do
count = 2
begin
sleep 0.2
stat = ProcFS.new(new_worker_process.pid).stat or raise "New worker process #{new_worker_process.pid} was died"
end until stat.state == 'S' && (count -= 1).zero?
end
# everything is ok. kill the old master process.
shutdown_old_master_process.call
rescue Exception
# something was failed. kill the new master process.
STDERR.puts "#{$!.class}: #{$!.to_s}"
STDERR.puts "Kill the new master process #{new_master_process.pid}"
Process.kill(:QUIT, new_master_process.pid)
exit 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment