1 require 'active_support'
 
   7   exit if fork                   # Parent exits, child continues.
 
   8   Process.setsid                 # Become session leader.
 
   9   exit if fork                   # Zap session leader. See [1].
 
  10   Dir.chdir "/"                  # Release old working directory.
 
  11   File.umask 0000                # Ensure sensible umask. Adjust as needed.
 
  12   STDIN.reopen "/dev/null"       # Free file descriptors and
 
  13   STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
 
  14   STDERR.reopen STDOUT           # STDOUT/ERR should better go to a logfile.
 
  18   def self.record_pid(name = "#{OPTIONS[:process]}.spawner", id = Process.pid)
 
  19     FileUtils.mkdir_p(OPTIONS[:pids])
 
  20     File.open(File.expand_path(OPTIONS[:pids] + "/#{name}.pid"), "w+") { |f| f.write(id) }
 
  24     OPTIONS[:instances].times do |i|
 
  25       port = OPTIONS[:port] + i
 
  26       print "Checking if something is already running on #{OPTIONS[:address]}:#{port}..."
 
  29         srv = TCPServer.new(OPTIONS[:address], port)
 
  34         puts "Starting dispatcher on port: #{OPTIONS[:address]}:#{port}"
 
  36         FileUtils.mkdir_p(OPTIONS[:pids])
 
  45 class FcgiSpawner < Spawner
 
  47     cmd = "#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid"
 
  48     cmd << " -a #{OPTIONS[:address]}" if can_bind_to_custom_address?
 
  52   def self.can_bind_to_custom_address?
 
  53     @@can_bind_to_custom_address ||= /^\s-a\s/.match `#{OPTIONS[:spawner]} -h`
 
  57 class MongrelSpawner < Spawner
 
  60       "mongrel_rails start -d " +
 
  61       "-a #{OPTIONS[:address]} " +
 
  63       "-P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid " +
 
  64       "-e #{OPTIONS[:environment]} " +
 
  65       "-c #{OPTIONS[:rails_root]} " +
 
  66       "-l #{OPTIONS[:rails_root]}/log/mongrel.log"
 
  68     # Add prefix functionality to spawner's call to mongrel_rails
 
  69     # Digging through mongrel's project subversion server, the earliest
 
  70     # Tag that has prefix implemented in the bin/mongrel_rails file
 
  71     # is 0.3.15 which also happens to be the earliest tag listed.
 
  72     # References: http://mongrel.rubyforge.org/svn/tags
 
  73     if Mongrel::Const::MONGREL_VERSION.to_f >=0.3 && !OPTIONS[:prefix].nil?
 
  74       cmd = cmd + " --prefix #{OPTIONS[:prefix]}"
 
  79   def self.can_bind_to_custom_address?
 
  86   require_library_or_gem 'fcgi'
 
  92   require_library_or_gem 'mongrel'
 
  94   # Mongrel not available
 
  97 server = case ARGV.first
 
  98   when "fcgi", "mongrel"
 
 103     elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `spawn-fcgi -version` }.blank? && defined?(FCGI)
 
 110     puts "=> Starting FCGI dispatchers"
 
 111     spawner_class = FcgiSpawner
 
 113     puts "=> Starting mongrel dispatchers"
 
 114     spawner_class = MongrelSpawner
 
 116     puts "Neither FCGI (spawn-fcgi) nor Mongrel was installed and available!"
 
 123   :environment => "production",
 
 124   :spawner     => '/usr/bin/env spawn-fcgi',
 
 125   :dispatcher  => File.expand_path(RELATIVE_RAILS_ROOT + '/public/dispatch.fcgi'),
 
 126   :pids        => File.expand_path(RELATIVE_RAILS_ROOT + "/tmp/pids"),
 
 127   :rails_root  => File.expand_path(RELATIVE_RAILS_ROOT),
 
 128   :process     => "dispatch",
 
 130   :address     => '0.0.0.0',
 
 136 ARGV.options do |opts|
 
 137   opts.banner = "Usage: spawner [platform] [options]"
 
 143     The spawner is a wrapper for spawn-fcgi and mongrel that makes it
 
 144     easier to start multiple processes running the Rails dispatcher. The
 
 145     spawn-fcgi command is included with the lighttpd web server, but can
 
 146     be used with both Apache and lighttpd (and any other web server
 
 147     supporting externally managed FCGI processes). Mongrel automatically
 
 148     ships with with mongrel_rails for starting dispatchers.
 
 150     The first choice you need to make is whether to spawn the Rails
 
 151     dispatchers as FCGI or Mongrel. By default, this spawner will prefer
 
 152     Mongrel, so if that's installed, and no platform choice is made,
 
 155     Then decide a starting port (default is 8000) and the number of FCGI
 
 156     process instances you'd like to run. So if you pick 9100 and 3
 
 157     instances, you'll start processes on 9100, 9101, and 9102.
 
 159     By setting the repeat option, you get a protection loop, which will
 
 160     attempt to restart any FCGI processes that might have been exited or
 
 163     You can select bind address for started processes. By default these
 
 164     listen on every interface. For single machine installations you would
 
 165     probably want to use 127.0.0.1, hiding them form the outside world.
 
 168        spawner               # starts instances on 8000, 8001, and 8002
 
 169                              # using Mongrel if available.
 
 170        spawner fcgi          # starts instances on 8000, 8001, and 8002
 
 172        spawner mongrel -i 5  # starts instances on 8000, 8001, 8002,
 
 173                              # 8003, and 8004 using Mongrel.
 
 174        spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to
 
 175                              # 9109 using Mongrel if available.
 
 176        spawner -p 9100 -r 5  # starts 3 instances counting from 9100 to
 
 177                              # 9102 and attempts start them every 5
 
 179        spawner -a 127.0.0.1  # starts 3 instances binding to localhost
 
 184   opts.on("-p", "--port=number",      Integer, "Starting port number (default: #{OPTIONS[:port]})")                { |v| OPTIONS[:port] = v }
 
 186   if spawner_class.can_bind_to_custom_address?
 
 187     opts.on("-a", "--address=ip",     String,  "Bind to IP address (default: #{OPTIONS[:address]})")                { |v| OPTIONS[:address] = v }
 
 190   opts.on("-p", "--port=number",      Integer, "Starting port number (default: #{OPTIONS[:port]})")                { |v| OPTIONS[:port] = v }
 
 191   opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})")            { |v| OPTIONS[:instances] = v }
 
 192   opts.on("-r", "--repeat=seconds",   Integer, "Repeat spawn attempts every n seconds (default: off)")             { |v| OPTIONS[:repeat] = v }
 
 193   opts.on("-e", "--environment=name", String,  "test|development|production (default: #{OPTIONS[:environment]})")  { |v| OPTIONS[:environment] = v }
 
 194   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 }
 
 195   opts.on("-n", "--process=name",     String,  "default: #{OPTIONS[:process]}")                                    { |v| OPTIONS[:process] = v }
 
 196   opts.on("-s", "--spawner=path",     String,  "default: #{OPTIONS[:spawner]}")                                    { |v| OPTIONS[:spawner] = v }
 
 197   opts.on("-d", "--dispatcher=path",  String,  "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) }
 
 201   opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
 
 206 ENV["RAILS_ENV"] = OPTIONS[:environment]
 
 210   trap("TERM") { exit }
 
 211   spawner_class.record_pid
 
 214     spawner_class.spawn_all
 
 215     sleep(OPTIONS[:repeat])
 
 218   spawner_class.spawn_all