From 6c473eb410a959607ee5f4a5c8597b684f1827c0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 4 May 2005 11:10:13 +0000 Subject: Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck] Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1284 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- railties/dispatches/dispatch.fcgi | 54 ++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) (limited to 'railties/dispatches/dispatch.fcgi') diff --git a/railties/dispatches/dispatch.fcgi b/railties/dispatches/dispatch.fcgi index 683a61f4c9..34a99a7105 100755 --- a/railties/dispatches/dispatch.fcgi +++ b/railties/dispatches/dispatch.fcgi @@ -1,27 +1,69 @@ #!/usr/local/bin/ruby +def dispatcher_log(level, path,msg) + Logger.new(path).send(level, msg) +rescue Object => log_error + STDERR << "Couldn't write to #{path}: #{msg}" +end + def dispatcher_error(path,e,msg="") error_message = "[#{Time.now}] Dispatcher failed to catch: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}\n#{msg}" - Logger.new(path).fatal(error_message) -rescue Object => log_error - STDERR << "Couldn't write to #{path} (#{e} [#{e.class}])\n" << error_message + dispatcher_log(:error, path, error_message) end +last_error_on = nil begin require File.dirname(__FILE__) + "/../config/environment" require 'dispatcher' require 'fcgi' log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log" + dispatcher_log(:info, log_file_path, "fcgi #{$$} starting") + + # Allow graceful exits by sending the process SIGUSR1. If the process is + # currently handling a request, the request will be allowed to complete and + # then will terminate itself. If a request is not being handled, the + # process is terminated immediately (via #exit). + + $please_exit_at_your_earliest_convenience = false + $i_am_currently_processing_a_request = false + trap("USR1") do + if $i_am_currently_processing_a_request + dispatcher_log(:info, log_file_path, "asking #{$$} to terminate ASAP") + $please_exit_at_your_earliest_convenience = true + else + dispatcher_log(:info, log_file_path, "telling #{$$} to terminate NOW") + exit + end + end + + # Process each request as it comes in, as a pseudo-CGI. FCGI.each_cgi do |cgi| begin + $i_am_currently_processing_a_request = true Dispatcher.dispatch(cgi) - rescue Object => rails_error - dispatcher_error(log_file_path, rails_error) + rescue Object => e + dispatcher_error(log_file_path, e) + ensure + $stdout.flush + $i_am_currently_processing_a_request = false + break if $please_exit_at_your_earliest_convenience end end + + dispatcher_log(:info, log_file_path, "fcgi #{$$} terminated gracefully") +rescue SystemExit => exit_error + dispatcher_log(:info, log_file_path, "fcgi #{$$} terminated by explicit exit") rescue Object => fcgi_error - dispatcher_error(log_file_path, fcgi_error, "FCGI process #{$$} killed by this error\n") + # 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) && (last_error_on.nil? || last_error_on - Time.now > 10) + last_error_on = Time.now + dispatcher_error(log_file_path, fcgi_error, "FCGI process #{$$} almost killed by this error\n") + retry + else + dispatcher_error(log_file_path, fcgi_error, "FCGI process #{$$} killed by this error\n") + end end \ No newline at end of file -- cgit v1.2.3