Skip to content
This repository
Browse code

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
David Heinemeier Hansson authored July 06, 2005
2  railties/Rakefile
@@ -137,7 +137,7 @@ task :copy_dispatches do
137 137
   chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi"
138 138
 
139 139
   copy_with_rewritten_ruby_path("dispatches/gateway.cgi", "#{PKG_DESTINATION}/public/gateway.cgi")
140  
-  chmod 0644, "#{PKG_DESTINATION}/public/gateway.cgi"
  140
+  chmod 0755, "#{PKG_DESTINATION}/public/gateway.cgi"
141 141
 end
142 142
 
143 143
 task :copy_html_files do
147  railties/bin/listener
... ...
@@ -1,103 +1,86 @@
1  
-require "drb"
2  
-ENV["RAILS_ENV"] = 'production'
3  
-require "#{File.dirname(__FILE__)}/../config/environment.rb"
  1
+#!/usr/local/bin/ruby
  2
+
  3
+require 'stringio'
  4
+require 'fileutils'
4 5
 require 'fcgi_handler'
5  
-require 'rbconfig'
6 6
 
7  
-VERBOSE = false
  7
+def message(s)
  8
+  $stderr.puts "listener: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
  9
+end
  10
+
  11
+class RemoteCGI < CGI
  12
+  attr_accessor :stdinput, :stdoutput, :env_table
  13
+  def initialize(env_table, input = nil, output = nil)
  14
+    self.env_table = env_table
  15
+    self.stdinput = input || StringIO.new
  16
+    self.stdoutput = output || StringIO.new
  17
+    super()
  18
+  end
  19
+
  20
+  def out(stream) # Ignore the requested output stream
  21
+    super(stdoutput)
  22
+  end
  23
+end
8 24
 
9 25
 class Listener
10 26
   include DRbUndumped
11  
-  attr_accessor :tracker
12  
-  
13  
-  def initialize(timeout = nil)
14  
-    @timeout = timeout
  27
+
  28
+  def initialize(timeout, socket_path)
  29
+    @socket = File.expand_path(socket_path)
15 30
     @mutex = Mutex.new
16 31
     @active = false
17  
-    
  32
+    @timeout = timeout
  33
+
18 34
     @handler = RailsFCGIHandler.new
19 35
     @handler.extend DRbUndumped
20  
-    @output = FakeOut.new
21  
-    $stdout = @output
22  
-  end
23  
-  
24  
-  def inform_up(tracker_uri)
25  
-    return unless tracker_uri
26  
-    tracker = DRbObject.new_with_uri(tracker_uri)
27  
-    tracker.register_listener self
28  
-    @tracker = tracker
29  
-  end
30  
-  def inform_down
31  
-    @tracker.remove_listener(self) if @tracker
32  
-  end
33 36
 
34  
-  def run(on_uri, tracker_uri)
35  
-    on_uri ||= "drbunix:"
36  
-    DRb.start_service(on_uri, self) # Start a server for us
37  
-    inform_up tracker_uri
38  
-    @handler.process!(self)
39  
-  end
40  
-  
41  
-  def die!
42  
-    inform_down
43  
-    Kernel.exit 0
44  
-  end 
45  
-  
46  
-  def process(input)
47  
-    $stderr.puts "listener: received request -- obtaining lock" if VERBOSE 
48  
-    @mutex.synchronize do
49  
-      @active = true
50  
-      
51  
-      $stderr.puts "listener: obtained -- swaping stdin" if VERBOSE
52  
-      $stdin = input
53  
-      cgi = CGI.new
54  
-      
55  
-      $stderr.puts "listener: yielding to FCGI handler..." if VERBOSE
56  
-      @cgi_block.call cgi
57  
-      $stderr.puts "listener: handler finished, releasing control" if VERBOSE
58  
-      
59  
-      return @output.read!
60  
-    end
  37
+    message 'opening socket'
  38
+    DRb.start_service("drbunix:#{@socket}", self)
  39
+
  40
+    message 'entering process loop'
  41
+    @handler.process! self
61 42
   end
62  
-  
63  
-  def each_cgi(&block)
64  
-    @cgi_block = block
  43
+
  44
+  def each_cgi(&cgi_block)
  45
+    @cgi_block = cgi_block
  46
+    message 'entering idle loop'
65 47
     loop do
66  
-      @timeout ? sleep(@timeout) : sleep
  48
+      sleep @timeout rescue nil
67 49
       die! unless @active
68 50
       @active = false
