From 9e4f5f33974b018fe4b6eba2766af263f8b06951 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 4 Jul 2005 19:16:28 +0000 Subject: Added an EXPERIMENTAL gateway.cgi for getting high-speed performance through vanilla CGI using a long-running, DRb-backed server in the background (using script/listener and script/tracker) #1603 [Nicholas Seckar] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1676 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/bin/listener | 99 +++++++++++++++++++++++++++++++++++++ railties/bin/tracker | 106 ++++++++++++++++++++++++++++++++++++++++ railties/dispatches/gateway.cgi | 57 +++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 railties/bin/listener create mode 100644 railties/bin/tracker create mode 100644 railties/dispatches/gateway.cgi (limited to 'railties') diff --git a/railties/bin/listener b/railties/bin/listener new file mode 100644 index 0000000000..2310282204 --- /dev/null +++ b/railties/bin/listener @@ -0,0 +1,99 @@ +require "drb" +ENV["RAILS_ENV"] = 'production' +require "#{File.dirname(__FILE__)}/../config/environment.rb" +require 'fcgi_handler' + +VERBOSE = false + +class Listener + include DRbUndumped + attr_accessor :tracker + + def initialize(timeout = nil) + @timeout = timeout + @mutex = Mutex.new + @active = false + + @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 + end + + def each_cgi(&block) + @cgi_block = block + loop do + @timeout ? sleep(@timeout) : sleep + 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 + +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 'ruby', __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 diff --git a/railties/bin/tracker b/railties/bin/tracker new file mode 100644 index 0000000000..3a0c3aafc9 --- /dev/null +++ b/railties/bin/tracker @@ -0,0 +1,106 @@ +require "drb" + +VERBOSE = false + +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 + 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 + end + 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 + end + @processed = false + 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 'ruby', 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 diff --git a/railties/dispatches/gateway.cgi b/railties/dispatches/gateway.cgi new file mode 100644 index 0000000000..b8d44acef2 --- /dev/null +++ b/railties/dispatches/gateway.cgi @@ -0,0 +1,57 @@ +#!/usr/bin/ruby + +# This is an experimental feature for getting high-speed CGI by using a long-running, DRb-backed server in the background + +require File.join(File.dirname(__FILE__), 'drb') +require 'cgi' + +VERBOSE = false + +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 start_tracker + tracker_path = File.join(File.dirname(__FILE__), '../script/tracker') + fork do + Process.setsid + STDIN.reopen "/dev/null" + STDOUT.reopen "/dev/null", "a" + + exec 'ruby', tracker_path, 'start', ConnectionUri + end + + $stderr.puts "dispatch: waiting for tracker to start..." if VERBOSE + 10.times do + sleep 0.5 + return if File.exists? SocketPath + 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 +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.flush +[$stdin, $stdout].each {|io| io.close} +$stderr.puts "dispatch: finished..." if VERBOSE + + -- cgit v1.2.3