class PhusionPassenger::Rack::ApplicationSpawner

Spawning of Rack applications.

Public Class Methods

new(options) click to toggle source

The following options are accepted:

  • 'app_root'

See PhusionPassenger::SpawnManager#spawn_application for information about the options.

Calls superclass method PhusionPassenger::Utils.new
# File lib/phusion_passenger/rack/application_spawner.rb, line 95
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
spawn_application(options = {}) click to toggle source

Spawn an instance of the given Rack application. When successful, an AppProcess object will be returned, which represents the spawned application.

Accepts the same options as PhusionPassenger::SpawnManager#spawn_application.

Raises:

  • AppInitError: The Rack application raised an exception or called exit() during startup.

  • SystemCallError, IOError, SocketError: Something went wrong.

# File lib/phusion_passenger/rack/application_spawner.rb, line 59
def self.spawn_application(options = {})
        options = sanitize_spawn_options(options)
        
        a, b = UNIXSocket.pair
        pid = safe_fork(self.class.to_s, 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)
                app = nil
                success = report_app_init_status(channel) do
                        prepare_app_process('config.ru', options)
                        app = load_rack_app
                        after_loading_app_code(options)
                end
                if success
                        start_request_handler(channel, app, false, options)
                end
        end
        b.close
        Process.waitpid(pid) rescue nil
        
        channel = MessageChannel.new(a)
        unmarshal_and_raise_errors(channel, options["print_exceptions"], "rack")
        
        # No exception was raised, so spawning succeeded.
        return AppProcess.read_from_channel(channel)
end

Private Class Methods

load_rack_app() click to toggle source
# File lib/phusion_passenger/rack/application_spawner.rb, line 215
def self.load_rack_app
        # Load Rack inside the spawned child process so that the spawn manager
        # itself doesn't preload Rack. This is necessary because some broken
        # Rails apps explicitly specify a Rack version as dependency.
        require 'rack'
        rackup_file = ENV["RACKUP_FILE"] || "config.ru"
        rackup_code = ::File.open(rackup_file, 'rb') do |f|
                f.read
        end
        eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, rackup_file)
end
start_request_handler(channel, app, forked, options) click to toggle source
# File lib/phusion_passenger/rack/application_spawner.rb, line 190
def self.start_request_handler(channel, app, forked, options)
        app_root = options["app_root"]
        $0 = "Rack: #{options['app_group_name']}"
        reader, writer = IO.pipe
        begin
                reader.close_on_exec!
                
                handler = RequestHandler.new(reader, app, options)
                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

Public Instance Methods

spawn_application(options = {}) click to toggle source

Spawns an instance of the Rack application. When successful, an AppProcess object will be returned, which represents the spawned Rack application.

options will be passed to the request handler's constructor.

Raises:

# File lib/phusion_passenger/rack/application_spawner.rb, line 112
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
start() click to toggle source

Overrided from PhusionPassenger::AbstractServer#start.

May raise these additional exceptions:

  • AppInitError: The Rack application raised an exception or called exit() during startup.

  • ApplicationSpawner::Error: The ApplicationSpawner server exited unexpectedly.

Calls superclass method PhusionPassenger::AbstractServer#start
# File lib/phusion_passenger/rack/application_spawner.rb, line 127
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

Private Instance Methods

handle_spawn_application(client, *options) click to toggle source
# File lib/phusion_passenger/rack/application_spawner.rb, line 162
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),
                                @app, 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