class PhusionPassenger::SpawnManager

The spawn manager is capable of spawning Ruby on Rails or Rack application instances. It acts like a simple fascade for the rest of the spawn manager system.

Note: SpawnManager may only be started synchronously with PhusionPassenger::AbstractServer#start_synchronously. Starting asynchronously has not been tested. Don't forget to call cleanup after the server's main loop has finished.

Ruby on Rails optimizations

Spawning a Ruby on Rails application is usually slow. But SpawnManager will preload and cache Ruby on Rails frameworks, as well as application code, so subsequent spawns will be very fast.

Internally, SpawnManager uses ClassicRails::FrameworkSpawner to preload and cache Ruby on Rails frameworks. ClassicRails::FrameworkSpawner, in turn, uses ClassicRails::ApplicationSpawner to preload and cache application code.

In case you're wondering why the namespace is “ClassicRails” and not “Rails”: it's to work around an obscure bug in ActiveSupport's Dispatcher.

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method PhusionPassenger::Utils.new
# File lib/phusion_passenger/spawn_manager.rb, line 61
def initialize(options = {})
        super("", "")
        @options = options
        @spawners = AbstractServerCollection.new
        define_message_handler(:spawn_application, :handle_spawn_application)
        define_message_handler(:reload, :handle_reload)
        define_signal_handler('SIGHUP', :reload)
        
        # Start garbage collector in order to free up some existing
        # heap slots. This prevents the heap from growing unnecessarily
        # during the startup phase.
        GC.start
        if GC.copy_on_write_friendly?
                # Preload libraries for copy-on-write semantics.
                require 'base64'
                require 'phusion_passenger/app_process'
                require 'phusion_passenger/classic_rails/framework_spawner'
                require 'phusion_passenger/classic_rails/application_spawner'
                require 'phusion_passenger/rack/application_spawner'
                require 'phusion_passenger/html_template'
                require 'phusion_passenger/platform_info'
                require 'phusion_passenger/exceptions'
        end
end

Public Instance Methods

cleanup() click to toggle source

Cleanup resources. Should be called when this SpawnManager is no longer needed.

# File lib/phusion_passenger/spawn_manager.rb, line 179
def cleanup
        @spawners.cleanup
end
reload(app_group_name = nil) click to toggle source

Remove the cached application instances at the given group name. If nil is specified as group name, then all cached application instances will be removed, no matter the group name.

Long description: Application code might be cached in memory. But once it a while, it will be necessary to reload the code for an application, such as after deploying a new version of the application. This method makes sure that any cached application code is removed, so that the next time an application instance is spawned, the application code will be freshly loaded into memory.

Raises AbstractServer::SpawnError if something went wrong.

# File lib/phusion_passenger/spawn_manager.rb, line 160
def reload(app_group_name = nil)
        @spawners.synchronize do
                if app_group_name
                        # Stop and delete associated ApplicationSpawner.
                        @spawners.delete("app:#{app_group_name}")
                        # Propagate reload command to associated FrameworkSpawner.
                        @spawners.each do |spawner|
                                if spawner.respond_to?(:reload)
                                        spawner.reload(app_group_name)
                                end
                        end
                else
                        # Stop and delete all spawners.
                        @spawners.clear
                end
        end
end
spawn_application(options) click to toggle source

Spawns an application with the given spawn options. When successful, an AppProcess object will be returned, which represents the spawned application process.

Most options are explained in PoolOptions.h.

Mandatory options:

  • 'app_root'

Optional options:

  • 'app_type'

  • 'environment'

  • 'spawn_method'

  • 'user',

  • 'group'

  • 'default_user'

  • 'default_group'

  • 'framework_spawner_timeout'

  • 'app_spawner_timeout'

  • 'environment_variables': Environment variables which should be passed to the spawned application process. 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'

  • 'print_exceptions'

Exceptions:

  • InvalidPath: app_root doesn't appear to be a valid Ruby on Rails application root.

  • VersionNotFound: The Ruby on Rails framework version that the given application requires is not installed.

  • AbstractServer::ServerError: One of the server processes exited unexpectedly.

  • FrameworkInitError: The Ruby on Rails framework that the application requires could not be loaded.

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

# File lib/phusion_passenger/spawn_manager.rb, line 119
def spawn_application(options)
        if !options["app_root"]
                raise ArgumentError, "The 'app_root' option must be given."
        end
        options = sanitize_spawn_options(options)
        
        case options["app_type"]
        when "rails"
                if !defined?(ClassicRails::FrameworkSpawner)
                        require 'phusion_passenger/classic_rails/framework_spawner'
                        require 'phusion_passenger/classic_rails/application_spawner'
                end
                return spawn_rails_application(options)
        when "rack"
                if !defined?(Rack::ApplicationSpawner)
                        require 'phusion_passenger/rack/application_spawner'
                end
                return spawn_rack_application(options)
        when "wsgi"
                if !defined?(WSGI::ApplicationSpawner)
                        require 'phusion_passenger/wsgi/application_spawner'
                end
                return WSGI::ApplicationSpawner.spawn_application(options)
        else
                raise ArgumentError, "Unknown 'app_type' value '#{options["app_type"]}'."
        end
end

Private Instance Methods

app_name(app_type) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 349
def app_name(app_type)
        if app_type == "rails"
                return "Ruby on Rails"
        else
                return "Ruby (Rack)"
        end
end
database_error?(e) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 332
def database_error?(e)
        return ( defined?(Mysql::Error) && e.child_exception.is_a?(Mysql::Error) ) ||
               ( e.child_exception.is_a?(UnknownError) &&
                   (
                       e.child_exception.real_class_name =~ /^ActiveRecord/ ||
                       e.child_exception.real_class_name =~ /^Mysql::/
                   )
               )
end
handle_reload(client, app_group_name) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 315
def handle_reload(client, app_group_name)
        reload(app_group_name)
end
handle_spawn_application(client, *options) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 268
def handle_spawn_application(client, *options)
        options     = sanitize_spawn_options(Hash[*options])
        app_process = nil
        app_root    = options["app_root"]
        app_type    = options["app_type"]
        begin
                app_process = spawn_application(options)
        rescue AbstractServer::ServerError => e
                send_error_page(client, 'general_error', :error => e)
        rescue VersionNotFound => e
                send_error_page(client, 'version_not_found', :error => e, :app_root => app_root)
        rescue AppInitError => e
                if database_error?(e)
                        send_error_page(client, 'database_error', :error => e,
                                :app_root => app_root, :app_name => app_name(app_type),
                                :app_type => app_type)
                elsif load_error?(e)
                        # A source file failed to load, maybe because of a
                        # missing gem. If that's the case then the sysadmin
                        # will install probably the gem. So we clear RubyGems's
                        # cache so that it can detect new gems.
                        Gem.clear_paths
                        send_error_page(client, 'load_error', :error => e, :app_root => app_root,
                                :app_name => app_name(app_type))
                elsif e.child_exception.is_a?(SystemExit)
                        send_error_page(client, 'app_exited_during_initialization', :error => e,
                                :app_root => app_root, :app_name => app_name(app_type))
                else
                        send_error_page(client, 'app_init_error', :error => e,
                                :app_root => app_root, :app_name => app_name(app_type))
                end
        rescue FrameworkInitError => e
                send_error_page(client, 'framework_init_error', :error => e)
        end
        if app_process
                begin
                        client.write('ok')
                        app_process.write_to_channel(client)
                rescue Errno::EPIPE
                        # The Apache module may be interrupted during a spawn command,
                        # in which case it will close the connection. We ignore this error.
                ensure
                        app_process.close
                end
        end
end
load_error?(e) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 342
def load_error?(e)
        return e.child_exception.is_a?(LoadError) || (
                   e.child_exception.is_a?(UnknownError) &&
                   e.child_exception.real_class_name == "MissingSourceFile"
        )
end
send_error_page(channel, template_name, options = {}) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 319
def send_error_page(channel, template_name, options = {})
        require 'phusion_passenger/html_template' unless defined?(HTMLTemplate)
        if !defined?(PlatformInfo)
                require 'phusion_passenger/platform_info'
                require 'phusion_passenger/platform_info/ruby'
        end
        options["enterprisey"] = File.exist?("#{SOURCE_ROOT}/enterprisey.txt") ||
                File.exist?("/etc/passenger_enterprisey.txt")
        data = HTMLTemplate.new(template_name, options).result
        channel.write('error_page')
        channel.write_scalar(data)
end
spawn_rack_application(options) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 234
def spawn_rack_application(options)
        app_group_name = options["app_group_name"]
        spawn_method   = options["spawn_method"]
        spawner        = nil
        create_spawner = nil
        key            = nil
        
        case spawn_method
        when nil, "", "smart", "smart-lv2"
                @spawners.synchronize do
                        key = "app:#{app_group_name}"
                        spawner = @spawners.lookup_or_add(key) do
                                spawner_timeout = options["app_spawner_timeout"]
                                spawner = Rack::ApplicationSpawner.new(
                                        @options.merge(options))
                                if spawner_timeout != -1
                                        spawner.max_idle_time = spawner_timeout
                                end
                                spawner.start
                                spawner
                        end
                        begin
                                return spawner.spawn_application(options)
                        rescue AbstractServer::ServerError
                                @spawners.delete(key)
                                raise
                        end
                end
        else
                return Rack::ApplicationSpawner.spawn_application(
                        @options.merge(options))
        end
end
spawn_rails_application(options) click to toggle source
# File lib/phusion_passenger/spawn_manager.rb, line 184
def spawn_rails_application(options)
        app_root       = options["app_root"]
        app_group_name = options["app_group_name"]
        spawn_method   = options["spawn_method"]
        spawner        = nil
        create_spawner = nil
        key            = nil
        
        case spawn_method
        when nil, "", "smart", "smart-lv2"
                if spawn_method != "smart-lv2"
                        framework_version = AppProcess.detect_framework_version(app_root)
                end
                if framework_version.nil? || framework_version == :vendor
                        key = "app:#{app_group_name}"
                        create_spawner = proc do
                                ClassicRails::ApplicationSpawner.new(@options.merge(options))
                        end
                        spawner_timeout = options["app_spawner_timeout"]
                else
                        key = "version:#{framework_version}"
                        create_spawner = proc do
                                options["framework_version"] = framework_version
                                ClassicRails::FrameworkSpawner.new(@options.merge(options))
                        end
                        spawner_timeout = options["framework_spawner_timeout"]
                end
                
                @spawners.synchronize do
                        spawner = @spawners.lookup_or_add(key) do
                                spawner = create_spawner.call
                                if spawner_timeout != -1
                                        spawner.max_idle_time = spawner_timeout
                                end
                                spawner.start
                                spawner
                        end
                        begin
                                return spawner.spawn_application(options)
                        rescue AbstractServer::ServerError
                                @spawners.delete(key)
                                raise
                        end
                end
        else
                return ClassicRails::ApplicationSpawner.spawn_application(
                        @options.merge(options))
        end
end