From: Tom Hughes Date: Mon, 15 Jun 2009 11:32:41 +0000 (+0000) Subject: Add irs_process_scripts plugin to replace process control scripts used X-Git-Tag: live~7023 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/719a06fdfa6eaf0244650c74e64ae5f9904fe865 Add irs_process_scripts plugin to replace process control scripts used by live site which were dropped in rails 2.3 core. --- diff --git a/script/process/inspector b/script/process/inspector index bf25ad86d..35c1bae9d 100755 --- a/script/process/inspector +++ b/script/process/inspector @@ -1,3 +1,4 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" require 'commands/process/inspector' diff --git a/script/process/reaper b/script/process/reaper index c77f04535..1ee7dfe12 100755 --- a/script/process/reaper +++ b/script/process/reaper @@ -1,3 +1,4 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" require 'commands/process/reaper' diff --git a/script/process/spawner b/script/process/spawner index 7118f3983..2d27c1b81 100755 --- a/script/process/spawner +++ b/script/process/spawner @@ -1,3 +1,4 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" require 'commands/process/spawner' diff --git a/vendor/plugins/irs_process_scripts/MIT-LICENSE b/vendor/plugins/irs_process_scripts/MIT-LICENSE new file mode 100644 index 000000000..df660347b --- /dev/null +++ b/vendor/plugins/irs_process_scripts/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2009 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/plugins/irs_process_scripts/README b/vendor/plugins/irs_process_scripts/README new file mode 100644 index 000000000..39d3f3a8b --- /dev/null +++ b/vendor/plugins/irs_process_scripts/README @@ -0,0 +1,11 @@ +IrsProcessScripts +================= + +Contains the deprecated process control scripts from Rails 2.2 that were removed in Rails 2.3. They are: + +* Inspector +* Spawner +* Reaper + + +Copyright (c) 2009 David Heinemeier Hansson, released under the MIT license diff --git a/vendor/plugins/irs_process_scripts/Rakefile b/vendor/plugins/irs_process_scripts/Rakefile new file mode 100644 index 000000000..aa8bd792d --- /dev/null +++ b/vendor/plugins/irs_process_scripts/Rakefile @@ -0,0 +1,23 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the irs_process_scripts plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the irs_process_scripts plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'IrsProcessScripts' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/irs_process_scripts/install.rb b/vendor/plugins/irs_process_scripts/install.rb new file mode 100644 index 000000000..f922ef895 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/install.rb @@ -0,0 +1,9 @@ +# Install hook code here +unless defined?(RAILS_ROOT) + $stderr.puts "$0 must be run from RAILS_ROOT with -rconfig/boot" + exit +end + +require 'fileutils' +FileUtils.rm_rf(RAILS_ROOT + '/script/process') # remove the old stubs first +FileUtils.cp_r(RAILS_ROOT + '/vendor/plugins/irs_process_scripts/script', RAILS_ROOT + '/script/process') diff --git a/vendor/plugins/irs_process_scripts/lib/commands/process/inspector.rb b/vendor/plugins/irs_process_scripts/lib/commands/process/inspector.rb new file mode 100644 index 000000000..8a6437e71 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/lib/commands/process/inspector.rb @@ -0,0 +1,68 @@ +require 'optparse' + +if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Inspector is only for Unix") end + +OPTIONS = { + :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), + :pattern => "dispatch.*.pid", + :ps => "ps -o pid,state,user,start,time,pcpu,vsz,majflt,command -p %s" +} + +class Inspector + def self.inspect(pid_path, pattern) + new(pid_path, pattern).inspect + end + + def initialize(pid_path, pattern) + @pid_path, @pattern = pid_path, pattern + end + + def inspect + header = `#{OPTIONS[:ps] % 1}`.split("\n")[0] + "\n" + lines = pids.collect { |pid| `#{OPTIONS[:ps] % pid}`.split("\n")[1] } + + puts(header + lines.join("\n")) + end + + private + def pids + pid_files.collect do |pid_file| + File.read(pid_file).to_i + end + end + + def pid_files + Dir.glob(@pid_path + "/" + @pattern) + end +end + + +ARGV.options do |opts| + opts.banner = "Usage: inspector [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + Displays system information about Rails dispatchers (or other processes that use pid files) through + the ps command. + + Examples: + inspector # default ps on all tmp/pids/dispatch.*.pid files + inspector -s 'ps -o user,start,majflt,pcpu,vsz -p %s' # custom ps, %s is where the pid is interleaved + EOF + + opts.on(" Options:") + + opts.on("-s", "--ps=command", "default: #{OPTIONS[:ps]}", String) { |v| OPTIONS[:ps] = v } + opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v } + opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +Inspector.inspect(OPTIONS[:pid_path], OPTIONS[:pattern]) diff --git a/vendor/plugins/irs_process_scripts/lib/commands/process/reaper.rb b/vendor/plugins/irs_process_scripts/lib/commands/process/reaper.rb new file mode 100644 index 000000000..95175d41e --- /dev/null +++ b/vendor/plugins/irs_process_scripts/lib/commands/process/reaper.rb @@ -0,0 +1,149 @@ +require 'optparse' +require 'net/http' +require 'uri' + +if RUBY_PLATFORM =~ /(:?mswin|mingw)/ then abort("Reaper is only for Unix") end + +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: + # + # Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid") + def process(action, pid_path, pattern, keyword) + new(pid_path, pattern, keyword).process(action) + end + + # Forces the (rails) application to reload by sending a +HUP+ signal to the + # process. + def reload(pid) + `kill -s HUP #{pid}` + 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 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, keyword=nil) + @pid_path, @pattern, @keyword = pid_path, pattern, keyword + end + + def process(action) + pids = find_processes + + if pids.empty? + warn "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'" + warn "(also looked for processes matching #{@keyword.inspect})" if @keyword + 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 + files = pid_files + if files.empty? + find_processes_via_grep + else + files.collect { |pid_file| File.read(pid_file).to_i } + end + end + + def find_processes_via_grep + lines = `ps axww -o 'pid command' | grep #{@keyword}`.split(/\n/). + reject { |line| line =~ /inq|ps axww|grep|spawn-fcgi|spawner|reaper/ } + lines.map { |line| line[/^\s*(\d+)/, 1].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", + :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), + :pattern => "dispatch.[0-9]*.pid", + :dispatcher => File.expand_path("#{RAILS_ROOT}/public/dispatch.fcgi") +} + +ARGV.options do |opts| + opts.banner = "Usage: reaper [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + 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: + + * restart : Restarts the application by reloading both application and framework code + * reload : Only reloads the application, but not the framework (like the development environment) + * graceful: Marks all of the processes for exit after the next request + * kill : Forcefully exists all processes regardless of whether they're currently serving a request + + Restart is the most common and default action. + + Examples: + 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) { |v| OPTIONS[:action] = v } + opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v } + opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v } + opts.on("-d", "--dispatcher=path", "DEPRECATED. default: #{OPTIONS[:dispatcher]}", String) { |v| OPTIONS[:dispatcher] = v } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern], OPTIONS[:dispatcher]) diff --git a/vendor/plugins/irs_process_scripts/lib/commands/process/spawner.rb b/vendor/plugins/irs_process_scripts/lib/commands/process/spawner.rb new file mode 100644 index 000000000..8bf47abb7 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/lib/commands/process/spawner.rb @@ -0,0 +1,219 @@ +require 'active_support' +require 'optparse' +require 'socket' +require 'fileutils' + +def daemonize #:nodoc: + exit if fork # Parent exits, child continues. + Process.setsid # Become session leader. + exit if fork # Zap session leader. See [1]. + Dir.chdir "/" # Release old working directory. + File.umask 0000 # Ensure sensible umask. Adjust as needed. + STDIN.reopen "/dev/null" # Free file descriptors and + STDOUT.reopen "/dev/null", "a" # point them somewhere sensible. + STDERR.reopen STDOUT # STDOUT/ERR should better go to a logfile. +end + +class Spawner + def self.record_pid(name = "#{OPTIONS[:process]}.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 #{OPTIONS[:address]}:#{port}..." + + begin + srv = TCPServer.new(OPTIONS[:address], port) + srv.close + srv = nil + + puts "NO" + puts "Starting dispatcher on port: #{OPTIONS[:address]}:#{port}" + + FileUtils.mkdir_p(OPTIONS[:pids]) + spawn(port) + rescue + puts "YES" + end + end + end +end + +class FcgiSpawner < Spawner + def self.spawn(port) + cmd = "#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid" + cmd << " -a #{OPTIONS[:address]}" if can_bind_to_custom_address? + system(cmd) + end + + def self.can_bind_to_custom_address? + @@can_bind_to_custom_address ||= /^\s-a\s/.match `#{OPTIONS[:spawner]} -h` + end +end + +class MongrelSpawner < Spawner + def self.spawn(port) + cmd = + "mongrel_rails start -d " + + "-a #{OPTIONS[:address]} " + + "-p #{port} " + + "-P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid " + + "-e #{OPTIONS[:environment]} " + + "-c #{OPTIONS[:rails_root]} " + + "-l #{OPTIONS[:rails_root]}/log/mongrel.log" + + # Add prefix functionality to spawner's call to mongrel_rails + # Digging through mongrel's project subversion server, the earliest + # Tag that has prefix implemented in the bin/mongrel_rails file + # is 0.3.15 which also happens to be the earliest tag listed. + # References: http://mongrel.rubyforge.org/svn/tags + if Mongrel::Const::MONGREL_VERSION.to_f >=0.3 && !OPTIONS[:prefix].nil? + cmd = cmd + " --prefix #{OPTIONS[:prefix]}" + end + system(cmd) + end + + def self.can_bind_to_custom_address? + true + end +end + + +begin + require_library_or_gem 'fcgi' +rescue Exception + # FCGI not available +end + +begin + require_library_or_gem 'mongrel' +rescue Exception + # Mongrel not available +end + +server = case ARGV.first + when "fcgi", "mongrel" + ARGV.shift + else + if defined?(Mongrel) + "mongrel" + elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `spawn-fcgi -version` }.blank? && defined?(FCGI) + "fcgi" + end +end + +case server + when "fcgi" + puts "=> Starting FCGI dispatchers" + spawner_class = FcgiSpawner + when "mongrel" + puts "=> Starting mongrel dispatchers" + spawner_class = MongrelSpawner + else + puts "Neither FCGI (spawn-fcgi) nor Mongrel was installed and available!" + exit(0) +end + + + +OPTIONS = { + :environment => "production", + :spawner => '/usr/bin/env spawn-fcgi', + :dispatcher => File.expand_path(RELATIVE_RAILS_ROOT + '/public/dispatch.fcgi'), + :pids => File.expand_path(RELATIVE_RAILS_ROOT + "/tmp/pids"), + :rails_root => File.expand_path(RELATIVE_RAILS_ROOT), + :process => "dispatch", + :port => 8000, + :address => '0.0.0.0', + :instances => 3, + :repeat => nil, + :prefix => nil +} + +ARGV.options do |opts| + opts.banner = "Usage: spawner [platform] [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + The spawner is a wrapper for spawn-fcgi and mongrel that makes it + easier to start multiple processes running the Rails dispatcher. The + spawn-fcgi command is included with the lighttpd web server, but can + be used with both Apache and lighttpd (and any other web server + supporting externally managed FCGI processes). Mongrel automatically + ships with with mongrel_rails for starting dispatchers. + + The first choice you need to make is whether to spawn the Rails + dispatchers as FCGI or Mongrel. By default, this spawner will prefer + Mongrel, so if that's installed, and no platform choice is made, + Mongrel is used. + + Then decide a starting port (default is 8000) and the number of FCGI + process instances you'd like to run. So if you pick 9100 and 3 + instances, you'll start processes on 9100, 9101, and 9102. + + By setting the repeat option, you get a protection loop, which will + attempt to restart any FCGI processes that might have been exited or + outright crashed. + + You can select bind address for started processes. By default these + listen on every interface. For single machine installations you would + probably want to use 127.0.0.1, hiding them form the outside world. + + Examples: + spawner # starts instances on 8000, 8001, and 8002 + # using Mongrel if available. + spawner fcgi # starts instances on 8000, 8001, and 8002 + # using FCGI. + spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, + # 8003, and 8004 using Mongrel. + spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to + # 9109 using Mongrel if available. + spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to + # 9102 and attempts start them every 5 + # seconds. + spawner -a 127.0.0.1 # starts 3 instances binding to localhost + EOF + + opts.on(" Options:") + + opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v } + + if spawner_class.can_bind_to_custom_address? + opts.on("-a", "--address=ip", String, "Bind to IP address (default: #{OPTIONS[:address]})") { |v| OPTIONS[:address] = v } + end + + opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v } + opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})") { |v| OPTIONS[:instances] = v } + opts.on("-r", "--repeat=seconds", Integer, "Repeat spawn attempts every n seconds (default: off)") { |v| OPTIONS[:repeat] = v } + opts.on("-e", "--environment=name", String, "test|development|production (default: #{OPTIONS[:environment]})") { |v| OPTIONS[:environment] = v } + opts.on("-P", "--prefix=path", String, "URL prefix for Rails app. [Used only with Mongrel > v0.3.15]: (default: #{OPTIONS[:prefix]})") { |v| OPTIONS[:prefix] = v } + opts.on("-n", "--process=name", String, "default: #{OPTIONS[:process]}") { |v| OPTIONS[:process] = v } + opts.on("-s", "--spawner=path", String, "default: #{OPTIONS[:spawner]}") { |v| OPTIONS[:spawner] = v } + opts.on("-d", "--dispatcher=path", String, "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +ENV["RAILS_ENV"] = OPTIONS[:environment] + +if OPTIONS[:repeat] + daemonize + trap("TERM") { exit } + spawner_class.record_pid + + loop do + spawner_class.spawn_all + sleep(OPTIONS[:repeat]) + end +else + spawner_class.spawn_all +end diff --git a/vendor/plugins/irs_process_scripts/script/inspector b/vendor/plugins/irs_process_scripts/script/inspector new file mode 100755 index 000000000..35c1bae9d --- /dev/null +++ b/vendor/plugins/irs_process_scripts/script/inspector @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" +require 'commands/process/inspector' diff --git a/vendor/plugins/irs_process_scripts/script/reaper b/vendor/plugins/irs_process_scripts/script/reaper new file mode 100755 index 000000000..1ee7dfe12 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/script/reaper @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" +require 'commands/process/reaper' diff --git a/vendor/plugins/irs_process_scripts/script/spawner b/vendor/plugins/irs_process_scripts/script/spawner new file mode 100755 index 000000000..2d27c1b81 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/script/spawner @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +$:.unshift "#{RAILS_ROOT}/vendor/plugins/irs_process_scripts/lib" +require 'commands/process/spawner' diff --git a/vendor/plugins/irs_process_scripts/uninstall.rb b/vendor/plugins/irs_process_scripts/uninstall.rb new file mode 100644 index 000000000..97196a914 --- /dev/null +++ b/vendor/plugins/irs_process_scripts/uninstall.rb @@ -0,0 +1,8 @@ +# Install hook code here +unless defined?(RAILS_ROOT) + $stderr.puts "$0 must be run from RAILS_ROOT with -rconfig/boot" + exit +end + +require 'fileutils' +FileUtils.rm_rf(RAILS_ROOT + '/script/process') \ No newline at end of file