src/helper-scripts/rack-preloader.rb in passenger-5.2.3 vs src/helper-scripts/rack-preloader.rb in passenger-5.3.0
- old
+ new
@@ -34,67 +34,58 @@
def self.app
@@app
end
- def self.format_exception(e)
- result = "#{e} (#{e.class})"
- if !(e.backtrace.nil? || e.backtrace.empty?)
- if e.respond_to?(:html?) && e.html?
- require 'erb' if !defined?(ERB)
- result << "\n<pre> " << ERB::Util.h(e.backtrace.join("\n ")) << "</pre>"
- else
- result << "\n " << e.backtrace.join("\n ")
- end
+ def self.init_passenger
+ STDOUT.sync = true
+ STDERR.sync = true
+
+ work_dir = ENV['PASSENGER_SPAWN_WORK_DIR'].to_s
+ if work_dir.empty?
+ abort "This program may only be invoked from Passenger (error: $PASSENGER_SPAWN_WORK_DIR not set)."
end
- result
+
+ record_journey_step_end('SUBPROCESS_EXEC_WRAPPER', 'STEP_PERFORMED')
+ record_journey_step_begin('SUBPROCESS_WRAPPER_PREPARATION', 'STEP_IN_PROGRESS')
+
+ ruby_libdir = File.read("#{work_dir}/args/ruby_libdir").strip
+ passenger_root = File.read("#{work_dir}/args/passenger_root").strip
+ require "#{ruby_libdir}/phusion_passenger"
+ PhusionPassenger.locate_directories(passenger_root)
+
+ PhusionPassenger.require_passenger_lib 'loader_shared_helpers'
+ PhusionPassenger.require_passenger_lib 'preloader_shared_helpers'
+ PhusionPassenger.require_passenger_lib 'utils/json'
+ require 'socket'
end
- def self.exit_code_for_exception(e)
- if e.is_a?(SystemExit)
- e.status
- else
- 1
+ def self.try_write_file(path, contents)
+ begin
+ File.open(path, 'wb') do |f|
+ f.write(contents)
+ end
+ rescue SystemCallError => e
+ STDERR.puts "Warning: unable to write to #{path}: #{e}"
end
end
- def self.handshake_and_read_startup_request
- STDOUT.sync = true
- STDERR.sync = true
- puts "!> I have control 1.0"
- abort "Invalid initialization header" if STDIN.readline != "You have control 1.0\n"
-
- @@options = {}
- while (line = STDIN.readline) != "\n"
- name, value = line.strip.split(/: */, 2)
- @@options[name] = value
- end
+ def self.record_journey_step_begin(step, state, work_dir = nil)
+ dir = work_dir || ENV['PASSENGER_SPAWN_WORK_DIR']
+ step_dir = "#{dir}/response/steps/#{step.downcase}"
+ try_write_file("#{step_dir}/state", state)
+ try_write_file("#{step_dir}/begin_time", Time.now.to_f)
end
- def self.init_passenger
- require "#{options["ruby_libdir"]}/phusion_passenger"
- PhusionPassenger.locate_directories(options["passenger_root"])
- PhusionPassenger.require_passenger_lib 'native_support'
- PhusionPassenger.require_passenger_lib 'ruby_core_enhancements'
- PhusionPassenger.require_passenger_lib 'ruby_core_io_enhancements'
- PhusionPassenger.require_passenger_lib 'preloader_shared_helpers'
- PhusionPassenger.require_passenger_lib 'loader_shared_helpers'
- PhusionPassenger.require_passenger_lib 'request_handler'
- PhusionPassenger.require_passenger_lib 'rack/thread_handler_extension'
- @@options = LoaderSharedHelpers.init(@@options)
- @@options = PreloaderSharedHelpers.init(@@options)
- if defined?(NativeSupport)
- NativeSupport.disable_stdio_buffering
+ def self.record_journey_step_end(step, state, work_dir = nil)
+ dir = work_dir || ENV['PASSENGER_SPAWN_WORK_DIR']
+ step_dir = "#{dir}/response/steps/#{step.downcase}"
+ try_write_file("#{step_dir}/state", state)
+ if !File.exist?("#{step_dir}/begin_time") && !File.exist?("#{step_dir}/begin_time_monotonic")
+ try_write_file("#{step_dir}/begin_time", Time.now.to_f)
end
- RequestHandler::ThreadHandler.send(:include, Rack::ThreadHandlerExtension)
- rescue Exception => e
- LoaderSharedHelpers.about_to_abort(options, e) if defined?(LoaderSharedHelpers)
- puts "!> Error"
- puts "!> html: true" if e.respond_to?(:html?) && e.html?
- puts "!> "
- puts format_exception(e)
- exit exit_code_for_exception(e)
+ try_write_file("#{step_dir}/end_time", Time.now.to_f)
end
def self.preload_app
LoaderSharedHelpers.before_loading_app_code_step1('config.ru', options)
LoaderSharedHelpers.run_load_path_setup_code(options)
@@ -110,52 +101,114 @@
@@app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app",
TOPLEVEL_BINDING, rackup_file)
LoaderSharedHelpers.after_loading_app_code(options)
rescue Exception => e
+ LoaderSharedHelpers.record_and_print_exception(e)
LoaderSharedHelpers.about_to_abort(options, e)
- puts "!> Error"
- puts "!> html: true" if e.respond_to?(:html?) && e.html?
- puts "!> "
- puts format_exception(e)
- exit exit_code_for_exception(e)
+ exit LoaderSharedHelpers.exit_code_for_exception(e)
end
- def self.negotiate_spawn_command
- puts "!> I have control 1.0"
- abort "Invalid initialization header" if STDIN.readline != "You have control 1.0\n"
+ def self.create_server(options)
+ if defined?(NativeSupport)
+ unix_path_max = NativeSupport::UNIX_PATH_MAX
+ else
+ unix_path_max = options.fetch('UNIX_PATH_MAX', 100).to_i
+ end
+ if options['socket_dir']
+ socket_dir = options['socket_dir']
+ socket_prefix = "preloader"
+ else
+ socket_dir = Dir.tmpdir
+ socket_prefix = "PsgPreloader"
+ end
+ socket_filename = nil
+ server = nil
+ Utils.retry_at_most(128, Errno::EADDRINUSE) do
+ socket_filename = "#{socket_dir}/#{socket_prefix}.#{rand(0xFFFFFFFF).to_s(36)}"
+ socket_filename = socket_filename.slice(0, unix_path_max - 10)
+ server = UNIXServer.new(socket_filename)
+ end
+ server.close_on_exec!
+ File.chmod(0600, socket_filename)
+
+ [server, socket_filename]
+ end
+
+ def self.reinitialize_std_channels(work_dir)
+ if File.exist?("#{work_dir}/stdin")
+ STDIN.reopen("#{work_dir}/stdin", 'r')
+ end
+ if File.exist?("#{work_dir}/stdout_and_err")
+ STDOUT.reopen("#{work_dir}/stdout_and_err", 'w')
+ STDERR.reopen(STDOUT)
+ end
+ STDOUT.sync = STDERR.sync = true
+ end
+
+ def self.negotiate_spawn_command
begin
- while (line = STDIN.readline) != "\n"
- name, value = line.strip.split(/: */, 2)
- options[name] = value
+ work_dir = ENV['PASSENGER_SPAWN_WORK_DIR']
+ @@options = File.open("#{work_dir}/args.json", 'rb') do |f|
+ Utils::JSON.parse(f.read)
end
- @@options = LoaderSharedHelpers.sanitize_spawn_options(@@options)
- LoaderSharedHelpers.before_handling_requests(true, options)
- handler = RequestHandler.new(STDIN, options.merge("app" => app))
+ reinitialize_std_channels(work_dir)
+
+ LoaderSharedHelpers.run_block_and_record_step_progress('SUBPROCESS_PREPARE_AFTER_FORKING_FROM_PRELOADER') do
+ LoaderSharedHelpers.before_handling_requests(true, options)
+ end
+
+ handler = nil
+ LoaderSharedHelpers.run_block_and_record_step_progress('SUBPROCESS_LISTEN') do
+ handler = RequestHandler.new(STDIN, options.merge("app" => app))
+ end
+
+ LoaderSharedHelpers.dump_all_information(options)
+ LoaderSharedHelpers.advertise_sockets(options, handler)
+ LoaderSharedHelpers.advertise_readiness(options)
rescue Exception => e
+ LoaderSharedHelpers.record_and_print_exception(e)
LoaderSharedHelpers.about_to_abort(options, e)
- puts "!> Error"
- puts "!> "
- puts format_exception(e)
- exit exit_code_for_exception(e)
+ exit LoaderSharedHelpers.exit_code_for_exception(e)
end
- LoaderSharedHelpers.advertise_readiness
- LoaderSharedHelpers.advertise_sockets(STDOUT, handler)
- puts "!> "
handler
end
################## Main code ##################
- handshake_and_read_startup_request
init_passenger
- preload_app
- if PreloaderSharedHelpers.run_main_loop(options) == :forked
+ @@options = PreloaderSharedHelpers.init(self)
+ LoaderSharedHelpers.record_journey_step_end('SUBPROCESS_WRAPPER_PREPARATION',
+ 'STEP_PERFORMED')
+
+ LoaderSharedHelpers.run_block_and_record_step_progress('SUBPROCESS_APP_LOAD_OR_EXEC') do
+ preload_app
+ end
+
+ server = nil
+ LoaderSharedHelpers.run_block_and_record_step_progress('SUBPROCESS_LISTEN') do
+ begin
+ server = create_server(options)
+ PreloaderSharedHelpers.advertise_sockets(options, server)
+ LoaderSharedHelpers.dump_all_information(options)
+ rescue Exception => e
+ LoaderSharedHelpers.record_and_print_exception(e)
+ LoaderSharedHelpers.about_to_abort(options, e)
+ exit LoaderSharedHelpers.exit_code_for_exception(e)
+ end
+ end
+
+ LoaderSharedHelpers.advertise_readiness(options)
+
+ subprocess_work_dir = PreloaderSharedHelpers.run_main_loop(server, options)
+ if subprocess_work_dir
+ # Inside forked subprocess
+ ENV['PASSENGER_SPAWN_WORK_DIR'] = subprocess_work_dir
handler = negotiate_spawn_command
handler.main_loop
handler.cleanup
LoaderSharedHelpers.after_handling_requests
end