From 3cc47a4297d9c43e88972555e853e2d5359d804f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 29 Jun 2005 11:07:20 +0000 Subject: Use SIGHUP to dynamically reload an fcgi process without restarting it. Refactored dispatch.fcgi so that the RailsFCGIHandler is in the lib dir. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1565 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/lib/dispatcher.rb | 19 ++++---- railties/lib/fcgi_handler.rb | 112 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 railties/lib/fcgi_handler.rb (limited to 'railties/lib') diff --git a/railties/lib/dispatcher.rb b/railties/lib/dispatcher.rb index 1455168ba9..aaea8dd277 100644 --- a/railties/lib/dispatcher.rb +++ b/railties/lib/dispatcher.rb @@ -33,9 +33,16 @@ class Dispatcher rescue Object => exception ActionController::Base.process_with_exception(request, response, exception).out(output) ensure - reset_application + reset_after_dispatch end end + + def reset_application! + Controllers.clear! + Dependencies.clear + Dependencies.remove_subclasses_for(ActiveRecord::Base, ActiveRecord::Observer, ActionController::Base) + Dependencies.remove_subclasses_for(ActionMailer::Base) if defined?(ActionMailer::Base) + end private def prepare_application @@ -44,14 +51,8 @@ class Dispatcher Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController) end - def reset_application - if Dependencies.load? - Controllers.clear! - Dependencies.clear - Dependencies.remove_subclasses_for(ActiveRecord::Base, ActiveRecord::Observer, ActionController::Base) - Dependencies.remove_subclasses_for(ActionMailer::Base) if defined?(ActionMailer::Base) - end - + def reset_after_dispatch + reset_application! if Dependencies.load? Breakpoint.deactivate_drb if defined?(BREAKPOINT_SERVER_PORT) end end diff --git a/railties/lib/fcgi_handler.rb b/railties/lib/fcgi_handler.rb new file mode 100644 index 0000000000..f615ff6d34 --- /dev/null +++ b/railties/lib/fcgi_handler.rb @@ -0,0 +1,112 @@ +require 'fcgi' +require 'logger' +require 'dispatcher' + +class RailsFCGIHandler + attr_reader :when_ready + attr_reader :processing + + def self.process! + new.process! + end + + def initialize(log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log") + @when_ready = nil + @processing = false + + trap("HUP", method(:restart_handler).to_proc) + trap("USR1", method(:trap_handler).to_proc) + + # initialize to 11 seconds ago to minimize special cases + @last_error_on = Time.now - 11 + + @log_file_path = log_file_path + dispatcher_log(:info, "starting") + end + + def process! + mark! + + FCGI.each_cgi do |cgi| + if when_ready == :restart + restore! + @when_ready = nil + dispatcher_log(:info, "restarted") + end + + process_request(cgi) + break if when_ready == :exit + end + + dispatcher_log(:info, "terminated gracefully") + + rescue SystemExit => exit_error + dispatcher_log(:info, "terminated by explicit exit") + + rescue Object => fcgi_error + # retry on errors that would otherwise have terminated the FCGI process, + # but only if they occur more than 10 seconds apart. + if !(SignalException === fcgi_error) && Time.now - @last_error_on > 10 + @last_error_on = Time.now + dispatcher_error(fcgi_error, "almost killed by this error") + retry + else + dispatcher_error(fcgi_error, "killed by this error") + end + end + + private + def logger + @logger ||= Logger.new(@log_file_path) + end + + def dispatcher_log(level, msg) + time_str = Time.now.strftime("%d/%b/%Y:%H:%M:%S") + logger.send(level, "[#{time_str} :: #{$$}] #{msg}") + rescue Object => log_error + STDERR << "Couldn't write to #{@log_file_path.inspect}: #{msg}\n" + STDERR << " #{log_error.class}: #{log_error.message}\n" + end + + def dispatcher_error(e,msg="") + error_message = + "Dispatcher failed to catch: #{e} (#{e.class})\n" + + " #{e.backtrace.join("\n ")}\n#{msg}" + dispatcher_log(:error, error_message) + end + + def trap_handler(signal) + if processing + dispatcher_log :info, "asked to terminate ASAP" + @when_ready = :exit + else + dispatcher_log :info, "told to terminate NOW" + exit + end + end + + def restart_handler(signal) + @when_ready = :restart + dispatcher_log :info, "asked to restart ASAP" + end + + def process_request(cgi) + @processing = true + Dispatcher.dispatch(cgi) + rescue Object => e + raise if SignalException === e + dispatcher_error(e) + ensure + @processing = false + end + + def mark! + @features = $".clone + end + + def restore! + $".replace @features + Dispatcher.reset_application! + ActionController::Routing::Routes.reload + end +end -- cgit v1.2.3