219 lines
7.7 KiB
Ruby
219 lines
7.7 KiB
Ruby
#!/usr/bin/env ruby
|
|
# encoding: binary
|
|
# Phusion Passenger - https://www.phusionpassenger.com/
|
|
# Copyright (c) 2013-2025 Asynchronous B.V.
|
|
#
|
|
# "Passenger", "Phusion Passenger" and "Union Station" are registered
|
|
# trademarks of Asynchronous B.V.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
|
|
|
|
module PhusionPassenger
|
|
module App
|
|
def self.options
|
|
@@options
|
|
end
|
|
|
|
def self.app
|
|
@@app
|
|
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
|
|
|
|
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.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.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.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
|
|
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)
|
|
LoaderSharedHelpers.before_loading_app_code_step2(options)
|
|
LoaderSharedHelpers.activate_gem 'rack'
|
|
|
|
app_root = options["app_root"]
|
|
rackup_file = LoaderSharedHelpers.maybe_make_path_relative_to_app_root(
|
|
app_root, options["startup_file"] || "#{app_root}/config.ru")
|
|
rackup_code = ::File.open(rackup_file, 'rb') do |f|
|
|
f.read
|
|
end
|
|
@@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)
|
|
exit LoaderSharedHelpers.exit_code_for_exception(e)
|
|
end
|
|
|
|
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
|
|
work_dir = ENV['PASSENGER_SPAWN_WORK_DIR']
|
|
@@options = File.open("#{work_dir}/args.json", 'rb') do |f|
|
|
Utils::JSON.parse(f.read)
|
|
end
|
|
|
|
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)
|
|
exit LoaderSharedHelpers.exit_code_for_exception(e)
|
|
end
|
|
|
|
handler
|
|
end
|
|
|
|
|
|
################## Main code ##################
|
|
|
|
|
|
init_passenger
|
|
@@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
|
|
|
|
end # module App
|
|
end # module PhusionPassenger
|