Spawning of Rails 1 and Rails 2 applications.
ClassicRails::ApplicationSpawner can operate in two modes:
Smart mode. In this mode, the Rails application's code is first preloaded into a temporary process, which can then further fork off application processes. Once the code has been preloaded, forking off application processes is very fast, and all the forked off application processes can share code memory with each other. To use this mode, create an ApplicationSpawner object, start it, and call spawn_application on it. A single ApplicationSpawner object can only handle a single Rails application.
Conservative mode. In this mode, a Rails app process is directly spawned without any preloading. This increases compatibility with applications. To use this mode, call ::spawn_application.
The application root of this spawner.
The following options are accepted:
'app_root'
See PhusionPassenger::SpawnManager#spawn_application for information about the options.
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 116 def initialize(options) super() @options = sanitize_spawn_options(options) @app_root = @options["app_root"] @canonicalized_app_root = canonicalize_path(@app_root) self.max_idle_time = DEFAULT_APP_SPAWNER_MAX_IDLE_TIME define_message_handler(:spawn_application, :handle_spawn_application) end
Spawns an instance of a Rails application. When successful, an AppProcess object will be returned, which represents the spawned Rails application.
This method spawns the application directly, without preloading its code. This method may only be called if no Rails framework has been loaded in the current Ruby VM.
The “app_root” option must be given. All other options are passed to the request handler's constructor.
Raises:
AppInitError: The Ruby on Rails application raised an exception or called exit() during startup.
SystemCallError, IOError, SocketError: Something went wrong.
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 80 def self.spawn_application(options) options = sanitize_spawn_options(options) a, b = UNIXSocket.pair pid = safe_fork('application', true) do a.close file_descriptors_to_leave_open = [0, 1, 2, b.fileno] NativeSupport.close_all_file_descriptors(file_descriptors_to_leave_open) close_all_io_objects_for_fds(file_descriptors_to_leave_open) channel = MessageChannel.new(b) success = report_app_init_status(channel) do prepare_app_process('config/environment.rb', options) require File.expand_path('config/environment') require 'dispatcher' after_loading_app_code(options) end if success start_request_handler(channel, false, options) end end b.close Process.waitpid(pid) rescue nil channel = MessageChannel.new(a) unmarshal_and_raise_errors(channel, options["print_exceptions"]) # No exception was raised, so spawning succeeded. return AppProcess.read_from_channel(channel) end
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 331 def self.find_rack_app if Rails::VERSION::MAJOR >= 3 File.read("config/application.rb") =~ /^module (.+)$/ app_module = Object.const_get($1) return app_module::Application else return ActionController::Dispatcher.new end end
Initialize the request handler and enter its main loop. Spawn information
will be sent back via channel
. The forked
argument indicates whether a new process was forked off after loading
environment.rb (i.e. whether smart spawning is being used).
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 300 def self.start_request_handler(channel, forked, options) app_root = options["app_root"] $0 = "Rails: #{options['app_group_name']}" reader, writer = IO.pipe begin reader.close_on_exec! if Rails::VERSION::STRING >= '2.3.0' rack_app = find_rack_app handler = Rack::RequestHandler.new(reader, rack_app, options) else handler = RequestHandler.new(reader, options) end app_process = AppProcess.new(app_root, Process.pid, writer, handler.server_sockets) app_process.write_to_channel(channel) writer.close channel.close before_handling_requests(forked, options) handler.main_loop ensure channel.close rescue nil writer.close rescue nil handler.cleanup rescue nil after_handling_requests end end
Spawns an instance of the Rails application. When successful, an AppProcess object will be returned, which represents the spawned Rails application.
options
will be passed to the request handler's
constructor.
Raises:
AbstractServer::ServerNotStarted: The ApplicationSpawner server hasn't already been started.
ApplicationSpawner::Error: The ApplicationSpawner server exited unexpectedly.
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 133 def spawn_application(options = {}) connect do |channel| channel.write("spawn_application", *options.to_a.flatten) return AppProcess.read_from_channel(channel) end rescue SystemCallError, IOError, SocketError => e raise Error, "The application spawner server exited unexpectedly: #{e}" end
Overrided from PhusionPassenger::AbstractServer#start.
May raise these additional exceptions:
AppInitError: The Ruby on Rails application raised an exception or called exit() during startup.
ApplicationSpawner::Error: The ApplicationSpawner server exited unexpectedly.
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 148 def start super begin channel = MessageChannel.new(@owner_socket) unmarshal_and_raise_errors(channel, @options["print_exceptions"]) rescue IOError, SystemCallError, SocketError => e stop if started? raise Error, "The application spawner server exited unexpectedly: #{e}" rescue stop if started? raise end end
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 267 def handle_spawn_application(client, *options) options = sanitize_spawn_options(Hash[*options]) a, b = UNIXSocket.pair safe_fork('application', true) do begin a.close client.close options = @options.merge(options) self.class.send(:start_request_handler, MessageChannel.new(b), true, options) rescue SignalException => e if e.message != AbstractRequestHandler::HARD_TERMINATION_SIGNAL && e.message != AbstractRequestHandler::SOFT_TERMINATION_SIGNAL raise end end end b.close worker_channel = MessageChannel.new(a) app_process = AppProcess.read_from_channel(worker_channel) app_process.write_to_channel(client) ensure a.close if a b.close if b && !b.closed? app_process.close if app_process end
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 196 def load_environment_with_passenger using_default_log_path = configuration.log_path == configuration.send(:default_log_path) if defined?(::RAILS_ENV) Object.send(:remove_const, :RAILS_ENV) end Object.const_set(:RAILS_ENV, (ENV['RAILS_ENV'] || 'development').dup) if using_default_log_path # We've changed the environment, so open the # correct log file. configuration.log_path = configuration.send(:default_log_path) end load_environment_without_passenger end
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 187 def preload_application Object.const_set(:RAILS_ROOT, @canonicalized_app_root) if defined?(::Rails::Initializer) ::Rails::Initializer.run(:set_load_path) # The Rails framework is loaded at the moment. # environment.rb may set ENV['RAILS_ENV']. So we re-initialize # RAILS_ENV in Rails::Initializer.load_environment. ::Rails::Initializer.class_eval do def load_environment_with_passenger using_default_log_path = configuration.log_path == configuration.send(:default_log_path) if defined?(::RAILS_ENV) Object.send(:remove_const, :RAILS_ENV) end Object.const_set(:RAILS_ENV, (ENV['RAILS_ENV'] || 'development').dup) if using_default_log_path # We've changed the environment, so open the # correct log file. configuration.log_path = configuration.send(:default_log_path) end load_environment_without_passenger end alias_method :load_environment_without_passenger, :load_environment alias_method :load_environment, :load_environment_with_passenger end end if File.exist?('config/preinitializer.rb') require File.expand_path('config/preinitializer') end require File.expand_path('config/environment') if ActionController::Base.page_cache_directory.blank? ActionController::Base.page_cache_directory = "#{RAILS_ROOT}/public" end if defined?(ActionController::Dispatcher) && ActionController::Dispatcher.respond_to?(:error_file_path) ActionController::Dispatcher.error_file_path = "#{RAILS_ROOT}/public" end require 'rails/version' if !defined?(::Rails::VERSION) if !defined?(Dispatcher) begin require 'dispatcher' rescue LoadError # Early versions of Rails 3 still had the dispatcher, but # later versions disposed of it, in which case we'll need # to use the application object. raise if Rails::VERSION::MAJOR < 3 end end # - No point in preloading the application sources if the garbage collector # isn't copy-on-write friendly. # - Rails >= 2.2 already preloads application sources by default, so no need # to do that again. if GC.copy_on_write_friendly? && !rails_will_preload_app_code? # Rails 2.2+ uses application_controller.rb while olde # versions use application.rb. require_dependency 'application' ['models','controllers','helpers'].each do |section| Dir.glob("app/#{section}}/*.rb").each do |file| require_dependency canonicalize_path(file) end end end end
# File lib/phusion_passenger/classic_rails/application_spawner.rb, line 259 def rails_will_preload_app_code? if defined?(Rails::Initializer) return ::Rails::Initializer.method_defined?(:load_application_classes) else return Rails::VERSION::MAJOR >= 3 end end