require 'optparse' require 'net/http' require 'uri' if RUBY_PLATFORM =~ /mswin32/ then abort("Reaper is only for Unix") end class Killer class << self # Searches for all processes matching the given keywords, and then invokes # a specific action on each of them. This is useful for (e.g.) reloading a # set of processes: # # Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid") def process(action, pid_path, pattern, keyword) new(pid_path, pattern, keyword).process(action) end # Forces the (rails) application to reload by sending a +HUP+ signal to the # process. def reload(pid) `kill -s HUP #{pid}` end # Force the (rails) application to restart by sending a +USR2+ signal to the # process. def restart(pid) `kill -s USR2 #{pid}` end # Forces the (rails) application to gracefully terminate by sending a # +TERM+ signal to the process. def graceful(pid) `kill -s TERM #{pid}` end # Forces the (rails) application to terminate immediately by sending a -9 # signal to the process. def kill(pid) `kill -9 #{pid}` end # Send a +USR1+ signal to the process. def usr1(pid) `kill -s USR1 #{pid}` end end def initialize(pid_path, pattern, keyword=nil) @pid_path, @pattern, @keyword = pid_path, pattern, keyword end def process(action) pids = find_processes if pids.empty? warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'" warn "(also looked for processes matching #{@keyword.inspect})" if @keyword else pids.each do |pid| puts "#{action.capitalize}ing #{pid}" self.class.send(action, pid) end delete_pid_files if terminating?(action) end end private def terminating?(action) [ "kill", "graceful" ].include?(action) end def find_processes files = pid_files if files.empty? find_processes_via_grep else files.collect { |pid_file| File.read(pid_file).to_i } end end def find_processes_via_grep lines = `ps axww -o 'pid command' | grep #{@keyword}`.split(/\n/). reject { |line| line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ } lines.map { |line| line[/^\s*(\d+)/, 1].to_i } end def delete_pid_files pid_files.each { |pid_file| File.delete(pid_file) } end def pid_files Dir.glob(@pid_path + "/" + @pattern) end end OPTIONS = { :action => "restart", :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), :pattern => "dispatch.[0-9]*.pid", :dispatcher => File.expand_path("#{RAILS_ROOT}/public/dispatch.fcgi") } ARGV.options do |opts| opts.banner = "Usage: reaper [options]" opts.separator "" opts.on <<-EOF Description: The reaper is used to restart, reload, gracefully exit, and forcefully exit processes running a Rails Dispatcher (or any other process responding to the same signals). This is commonly done when a new version of the application is available, so the existing processes can be updated to use the latest code. It uses pid files to work on the processes and by default assume them to be located in RAILS_ROOT/tmp/pids. The reaper actions are: * restart : Restarts the application by reloading both application and framework code * reload : Only reloads the application, but not the framework (like the development environment) * graceful: Marks all of the processes for exit after the next request * kill : Forcefully exists all processes regardless of whether they're currently serving a request Restart is the most common and default action. Examples: reaper # restarts the default dispatchers reaper -a reload # reload the default dispatchers reaper -a kill -r *.pid # kill all processes that keep pids in tmp/pids EOF opts.on(" Options:") opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |v| OPTIONS[:action] = v } opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v } opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v } opts.on("-d", "--dispatcher=path", "DEPRECATED. default: #{OPTIONS[:dispatcher]}", String) { |v| OPTIONS[:dispatcher] = v } opts.separator "" opts.on("-h", "--help", "Show this help message.") { puts opts; exit } opts.parse! end Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher])