diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2006-04-28 00:00:50 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2006-04-28 00:00:50 +0000 |
commit | 7a8ed3bbbf44b273622be0e324a616065f5656a8 (patch) | |
tree | 02ef3b281ef8470a43f958062582b03387e68d90 /railties/lib/commands/process | |
parent | 1eade4137a96a55ed699d49c18fe177224363e7d (diff) | |
download | rails-7a8ed3bbbf44b273622be0e324a616065f5656a8.tar.gz rails-7a8ed3bbbf44b273622be0e324a616065f5656a8.tar.bz2 rails-7a8ed3bbbf44b273622be0e324a616065f5656a8.zip |
Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4294 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'railties/lib/commands/process')
-rw-r--r-- | railties/lib/commands/process/reaper.rb | 145 | ||||
-rw-r--r-- | railties/lib/commands/process/spawner.rb | 58 |
2 files changed, 117 insertions, 86 deletions
diff --git a/railties/lib/commands/process/reaper.rb b/railties/lib/commands/process/reaper.rb index 73b6b97f48..6ac2f8e8bd 100644 --- a/railties/lib/commands/process/reaper.rb +++ b/railties/lib/commands/process/reaper.rb @@ -4,89 +4,89 @@ require 'uri' if RUBY_PLATFORM =~ /mswin32/ then abort("Reaper is only for Unix") end -# Instances of this class represent a single running process. Processes may -# be queried by "keyword" to find those that meet a specific set of criteria. -class ProgramProcess +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: # - # ProgramProcess.process_keywords(:reload, "basecamp") - def process_keywords(action, *keywords) - processes = keywords.collect { |keyword| find_by_keyword(keyword) }.flatten - - if processes.empty? - puts "Couldn't find any process matching: #{keywords.join(" or ")}" - else - processes.each do |process| - puts "#{action.capitalize}ing #{process}" - process.send(action) - end - end + # Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid") + def process(action, pid_path, pattern) + new(pid_path, pattern).process(action) end - # Searches for all processes matching the given keyword: - # - # ProgramProcess.find_by_keyword("basecamp") - def find_by_keyword(keyword) - process_lines_with_keyword(keyword).split("\n").collect { |line| - next if line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ - pid, *command = line.split - new(pid, command.join(" ")) - }.compact + # Forces the (rails) application to reload by sending a +HUP+ signal to the + # process. + def reload(pid) + `kill -s HUP #{pid}` end - private - def process_lines_with_keyword(keyword) - `ps axww -o 'pid command' | grep #{keyword}` - end - end - - # Create a new ProgramProcess instance that represents the process with the - # given pid, running the given command. - def initialize(pid, command) - @pid, @command = pid, command - 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 reload by sending a +HUP+ signal to the - # process. - def reload - `kill -s HUP #{@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 gracefully terminate by sending a - # +TERM+ signal to the process. - def graceful - `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 - # Forces the (rails) application to terminate immediately by sending a -9 - # signal to the process. - def kill - `kill -9 #{@pid}` + # Send a +USR1+ signal to the process. + def usr1(pid) + `kill -s USR1 #{pid}` + end end - # Send a +USR1+ signal to the process. - def usr1 - `kill -s USR1 #{@pid}` + def initialize(pid_path, pattern) + @pid_path, @pattern = pid_path, pattern end - # Force the (rails) application to restart by sending a +USR2+ signal to the - # process. - def restart - `kill -s USR2 #{@pid}` - end + def process(action) + pids = find_processes - def to_s #:nodoc: - "[#{@pid}] #{@command}" + if pids.empty? + puts "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'" + 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 + pid_files.collect { |pid_file| File.read(pid_file).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", - :dispatcher => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi') + :action => "restart", + :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), + :pattern => "dispatch.*.pid" } ARGV.options do |opts| @@ -96,9 +96,13 @@ ARGV.options do |opts| opts.on <<-EOF Description: - The reaper is used to restart, reload, gracefully exit, and forcefully exit FCGI processes - running a Rails Dispatcher. 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. + 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: @@ -110,15 +114,16 @@ ARGV.options do |opts| Restart is the most common and default action. Examples: - reaper # restarts the default dispatcher - reaper -a reload - reaper -a exit -d /my/special/dispatcher.fcgi + 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) { |OPTIONS[:action]| } - opts.on("-d", "--dispatcher=path", "default: #{OPTIONS[:dispatcher]}", String) { |OPTIONS[:dispatcher]| } + opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |OPTIONS[:pid_path]| } + opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |OPTIONS[:pattern]| } opts.separator "" @@ -127,4 +132,4 @@ ARGV.options do |opts| opts.parse! end -ProgramProcess.process_keywords(OPTIONS[:action], OPTIONS[:dispatcher])
\ No newline at end of file +Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern])
\ No newline at end of file diff --git a/railties/lib/commands/process/spawner.rb b/railties/lib/commands/process/spawner.rb index 84364589c8..9424dacc74 100644 --- a/railties/lib/commands/process/spawner.rb +++ b/railties/lib/commands/process/spawner.rb @@ -1,5 +1,6 @@ require 'optparse' require 'socket' +require 'fileutils' def daemonize #:nodoc: exit if fork # Parent exits, child continues. @@ -12,28 +13,52 @@ def daemonize #:nodoc: STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile. end -def spawn(port) - print "Checking if something is already running on port #{port}..." - begin - srv = TCPServer.new('0.0.0.0', port) - srv.close - srv = nil - print "NO\n " - print "Starting FCGI on port: #{port}\n " - system("#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port}") - rescue - print "YES\n" +class Spawner + def self.record_pid(name = "spawner", id = Process.pid) + FileUtils.mkdir_p(OPTIONS[:pids]) + File.open(File.expand_path(OPTIONS[:pids] + "/#{name}.pid"), "w+") { |f| f.write(id) } + end + + def self.spawn_all + OPTIONS[:instances].times do |i| + port = OPTIONS[:port] + i + print "Checking if something is already running on port #{port}..." + + begin + srv = TCPServer.new('0.0.0.0', port) + srv.close + srv = nil + + print "NO\n " + print "Starting FCGI on port: #{port}\n " + + FileUtils.mkdir_p(OPTIONS[:pids]) + spawn(port) + rescue + print "YES\n" + end + end end end - -def spawn_all - OPTIONS[:instances].times { |i| spawn(OPTIONS[:port] + i) } + +class FcgiSpawner < Spawner + def self.spawn(port) + system("#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/dispatch.#{port}.pid") + end end +# TODO: +# class MongrelSpawner < Spawner +# def self.spawn(port) +# end +# end + + OPTIONS = { :environment => "production", :spawner => '/usr/bin/env spawn-fcgi', :dispatcher => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi'), + :pids => File.expand_path(RAILS_ROOT + "/tmp/pids"), :port => 8000, :instances => 3, :repeat => nil @@ -84,11 +109,12 @@ ENV["RAILS_ENV"] = OPTIONS[:environment] if OPTIONS[:repeat] daemonize trap("TERM") { exit } + FcgiSpawner.record_pid loop do - spawn_all + FcgiSpawner.spawn_all sleep(OPTIONS[:repeat]) end else - spawn_all + FcgiSpawner.spawn_all end |