1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
# Phusion Passenger - http://www.modrails.com/
# Copyright (c) 2010 Phusion
#
# "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
require 'rubygems'
require 'socket'
require 'etc'
require 'fcntl'
require 'phusion_passenger/abstract_server'
require 'phusion_passenger/app_process'
require 'phusion_passenger/constants'
require 'phusion_passenger/debug_logging'
require 'phusion_passenger/classic_rails/request_handler'
require 'phusion_passenger/rack/request_handler'
require 'phusion_passenger/public_api'
require 'phusion_passenger/exceptions'
require 'phusion_passenger/utils'
module PhusionPassenger
module ClassicRails
# 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 ApplicationSpawner.spawn_application.
class ApplicationSpawner < AbstractServer
include Utils
extend Utils
include DebugLogging
# This exception means that the ApplicationSpawner server process exited unexpectedly.
class Error < AbstractServer::ServerError
end
# The application root of this spawner.
attr_reader :app_root
# 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.
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
# The following options are accepted:
# - 'app_root'
#
# See SpawnManager#spawn_application for information about the options.
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 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.
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 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.
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
protected
# Overrided method.
def before_fork # :nodoc:
if GC.copy_on_write_friendly?
# Garbage collect now so that the child process doesn't have to
# do that (to prevent making pages dirty).
GC.start
end
end
# Overrided method.
def initialize_server # :nodoc:
report_app_init_status(MessageChannel.new(@owner_socket)) do
$0 = "Passenger ApplicationSpawner: #{@options['app_group_name']}"
prepare_app_process('config/environment.rb', @options)
if defined?(RAILS_ENV)
Object.send(:remove_const, :RAILS_ENV)
Object.const_set(:RAILS_ENV, ENV['RAILS_ENV'])
end
preload_application
after_loading_app_code(@options)
end
end
private
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
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
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
# 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).
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
private_class_method :start_request_handler
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
private_class_method :find_rack_app
end
end # module ClassicRails
end # module PhusionPassenger
|