Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use SIGHUP to dynamically reload an fcgi process without restarting i…
…t. 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
- Loading branch information
Showing
5 changed files
with
182 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,98 +1,6 @@ | |||
#!/usr/local/bin/ruby | #!/usr/local/bin/ruby | ||
|
|
||
# to allow unit testing | require File.dirname(__FILE__) + "/../config/environment" | ||
if !defined?(RAILS_ROOT) | require 'fcgi_handler' | ||
require File.dirname(__FILE__) + "/../config/environment" | |||
end | |||
|
|
||
require 'dispatcher' | RailsFCGIHandler.process! | ||
require 'fcgi' | |||
require 'logger' | |||
|
|||
class RailsFCGIHandler | |||
attr_reader :please_exit_at_your_earliest_convenience | |||
attr_reader :i_am_currently_processing_a_request | |||
|
|||
def initialize(log_file_path = "#{RAILS_ROOT}/log/fastcgi.crash.log") | |||
@please_exit_at_your_earliest_convenience = false | |||
@i_am_currently_processing_a_request = false | |||
|
|||
trap_handler = method(:trap_handler).to_proc | |||
trap("HUP", trap_handler) | |||
trap("USR1", trap_handler) | |||
|
|||
# initialize to 11 seconds from now to minimize special cases | |||
@last_error_on = Time.now - 11 | |||
|
|||
@log_file_path = log_file_path | |||
dispatcher_log(:info, "fcgi #{$$} starting") | |||
end | |||
|
|||
def process! | |||
FCGI.each_cgi do |cgi| | |||
process_request(cgi) | |||
break if please_exit_at_your_earliest_convenience | |||
end | |||
|
|||
dispatcher_log(:info, "fcgi #{$$} terminated gracefully") | |||
|
|||
rescue SystemExit => exit_error | |||
dispatcher_log(:info, "fcgi #{$$} 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, | |||
"FCGI process #{$$} almost killed by this error\n") | |||
retry | |||
else | |||
dispatcher_error(fcgi_error, "FCGI process #{$$} killed by this error\n") | |||
end | |||
end | |||
|
|||
private | |||
def logger | |||
@logger ||= Logger.new(@log_file_path) | |||
end | |||
|
|||
def dispatcher_log(level, msg) | |||
logger.send(level, 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 = | |||
"[#{Time.now}] 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 i_am_currently_processing_a_request | |||
dispatcher_log(:info, "asking #{$$} to terminate ASAP") | |||
@please_exit_at_your_earliest_convenience = true | |||
else | |||
dispatcher_log(:info, "telling #{$$} to terminate NOW") | |||
exit | |||
end | |||
end | |||
|
|||
def process_request(cgi) | |||
@i_am_currently_processing_a_request = true | |||
Dispatcher.dispatch(cgi) | |||
rescue Object => e | |||
raise if SignalException === e | |||
dispatcher_error(e) | |||
ensure | |||
@i_am_currently_processing_a_request = false | |||
end | |||
end | |||
|
|||
if __FILE__ == $0 | |||
handler = RailsFCGIHandler.new | |||
handler.process! | |||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters