Changeset 1592
- Timestamp:
- 07/02/05 04:52:14 (3 years ago)
- Files:
-
- trunk/railties/CHANGELOG (modified) (1 diff)
- trunk/railties/dispatches/dispatch.fcgi (modified) (1 diff)
- trunk/railties/lib/fcgi_handler.rb (modified) (4 diffs)
- trunk/railties/test/fcgi_dispatcher_test.rb (modified) (5 diffs)
- trunk/railties/test/mocks/dispatcher.rb (modified) (1 diff)
- trunk/railties/test/mocks/fcgi.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/railties/CHANGELOG
r1565 r1592 1 1 *SVN* 2 3 * SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows. 4 5 * Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi. [skaes@web.de] 2 6 3 7 * Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them. trunk/railties/dispatches/dispatch.fcgi
r1565 r1592 1 1 #!/usr/local/bin/ruby 2 2 # 3 # You may specify the path to the FastCGI crash log (a log of unhandled 4 # exceptions which forced the FastCGI instance to exit, great for debugging) 5 # and the number of requests to process before running garbage collection. 6 # 7 # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log 8 # and the GC period is nil (turned off). A reasonable number of requests 9 # could range from 10-100 depending on the memory footprint of your app. 10 # 11 # Example: 12 # # Default log path, normal GC behavior. 13 # RailsFCGIHandler.process! 14 # 15 # # Default log path, 50 requests between GC. 16 # RailsFCGIHandler.process! nil, 50 17 # 18 # # Custom log path, normal GC behavior. 19 # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' 20 # 3 21 require File.dirname(__FILE__) + "/../config/environment" 4 22 require 'fcgi_handler' trunk/railties/lib/fcgi_handler.rb
r1565 r1592 4 4 5 5 class RailsFCGIHandler 6 SIGNALS = { 7 'HUP' => :reload, 8 'TERM' => :graceful_exit, 9 'USR1' => :graceful_exit 10 } 11 6 12 attr_reader :when_ready 7 13 attr_reader :processing 8 14 9 def self.process! 10 new.process! 15 attr_accessor :log_file_path 16 attr_accessor :gc_request_period 17 18 19 # Initialize and run the FastCGI instance, passing arguments through to new. 20 def self.process!(*args, &block) 21 new(*args, &block).process! 11 22 end 12 23 13 def initialize(log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log") 24 # Initialize the FastCGI instance with the path to a crash log 25 # detailing unhandled exceptions (default RAILS_ROOT/log/fastcgi.crash.log) 26 # and the number of requests to process between garbage collection runs 27 # (default nil for normal GC behavior.) Optionally, pass a block which 28 # takes this instance as an argument for further configuration. 29 def initialize(log_file_path = nil, gc_request_period = nil) 14 30 @when_ready = nil 15 31 @processing = false 16 32 17 trap("HUP", method(:restart_handler).to_proc)18 trap("USR1", method(:trap_handler).to_proc)33 self.log_file_path = log_file_path || "#{RAILS_ROOT}/log/fastcgi.crash.log" 34 self.gc_request_period = gc_request_period 19 35 20 # initialize to 11 seconds ago to minimize special cases 36 # Yield for additional configuration. 37 yield self if block_given? 38 39 # Safely install signal handlers. 40 install_signal_handlers 41 42 # Start error timestamp at 11 seconds ago. 21 43 @last_error_on = Time.now - 11 22 44 23 @log_file_path = log_file_path24 45 dispatcher_log(:info, "starting") 25 46 end 26 47 27 48 def process! 49 # Make a note of $" so we can safely reload this instance. 28 50 mark! 29 51 52 # Begin countdown to garbage collection. 53 run_gc! if gc_request_period 54 30 55 FCGI.each_cgi do |cgi| 31 if when_ready == :restart 56 # Safely reload this instance if requested. 57 if when_ready == :reload 58 run_gc! if gc_request_period 32 59 restore! 33 60 @when_ready = nil 34 dispatcher_log(:info, "re started")61 dispatcher_log(:info, "reloaded") 35 62 end 36 63 37 64 process_request(cgi) 65 66 # Break if graceful exit requested. 38 67 break if when_ready == :exit 68 69 # Garbage collection countdown. 70 if gc_request_period 71 @gc_request_countdown -= 1 72 run_gc! if @gc_request_countdown <= 0 73 end 39 74 end 40 75 76 GC.enable 41 77 dispatcher_log(:info, "terminated gracefully") 42 78 … … 76 112 end 77 113 78 def trap_handler(signal) 114 def install_signal_handlers 115 SIGNALS.each do |signal, handler_name| 116 install_signal_handler signal, method("#{handler_name}_handler").to_proc 117 end 118 end 119 120 def install_signal_handler(signal, handler) 121 trap signal, handler 122 rescue ArgumentError 123 dispatcher_log :warn, "Ignoring unsupported signal #{signal}." 124 end 125 126 def graceful_exit_handler(signal) 79 127 if processing 80 128 dispatcher_log :info, "asked to terminate ASAP" … … 86 134 end 87 135 88 def re start_handler(signal)89 @when_ready = :re start90 dispatcher_log :info, "asked to re startASAP"136 def reload_handler(signal) 137 @when_ready = :reload 138 dispatcher_log :info, "asked to reload ASAP" 91 139 end 92 140 … … 110 158 ActionController::Routing::Routes.reload 111 159 end 160 161 def run_gc! 162 @gc_request_countdown = gc_request_period 163 GC.enable; GC.start; GC.disable 164 end 112 165 end trunk/railties/test/fcgi_dispatcher_test.rb
r1565 r1592 10 10 class RailsFCGIHandler 11 11 attr_reader :exit_code 12 attr_reader :re started12 attr_reader :reloaded 13 13 attr_accessor :thread 14 attr_reader :gc_runs 14 15 15 16 def trap(signal, handler, &block) … … 28 29 29 30 def restore! 30 @restarted = true 31 @reloaded = true 32 end 33 34 alias_method :old_run_gc!, :run_gc! 35 def run_gc! 36 @gc_runs ||= 0 37 @gc_runs += 1 38 old_run_gc! 31 39 end 32 40 end … … 58 66 assert_nil @handler.when_ready 59 67 assert !@handler.processing 60 assert @handler.re started68 assert @handler.reloaded 61 69 end 62 70 … … 68 76 @handler.thread.join 69 77 assert_nil @handler.exit_code 70 assert_equal :re start, @handler.when_ready78 assert_equal :reload, @handler.when_ready 71 79 assert !@handler.processing 72 80 end … … 120 128 end 121 129 end 130 131 class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase 132 def setup 133 @log = StringIO.new 134 FCGI.time_to_sleep = nil 135 FCGI.raise_exception = nil 136 FCGI.each_cgi_count = nil 137 Dispatcher.time_to_sleep = nil 138 Dispatcher.raise_exception = nil 139 Dispatcher.dispatch_hook = nil 140 end 141 142 def teardown 143 FCGI.each_cgi_count = nil 144 Dispatcher.dispatch_hook = nil 145 GC.enable 146 end 147 148 def test_normal_gc 149 @handler = RailsFCGIHandler.new(@log) 150 assert_nil @handler.gc_request_period 151 152 # When GC is enabled, GC.disable disables and returns false. 153 assert_equal false, GC.disable 154 end 155 156 def test_periodic_gc 157 Dispatcher.dispatch_hook = lambda do |cgi| 158 # When GC is disabled, GC.enable enables and returns true. 159 assert_equal true, GC.enable 160 GC.disable 161 end 162 163 @handler = RailsFCGIHandler.new(@log, 10) 164 assert_equal 10, @handler.gc_request_period 165 FCGI.each_cgi_count = 1 166 @handler.process! 167 assert_equal 1, @handler.gc_runs 168 169 FCGI.each_cgi_count = 10 170 @handler.process! 171 assert_equal 3, @handler.gc_runs 172 173 FCGI.each_cgi_count = 25 174 @handler.process! 175 assert_equal 6, @handler.gc_runs 176 177 assert_nil @handler.exit_code 178 assert_nil @handler.when_ready 179 assert !@handler.processing 180 end 181 end trunk/railties/test/mocks/dispatcher.rb
r1479 r1592 3 3 attr_accessor :time_to_sleep 4 4 attr_accessor :raise_exception 5 attr_accessor :dispatch_hook 5 6 6 7 def dispatch(cgi) 8 dispatch_hook.call(cgi) if dispatch_hook 7 9 sleep(time_to_sleep || 0) 8 10 raise raise_exception, "Something died" if raise_exception trunk/railties/test/mocks/fcgi.rb
r1479 r1592 3 3 attr_accessor :time_to_sleep 4 4 attr_accessor :raise_exception 5 attr_accessor :each_cgi_count 5 6 6 7 def each_cgi 7 sleep(time_to_sleep || 0) 8 raise raise_exception, "Something died" if raise_exception 9 yield "mock cgi value" 8 (each_cgi_count || 1).times do 9 sleep(time_to_sleep || 0) 10 raise raise_exception, "Something died" if raise_exception 11 yield "mock cgi value" 12 end 10 13 end 11 14 end