1
0
Fork 0
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:
David Heinemeier Hansson 2006-04-28 00:00:50 +00:00
parent 1eade4137a
commit 7a8ed3bbbf
7 changed files with 140 additions and 95 deletions

View file

@ -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]

View file

@ -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 )

View file

@ -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"

View file

@ -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])

View file

@ -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

View file

@ -142,6 +142,7 @@ class AppGenerator < Rails::Generator::Base
tmp/sessions
tmp/sockets
tmp/cache
tmp/pids
)
MYSQL_SOCKET_LOCATIONS = [

View file

@ -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