Class: PhusionPassenger::Railz::ApplicationSpawner

Inherits:
AbstractServer show all
Includes:
Utils
Defined in:
lib/phusion_passenger/railz/application_spawner.rb

Overview

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.

Defined Under Namespace

Classes: Error

Constant Summary collapse

ROOT_UID =

The user ID of the root user.

0
ROOT_GID =

The group ID of the root user.

0

Constants inherited from AbstractServer

AbstractServer::SERVER_TERMINATION_SIGNAL

Instance Attribute Summary collapse

Attributes inherited from AbstractServer

#last_activity_time, #max_idle_time, #next_cleaning_time

Instance Method Summary collapse

Methods inherited from AbstractServer

#server_pid, #start_synchronously, #started?, #stop

Constructor Details

#initialize(app_root, options = {}) ⇒ ApplicationSpawner

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.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/phusion_passenger/railz/application_spawner.rb', line 102

def initialize(app_root, options = {})
	super()
	@app_root = app_root
	@canonicalized_app_root = canonicalize_path(app_root)
	@options = sanitize_spawn_options(options)
	@lower_privilege = @options["lower_privilege"]
	@lowest_user     = @options["lowest_user"]
	@environment     = @options["environment"]
	@encoded_environment_variables = @options["environment_variables"]
	@base_uri = @options["base_uri"] if @options["base_uri"] && @options["base_uri"] != "/"
	@print_exceptions = @options["print_exceptions"]
	self.max_idle_time = DEFAULT_APP_SPAWNER_MAX_IDLE_TIME
	assert_valid_app_root(@app_root)
	define_message_handler(:spawn_application, :handle_spawn_application)
end

Instance Attribute Details

#app_rootObject (readonly)

The application root of this spawner.



64
65
66
# File 'lib/phusion_passenger/railz/application_spawner.rb', line 64

def app_root
  @app_root
end

Instance Method Details

#spawn_applicationObject

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.



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/phusion_passenger/railz/application_spawner.rb', line 124

def spawn_application
	server.write("spawn_application")
	pid, socket_name, socket_type = server.read
	if pid.nil?
		raise IOError, "Connection closed"
	end
	owner_pipe = server.recv_io
	return Application.new(@app_root, pid, socket_name,
		socket_type, owner_pipe)
rescue SystemCallError, IOError, SocketError => e
	raise Error, "The application spawner server exited unexpectedly: #{e}"
end

#spawn_application!Object

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.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/phusion_passenger/railz/application_spawner.rb', line 151

def spawn_application!
	a, b = UNIXSocket.pair
	pid = safe_fork('application', true) do
		begin
			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
				ENV['RAILS_ENV'] = @environment
				ENV['RAILS_RELATIVE_URL_ROOT'] = @base_uri
				Dir.chdir(@app_root)
				if @encoded_environment_variables
					set_passed_environment_variables
				end
				if @lower_privilege
					lower_privilege('config/environment.rb', @lowest_user)
				end
				# Make sure RubyGems uses any new environment variable values
				# that have been set now (e.g. $HOME, $GEM_HOME, etc) and that
				# it is able to detect newly installed gems.
				Gem.clear_paths
				
				require File.expand_path('config/environment')
				require 'dispatcher'
			end
			if success
				start_request_handler(channel, false)
			end
		rescue SignalException => e
			if e.message != AbstractRequestHandler::HARD_TERMINATION_SIGNAL &&
			   e.message != AbstractRequestHandler::SOFT_TERMINATION_SIGNAL
				raise
			end
		end
	end
	b.close
	Process.waitpid(pid) rescue nil
	
	channel = MessageChannel.new(a)
	unmarshal_and_raise_errors(channel, @print_exceptions)
	
	# No exception was raised, so spawning succeeded.
	pid, socket_name, socket_type = channel.read
	if pid.nil?
		raise IOError, "Connection closed"
	end
	owner_pipe = channel.recv_io
	return Application.new(@app_root, pid, socket_name,
		socket_type, owner_pipe)
end

#startObject

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.



212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/phusion_passenger/railz/application_spawner.rb', line 212

def start
	super
	begin
		unmarshal_and_raise_errors(server, @print_exceptions)
	rescue IOError, SystemCallError, SocketError => e
		stop
		raise Error, "The application spawner server exited unexpectedly: #{e}"
	rescue
		stop
		raise
	end
end