1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge commit 'rails/master'

This commit is contained in:
Emilio Tagua 2009-10-05 15:33:59 -03:00
commit 97aba353c8
15 changed files with 64 additions and 809 deletions

View file

@ -50,7 +50,7 @@ module ActionController
def new
# DEPRECATE Rails application fallback
Rails.application
Rails.application.new
end
end
end

View file

@ -1,86 +0,0 @@
#!/usr/bin/env ruby
require 'stringio'
require 'fileutils'
require 'fcgi_handler'
def message(s)
$stderr.puts "listener: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
end
class RemoteCGI < CGI
attr_accessor :stdinput, :stdoutput, :env_table
def initialize(env_table, input = nil, output = nil)
self.env_table = env_table
self.stdinput = input || StringIO.new
self.stdoutput = output || StringIO.new
super()
end
def out(stream) # Ignore the requested output stream
super(stdoutput)
end
end
class Listener
include DRbUndumped
def initialize(timeout, socket_path)
@socket = File.expand_path(socket_path)
@mutex = Mutex.new
@active = false
@timeout = timeout
@handler = RailsFCGIHandler.new
@handler.extend DRbUndumped
message 'opening socket'
DRb.start_service("drbunix:#{@socket}", self)
message 'entering process loop'
@handler.process! self
end
def each_cgi(&cgi_block)
@cgi_block = cgi_block
message 'entering idle loop'
loop do
sleep @timeout rescue nil
die! unless @active
@active = false
end
end
def process(env, input)
message 'received request'
@mutex.synchronize do
@active = true
message 'creating input stream'
input_stream = StringIO.new(input)
message 'building CGI instance'
cgi = RemoteCGI.new(eval(env), input_stream)
message 'yielding to fcgi handler'
@cgi_block.call cgi
message 'yield finished -- sending output'
cgi.stdoutput.seek(0)
output = cgi.stdoutput.read
return output
end
end
def die!
message 'shutting down'
DRb.stop_service
FileUtils.rm_f @socket
Kernel.exit 0
end
end
socket_path = ARGV.shift
timeout = (ARGV.shift || 90).to_i
Listener.new(timeout, socket_path)

View file

@ -1,239 +0,0 @@
require 'fcgi'
require 'logger'
require 'rails/dispatcher'
require 'rbconfig'
class RailsFCGIHandler
SIGNALS = {
'HUP' => :reload,
'INT' => :exit_now,
'TERM' => :exit_now,
'USR1' => :exit,
'USR2' => :restart
}
GLOBAL_SIGNALS = SIGNALS.keys - %w(USR1)
attr_reader :when_ready
attr_accessor :log_file_path
attr_accessor :gc_request_period
# Initialize and run the FastCGI instance, passing arguments through to new.
def self.process!(*args, &block)
new(*args, &block).process!
end
# Initialize the FastCGI instance with the path to a crash log
# detailing unhandled exceptions (default RAILS_ROOT/log/fastcgi.crash.log)
# and the number of requests to process between garbage collection runs
# (default nil for normal GC behavior.) Optionally, pass a block which
# takes this instance as an argument for further configuration.
def initialize(log_file_path = nil, gc_request_period = nil)
self.log_file_path = log_file_path || "#{RAILS_ROOT}/log/fastcgi.crash.log"
self.gc_request_period = gc_request_period
# Yield for additional configuration.
yield self if block_given?
# Safely install signal handlers.
install_signal_handlers
@app = Dispatcher.new
# Start error timestamp at 11 seconds ago.
@last_error_on = Time.now - 11
end
def process!(provider = FCGI)
mark_features!
dispatcher_log :info, 'starting'
process_each_request provider
dispatcher_log :info, 'stopping gracefully'
rescue Exception => error
case error
when SystemExit
dispatcher_log :info, 'stopping after explicit exit'
when SignalException
dispatcher_error error, 'stopping after unhandled signal'
else
# Retry if exceptions occur more than 10 seconds apart.
if Time.now - @last_error_on > 10
@last_error_on = Time.now
dispatcher_error error, 'retrying after unhandled exception'
retry
else
dispatcher_error error, 'stopping after unhandled exception within 10 seconds of the last'
end
end
end
protected
def process_each_request(provider)
request = nil
catch :exit do
provider.each do |request|
process_request(request)
case when_ready
when :reload
reload!
when :restart
close_connection(request)
restart!
when :exit
close_connection(request)
throw :exit
end
end
end
rescue SignalException => signal
raise unless signal.message == 'SIGUSR1'
close_connection(request)
end
def process_request(request)
@processing, @when_ready = true, nil
gc_countdown
with_signal_handler 'USR1' do
begin
::Rack::Handler::FastCGI.serve(request, @app)
rescue SignalException, SystemExit
raise
rescue Exception => error
dispatcher_error error, 'unhandled dispatch error'
end
end
ensure
@processing = false
end
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 Exception => log_error # Logger errors
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 install_signal_handlers
GLOBAL_SIGNALS.each { |signal| install_signal_handler(signal) }
end
def install_signal_handler(signal, handler = nil)
if SIGNALS.include?(signal) && self.class.method_defined?(name = "#{SIGNALS[signal]}_handler")
handler ||= method(name).to_proc
begin
trap(signal, handler)
rescue ArgumentError
dispatcher_log :warn, "Ignoring unsupported signal #{signal}."
end
else
dispatcher_log :warn, "Ignoring unsupported signal #{signal}."
end
end
def with_signal_handler(signal)
install_signal_handler(signal)
yield
ensure
install_signal_handler(signal, 'DEFAULT')
end
def exit_now_handler(signal)
dispatcher_log :info, "asked to stop immediately"
exit
end
def exit_handler(signal)
dispatcher_log :info, "asked to stop ASAP"
if @processing
@when_ready = :exit
else
throw :exit
end
end
def reload_handler(signal)
dispatcher_log :info, "asked to reload ASAP"
if @processing
@when_ready = :reload
else
reload!
end
end
def restart_handler(signal)
dispatcher_log :info, "asked to restart ASAP"
if @processing
@when_ready = :restart
else
restart!
end
end
def restart!
config = ::Config::CONFIG
ruby = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT']
command_line = [ruby, $0, ARGV].flatten.join(' ')
dispatcher_log :info, "restarted"
# close resources as they won't be closed by
# the OS when using exec
logger.close rescue nil
Rails.logger.close rescue nil
exec(command_line)
end
def reload!
run_gc! if gc_request_period
restore!
@when_ready = nil
dispatcher_log :info, "reloaded"
end
# Make a note of $" so we can safely reload this instance.
def mark_features!
@features = $".clone
end
def restore!
$".replace @features
Dispatcher.reset_application!
ActionController::Routing::Routes.reload
end
def run_gc!
@gc_request_countdown = gc_request_period
GC.enable; GC.start; GC.disable
end
def gc_countdown
if gc_request_period
@gc_request_countdown ||= gc_request_period
@gc_request_countdown -= 1
run_gc! if @gc_request_countdown <= 0
end
end
def close_connection(request)
request.finish if request
end
end

View file

@ -18,9 +18,6 @@ module Rails::Generators
class_option :template, :type => :string, :aliases => "-m",
:desc => "Path to an application template (can be a filesystem path or URL)."
class_option :with_dispatchers, :type => :boolean, :aliases => "-D", :default => false,
:desc => "Add CGI/FastCGI/mod_ruby dispatchers code"
class_option :skip_activerecord, :type => :boolean, :aliases => "-O", :default => false,
:desc => "Skip ActiveRecord files"
@ -113,19 +110,6 @@ module Rails::Generators
directory "public", "public", :recursive => false # Do small steps, so anyone can overwrite it.
end
def create_dispatch_files
return unless options[:with_dispatchers]
template "dispatchers/dispatch.rb", "public/dispatch.rb"
chmod "public/dispatch.rb", 0755, :verbose => false
template "dispatchers/dispatch.rb", "public/dispatch.cgi"
chmod "public/dispatch.cgi", 0755, :verbose => false
template "dispatchers/dispatch.fcgi", "public/dispatch.fcgi"
chmod "public/dispatch.fcgi", 0755, :verbose => false
end
def create_public_image_files
directory "public/images"
end

View file

@ -1,24 +0,0 @@
<%= shebang %>
#
# You may specify the path to the FastCGI crash log (a log of unhandled
# exceptions which forced the FastCGI instance to exit, great for debugging)
# and the number of requests to process before running garbage collection.
#
# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
# and the GC period is nil (turned off). A reasonable number of requests
# could range from 10-100 depending on the memory footprint of your app.
#
# Example:
# # Default log path, normal GC behavior.
# RailsFCGIHandler.process!
#
# # Default log path, 50 requests between GC.
# RailsFCGIHandler.process! nil, 50
#
# # Custom log path, normal GC behavior.
# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
#
require File.dirname(__FILE__) + "/../config/environment"
require 'fcgi_handler'
RailsFCGIHandler.process!

View file

@ -1,10 +0,0 @@
<%= shebang %>
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch

View file

@ -1,97 +0,0 @@
<%= shebang %>
require 'drb'
# This file includes an experimental gateway CGI implementation. It will work
# only on platforms which support both fork and sockets.
#
# To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi.
#
# Next, create the directory log/drb_gateway and grant the apache user rw access
# to said directory.
#
# On the next request to your server, the gateway tracker should start up, along
# with a few listener processes. This setup should provide you with much better
# speeds than dispatch.cgi.
#
# Keep in mind that the first request made to the server will be slow, as the
# tracker and listeners will have to load. Also, the tracker and listeners will
# shutdown after a period if inactivity. You can set this value below -- the
# default is 90 seconds.
TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
DieAfter = 90 # Seconds
Listeners = 3
def message(s)
$stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
end
def listener_socket(number)
File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock"))
end
unless File.exist? TrackerSocket
message "Starting tracker and #{Listeners} listeners"
fork do
Process.setsid
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
root = File.expand_path(File.dirname(__FILE__) + '/..')
message "starting tracker"
fork do
ARGV.clear
ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s
load File.join(root, 'script', 'tracker')
end
message "starting listeners"
require File.join(root, 'config/environment.rb')
Listeners.times do |number|
fork do
ARGV.clear
ARGV << listener_socket(number) << DieAfter.to_s
load File.join(root, 'script', 'listener')
end
end
end
message "waiting for tracker and listener to arise..."
ready = false
10.times do
sleep 0.5
break if (ready = File.exist?(TrackerSocket) && File.exist?(listener_socket(0)))
end
if ready
message "tracker and listener are ready"
else
message "Waited 5 seconds, listener and tracker not ready... dropping request"
Kernel.exit 1
end
end
DRb.start_service
message "connecting to tracker"
tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
input = $stdin.read
$stdin.close
env = ENV.inspect
output = nil
tracker.with_listener do |number|
message "connecting to listener #{number}"
socket = listener_socket(number)
listener = DRbObject.new_with_uri("drbunix:#{socket}")
output = listener.process(env, input)
message "listener #{number} has finished, writing output"
end
$stdout.write output
$stdout.flush
$stdout.close

View file

@ -110,11 +110,6 @@ namespace :rails do
invoke_from_app_generator :create_prototype_files
end
desc "Generate dispatcher files in RAILS_ROOT/public"
task :generate_dispatchers do
invoke_from_app_generator :create_dispatch_files
end
desc "Add new scripts to the application script/ directory"
task :scripts do
invoke_from_app_generator :create_script_files

View file

@ -25,11 +25,3 @@ if defined?(RAILS_ROOT)
else
RAILS_ROOT = File.dirname(__FILE__)
end
def uses_gem(gem_name, test_name, version = '> 0')
gem gem_name.to_s, version
require gem_name.to_s
yield
rescue LoadError
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
end

View file

