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