]> git.openstreetmap.org Git - rails.git/blob - vendor/plugins/irs_process_scripts/lib/commands/process/reaper.rb
95175d41e0d71230501d9a823029c0272df8fa03
[rails.git] / vendor / plugins / irs_process_scripts / lib / commands / process / reaper.rb
1 require 'optparse'
2 require 'net/http'
3 require 'uri'
4
5 if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Reaper is only for Unix") end
6
7 class Killer
8   class << self
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
11     # set of processes:
12     #
13     #   Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid")
14     def process(action, pid_path, pattern, keyword)
15       new(pid_path, pattern, keyword).process(action)
16     end
17
18     # Forces the (rails) application to reload by sending a +HUP+ signal to the
19     # process.
20     def reload(pid)
21       `kill -s HUP #{pid}`
22     end
23
24     # Force the (rails) application to restart by sending a +USR2+ signal to the
25     # process.
26     def restart(pid)
27       `kill -s USR2 #{pid}`
28     end
29
30     # Forces the (rails) application to gracefully terminate by sending a
31     # +TERM+ signal to the process.
32     def graceful(pid)
33       `kill -s TERM #{pid}`
34     end
35
36     # Forces the (rails) application to terminate immediately by sending a -9
37     # signal to the process.
38     def kill(pid)
39       `kill -9 #{pid}`
40     end
41
42     # Send a +USR1+ signal to the process.
43     def usr1(pid)
44       `kill -s USR1 #{pid}`
45     end
46   end
47
48   def initialize(pid_path, pattern, keyword=nil)
49     @pid_path, @pattern, @keyword = pid_path, pattern, keyword
50   end
51
52   def process(action)
53     pids = find_processes
54
55     if pids.empty?
56       warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'"
57       warn "(also looked for processes matching #{@keyword.inspect})" if @keyword
58     else
59       pids.each do |pid|
60         puts "#{action.capitalize}ing #{pid}"
61         self.class.send(action, pid)
62       end
63       
64       delete_pid_files if terminating?(action)
65     end      
66   end
67   
68   private
69     def terminating?(action)
70       [ "kill", "graceful" ].include?(action)
71     end
72   
73     def find_processes
74       files = pid_files
75       if files.empty?
76         find_processes_via_grep
77       else
78         files.collect { |pid_file| File.read(pid_file).to_i }
79       end
80     end
81
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 }
86     end
87     
88     def delete_pid_files
89       pid_files.each { |pid_file| File.delete(pid_file) }
90     end
91     
92     def pid_files
93       Dir.glob(@pid_path + "/" + @pattern)
94     end
95 end
96
97
98 OPTIONS = {
99   :action     => "restart",
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")
103 }
104
105 ARGV.options do |opts|
106   opts.banner = "Usage: reaper [options]"
107
108   opts.separator ""
109
110   opts.on <<-EOF
111   Description:
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.
116
117     It uses pid files to work on the processes and by default assume them to be located
118     in RAILS_ROOT/tmp/pids. 
119
120     The reaper actions are:
121
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
126
127     Restart is the most common and default action.
128
129   Examples:
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
133   EOF
134
135   opts.on("  Options:")
136
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 }
141
142   opts.separator ""
143
144   opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
145
146   opts.parse!
147 end
148
149 Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher])