This class is capable of spawning instances of a single Ruby on Rails application. It does so by preloading as much of the application’s code as possible, then creating instances of the application using what is already preloaded. This makes it spawning application instances very fast, except for the first spawn.
Use multiple instances of ApplicationSpawner if you need to spawn multiple different Ruby on Rails applications.
Note: ApplicationSpawner may only be started asynchronously with AbstractServer#start. Starting it synchronously with AbstractServer#start_synchronously has not been tested.
app_root is the root directory of this application, i.e. the directory that contains ‘app/’, ‘public/’, etc. If given an invalid directory, or a directory that doesn’t appear to be a Rails application root directory, then an InvalidPath will be raised.
Additional options are:
lower_privilege and lowest_user: If lower_privilege is true, then ApplicationSpawner will attempt to switch to the user who owns the application’s config/environment.rb, and to the default group of that user.
If that user doesn’t exist on the system, or if that user is root, then ApplicationSpawner will attempt to switch to the username given by lowest_user (and to the default group of that user). If lowest_user doesn’t exist either, or if switching user failed (because the current process does not have the privilege to do so), then ApplicationSpawner will continue without reporting an error.
environment: Allows one to specify the RAILS_ENV environment to use.
environment_variables: Environment variables which should be passed to the spawned application. This is NULL-seperated string of key-value pairs, encoded in base64. The last byte in the unencoded data must be a NULL.
base_uri: The base URI on which this application is deployed. It equals “/” string if the application is deployed on the root URI. It must not equal the empty string.
print_exceptions: Whether exceptions that have occurred during application initialization should be printed to STDERR. The default is true.
All other options will be passed on to RequestHandler.
# File lib/phusion_passenger/railz/application_spawner.rb, line 102 102: def initialize(app_root, options = {}) 103: super() 104: @app_root = app_root 105: @canonicalized_app_root = canonicalize_path(app_root) 106: @options = sanitize_spawn_options(options) 107: @lower_privilege = @options["lower_privilege"] 108: @lowest_user = @options["lowest_user"] 109: @environment = @options["environment"] 110: @encoded_environment_variables = @options["environment_variables"] 111: @base_uri = @options["base_uri"] if @options["base_uri"] && @options["base_uri"] != "/" 112: @print_exceptions = @options["print_exceptions"] 113: self.max_idle_time = DEFAULT_APP_SPAWNER_MAX_IDLE_TIME 114: assert_valid_app_root(@app_root) 115: define_message_handler(:spawn_application, :handle_spawn_application) 116: end
Spawn an instance of the RoR application. When successful, an Application object will be returned, which represents the spawned RoR application.
Raises:
AbstractServer::ServerNotStarted: The ApplicationSpawner server hasn’t already been started.
ApplicationSpawner::Error: The ApplicationSpawner server exited unexpectedly.
# File lib/phusion_passenger/railz/application_spawner.rb, line 124 124: def spawn_application 125: server.write("spawn_application") 126: pid, socket_name, socket_type = server.read 127: if pid.nil? 128: raise IOError, "Connection closed" 129: end 130: owner_pipe = server.recv_io 131: return Application.new(@app_root, pid, socket_name, 132: socket_type, owner_pipe) 133: rescue SystemCallError, IOError, SocketError => e 134: raise Error, "The application spawner server exited unexpectedly: #{e}" 135: end
Spawn an instance of the RoR application. When successful, an Application object will be returned, which represents the spawned RoR application.
Unlike spawn_application, this method may be called even when the ApplicationSpawner server isn’t started. This allows one to spawn a RoR application without preloading any source files.
This method may only be called if no Rails framework has been loaded in the current Ruby VM.
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/railz/application_spawner.rb, line 151 151: def spawn_application! 152: a, b = UNIXSocket.pair 153: pid = safe_fork('application', true) do 154: begin 155: a.close 156: 157: file_descriptors_to_leave_open = [0, 1, 2, b.fileno] 158: NativeSupport.close_all_file_descriptors(file_descriptors_to_leave_open) 159: close_all_io_objects_for_fds(file_descriptors_to_leave_open) 160: 161: channel = MessageChannel.new(b) 162: success = report_app_init_status(channel) do 163: ENV['RAILS_ENV'] = @environment 164: ENV['RACK_ENV'] = @environment 165: ENV['RAILS_RELATIVE_URL_ROOT'] = @base_uri 166: Dir.chdir(@app_root) 167: if @encoded_environment_variables 168: set_passed_environment_variables 169: end 170: if @lower_privilege 171: lower_privilege('config/environment.rb', @lowest_user) 172: end 173: # Make sure RubyGems uses any new environment variable values 174: # that have been set now (e.g. $HOME, $GEM_HOME, etc) and that 175: # it is able to detect newly installed gems. 176: Gem.clear_paths 177: setup_bundler_support 178: 179: require File.expand_path('config/environment') 180: begin 181: require 'dispatcher' 182: rescue LoadError 183: # Early versions of Rails 3 still had the dispatcher, but 184: # later versions disposed of it, in which case we'll need 185: # to use the application object. 186: raise if Rails::VERSION::MAJOR < 3 187: end 188: end 189: if success 190: start_request_handler(channel, false) 191: end 192: rescue SignalException => e 193: if e.message != AbstractRequestHandler::HARD_TERMINATION_SIGNAL && 194: e.message != AbstractRequestHandler::SOFT_TERMINATION_SIGNAL 195: raise 196: end 197: end 198: end 199: b.close 200: Process.waitpid(pid) rescue nil 201: 202: channel = MessageChannel.new(a) 203: unmarshal_and_raise_errors(channel, @print_exceptions) 204: 205: # No exception was raised, so spawning succeeded. 206: pid, socket_name, socket_type = channel.read 207: if pid.nil? 208: raise IOError, "Connection closed" 209: end 210: owner_pipe = channel.recv_io 211: return Application.new(@app_root, pid, socket_name, 212: socket_type, owner_pipe) 213: end
Overrided from 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/railz/application_spawner.rb, line 221 221: def start 222: super 223: begin 224: unmarshal_and_raise_errors(server, @print_exceptions) 225: rescue IOError, SystemCallError, SocketError => e 226: stop 227: raise Error, "The application spawner server exited unexpectedly: #{e}" 228: rescue 229: stop 230: raise 231: end 232: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 450 450: def find_rack_app 451: if Rails::VERSION::MAJOR >= 3 452: File.read("config/application.rb") =~ /^module (.+)$/ 453: app_module = Object.const_get($1) 454: return app_module::Application 455: else 456: return ActionController::Dispatcher.new 457: end 458: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 375 375: def handle_spawn_application 376: a, b = UNIXSocket.pair 377: safe_fork('application', true) do 378: begin 379: a.close 380: client.close 381: start_request_handler(MessageChannel.new(b), true) 382: rescue SignalException => e 383: if e.message != AbstractRequestHandler::HARD_TERMINATION_SIGNAL && 384: e.message != AbstractRequestHandler::SOFT_TERMINATION_SIGNAL 385: raise 386: end 387: end 388: end 389: 390: b.close 391: worker_channel = MessageChannel.new(a) 392: info = worker_channel.read 393: owner_pipe = worker_channel.recv_io 394: client.write(*info) 395: client.send_io(owner_pipe) 396: ensure 397: a.close if a 398: b.close if b && !b.closed? 399: owner_pipe.close if owner_pipe 400: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 292 292: def load_environment_with_passenger 293: using_default_log_path = 294: configuration.log_path == 295: configuration.send(:default_log_path) 296: 297: if defined?(::RAILS_ENV) 298: Object.send(:remove_const, :RAILS_ENV) 299: end 300: Object.const_set(:RAILS_ENV, (ENV['RAILS_ENV'] || 'development').dup) 301: 302: if using_default_log_path 303: # We've changed the environment, so open the 304: # correct log file. 305: configuration.log_path = configuration.send(:default_log_path) 306: end 307: 308: load_environment_without_passenger 309: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 283 283: def preload_application 284: Object.const_set(:RAILS_ROOT, @canonicalized_app_root) 285: if defined?(::Rails::Initializer) 286: ::Rails::Initializer.run(:set_load_path) 287: 288: # The Rails framework is loaded at the moment. 289: # environment.rb may set ENV['RAILS_ENV']. So we re-initialize 290: # RAILS_ENV in Rails::Initializer.load_environment. 291: ::Rails::Initializer.class_eval do 292: def load_environment_with_passenger 293: using_default_log_path = 294: configuration.log_path == 295: configuration.send(:default_log_path) 296: 297: if defined?(::RAILS_ENV) 298: Object.send(:remove_const, :RAILS_ENV) 299: end 300: Object.const_set(:RAILS_ENV, (ENV['RAILS_ENV'] || 'development').dup) 301: 302: if using_default_log_path 303: # We've changed the environment, so open the 304: # correct log file. 305: configuration.log_path = configuration.send(:default_log_path) 306: end 307: 308: load_environment_without_passenger 309: end 310: 311: alias_method :load_environment_without_passenger, :load_environment 312: alias_method :load_environment, :load_environment_with_passenger 313: end 314: end 315: if File.exist?('config/preinitializer.rb') 316: require File.expand_path('config/preinitializer') 317: end 318: require File.expand_path('config/environment') 319: if ActionController::Base.page_cache_directory.blank? 320: ActionController::Base.page_cache_directory = "#{RAILS_ROOT}/public" 321: end 322: if defined?(ActionController::Dispatcher) && ActionController::Dispatcher.respond_to?(:error_file_path) 323: ActionController::Dispatcher.error_file_path = "#{RAILS_ROOT}/public" 324: end 325: require 'rails/version' if !defined?(::Rails::VERSION) 326: if !defined?(Dispatcher) 327: begin 328: require 'dispatcher' 329: rescue LoadError 330: # Early versions of Rails 3 still had the dispatcher, but 331: # later versions disposed of it, in which case we'll need 332: # to use the application object. 333: raise if Rails::VERSION::MAJOR < 3 334: end 335: end 336: # Rails 2.2+ uses application_controller.rb while older versions use application.rb. 337: begin 338: require_dependency 'application_controller' 339: rescue LoadError => e 340: begin 341: require_dependency 'application' 342: rescue LoadError 343: # Considering that most apps these das are written in Rails 344: # 2.2+, if application.rb cannot be loaded either then it 345: # probably just means that application_controller.rb threw 346: # a LoadError. So we raise the original error here; if the 347: # app is based on Rails < 2.2 then the error will make less 348: # sense but we can only choose one or the other. 349: raise e 350: end 351: end 352: 353: # - No point in preloading the application sources if the garbage collector 354: # isn't copy-on-write friendly. 355: # - Rails >= 2.2 already preloads application sources by default, so no need 356: # to do that again. 357: if GC.copy_on_write_friendly? && !rails_will_preload_app_code? 358: ['models','controllers','helpers'].each do |section| 359: Dir.glob("app/#{section}}/*.rb").each do |file| 360: require_dependency canonicalize_path(file) 361: end 362: end 363: end 364: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 367 367: def rails_will_preload_app_code? 368: if defined?(Rails::Initializer) 369: return ::Rails::Initializer.method_defined?(:load_application_classes) 370: else 371: return Rails::VERSION::MAJOR >= 3 372: end 373: end
# File lib/phusion_passenger/railz/application_spawner.rb, line 272 272: def set_passed_environment_variables 273: env_vars_string = @encoded_environment_variables.unpack("m").first 274: # Prevent empty string as last item from b0rking the Hash[...] statement. 275: # See comment in Hooks.cpp (sendHeaders) for details. 276: env_vars_string << "_\00__" 277: env_vars = Hash[*env_vars_string.split("\00"")] 278: env_vars.each_pair do |key, value| 279: ENV[key] = value 280: end 281: 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/railz/application_spawner.rb, line 407 407: def start_request_handler(channel, forked) 408: $0 = "Rails: #{@app_root}" 409: reader, writer = IO.pipe 410: begin 411: # Clear or re-establish connection if a connection was established 412: # in environment.rb. This prevents us from concurrently 413: # accessing the same MySQL connection handle. 414: if defined?(::ActiveRecord::Base) 415: if ::ActiveRecord::Base.respond_to?(:clear_all_connections!) 416: ::ActiveRecord::Base.clear_all_connections! 417: elsif ::ActiveRecord::Base.respond_to?(:clear_active_connections!) 418: ::ActiveRecord::Base.clear_active_connections! 419: elsif ::ActiveRecord::Base.respond_to?(:connected?) && 420: ::ActiveRecord::Base.connected? 421: ::ActiveRecord::Base.establish_connection 422: end 423: end 424: 425: reader.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) 426: 427: if Rails::VERSION::STRING >= '2.3.0' 428: rack_app = find_rack_app 429: handler = Rack::RequestHandler.new(reader, rack_app, @options) 430: else 431: handler = RequestHandler.new(reader, @options) 432: end 433: 434: channel.write(Process.pid, handler.socket_name, 435: handler.socket_type) 436: channel.send_io(writer) 437: writer.close 438: channel.close 439: 440: PhusionPassenger.call_event(:starting_worker_process, forked) 441: handler.main_loop 442: ensure 443: channel.close rescue nil 444: writer.close rescue nil 445: handler.cleanup rescue nil 446: PhusionPassenger.call_event(:stopping_worker_process) 447: end 448: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.