@ -0,0 +1,52 @@
require 'isolation/abstract_unit'
class ConsoleTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
def setup
build_app
boot_rails
# Load steps taken from rails/commands/console.rb
require "#{rails_root}/config/environment"
require 'rails/console_app'
require 'rails/console_with_helpers'
end
def test_app_method_should_return_integration_session
console_session = app
assert_not_nil console_session
assert_instance_of ActionController::Integration::Session, console_session
end
def test_new_session_should_return_integration_session
session = new_session
assert_not_nil session
assert_instance_of ActionController::Integration::Session, session
end
def test_reload_should_fire_preparation_callbacks
a = b = c = nil
# TODO: These should be defined on the initializer
ActionDispatch::Callbacks.to_prepare { a = b = c = 1 }
ActionDispatch::Callbacks.to_prepare { b = c = 2 }
ActionDispatch::Callbacks.to_prepare { c = 3 }
# Hide Reloading... output
silence_stream(STDOUT) do
reload!
end
assert_equal 1, a
assert_equal 2, b
assert_equal 3, c
end
def test_access_to_helpers
assert_not_nil helper
assert_instance_of ActionView::Base, helper
assert_equal 'Once upon a time in a world...',
helper.truncate('Once upon a time in a world far far away')
end
end

View file

@ -43,6 +43,13 @@ module ApplicationTests
assert Rails.application.new.is_a?(Rails::Application)
end
# Passenger still uses AC::Dispatcher, so we need to
# keep it working for now
test "deprecated ActionController::Dispatcher still works" do
rackup
assert ActionController::Dispatcher.new.is_a?(Rails::Application)
end
test "the config object is available on the application object" do
rackup
assert_equal 'UTC', Rails.application.config.time_zone

View file

@ -1,43 +0,0 @@
require 'abstract_unit'
require 'action_controller' # console_app uses 'action_controller/integration'
require 'rails/dispatcher'
require 'rails/console_app'
module Rails
def self.application
ActionController::Routing::Routes
end
end
# console_app sets Test::Unit.run to work around the at_exit hook in test/unit, which kills IRB
if Test::Unit.respond_to?(:run=)
Test::Unit.run = false
class ConsoleAppTest < Test::Unit::TestCase
def test_app_method_should_return_integration_session
assert_nothing_thrown do
console_session = app
assert_not_nil console_session
assert_instance_of ActionController::Integration::Session,
console_session
end
end
def test_reload_should_fire_preparation_callbacks
a = b = c = nil
ActionDispatch::Callbacks.to_prepare { a = b = c = 1 }
ActionDispatch::Callbacks.to_prepare { b = c = 2 }
ActionDispatch::Callbacks.to_prepare { c = 3 }
ActionController::Routing::Routes.expects(:reload)
reload!
assert_equal 1, a
assert_equal 2, b
assert_equal 3, c
end
end
end

View file