69 51
     end
70 52
   end
71  
-end
72 53
 
73  
-class FakeOut < Struct.new(:contents)
74  
-  def initialize
75  
-    super("")
76  
-  end
77  
-  def write(str)
78  
-    contents << str
  54
+  def process(env, input)
  55
+    message 'received request'
  56
+    @mutex.synchronize do
  57
+      @active = true
  58
+
  59
+      message 'creating input stream'
  60
+      input_stream = StringIO.new(input)
  61
+      message 'building CGI instance'
  62
+      cgi = RemoteCGI.new(eval(env), input_stream)
  63
+
  64
+      message 'yielding to fcgi handler'
  65
+      @cgi_block.call cgi
  66
+      message 'yield finished -- sending output'
  67
+
  68
+      cgi.stdoutput.seek(0)
  69
+      output = cgi.stdoutput.read
  70
+
  71
+      return output
  72
+    end
79 73
   end
80  
-  def read!
81  
-    c = contents
82  
-    self.contents = ''
83  
-    return c
  74
+
  75
+  def die!
  76
+    message 'shutting down'
  77
+    DRb.stop_service
  78
+    FileUtils.rm_f @socket
  79
+    Kernel.exit 0
84 80
   end
85 81
 end
86 82
 
87  
-if ARGV.shift == 'start-listeners'
88  
-  tracker = ARGV.shift
89  
-  number = (ARGV.shift || '1').to_i
90  
-  exit(0) if number.zero?
91  
-  
92  
-  if number > 1
93  
-    fork do
94  
-      exec(
95  
-        File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']), 
96  
-        __FILE__, 'start-listeners', tracker, (number - 1).to_s
97  
-      )
98  
-    end
99  
-  end
100  
-  
101  
-  l = Listener.new(90)
102  
-  l.run(nil, tracker)
103  
-end
  83
+socket_path = ARGV.shift
  84
+timeout = (ARGV.shift || 90).to_i
  85
+
  86
+Listener.new(timeout, socket_path)
149  railties/bin/tracker
... ...
@@ -1,110 +1,69 @@
1  
-require "drb"
2  
-require "rbconfig"
  1
+#!/usr/local/bin/ruby
3 2
 
4  
-VERBOSE = false
  3
+require 'drb'
  4
+require 'thread'
  5
+
  6
+def message(s)
  7
+  $stderr.puts "tracker: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
  8
+end
5 9
 
6 10
 class Tracker
7 11
   include DRbUndumped
8  
-  
9  
-  def initialize(timeout = 90, uri = nil)
10  
-    @timeout = timeout
11  
-    @waiting = []
12  
-    @working = []
13  
-    
14  
-    @waiting_mutex = Mutex.new
15  
-    
16  
-    DRb.start_service(uri, self)
17  
-    @uri = DRb.uri
18  
-  end
19  
-  def run
20  
-    start_listener 3
21  
-    sleep 3
22  
-    
23  
-    background
24  
-  end
25  
-  
26  
-  def register_listener(listener)
27  
-    @waiting.push listener
28  
-    nil
29  
-  end
30  
-  def remove_listener(listener)
31  
-    @waiting.delete listener
32  
-    @working.delete listener
33  
-    nil
  12
+
  13
+  def initialize(instances, socket_path)
  14
+    @instances = instances
  15
+    @socket = File.expand_path(socket_path)
  16
+    @active = false
  17
+
  18
+    @listeners = []
  19
+    @instances.times { @listeners << Mutex.new }
  20
+
  21
+    message "using #{@listeners.length} listeners"
  22
+    message "opening socket at #{@socket}"
  23
+
  24
+    @service = DRb.start_service("drbunix://#{@socket}", self)
34 25
   end
35  
-  
  26
+
36 27
   def with_listener
37  
-    listener = @waiting.shift
38  
-    unless listener
39  
-      start_listener(2) unless @waiting.length + @working.length > 6
40  
-      @waiting_mutex.synchronize do
41  
-	10.times do
42  
-          sleep 0.5
43  
-          listener = @waiting.shift
44  
-          break if listener
45  
-        end
46  
-        unless listener
47  
-          ($stderr.puts "Dropping request due to lack of listeners!!!" unless listener) if VERBOSE
48  
-          return
49  
-        end
  28
+    message "listener requested"
  29
+
  30
+    mutex = has_lock = index = nil
  31
+    3.times do 
  32
+      @listeners.each_with_index do |mutex, index|
  33
+        has_lock = mutex.try_lock
  34
