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