Class PhusionPassenger::Railz::ApplicationSpawner |
In: | lib/phusion_passenger/railz/application_spawner.rb
Parent: | AbstractServer

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.

Methods

- new
- spawn_application
- spawn_application!
- start

Included Modules

- Utils

Classes and Modules
Class PhusionPassenger::Railz::ApplicationSpawner::Error
Constants

ROOT_UID | = | 0
| The user ID of the root user.
ROOT_GID | = | 0
| The group ID of the root user.

Attributes

[R] | app_root | The application root of this spawner.

Public Class methods
new(app_root, options = {})

`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.

[ show source ]

         # 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()

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.

[ show source ]

         # 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!()

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.

[ show source ]

         # 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['RAILS_RELATIVE_URL_ROOT'] = @base_uri
    165:                                         Dir.chdir(@app_root)
    166:                                         if @encoded_environment_variables
    167:                                                 set_passed_environment_variables
    168:                                         end
    169:                                         if @lower_privilege
    170:                                                 lower_privilege('config/environment.rb', @lowest_user)
    171:                                         end
    172:
    173:                                         # require Rails' environment, using the same path as the original rails dispatcher,
    174:                                         # which normally does: require File.dirname(__FILE__) + "/../config/environment"
    175:                                         # thus avoiding the possibility of including the same file twice.
    176:                                         require 'public/../config/environment'
    177:
    178:                                         require 'dispatcher'
    179:                                 end
    180:                                 if success
    181:                                         start_request_handler(channel, false)
    182:                                 end
    183:                         rescue SignalException => e
    184:                                 if e.message != AbstractRequestHandler::HARD_TERMINATION_SIGNAL &&
    185:                                    e.message != AbstractRequestHandler::SOFT_TERMINATION_SIGNAL
    186:                                         raise
    187:                                 end
    188:                         end
    189:                 end
    190:                 b.close
    191:                 Process.waitpid(pid) rescue nil
    192:
    193:                 channel = MessageChannel.new(a)
    194:                 unmarshal_and_raise_errors(channel, @print_exceptions)
    195:
    196:                 # No exception was raised, so spawning succeeded.
    197:                 pid, socket_name, socket_type = channel.read
    198:                 if pid.nil?
    199:                         raise IOError, "Connection closed"
    200:                 end
    201:                 owner_pipe = channel.recv_io
    202:                 return Application.new(@app_root, pid, socket_name,
    203:                         socket_type, owner_pipe)
    204:         end

start()

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.

[ show source ]

         # File lib/phusion_passenger/railz/application_spawner.rb, line 212
    212:         def start
    213:                 super
    214:                 begin
    215:                         unmarshal_and_raise_errors(server, @print_exceptions)
    216:                 rescue IOError, SystemCallError, SocketError => e
    217:                         stop
    218:                         raise Error, "The application spawner server exited unexpectedly: #{e}"
    219:                 rescue
    220:                         stop
    221:                         raise
    222:                 end
    223:         end