+        break if has_lock
50 35
       end
  36
+      break if has_lock
  37
+      sleep 0.05
51 38
     end
52  
-    
53  
-    @working << listener
54  
-    yield listener
55  
-  ensure
56  
-    if listener
57  
-      @working.delete listener
58  
-      @waiting << listener
59  
-    end
60  
-  end
61  
-  
62  
-  def background
63  
-    loop do
64  
-      @timeout ? sleep(@timeout) : sleep
65  
-      unless @processed
66  
-        $stderr.puts "Idle for #{@timeout} -- shutting down tracker." if VERBOSE
67  
-        Kernel.exit 0 
  39
+
  40
+    if has_lock
  41
+      message "obtained listener #{index}"
  42
+      @active = true
  43
+      begin yield index
  44
+      ensure
  45
+        mutex.unlock
  46
+        message "released listener #{index}"
68 47
       end
69  
-      @processed = false
  48
+    else
  49
+      message "dropping request because no listeners are available!"
70 50
     end
71 51
   end
72  
-    
73  
-  def process(input)
74  
-    output = nil
75  
-    $stderr.puts "tracker: received request.. obtaining listener" if VERBOSE
76  
-    with_listener do |listener|
77  
-      $stderr.puts "tracker: obtained -- forwarding request to listener.." if VERBOSE
78  
-      @processed = true
79  
-      output = listener.process(input)
80  
-      $stderr.puts "tracker: listener released control." if VERBOSE
81  
-    end
82  
-    return output
83  
-  end
84  
-  
85  
-  def start_listener(n = 1)
86  
-    tracker_uri = @uri
87  
-    listener_path = File.join(File.dirname(__FILE__), 'listener')
88  
-    fork do
89  
-      exec(
90  
-        File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']), 
91  
-        listener_path, 'start-listeners', tracker_uri, n.to_s
92  
-      )
  52
+
  53
+  def background(check_interval = nil)
  54
+    if check_interval
  55
+      loop do
  56
+        sleep check_interval
  57
+        message "Idle for #{check_interval}, shutting down" unless @active
  58
+        @active = false
  59
+        Kernel.exit 0
  60
+      end
  61
+    else DRb.thread.join
93 62
     end
94 63
   end
95  
-  
96  
-  def ping
97  
-    true
98  
-  end
99 64
 end
100 65
 
101  
-if ARGV.first == "start"
102  
-  tracker = Tracker.new(90, ARGV[1])
103  
-  socket = (/druby:([^?]*)\?/ =~ ARGV[1]) ? $1 : nil
104  
-  require 'fileutils' if socket
105  
-
106  
-  begin tracker.run
107  
-  ensure 
108  
-    FileUtils.rm_f(socket) if socket
109  
-  end
110  
-end
  66
+socket_path = ARGV.shift
  67
+instances = ARGV.shift.to_i
  68
+t = Tracker.new(instances, socket_path)
  69
+t.background(ARGV.first ? ARGV.shift.to_i : 90)
113  railties/dispatches/gateway.cgi
... ...
@@ -1,58 +1,97 @@
1  
-#!/usr/bin/ruby
2  
-
3  
-# This is an experimental feature for getting high-speed CGI by using a long-running, DRb-backed server in the background
  1
+#!/usr/local/bin/ruby
4 2
 
5 3
 require 'drb'
6  
-require 'cgi'
7  
-require 'rbconfig'
8 4
 
9  
-VERBOSE = false
  5
+# This file includes an experimental gateway CGI implementation. It will work
  6
+# only on platforms which support both fork and sockets.
  7
+#
  8
+# To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi.
  9
+#
  10
+# Next, create the directory log/drb_gateway and grant the apache user rw access
  11
+# to said directory.
  12
+#
  13
+# On the next request to your server, the gateway tracker should start up, along
  14
+# with a few listener processes. This setup should provide you with much better
  15
+# speeds than dispatch.cgi.
  16
+#
  17
+# Keep in mind that the first request made to the server will be slow, as the
  18
+# tracker and listeners will have to load. Also, the tracker and listeners will
  19
+# shutdown after a period if inactivity. You can set this value below -- the
  20
+# default is 90 seconds.
  21
+
  22
+TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
  23
+DieAfter = 90 # Seconds
  24
+Listeners = 3
10 25
 
11  
-AppName = File.split(File.expand_path(File.join(__FILE__, '..'))).last
12  
-SocketPath = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway.sock'))
13  
-ConnectionUri = "drbunix:#{SocketPath}"
14  
-attempted_start = false
  26
