Added fixed gateway script [Nicholas Seckar]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1721 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
3170295136
commit
707106f347
@ -137,7 +137,7 @@ task :copy_dispatches do
|
||||
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi"
|
||||
|
||||
copy_with_rewritten_ruby_path("dispatches/gateway.cgi", "#{PKG_DESTINATION}/public/gateway.cgi")
|
||||
chmod 0644, "#{PKG_DESTINATION}/public/gateway.cgi"
|
||||
chmod 0755, "#{PKG_DESTINATION}/public/gateway.cgi"
|
||||
end
|
||||
|
||||
task :copy_html_files do
|
||||
|
@ -1,103 +1,86 @@
|
||||
require "drb"
|
||||
ENV["RAILS_ENV"] = 'production'
|
||||
require "#{File.dirname(__FILE__)}/../config/environment.rb"
|
||||
require 'fcgi_handler'
|
||||
require 'rbconfig'
|
||||
#!/usr/local/bin/ruby
|
||||
|
||||
VERBOSE = false
|
||||
require 'stringio'
|
||||
require 'fileutils'
|
||||
require 'fcgi_handler'
|
||||
|
||||
def message(s)
|
||||
$stderr.puts "listener: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
|
||||
end
|
||||
|
||||
class RemoteCGI < CGI
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
def initialize(env_table, input = nil, output = nil)
|
||||
self.env_table = env_table
|
||||
self.stdinput = input || StringIO.new
|
||||
self.stdoutput = output || StringIO.new
|
||||
super()
|
||||
end
|
||||
|
||||
def out(stream) # Ignore the requested output stream
|
||||
super(stdoutput)
|
||||
end
|
||||
end
|
||||
|
||||
class Listener
|
||||
include DRbUndumped
|
||||
attr_accessor :tracker
|
||||
|
||||
def initialize(timeout = nil)
|
||||
@timeout = timeout
|
||||
|
||||
def initialize(timeout, socket_path)
|
||||
@socket = File.expand_path(socket_path)
|
||||
@mutex = Mutex.new
|
||||
@active = false
|
||||
|
||||
@timeout = timeout
|
||||
|
||||
@handler = RailsFCGIHandler.new
|
||||
@handler.extend DRbUndumped
|
||||
@output = FakeOut.new
|
||||
$stdout = @output
|
||||
end
|
||||
|
||||
def inform_up(tracker_uri)
|
||||
return unless tracker_uri
|
||||
tracker = DRbObject.new_with_uri(tracker_uri)
|
||||
tracker.register_listener self
|
||||
@tracker = tracker
|
||||
end
|
||||
def inform_down
|
||||
@tracker.remove_listener(self) if @tracker
|
||||
|
||||
message 'opening socket'
|
||||
DRb.start_service("drbunix:#{@socket}", self)
|
||||
|
||||
message 'entering process loop'
|
||||
@handler.process! self
|
||||
end
|
||||
|
||||
def run(on_uri, tracker_uri)
|
||||
on_uri ||= "drbunix:"
|
||||
DRb.start_service(on_uri, self) # Start a server for us
|
||||
inform_up tracker_uri
|
||||
@handler.process!(self)
|
||||
end
|
||||
|
||||
def die!
|
||||
inform_down
|
||||
Kernel.exit 0
|
||||
end
|
||||
|
||||
def process(input)
|
||||
$stderr.puts "listener: received request -- obtaining lock" if VERBOSE
|
||||
@mutex.synchronize do
|
||||
@active = true
|
||||
|
||||
$stderr.puts "listener: obtained -- swaping stdin" if VERBOSE
|
||||
$stdin = input
|
||||
cgi = CGI.new
|
||||
|
||||
$stderr.puts "listener: yielding to FCGI handler..." if VERBOSE
|
||||
@cgi_block.call cgi
|
||||
$stderr.puts "listener: handler finished, releasing control" if VERBOSE
|
||||
|
||||
return @output.read!
|
||||
end
|
||||
end
|
||||
|
||||
def each_cgi(&block)
|
||||
@cgi_block = block
|
||||
def each_cgi(&cgi_block)
|
||||
@cgi_block = cgi_block
|
||||
message 'entering idle loop'
|
||||
loop do
|
||||
@timeout ? sleep(@timeout) : sleep
|
||||
sleep @timeout rescue nil
|
||||
die! unless @active
|
||||
@active = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FakeOut < Struct.new(:contents)
|
||||
def initialize
|
||||
super("")
|
||||
end
|
||||
def write(str)
|
||||
contents << str
|
||||
end
|
||||
def read!
|
||||
c = contents
|
||||
self.contents = ''
|
||||
return c
|
||||
end
|
||||
end
|
||||
def process(env, input)
|
||||
message 'received request'
|
||||
@mutex.synchronize do
|
||||
@active = true
|
||||
|
||||
if ARGV.shift == 'start-listeners'
|
||||
tracker = ARGV.shift
|
||||
number = (ARGV.shift || '1').to_i
|
||||
exit(0) if number.zero?
|
||||
|
||||
if number > 1
|
||||
fork do
|
||||
exec(
|
||||
File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']),
|
||||
__FILE__, 'start-listeners', tracker, (number - 1).to_s
|
||||
)
|
||||
message 'creating input stream'
|
||||
input_stream = StringIO.new(input)
|
||||
message 'building CGI instance'
|
||||
cgi = RemoteCGI.new(eval(env), input_stream)
|
||||
|
||||
message 'yielding to fcgi handler'
|
||||
@cgi_block.call cgi
|
||||
message 'yield finished -- sending output'
|
||||
|
||||
cgi.stdoutput.seek(0)
|
||||
output = cgi.stdoutput.read
|
||||
|
||||
return output
|
||||
end
|
||||
end
|
||||
|
||||
l = Listener.new(90)
|
||||
l.run(nil, tracker)
|
||||
end
|
||||
|
||||
def die!
|
||||
message 'shutting down'
|
||||
DRb.stop_service
|
||||
FileUtils.rm_f @socket
|
||||
Kernel.exit 0
|
||||
end
|
||||
end
|
||||
|
||||
socket_path = ARGV.shift
|
||||
timeout = (ARGV.shift || 90).to_i
|
||||
|
||||
Listener.new(timeout, socket_path)
|
@ -1,110 +1,69 @@
|
||||
require "drb"
|
||||
require "rbconfig"
|
||||
#!/usr/local/bin/ruby
|
||||
|
||||
VERBOSE = false
|
||||
require 'drb'
|
||||
require 'thread'
|
||||
|
||||
def message(s)
|
||||
$stderr.puts "tracker: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
|
||||
end
|
||||
|
||||
class Tracker
|
||||
include DRbUndumped
|
||||
|
||||
def initialize(timeout = 90, uri = nil)
|
||||
@timeout = timeout
|
||||
@waiting = []
|
||||
@working = []
|
||||
|
||||
@waiting_mutex = Mutex.new
|
||||
|
||||
DRb.start_service(uri, self)
|
||||
@uri = DRb.uri
|
||||
|
||||
def initialize(instances, socket_path)
|
||||
@instances = instances
|
||||
@socket = File.expand_path(socket_path)
|
||||
@active = false
|
||||
|
||||
@listeners = []
|
||||
@instances.times { @listeners << Mutex.new }
|
||||
|
||||
message "using #{@listeners.length} listeners"
|
||||
message "opening socket at #{@socket}"
|
||||
|
||||
@service = DRb.start_service("drbunix://#{@socket}", self)
|
||||
end
|
||||
def run
|
||||
start_listener 3
|
||||
sleep 3
|
||||
|
||||
background
|
||||
end
|
||||
|
||||
def register_listener(listener)
|
||||
@waiting.push listener
|
||||
nil
|
||||
end
|
||||
def remove_listener(listener)
|
||||
@waiting.delete listener
|
||||
@working.delete listener
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def with_listener
|
||||
listener = @waiting.shift
|
||||
unless listener
|
||||
start_listener(2) unless @waiting.length + @working.length > 6
|
||||
@waiting_mutex.synchronize do
|
||||
10.times do
|
||||
sleep 0.5
|
||||
listener = @waiting.shift
|
||||
break if listener
|
||||
end
|
||||
unless listener
|
||||
($stderr.puts "Dropping request due to lack of listeners!!!" unless listener) if VERBOSE
|
||||
return
|
||||
end
|
||||
message "listener requested"
|
||||
|
||||
mutex = has_lock = index = nil
|
||||
3.times do
|
||||
@listeners.each_with_index do |mutex, index|
|
||||
has_lock = mutex.try_lock
|
||||
break if has_lock
|
||||
end
|
||||
break if has_lock
|
||||
sleep 0.05
|
||||
end
|
||||
|
||||
@working << listener
|
||||
yield listener
|
||||
ensure
|
||||
if listener
|
||||
@working.delete listener
|
||||
@waiting << listener
|
||||
end
|
||||
end
|
||||
|
||||
def background
|
||||
loop do
|
||||
@timeout ? sleep(@timeout) : sleep
|
||||
unless @processed
|
||||
$stderr.puts "Idle for #{@timeout} -- shutting down tracker." if VERBOSE
|
||||
Kernel.exit 0
|
||||
|
||||
if has_lock
|
||||
message "obtained listener #{index}"
|
||||
@active = true
|
||||
begin yield index
|
||||
ensure
|
||||
mutex.unlock
|
||||
message "released listener #{index}"
|
||||
end
|
||||
@processed = false
|
||||
else
|
||||
message "dropping request because no listeners are available!"
|
||||
end
|
||||
end
|
||||
|
||||
def process(input)
|
||||
output = nil
|
||||
$stderr.puts "tracker: received request.. obtaining listener" if VERBOSE
|
||||
with_listener do |listener|
|
||||
$stderr.puts "tracker: obtained -- forwarding request to listener.." if VERBOSE
|
||||
@processed = true
|
||||
output = listener.process(input)
|
||||
$stderr.puts "tracker: listener released control." if VERBOSE
|
||||
|
||||
def background(check_interval = nil)
|
||||
if check_interval
|
||||
loop do
|
||||
sleep check_interval
|
||||
message "Idle for #{check_interval}, shutting down" unless @active
|
||||
@active = false
|
||||
Kernel.exit 0
|
||||
end
|
||||
else DRb.thread.join
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
def start_listener(n = 1)
|
||||
tracker_uri = @uri
|
||||
listener_path = File.join(File.dirname(__FILE__), 'listener')
|
||||
fork do
|
||||
exec(
|
||||
File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']),
|
||||
listener_path, 'start-listeners', tracker_uri, n.to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def ping
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if ARGV.first == "start"
|
||||
tracker = Tracker.new(90, ARGV[1])
|
||||
socket = (/druby:([^?]*)\?/ =~ ARGV[1]) ? $1 : nil
|
||||
require 'fileutils' if socket
|
||||
|
||||
begin tracker.run
|
||||
ensure
|
||||
FileUtils.rm_f(socket) if socket
|
||||
end
|
||||
end
|
||||
socket_path = ARGV.shift
|
||||
instances = ARGV.shift.to_i
|
||||
t = Tracker.new(instances, socket_path)
|
||||
t.background(ARGV.first ? ARGV.shift.to_i : 90)
|
@ -1,58 +1,97 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
# This is an experimental feature for getting high-speed CGI by using a long-running, DRb-backed server in the background
|
||||
#!/usr/local/bin/ruby
|
||||
|
||||
require 'drb'
|
||||
require 'cgi'
|
||||
require 'rbconfig'
|
||||
|
||||
VERBOSE = false
|
||||
# This file includes an experimental gateway CGI implementation. It will work
|
||||
# only on platforms which support both fork and sockets.
|
||||
#
|
||||
# To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi.
|
||||
#
|
||||
# Next, create the directory log/drb_gateway and grant the apache user rw access
|
||||
# to said directory.
|
||||
#
|
||||
# On the next request to your server, the gateway tracker should start up, along
|
||||
# with a few listener processes. This setup should provide you with much better
|
||||
# speeds than dispatch.cgi.
|
||||
#
|
||||
# Keep in mind that the first request made to the server will be slow, as the
|
||||
# tracker and listeners will have to load. Also, the tracker and listeners will
|
||||
# shutdown after a period if inactivity. You can set this value below -- the
|
||||
# default is 90 seconds.
|
||||
|
||||
AppName = File.split(File.expand_path(File.join(__FILE__, '..'))).last
|
||||
SocketPath = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway.sock'))
|
||||
ConnectionUri = "drbunix:#{SocketPath}"
|
||||
attempted_start = false
|
||||
TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
|
||||
DieAfter = 90 # Seconds
|
||||
Listeners = 3
|
||||
|
||||
def start_tracker
|
||||
tracker_path = File.join(File.dirname(__FILE__), '../script/tracker')
|
||||
def message(s)
|
||||
$stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
|
||||
end
|
||||
|
||||
def listener_socket(number)
|
||||
File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock"))
|
||||
end
|
||||
|
||||
unless File.exists? TrackerSocket
|
||||
message "Starting tracker and #{Listeners} listeners"
|
||||
fork do
|
||||
Process.setsid
|
||||
STDIN.reopen "/dev/null"
|
||||
STDOUT.reopen "/dev/null", "a"
|
||||
|
||||
exec(File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']), tracker_path, 'start', ConnectionUri)
|
||||
|
||||
root = File.expand_path(File.dirname(__FILE__) + '/..')
|
||||
|
||||
message "starting tracker"
|
||||
fork do
|
||||
ARGV.clear
|
||||
ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s
|
||||
load File.join(root, 'script', 'tracker')
|
||||
end
|
||||
|
||||
message "starting listeners"
|
||||
require File.join(root, 'config/environment.rb')
|
||||
Listeners.times do |number|
|
||||
fork do
|
||||
ARGV.clear
|
||||
ARGV << listener_socket(number) << DieAfter.to_s
|
||||
load File.join(root, 'script', 'listener')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$stderr.puts "dispatch: waiting for tracker to start..." if VERBOSE
|
||||
|
||||
message "waiting for tracker and listener to arise..."
|
||||
ready = false
|
||||
10.times do
|
||||
sleep 0.5
|
||||
return if File.exists? SocketPath
|
||||
break if (ready = File.exists?(TrackerSocket) && File.exists?(listener_socket(0)))
|
||||
end
|
||||
|
||||
if ready
|
||||
message "tracker and listener are ready"
|
||||
else
|
||||
message "Waited 5 seconds, listener and tracker not ready... dropping request"
|
||||
Kernel.exit 1
|
||||
end
|
||||
|
||||
$stderr.puts "Can't start tracker!!! Dropping request!"
|
||||
Kernel.exit 1
|
||||
end
|
||||
|
||||
unless File.exists?(SocketPath)
|
||||
$stderr.puts "tracker not running: starting it..." if VERBOSE
|
||||
start_tracker
|
||||
DRb.start_service
|
||||
|
||||
message "connecting to tracker"
|
||||
tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
|
||||
|
||||
input = $stdin.read
|
||||
$stdin.close
|
||||
|
||||
env = ENV.inspect
|
||||
|
||||
output = nil
|
||||
tracker.with_listener do |number|
|
||||
message "connecting to listener #{number}"
|
||||
socket = listener_socket(number)
|
||||
listener = DRbObject.new_with_uri("drbunix:#{socket}")
|
||||
output = listener.process(env, input)
|
||||
message "listener #{number} has finished, writing output"
|
||||
end
|
||||
|
||||
$stderr.puts "dispatch: attempting to contact tracker..." if VERBOSE
|
||||
tracker = DRbObject.new_with_uri(ConnectionUri)
|
||||
tracker.ping # Test connection
|
||||
|
||||
$stdout.extend DRbUndumped
|
||||
$stdin.extend DRbUndumped
|
||||
|
||||
DRb.start_service "drbunix:", $stdin
|
||||
$stderr.puts "dispatch: publishing stdin..." if VERBOSE
|
||||
|
||||
$stderr.puts "dispatch: sending request to tracker" if VERBOSE
|
||||
puts tracker.process($stdin)
|
||||
|
||||
$stdout.write output
|
||||
$stdout.flush
|
||||
[$stdin, $stdout].each {|io| io.close}
|
||||
$stderr.puts "dispatch: finished..." if VERBOSE
|
||||
|
||||
|
||||
$stdout.close
|
@ -60,6 +60,7 @@
|
||||
<li>See all the tests run by running <code>rake</code>.
|
||||
<li>Develop your Rails application!
|
||||
<li>Setup Apache with <a href="http://www.fastcgi.com">FastCGI</a> (and <a href="http://raa.ruby-lang.org/list.rhtml?name=fcgi">Ruby bindings</a>), if you need better performance
|
||||
<li>Remove the dispatches you don't use (so if you're on FastCGI, delete/move dispatch.rb, dispatch.cgi and gateway.cgi)</li>
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
|
Loading…
Reference in New Issue
Block a user