@ -1,268 +0,0 @@
require 'abstract_unit'
uses_gem "fcgi", "0.8.7" do
require 'action_controller'
require 'rails/fcgi_handler'
module Rails
def self.application
ActionController::Routing::Routes
end
end
class RailsFCGIHandlerTest < Test::Unit::TestCase
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
end
def test_process_restart
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).returns(:restart)
@handler.expects(:close_connection).with(request)
@handler.expects(:reload!).never
@handler.expects(:restart!)
@handler.process!
end
def test_process_exit
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).returns(:exit)
@handler.expects(:close_connection).with(request)
@handler.expects(:reload!).never
@handler.expects(:restart!).never
@handler.process!
end
def test_process_with_system_exit_exception
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once.raises(SystemExit)
@handler.stubs(:dispatcher_log)
@handler.expects(:dispatcher_log).with(:info, regexp_matches(/^stopping/))
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).never
@handler.expects(:close_connection).never
@handler.expects(:reload!).never
@handler.expects(:restart!).never
@handler.process!
end
def test_restart_handler_outside_request
@handler.expects(:dispatcher_log).with(:info, "asked to restart ASAP")
@handler.expects(:restart!).once
@handler.send(:restart_handler, nil)
assert_equal nil, @handler.when_ready
end
def test_install_signal_handler_should_log_on_bad_signal
@handler.stubs(:trap).raises(ArgumentError)
@handler.expects(:dispatcher_log).with(:warn, "Ignoring unsupported signal CHEESECAKE.")
@handler.send(:install_signal_handler, "CHEESECAKE", nil)
end
def test_reload
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.send(:reload!)
assert_nil @handler.when_ready
end
def test_reload_runs_gc_when_gc_request_period_set
@handler.expects(:run_gc!)
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.gc_request_period = 10
@handler.send(:reload!)
end
def test_reload_doesnt_run_gc_if_gc_request_period_isnt_set
@handler.expects(:run_gc!).never
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.send(:reload!)
end
def test_restart!
@handler.expects(:dispatcher_log).with(:info, "restarted")
@handler.expects(:exec).returns('restarted')
assert_equal 'restarted', @handler.send(:restart!)
end
def test_restore!
$".expects(:replace)
Dispatcher.expects(:reset_application!)
ActionController::Routing::Routes.expects(:reload)
@handler.send(:restore!)
end
def test_uninterrupted_processing
request = mock
FCGI.expects(:each).yields(request)
@handler.expects(:process_request).with(request)
@handler.process!
assert_nil @handler.when_ready
end
end
class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
class ::RailsFCGIHandler
attr_accessor :signal
alias_method :old_gc_countdown, :gc_countdown
def gc_countdown
signal ? Process.kill(signal, $$) : old_gc_countdown
end
end
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
@dispatcher = mock
Dispatcher.stubs(:new).returns(@dispatcher)
end
def test_interrupted_via_HUP_when_not_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('HUP')
@handler.expects(:reload!).once
@handler.expects(:close_connection).never
@handler.expects(:exit).never
@handler.process!
assert_equal :reload, @handler.when_ready
end
def test_interrupted_via_USR1_when_not_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('USR1')
@handler.expects(:exit_handler).never
@handler.expects(:reload!).never
@handler.expects(:close_connection).with(request).once
@handler.expects(:exit).never
@handler.process!
assert_nil @handler.when_ready
end
def test_restart_via_USR2_when_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('USR2')
@handler.expects(:exit_handler).never
@handler.expects(:reload!).never
@handler.expects(:close_connection).with(request).once
@handler.expects(:exit).never
@handler.expects(:restart!).once
@handler.process!
assert_equal :restart, @handler.when_ready
end
def test_interrupted_via_TERM
request = mock
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.returns('TERM')
@handler.expects(:reload!).never
@handler.expects(:close_connection).never
@handler.process!
assert_nil @handler.when_ready
end
def test_runtime_exception_in_fcgi
error = RuntimeError.new('foo')
FCGI.expects(:each).times(2).raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^retrying/))
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
def test_runtime_error_in_dispatcher
request = mock
error = RuntimeError.new('foo')
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^unhandled/))
@handler.process!
end
def test_signal_exception_in_fcgi
error = SignalException.new('USR2')
FCGI.expects(:each).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
def test_signal_exception_in_dispatcher
request = mock
error = SignalException.new('USR2')
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
end
class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase
def setup
@log = StringIO.new
end
def teardown
GC.enable
end
def test_normal_gc
@handler = RailsFCGIHandler.new(@log)
assert_nil @handler.gc_request_period
# When GC is enabled, GC.disable disables and returns false.
assert_equal false, GC.disable
end
def test_periodic_gc
@handler = RailsFCGIHandler.new(@log, 10)
assert_equal 10, @handler.gc_request_period
request = mock
FCGI.expects(:each).times(10).yields(request)
@handler.expects(:run_gc!).never
9.times { @handler.process! }
@handler.expects(:run_gc!).once
@handler.process!
assert_nil @handler.when_ready
end
end
end # uses_gem "fcgi"

View file

@ -53,18 +53,6 @@ class AppGeneratorTest < GeneratorsTestCase
assert_match /Invalid value for \-\-database option/, content
end
def test_dispatchers_are_not_added_by_default
run_generator
assert_no_file "public/dispatch.cgi"
assert_no_file "public/dispatch.fcgi"
end
def test_dispatchers_are_added_if_required
run_generator ["--with-dispatchers"]
assert_file "public/dispatch.cgi"
assert_file "public/dispatch.fcgi"
end
def test_config_database_is_added_by_default
run_generator
assert_file "config/database.yml", /sqlite3/

View file

@ -30,6 +30,10 @@ module TestHelpers
def app_path(*args)
tmp_path(*%w[app] + args)
end
def rails_root
app_path
end
end
module Rack