+def message(s)
  27
+  $stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
  28
+end
15 29
 
16  
-def start_tracker
17  
-  tracker_path = File.join(File.dirname(__FILE__), '../script/tracker')
  30
+def listener_socket(number)
  31
+  File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock"))
  32
+end
  33
+
  34
+unless File.exists? TrackerSocket
  35
+  message "Starting tracker and #{Listeners} listeners"
18 36
   fork do
19 37
     Process.setsid
20 38
     STDIN.reopen "/dev/null"
21 39
     STDOUT.reopen "/dev/null", "a"
22  
-    
23  
-    exec(File.join(Config::CONFIG['bin_dir'], Config::CONFIG['RUBY_SO_NAME']), tracker_path, 'start', ConnectionUri)
  40
+
  41
+    root = File.expand_path(File.dirname(__FILE__) + '/..')
  42
+
  43
+    message "starting tracker"
  44
+    fork do
  45
+      ARGV.clear
  46
+      ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s
  47
+      load File.join(root, 'script', 'tracker')
  48
+    end
  49
+
  50
+    message "starting listeners"
  51
+    require File.join(root, 'config/environment.rb')
  52
+    Listeners.times do |number|
  53
+      fork do
  54
+        ARGV.clear
  55
+        ARGV << listener_socket(number) << DieAfter.to_s
  56
+        load File.join(root, 'script', 'listener')
  57
+      end
  58
+    end
24 59
   end
25  
-  
26  
-  $stderr.puts "dispatch: waiting for tracker to start..." if VERBOSE
  60
+
  61
+  message "waiting for tracker and listener to arise..."
  62
+  ready = false
27 63
   10.times do
28 64
     sleep 0.5
29  
-    return if File.exists? SocketPath
  65
+    break if (ready = File.exists?(TrackerSocket) && File.exists?(listener_socket(0)))
30 66
   end
31  
-  
32  
-  $stderr.puts "Can't start tracker!!! Dropping request!"
33  
-  Kernel.exit 1
34  
-end
35 67
 
36  
-unless File.exists?(SocketPath)
37  
-  $stderr.puts "tracker not running: starting it..." if VERBOSE
38  
-  start_tracker
  68
+  if ready
  69
+    message "tracker and listener are ready"
  70
+  else
  71
+    message "Waited 5 seconds, listener and tracker not ready... dropping request"
  72
+    Kernel.exit 1
  73
+  end
39 74
 end
40 75
 
41  
-$stderr.puts "dispatch: attempting to contact tracker..." if VERBOSE
42  
-tracker = DRbObject.new_with_uri(ConnectionUri)
43  
-tracker.ping # Test connection
  76
+DRb.start_service
44 77
 
45  
-$stdout.extend DRbUndumped
46  
-$stdin.extend DRbUndumped
47  
-  
48  
-DRb.start_service "drbunix:", $stdin
49  
-$stderr.puts "dispatch: publishing stdin..." if VERBOSE
  78
+message "connecting to tracker"
  79
+tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
50 80
 
51  
-$stderr.puts "dispatch: sending request to tracker" if VERBOSE
52  
-puts tracker.process($stdin)
  81
+input = $stdin.read
  82
+$stdin.close
53 83
 
54  
-$stdout.flush
55  
-[$stdin, $stdout].each {|io| io.close}
56  
-$stderr.puts "dispatch: finished..." if VERBOSE
  84
+env = ENV.inspect
57 85
 
  86
+output = nil
  87
+tracker.with_listener do |number|
  88
+  message "connecting to listener #{number}"
  89
+  socket = listener_socket(number)
  90
+  listener = DRbObject.new_with_uri("drbunix:#{socket}")
  91
+  output = listener.process(env, input)
  92
+  message "listener #{number} has finished, writing output"
  93
+end
58 94
 
  95
+$stdout.write output
  96
+$stdout.flush
  97
+$stdout.close
1  railties/html/index.html
@@ -60,6 +60,7 @@
60 60
   <li>See all the tests run by running <code>rake</code>.
61 61
   <li>Develop your Rails application!
62 62
   <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
  63
+  <li>Remove the dispatches you don't use (so if you're on FastCGI, delete/move dispatch.rb, dispatch.cgi and gateway.cgi)</li>
63 64
 </ol>
64 65
 
65 66
 <p>

0 notes on commit 707106f

Please sign in to comment.
Something went wrong with that request. Please try again.