Included Modules

Class Index [+]

Quicksearch

PhusionPassenger::Railz::ApplicationSpawner

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.

Constants

ROOT_UID

The user ID of the root user.

ROOT_GID

The group ID of the root user.

Attributes

app_root[R]

The application root of this spawner.

Public Class Methods

new(app_root, options = {}) click to toggle source

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

Public Instance Methods

spawn_application() click to toggle source

Spawn an instance of the RoR application. When successful, an Application object will be returned, which represents the spawned RoR application.

Raises:

     # 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_application!() click to toggle source

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
start() click to toggle source

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

Private Instance Methods

find_rack_app() click to toggle source
     # 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
handle_spawn_application() click to toggle source
     # 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
load_environment_with_passenger() click to toggle source
     # 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
preload_application() click to toggle source
     # 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
rails_will_preload_app_code?() click to toggle source
     # 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
set_passed_environment_variables() click to toggle source
     # 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
start_request_handler(channel, forked) click to toggle source

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.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.