Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added fixed gateway script [Nicholas Seckar]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1721 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 707106f347fada1ac9a7c9e2da7e6d2342f8441d 1 parent 3170295
@dhh dhh authored
View
2  railties/Rakefile
@@ -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
View
147 railties/bin/listener
@@ -1,103 +1,86 @@
-require "drb"
-ENV["RAILS_ENV"] = 'production'
-require "#{File.dirname(__FILE__)}/../config/environment.rb"
+#!/usr/local/bin/ruby
+
+require 'stringio'
+require 'fileutils'
require 'fcgi_handler'
-require 'rbconfig'
-VERBOSE = false
+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
- 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
+ message 'opening socket'
+ DRb.start_service("drbunix:#{@socket}", self)
+
+ message 'entering process loop'
+ @handler.process! self
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
+ def process(env, input)
+ message 'received request'
+ @mutex.synchronize do
+ @active = true
+
+ 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
- def read!
- c = contents
- self.contents = ''
- return c
+
+ def die!
+ message 'shutting down'
+ DRb.stop_service
+ FileUtils.rm_f @socket
+ Kernel.exit 0
end
end
-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
- )
- end
- end
-
- l = Listener.new(90)
- l.run(nil, tracker)
-end
+socket_path = ARGV.shift
+timeout = (ARGV.shift || 90).to_i
+
+Listener.new(timeout, socket_path)
View
149 railties/bin/tracker
@@ -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
- 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
+
+ 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 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
- 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
- )
+
+ 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
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)
View
113 railties/dispatches/gateway.cgi
@@ -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.
+
+TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
+DieAfter = 90 # Seconds
+Listeners = 3
-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
+def message(s)
+ $stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
+end
-def start_tracker
- tracker_path = File.join(File.dirname(__FILE__), '../script/tracker')
+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
-
- $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
+ if ready
+ message "tracker and listener are ready"
+ else
+ message "Waited 5 seconds, listener and tracker not ready... dropping request"
+ Kernel.exit 1
+ end
end
-$stderr.puts "dispatch: attempting to contact tracker..." if VERBOSE
-tracker = DRbObject.new_with_uri(ConnectionUri)
-tracker.ping # Test connection
+DRb.start_service
-$stdout.extend DRbUndumped
-$stdin.extend DRbUndumped
-
-DRb.start_service "drbunix:", $stdin
-$stderr.puts "dispatch: publishing stdin..." if VERBOSE
+message "connecting to tracker"
+tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
-$stderr.puts "dispatch: sending request to tracker" if VERBOSE
-puts tracker.process($stdin)
+input = $stdin.read
+$stdin.close
-$stdout.flush
-[$stdin, $stdout].each {|io| io.close}
-$stderr.puts "dispatch: finished..." if VERBOSE
+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
+$stdout.write output
+$stdout.flush
+$stdout.close
View
1  railties/html/index.html
@@ -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>
Please sign in to comment.
Something went wrong with that request. Please try again.