5 if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Reaper is only for Unix") end
 
   9     # Searches for all processes matching the given keywords, and then invokes
 
  10     # a specific action on each of them. This is useful for (e.g.) reloading a
 
  13     #   Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid")
 
  14     def process(action, pid_path, pattern, keyword)
 
  15       new(pid_path, pattern, keyword).process(action)
 
  18     # Forces the (rails) application to reload by sending a +HUP+ signal to the
 
  24     # Force the (rails) application to restart by sending a +USR2+ signal to the
 
  30     # Forces the (rails) application to gracefully terminate by sending a
 
  31     # +TERM+ signal to the process.
 
  36     # Forces the (rails) application to terminate immediately by sending a -9
 
  37     # signal to the process.
 
  42     # Send a +USR1+ signal to the process.
 
  48   def initialize(pid_path, pattern, keyword=nil)
 
  49     @pid_path, @pattern, @keyword = pid_path, pattern, keyword
 
  56       warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'"
 
  57       warn "(also looked for processes matching #{@keyword.inspect})" if @keyword
 
  60         puts "#{action.capitalize}ing #{pid}"
 
  61         self.class.send(action, pid)
 
  64       delete_pid_files if terminating?(action)
 
  69     def terminating?(action)
 
  70       [ "kill", "graceful" ].include?(action)
 
  76         find_processes_via_grep
 
  78         files.collect { |pid_file| File.read(pid_file).to_i }
 
  82     def find_processes_via_grep
 
  83       lines = `ps axww -o 'pid command' | grep #{@keyword}`.split(/\n/).
 
  84         reject { |line| line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ }
 
  85       lines.map { |line| line[/^\s*(\d+)/, 1].to_i }
 
  89       pid_files.each { |pid_file| File.delete(pid_file) }
 
  93       Dir.glob(@pid_path + "/" + @pattern)
 
 100   :pid_path   => File.expand_path(RAILS_ROOT + '/tmp/pids'),
 
 101   :pattern    => "dispatch.[0-9]*.pid",
 
 102   :dispatcher => File.expand_path("#{RAILS_ROOT}/public/dispatch.fcgi")
 
 105 ARGV.options do |opts|
 
 106   opts.banner = "Usage: reaper [options]"
 
 112     The reaper is used to restart, reload, gracefully exit, and forcefully exit processes
 
 113     running a Rails Dispatcher (or any other process responding to the same signals). This
 
 114     is commonly done when a new version of the application is available, so the existing
 
 115     processes can be updated to use the latest code.
 
 117     It uses pid files to work on the processes and by default assume them to be located
 
 118     in RAILS_ROOT/tmp/pids. 
 
 120     The reaper actions are:
 
 122     * restart : Restarts the application by reloading both application and framework code
 
 123     * reload  : Only reloads the application, but not the framework (like the development environment)
 
 124     * graceful: Marks all of the processes for exit after the next request
 
 125     * kill    : Forcefully exists all processes regardless of whether they're currently serving a request
 
 127     Restart is the most common and default action.
 
 130     reaper                  # restarts the default dispatchers
 
 131     reaper -a reload        # reload the default dispatchers
 
 132     reaper -a kill -r *.pid # kill all processes that keep pids in tmp/pids
 
 137   opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String)  { |v| OPTIONS[:action] = v }
 
 138   opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String)                      { |v| OPTIONS[:pid_path] = v }
 
 139   opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String)                    { |v| OPTIONS[:pattern] = v }
 
 140   opts.on("-d", "--dispatcher=path", "DEPRECATED. default: #{OPTIONS[:dispatcher]}", String)     { |v| OPTIONS[:dispatcher] = v }
 
 144   opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
 
 149 Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher])