diff options
| author | David Heinemeier Hansson <david@loudthinking.com> | 2005-07-06 04:29:18 +0000 | 
|---|---|---|
| committer | David Heinemeier Hansson <david@loudthinking.com> | 2005-07-06 04:29:18 +0000 | 
| commit | 707106f347fada1ac9a7c9e2da7e6d2342f8441d (patch) | |
| tree | 41f972702ec8cd2e47983120618bbb9d89c420f8 | |
| parent | 31702951365c26f930a5f39698cb51309e111e98 (diff) | |
| download | rails-707106f347fada1ac9a7c9e2da7e6d2342f8441d.tar.gz rails-707106f347fada1ac9a7c9e2da7e6d2342f8441d.tar.bz2 rails-707106f347fada1ac9a7c9e2da7e6d2342f8441d.zip | |
Added fixed gateway script [Nicholas Seckar]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1721 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
| -rw-r--r-- | railties/Rakefile | 2 | ||||
| -rw-r--r-- | railties/bin/listener | 147 | ||||
| -rw-r--r-- | railties/bin/tracker | 149 | ||||
| -rw-r--r-- | railties/dispatches/gateway.cgi | 113 | ||||
| -rw-r--r-- | railties/html/index.html | 1 | 
5 files changed, 197 insertions, 215 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" +#!/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
\ No newline at end of file +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 -  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)
\ 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. + +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
\ 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 @@    <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> | 
