Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 1592

Show
Ignore:
Timestamp:
07/02/05 04:52:14 (3 years ago)
Author:
bitsweat
Message:

r1588@asus: jeremy | 2005-07-02 03:14:45 -0700
Optional periodic garbage collection for dispatch.fcgi. Graceful exit on TERM also (a la Apache1). Ignore signals the platform does not support, such as USR1 on Windows.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/railties/CHANGELOG

    r1565 r1592  
    11*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] 
    26 
    37* 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  
    11#!/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
    321require File.dirname(__FILE__) + "/../config/environment" 
    422require 'fcgi_handler' 
  • trunk/railties/lib/fcgi_handler.rb

    r1565 r1592  
    44 
    55class RailsFCGIHandler 
     6  SIGNALS = { 
     7    'HUP'  => :reload, 
     8    'TERM' => :graceful_exit, 
     9    'USR1' => :graceful_exit 
     10  } 
     11 
    612  attr_reader :when_ready 
    713  attr_reader :processing 
    814 
    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! 
    1122  end 
    1223 
    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) 
    1430    @when_ready = nil 
    1531    @processing = false 
    1632 
    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 
    1935 
    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. 
    2143    @last_error_on = Time.now - 11 
    2244 
    23     @log_file_path = log_file_path 
    2445    dispatcher_log(:info, "starting") 
    2546  end 
    2647 
    2748  def process! 
     49    # Make a note of $" so we can safely reload this instance. 
    2850    mark! 
    2951 
     52    # Begin countdown to garbage collection. 
     53    run_gc! if gc_request_period 
     54 
    3055    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 
    3259        restore! 
    3360        @when_ready = nil 
    34         dispatcher_log(:info, "restarted") 
     61        dispatcher_log(:info, "reloaded") 
    3562      end 
    3663 
    3764      process_request(cgi) 
     65 
     66      # Break if graceful exit requested. 
    3867      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 
    3974    end 
    4075 
     76    GC.enable 
    4177    dispatcher_log(:info, "terminated gracefully") 
    4278 
     
    76112    end 
    77113 
    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) 
    79127      if processing 
    80128        dispatcher_log :info, "asked to terminate ASAP" 
     
    86134    end 
    87135 
    88     def restart_handler(signal) 
    89       @when_ready = :restart 
    90       dispatcher_log :info, "asked to restart ASAP" 
     136    def reload_handler(signal) 
     137      @when_ready = :reload 
     138      dispatcher_log :info, "asked to reload ASAP" 
    91139    end 
    92140 
     
    110158      ActionController::Routing::Routes.reload 
    111159    end 
     160 
     161    def run_gc! 
     162      @gc_request_countdown = gc_request_period 
     163      GC.enable; GC.start; GC.disable 
     164    end 
    112165end 
  • trunk/railties/test/fcgi_dispatcher_test.rb

    r1565 r1592  
    1010class RailsFCGIHandler 
    1111  attr_reader :exit_code 
    12   attr_reader :restarted 
     12  attr_reader :reloaded 
    1313  attr_accessor :thread 
     14  attr_reader :gc_runs 
    1415 
    1516  def trap(signal, handler, &block) 
     
    2829 
    2930  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! 
    3139  end 
    3240end 
     
    5866    assert_nil @handler.when_ready 
    5967    assert !@handler.processing 
    60     assert @handler.restarted 
     68    assert @handler.reloaded 
    6169  end 
    6270 
     
    6876    @handler.thread.join 
    6977    assert_nil @handler.exit_code 
    70     assert_equal :restart, @handler.when_ready 
     78    assert_equal :reload, @handler.when_ready 
    7179    assert !@handler.processing 
    7280  end 
     
    120128  end 
    121129end 
     130 
     131class 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 
     181end 
  • trunk/railties/test/mocks/dispatcher.rb

    r1479 r1592  
    33    attr_accessor :time_to_sleep 
    44    attr_accessor :raise_exception 
     5    attr_accessor :dispatch_hook 
    56 
    67    def dispatch(cgi) 
     8      dispatch_hook.call(cgi) if dispatch_hook 
    79      sleep(time_to_sleep || 0) 
    810      raise raise_exception, "Something died" if raise_exception 
  • trunk/railties/test/mocks/fcgi.rb

    r1479 r1592  
    33    attr_accessor :time_to_sleep 
    44    attr_accessor :raise_exception 
     5    attr_accessor :each_cgi_count 
    56 
    67    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 
    1013    end 
    1114  end