From 707106f347fada1ac9a7c9e2da7e6d2342f8441d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 6 Jul 2005 04:29:18 +0000 Subject: [PATCH] Added fixed gateway script [Nicholas Seckar] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1721 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/Rakefile | 2 +- railties/bin/listener | 151 ++++++++++++++------------------ railties/bin/tracker | 149 ++++++++++++------------------- railties/dispatches/gateway.cgi | 119 ++++++++++++++++--------- railties/html/index.html | 1 + 5 files changed, 202 insertions(+), 220 deletions(-) diff --git a/railties/Rakefile b/railties/Rakefile index dc2b7a80f8..ffe2cee774 100644 --- a/railties/Rakefile +++ b/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 diff --git a/railties/bin/listener b/railties/bin/listener index 493ac9dca4..421c453f23 100644 --- a/railties/bin/listener +++ b/railties/bin/listener @@ -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 \ No newline at end of file + + 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) \ No newline at end of file diff --git a/railties/bin/tracker b/railties/bin/tracker index 17fce67e54..859c9fa0e0 100644 --- a/railties/bin/tracker +++ b/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 + + 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) \ No newline at end of file diff --git a/railties/dispatches/gateway.cgi b/railties/dispatches/gateway.cgi index d9f0c17846..d21bf0991a 100644 --- a/railties/dispatches/gateway.cgi +++ b/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. -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 \ No newline at end of file diff --git a/railties/html/index.html b/railties/html/index.html index d547e7fa04..d780f8e029 100644 --- a/railties/html/index.html +++ b/railties/html/index.html @@ -60,6 +60,7 @@
  • See all the tests run by running rake.
  • Develop your Rails application!
  • Setup Apache with FastCGI (and Ruby bindings), if you need better performance +
  • Remove the dispatches you don't use (so if you're on FastCGI, delete/move dispatch.rb, dispatch.cgi and gateway.cgi)