mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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
This commit is contained in:
parent
1eade4137a
commit
7a8ed3bbbf
7 changed files with 140 additions and 95 deletions
|
@ -1,3 +1,8 @@
|
|||
*SVN*
|
||||
|
||||
* 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]
|
||||
|
||||
|
||||
*1.1.2* (April 9th, 2005)
|
||||
|
||||
* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. Closes #4857. [Alisdair McDiarmid]
|
||||
|
|
|
@ -30,7 +30,7 @@ RUBY_FORGE_USER = "webster132"
|
|||
|
||||
BASE_DIRS = %w(
|
||||
app config/environments components db doc log lib lib/tasks public script script/performance script/process test vendor vendor/plugins
|
||||
tmp/sessions tmp/cache tmp/sockets
|
||||
tmp/sessions tmp/cache tmp/sockets tmp/pids
|
||||
)
|
||||
|
||||
APP_DIRS = %w( models controllers helpers views views/layouts )
|
||||
|
|
|
@ -7,6 +7,7 @@ server.port = 3000
|
|||
server.modules = ( "mod_rewrite", "mod_accesslog", "mod_fastcgi", "mod_compress", "mod_expire" )
|
||||
|
||||
server.error-handler-404 = "/dispatch.fcgi"
|
||||
server.pid-file = CWD + "/tmp/pids/lighttpd.pid"
|
||||
server.document-root = CWD + "/public/"
|
||||
|
||||
server.errorlog = CWD + "/log/lighttpd.error.log"
|
||||
|
|
|
@ -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}`
|
||||
# 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)
|
||||
@pid_path, @pattern = pid_path, pattern
|
||||
end
|
||||
|
||||
def process(action)
|
||||
pids = find_processes
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
`kill -s TERM #{@pid}`
|
||||
end
|
||||
|
||||
# Forces the (rails) application to terminate immediately by sending a -9
|
||||
# signal to the process.
|
||||
def kill
|
||||
`kill -9 #{@pid}`
|
||||
end
|
||||
|
||||
# Send a +USR1+ signal to the process.
|
||||
def usr1
|
||||
`kill -s USR1 #{@pid}`
|
||||
end
|
||||
|
||||
# Force the (rails) application to restart by sending a +USR2+ signal to the
|
||||
# process.
|
||||
def restart
|
||||
`kill -s USR2 #{@pid}`
|
||||
end
|
||||
|
||||
def to_s #:nodoc:
|
||||
"[#{@pid}] #{@command}"
|
||||
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])
|
||||
Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern])
|
|
@ -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
|
||||
|
|
|
@ -142,6 +142,7 @@ class AppGenerator < Rails::Generator::Base
|
|||
tmp/sessions
|
||||
tmp/sockets
|
||||
tmp/cache
|
||||
tmp/pids
|
||||
)
|
||||
|
||||
MYSQL_SOCKET_LOCATIONS = [
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace :tmp do
|
|||
|
||||
desc "Creates tmp directories for sessions, cache, and sockets"
|
||||
task :create do
|
||||
FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets ))
|
||||
FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids ))
|
||||
end
|
||||
|
||||
namespace :sessions do
|
||||
|
@ -27,4 +27,11 @@ namespace :tmp do
|
|||
FileUtils.rm(Dir['tmp/sockets/[^.]*'])
|
||||
end
|
||||
end
|
||||
|
||||
namespace :pids do
|
||||
desc "Clears all files in tmp/pids"
|
||||
task :clear do
|
||||
FileUtils.rm(Dir['tmp/pids/[^.]*'])
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue