class PhusionPassenger::SpawnManager
it’s to work around an obscure bug in ActiveSupport’s Dispatcher.
In case you’re wondering why the namespace is “ClassicRails” and not “Rails”:
ClassicRails::ApplicationSpawner to preload and cache application code.
Ruby on Rails frameworks. ClassicRails::FrameworkSpawner, in turn, uses
Internally, SpawnManager uses ClassicRails::FrameworkSpawner to preload and cache
code, so subsequent spawns will be very fast.
will preload and cache Ruby on Rails frameworks, as well as application
Spawning a Ruby on Rails application is usually slow. But SpawnManager
== Ruby on Rails optimizations
finished.
tested. Don’t forget to call cleanup after the server’s main loop has
AbstractServer#start_synchronously. Starting asynchronously has not been
Note: SpawnManager may only be started synchronously with
system.
instances. It acts like a simple fascade for the rest of the spawn manager
The spawn manager is capable of spawning Ruby on Rails or Rack application
def app_name(app_type)
def app_name(app_type) if app_type == "rails" return "Ruby on Rails" else return "Ruby (Rack)" end end
def cleanup
def cleanup @spawners.cleanup end
def database_error?(e)
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
def handle_reload(client, app_group_name)
def handle_reload(client, app_group_name) reload(app_group_name) end
def handle_spawn_application(client, *options)
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
def initialize(options = {})
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
def load_error?(e)
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
def reload(app_group_name = nil)
loaded into memory.
application instance is spawned, the application code will be freshly
any cached application code is removed, so that the next time an
deploying a new version of the application. This method makes sure that
be necessary to reload the code for an application, such as after
Application code might be cached in memory. But once it a while, it will
Long description:
instances will be removed, no matter the group name.
If nil is specified as group name, then all cached application
Remove the cached application instances at the given group name.
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
def send_error_page(channel, template_name, options = {})
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
def spawn_application(options)
- FrameworkInitError: The Ruby on Rails framework that the application requires could not be loaded.
- AbstractServer::ServerError: One of the server processes exited unexpectedly.
is not installed.
- VersionNotFound: The Ruby on Rails framework version that the given application requires
- InvalidPath: +app_root+ doesn't appear to be a valid Ruby on Rails application root.
Exceptions:
- 'print_exceptions'
- 'base_uri'
data must be a NULL.
key-value pairs, encoded in base64. The last byte in the unencoded
to the spawned application process. This is NULL-seperated string of
- 'environment_variables': Environment variables which should be passed
- 'app_spawner_timeout'
- 'framework_spawner_timeout'
- 'default_group'
- 'default_user'
- 'group'
- 'user',
- 'spawn_method'
- 'environment'
- 'app_type'
Optional options:
- 'app_root'
Mandatory options:
Most options are explained in PoolOptions.h.
process.
AppProcess object will be returned, which represents the spawned application
Spawns an application with the given spawn options. When successful, an
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
def spawn_rack_application(options)
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
def spawn_rails_application(options)
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