Utility functions.
- after_handling_requests
- after_loading_app_code
- assert_valid_directory
- assert_valid_file
- assert_valid_groupname
- assert_valid_username
- at_exit
- before_handling_requests
- canonicalize_path
- check_directory_tree_permissions
- close_all_io_objects_for_fds
- connect_to_server
- generate_random_id
- get_socket_address_type
- global_backtrace_report
- local_socket_address?
- lower_privilege
- lower_privilege_called
- marshal_exception
- new
- opens_files?
- passenger_tmpdir
- passenger_tmpdir
- passenger_tmpdir=
- prepare_app_process
- print_exception
- private_class_method
- process_is_alive?
- report_app_init_status
- safe_fork
- sanitize_spawn_options
- split_by_null_into_hash
- split_by_null_into_hash
- to_boolean
- unmarshal_and_raise_errors
- unmarshal_exception
Class PhusionPassenger::Utils::HostsFileParser
Class PhusionPassenger::Utils::PseudoIO
Class PhusionPassenger::Utils::RewindableInput
Class PhusionPassenger::Utils::UnseekableSocket
PADDING | = | "_" |
NULL | = | "\0" |
FileSystemWatcher | = | NativeSupport::FileSystemWatcher |
[ show source ]
# File lib/phusion_passenger/utils/file_system_watcher.rb, line 60 60: def self.new(filenames, termination_pipe = nil) 61: # Default parameter values, type conversion and exception 62: # handling in C is too much of a pain. 63: filenames = filenames.map do |filename| 64: filename.to_s 65: end 66: return _new(filenames, termination_pipe) 67: end
[ show source ]
# File lib/phusion_passenger/utils/file_system_watcher.rb, line 69 69: def self.opens_files? 70: return true 71: end
No-op, hook for unit tests.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 649 649: def self.lower_privilege_called 650: end
Returns the directory in which to store Phusion Passenger-specific temporary files. If create is true, then this method creates the directory if it doesn‘t exist.
[ show source ]
# File lib/phusion_passenger/utils/tmpdir.rb, line 37 37: def self.passenger_tmpdir(create = true) 38: dir = @@passenger_tmpdir 39: if dir.nil? || dir.empty? 40: tmpdir = "/tmp" 41: ["PASSENGER_TEMP_DIR", "PASSENGER_TMPDIR"].each do |name| 42: if ENV.has_key?(name) && !ENV[name].empty? 43: tmpdir = ENV[name] 44: break 45: end 46: end 47: dir = "#{tmpdir}/passenger.1.0.#{Process.pid}" 48: dir.gsub!(%r{//+}, '/') 49: @@passenger_tmpdir = dir 50: end 51: if create && !File.exist?(dir) 52: # This is a very minimal implementation of the subdirectory 53: # creation logic in ServerInstanceDir.h. This implementation 54: # is only meant to make the unit tests pass. For production 55: # systems one should pre-create the temp directory with 56: # ServerInstanceDir.h. 57: system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", dir) 58: system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/generation-0") 59: system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/backends") 60: system("mkdir", "-p", "-m", "u=rwxs,g=rwx,o=rwx", "#{dir}/spawn-server") 61: end 62: return dir 63: end
[ show source ]
# File lib/phusion_passenger/utils/tmpdir.rb, line 65 65: def self.passenger_tmpdir=(dir) 66: @@passenger_tmpdir = dir 67: end
To be called after the request handler main loop is exited. This function will fire off necessary events perform necessary cleanup tasks.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 420 420: def after_handling_requests 421: PhusionPassenger.call_event(:stopping_worker_process) 422: Kernel.passenger_call_at_exit_blocks 423: end
This method is to be called after loading the application code but before forking a worker process.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 357 357: def after_loading_app_code(options) 358: # Even though prepare_app_process() restores the Phusion Passenger 359: # load path after setting up Bundler, the app itself might also 360: # remove Phusion Passenger from the load path for whatever reason, 361: # so here we restore the load path again. 362: if $LOAD_PATH.first != LIBDIR 363: $LOAD_PATH.unshift(LIBDIR) 364: $LOAD_PATH.uniq! 365: end 366: 367: # Post-install framework extensions. Possibly preceded by a call to 368: # PhusionPassenger.install_framework_extensions! 369: require 'rails/version' if defined?(::Rails) && !defined?(::Rails::VERSION) 370: if defined?(::Rails) && ::Rails::VERSION::MAJOR <= 2 371: require 'phusion_passenger/classic_rails_extensions/init' 372: ClassicRailsExtensions.init!(options) 373: # Rails 3 extensions are installed by 374: # PhusionPassenger.install_framework_extensions! 375: end 376: 377: PhusionPassenger._spawn_options = nil 378: end
Assert that path is a directory. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 64 64: def assert_valid_directory(path) 65: if !File.directory?(path) 66: raise InvalidPath, "'#{path}' is not a valid directory." 67: end 68: end
Assert that path is a file. Raises InvalidPath if it isn‘t.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 71 71: def assert_valid_file(path) 72: if !File.file?(path) 73: raise InvalidPath, "'#{path}' is not a valid file." 74: end 75: end
Assert that groupname is a valid group name. Raises ArgumentError if that is not the case.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 86 86: def assert_valid_groupname(groupname) 87: # If groupname does not exist then getgrnam() will raise an ArgumentError. 88: groupname && Etc.getgrnam(groupname) 89: end
Assert that username is a valid username. Raises ArgumentError if that is not the case.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 79 79: def assert_valid_username(username) 80: # If username does not exist then getpwnam() will raise an ArgumentError. 81: username && Etc.getpwnam(username) 82: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 267 267: def at_exit(&block) 268: return Kernel.passenger_at_exit(&block) 269: end
To be called before the request handler main loop is entered, but after the app startup file has been loaded. This function will fire off necessary events and perform necessary preparation tasks.
forked indicates whether the current worker process is forked off from an ApplicationSpawner that has preloaded the app code. options are the spawn options that were passed.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 387 387: def before_handling_requests(forked, options) 388: if forked && options["analytics_logger"] 389: options["analytics_logger"].clear_connection 390: end 391: 392: # If we were forked from a preloader process then clear or 393: # re-establish ActiveRecord database connections. This prevents 394: # child processes from concurrently accessing the same 395: # database connection handles. 396: if forked && defined?(::ActiveRecord::Base) 397: if ::ActiveRecord::Base.respond_to?(:clear_all_connections!) 398: ::ActiveRecord::Base.clear_all_connections! 399: elsif ::ActiveRecord::Base.respond_to?(:clear_active_connections!) 400: ::ActiveRecord::Base.clear_active_connections! 401: elsif ::ActiveRecord::Base.respond_to?(:connected?) && 402: ::ActiveRecord::Base.connected? 403: ::ActiveRecord::Base.establish_connection 404: end 405: end 406: 407: # Fire off events. 408: PhusionPassenger.call_event(:starting_worker_process, forked) 409: if options["pool_account_username"] && options["pool_account_password_base64"] 410: password = options["pool_account_password_base64"].unpack('m').first 411: PhusionPassenger.call_event(:credentials, 412: options["pool_account_username"], password) 413: else 414: PhusionPassenger.call_event(:credentials, nil, nil) 415: end 416: end
Return the canonicalized version of path. This path is guaranteed to to be "normal", i.e. it doesn‘t contain stuff like ".." or "/", and it fully resolves symbolic links.
Raises SystemCallError if something went wrong. Raises ArgumentError if path is nil. Raises InvalidPath if path does not appear to be a valid path.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 56 56: def canonicalize_path(path) 57: raise ArgumentError, "The 'path' argument may not be nil" if path.nil? 58: return Pathname.new(path).realpath.to_s 59: rescue Errno::ENOENT => e 60: raise InvalidAPath, e.message 61: end
Checks the permissions of all parent directories of dir as well as dir itself.
dir must be a canonical path.
If one of the parent directories has wrong permissions, causing dir to be inaccessible by the current process, then this function returns [path, true] where path is the path of the top-most directory with wrong permissions.
If dir itself is not executable by the current process then this function returns [dir, false].
Otherwise, nil is returned.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 749 749: def check_directory_tree_permissions(dir) 750: components = dir.split("/") 751: components.shift 752: i = 0 753: # We can't use File.readable() and friends here because they 754: # don't always work right with ACLs. Instead of we use 'real' 755: # checks. 756: while i < components.size 757: path = "/" + components[0..i].join("/") 758: begin 759: File.stat(path) 760: rescue Errno::EACCES 761: return [File.dirname(path), true] 762: end 763: i += 1 764: end 765: begin 766: Dir.chdir(dir) do 767: return nil 768: end 769: rescue Errno::EACCES 770: return [dir, false] 771: end 772: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 109 109: def close_all_io_objects_for_fds(file_descriptors_to_leave_open) 110: ObjectSpace.each_object(IO) do |io| 111: begin 112: if !file_descriptors_to_leave_open.include?(io.fileno) && !io.closed? 113: io.close 114: end 115: rescue 116: end 117: end 118: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 435 435: def connect_to_server(address) 436: case get_socket_address_type(address) 437: when :unix 438: return UNIXSocket.new(address.sub(/^unix:/, '')) 439: when :tcp 440: host, port = address.sub(%r{^tcp://}, '').split(':', 2) 441: port = port.to_i 442: return TCPSocket.new(host, port) 443: else 444: raise ArgumentError, "Unknown socket address type for '#{address}'." 445: end 446: end
Generate a long, cryptographically secure random ID string, which is also a valid filename.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 93 93: def generate_random_id(method) 94: case method 95: when :base64 96: data = [File.read("/dev/urandom", 64)].pack('m') 97: data.gsub!("\n", '') 98: data.gsub!("+", '') 99: data.gsub!("/", '') 100: data.gsub!(/==$/, '') 101: return data 102: when :hex 103: return File.read("/dev/urandom", 64).unpack('H*')[0] 104: else 105: raise ArgumentError, "Invalid method #{method.inspect}" 106: end 107: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 425 425: def get_socket_address_type(address) 426: if address =~ %r{^unix:.} 427: return :unix 428: elsif address =~ %r{^tcp://.} 429: return :tcp 430: else 431: return :unknown 432: end 433: end
Returns a string which reports the backtraces for all threads, or if that‘s not supported the backtrace for the current thread.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 776 776: def global_backtrace_report 777: if Kernel.respond_to?(:caller_for_all_threads) 778: output = "========== Process #{Process.pid}: backtrace dump ==========\n" 779: caller_for_all_threads.each_pair do |thread, stack| 780: output << ("-" * 60) << "\n" 781: output << "# Thread: #{thread.inspect}, " 782: if thread == Thread.main 783: output << "[main thread], " 784: end 785: if thread == Thread.current 786: output << "[current thread], " 787: end 788: output << "alive = #{thread.alive?}\n" 789: output << ("-" * 60) << "\n" 790: output << " " << stack.join("\n ") 791: output << "\n\n" 792: end 793: else 794: output = "========== Process #{Process.pid}: backtrace dump ==========\n" 795: output << ("-" * 60) << "\n" 796: output << "# Current thread: #{Thread.current.inspect}\n" 797: output << ("-" * 60) << "\n" 798: output << " " << caller.join("\n ") 799: end 800: return output 801: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 448 448: def local_socket_address?(address) 449: case get_socket_address_type(address) 450: when :unix 451: return true 452: when :tcp 453: host, port = address.sub(%r{^tcp://}, '').split(':', 2) 454: return host == "127.0.0.1" || host == "::1" || host == "localhost" 455: else 456: raise ArgumentError, "Unknown socket address type for '#{address}'." 457: end 458: end
Lowers the current process‘s privilege based on the documented rules for the "user", "group", "default_user" and "default_group" options.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 654 654: def lower_privilege(startup_file, options) 655: Utils.lower_privilege_called 656: return if Process.euid != 0 657: 658: if options["default_user"] && !options["default_user"].empty? 659: default_user = options["default_user"] 660: else 661: default_user = "nobody" 662: end 663: if options["default_group"] && !options["default_group"].empty? 664: default_group = options["default_group"] 665: else 666: default_group = Etc.getgrgid(Etc.getpwnam(default_user).gid).name 667: end 668: 669: if options["user"] && !options["user"].empty? 670: begin 671: user_info = Etc.getpwnam(options["user"]) 672: rescue ArgumentError 673: user_info = nil 674: end 675: else 676: uid = File.lstat(startup_file).uid 677: begin 678: user_info = Etc.getpwuid(uid) 679: rescue ArgumentError 680: user_info = nil 681: end 682: end 683: if !user_info || user_info.uid == 0 684: begin 685: user_info = Etc.getpwnam(default_user) 686: rescue ArgumentError 687: user_info = nil 688: end 689: end 690: 691: if options["group"] && !options["group"].empty? 692: if options["group"] == "!STARTUP_FILE!" 693: gid = File.lstat(startup_file).gid 694: begin 695: group_info = Etc.getgrgid(gid) 696: rescue ArgumentError 697: group_info = nil 698: end 699: else 700: begin 701: group_info = Etc.getgrnam(options["group"]) 702: rescue ArgumentError 703: group_info = nil 704: end 705: end 706: elsif user_info 707: begin 708: group_info = Etc.getgrgid(user_info.gid) 709: rescue ArgumentError 710: group_info = nil 711: end 712: else 713: group_info = nil 714: end 715: if !group_info || group_info.gid == 0 716: begin 717: group_info = Etc.getgrnam(default_group) 718: rescue ArgumentError 719: group_info = nil 720: end 721: end 722: 723: if !user_info 724: raise SecurityError, "Cannot determine a user to lower privilege to" 725: end 726: if !group_info 727: raise SecurityError, "Cannot determine a group to lower privilege to" 728: end 729: 730: NativeSupport.switch_user(user_info.name, user_info.uid, group_info.gid) 731: ENV['USER'] = user_info.name 732: ENV['HOME'] = user_info.dir 733: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 120 120: def marshal_exception(exception) 121: data = { 122: :message => exception.message, 123: :class => exception.class.to_s, 124: :backtrace => exception.backtrace 125: } 126: if exception.is_a?(InitializationError) 127: data[:is_initialization_error] = true 128: if exception.child_exception 129: data[:child_exception] = marshal_exception(exception.child_exception) 130: child_exception = exception.child_exception 131: exception.child_exception = nil 132: data[:exception] = Marshal.dump(exception) 133: exception.child_exception = child_exception 134: end 135: else 136: begin 137: data[:exception] = Marshal.dump(exception) 138: rescue ArgumentError, TypeError 139: e = UnknownError.new(exception.message, exception.class.to_s, 140: exception.backtrace) 141: data[:exception] = Marshal.dump(e) 142: end 143: end 144: return Marshal.dump(data) 145: end
[ show source ]
# File lib/phusion_passenger/utils/tmpdir.rb, line 30 30: def passenger_tmpdir(create = true) 31: PhusionPassenger::Utils.passenger_tmpdir(create) 32: end
Prepare an application process using rules for the given spawn options. This method is to be called before loading the application code.
startup_file is the application type‘s startup file, e.g. "config/environment.rb" for Rails apps and "config.ru" for Rack apps. See SpawnManager#spawn_application for options.
This function may modify options. The modified options are to be passed to the request handler.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 194 194: def prepare_app_process(startup_file, options) 195: options["app_root"] = canonicalize_path(options["app_root"]) 196: Dir.chdir(options["app_root"]) 197: 198: lower_privilege(startup_file, options) 199: path, is_parent = check_directory_tree_permissions(options["app_root"]) 200: if path 201: username = Etc.getpwuid(Process.euid).name 202: groupname = Etc.getgrgid(Process.egid).name 203: message = "This application process is currently running as " + 204: "user '#{username}' and group '#{groupname}' and must be " + 205: "able to access its application root directory " + 206: "'#{options["app_root"]}'. " 207: if is_parent 208: message << "However the parent directory '#{path}' " + 209: "has wrong permissions, thereby preventing " + 210: "this process from accessing its application " + 211: "root directory. Please fix the permissions " + 212: "of the directory '#{path}' first." 213: else 214: message << "However this directory is not accessible " + 215: "because it has wrong permissions. Please fix " + 216: "these permissions first." 217: end 218: raise(message) 219: end 220: 221: ENV["RAILS_ENV"] = ENV["RACK_ENV"] = options["environment"] 222: 223: base_uri = options["base_uri"] 224: if base_uri && !base_uri.empty? && base_uri != "/" 225: ENV["RAILS_RELATIVE_URL_ROOT"] = base_uri 226: ENV["RACK_BASE_URI"] = base_uri 227: end 228: 229: encoded_environment_variables = options["environment_variables"] 230: if encoded_environment_variables 231: env_vars_string = encoded_environment_variables.unpack("m").first 232: env_vars_array = env_vars_string.split("\0", -1) 233: env_vars_array.pop 234: env_vars = Hash[*env_vars_array] 235: env_vars.each_pair do |key, value| 236: ENV[key] = value 237: end 238: end 239: 240: # Instantiate the analytics logger if requested. Can be nil. 241: require 'phusion_passenger/analytics_logger' 242: options["analytics_logger"] = AnalyticsLogger.new_from_options(options) 243: 244: # Make sure RubyGems uses any new environment variable values 245: # that have been set now (e.g. $HOME, $GEM_HOME, etc) and that 246: # it is able to detect newly installed gems. 247: Gem.clear_paths 248: 249: # Because spawned app processes exit using #exit!, #at_exit 250: # blocks aren't called. Here we ninja patch Kernel so that 251: # we can call #at_exit blocks during app process shutdown. 252: class << Kernel 253: def passenger_call_at_exit_blocks 254: @passenger_at_exit_blocks ||= [] 255: @passenger_at_exit_blocks.reverse_each do |block| 256: block.call 257: end 258: end 259: 260: def passenger_at_exit(&block) 261: @passenger_at_exit_blocks ||= [] 262: @passenger_at_exit_blocks << block 263: return block 264: end 265: end 266: Kernel.class_eval do 267: def at_exit(&block) 268: return Kernel.passenger_at_exit(&block) 269: end 270: end 271: 272: 273: # Rack::ApplicationSpawner depends on the 'rack' library, but the app 274: # might want us to use a bundled version instead of a 275: # gem/apt-get/yum/whatever-installed version. Therefore we must setup 276: # the correct load paths before requiring 'rack'. 277: # 278: # The most popular tool for bundling dependencies is Bundler. Bundler 279: # works as follows: 280: # - If the bundle is locked then a file .bundle/environment.rb exists 281: # which will setup the load paths. 282: # - If the bundle is not locked then the load paths must be set up by 283: # calling Bundler.setup. 284: # - Rails 3's boot.rb automatically loads .bundle/environment.rb or 285: # calls Bundler.setup if that's not available. 286: # - Other Rack apps might not have a boot.rb but we still want to setup 287: # Bundler. 288: # - Some Rails 2 apps might have explicitly added Bundler support. 289: # These apps call Bundler.setup in their preinitializer.rb. 290: # 291: # So the strategy is as follows: 292: 293: # Our strategy might be completely unsuitable for the app or the 294: # developer is using something other than Bundler, so we let the user 295: # manually specify a load path setup file. 296: if options["load_path_setup_file"] 297: require File.expand_path(options["load_path_setup_file"]) 298: 299: # The app developer may also override our strategy with this magic file. 300: elsif File.exist?('config/setup_load_paths.rb') 301: require File.expand_path('config/setup_load_paths') 302: 303: # If the Bundler lock environment file exists then load that. If it 304: # exists then there's a 99.9% chance that loading it is the correct 305: # thing to do. 306: elsif File.exist?('.bundle/environment.rb') 307: require File.expand_path('.bundle/environment') 308: 309: # If the Bundler environment file doesn't exist then there are two 310: # possibilities: 311: # 1. Bundler is not used, in which case we don't have to do anything. 312: # 2. Bundler *is* used, but the gems are not locked and we're supposed 313: # to call Bundler.setup. 314: # 315: # The existence of Gemfile indicates whether (2) is true: 316: elsif File.exist?('Gemfile') 317: # In case of Rails 3, config/boot.rb already calls Bundler.setup. 318: # However older versions of Rails may not so loading boot.rb might 319: # not be the correct thing to do. To be on the safe side we 320: # call Bundler.setup ourselves; calling Bundler.setup twice is 321: # harmless. If this isn't the correct thing to do after all then 322: # there's always the load_path_setup_file option and 323: # setup_load_paths.rb. 324: require 'rubygems' 325: require 'bundler' 326: Bundler.setup 327: end 328: 329: # Bundler might remove Phusion Passenger from the load path in its zealous 330: # attempt to un-require RubyGems, so here we put Phusion Passenger back 331: # into the load path. This must be done before loading the app's startup 332: # file because the app might require() Phusion Passenger files. 333: if $LOAD_PATH.first != LIBDIR 334: $LOAD_PATH.unshift(LIBDIR) 335: $LOAD_PATH.uniq! 336: end 337: 338: 339: # !!! NOTE !!! 340: # If the app is using Bundler then any dependencies required past this 341: # point must be specified in the Gemfile. Like ruby-debug if debugging is on... 342: 343: if options["debugger"] 344: require 'ruby-debug' 345: if !Debugger.respond_to?(:ctrl_port) 346: raise "Your version of ruby-debug is too old. Please upgrade to the latest version." 347: end 348: Debugger.start_remote('127.0.0.1', [0, 0]) 349: Debugger.start 350: end 351: 352: PhusionPassenger._spawn_options = options 353: end
Print the given exception, including the stack trace, to STDERR.
current_location is a string which describes where the code is currently at. Usually the current class name will be enough.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 172 172: def print_exception(current_location, exception, destination = nil) 173: if !exception.is_a?(SystemExit) 174: data = exception.backtrace_string(current_location) 175: if defined?(DebugLogging) && self.is_a?(DebugLogging) 176: error(data) 177: else 178: destination ||= STDERR 179: destination.puts(data) 180: destination.flush if destination.respond_to?(:flush) 181: end 182: end 183: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 44 44: def private_class_method(name) 45: metaclass = class << self; self; end 46: metaclass.send(:private, name) 47: end
Checks whether the given process exists.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 502 502: def process_is_alive?(pid) 503: begin 504: Process.kill(0, pid) 505: return true 506: rescue Errno::ESRCH 507: return false 508: rescue SystemCallError => e 509: return true 510: end 511: end
Run the given block. A message will be sent through channel (a MessageChannel object), telling the remote side whether the block raised an exception, called exit(), or succeeded.
If sink is non-nil, then every operation on $stderr/STDERR inside the block will be performed on sink as well. If sink is nil then all operations on $stderr/STDERR inside the block will be silently discarded, i.e. if one writes to $stderr/STDERR then nothing will be actually written to the console.
Returns whether the block succeeded, i.e. whether it didn‘t raise an exception.
Exceptions are not propagated, except SystemExit and a few non-StandardExeption classes such as SignalException. Of the exceptions that are propagated, only SystemExit will be reported.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 552 552: def report_app_init_status(channel, sink = STDERR) 553: begin 554: old_global_stderr = $stderr 555: old_stderr = STDERR 556: stderr_output = "" 557: 558: pseudo_stderr = PseudoIO.new(sink) 559: Object.send(:remove_const, 'STDERR') rescue nil 560: Object.const_set('STDERR', pseudo_stderr) 561: $stderr = pseudo_stderr 562: 563: begin 564: yield 565: ensure 566: Object.send(:remove_const, 'STDERR') rescue nil 567: Object.const_set('STDERR', old_stderr) 568: $stderr = old_global_stderr 569: stderr_output = pseudo_stderr.done! 570: end 571: 572: channel.write('success') 573: return true 574: rescue StandardError, ScriptError, NoMemoryError => e 575: channel.write('exception') 576: channel.write_scalar(marshal_exception(e)) 577: channel.write_scalar(stderr_output) 578: return false 579: rescue SystemExit => e 580: channel.write('exit') 581: channel.write_scalar(marshal_exception(e)) 582: channel.write_scalar(stderr_output) 583: raise 584: end 585: end
Fork a new process and run the given block inside the child process, just like fork(). Unlike fork(), this method is safe, i.e. there‘s no way for the child process to escape the block. Any uncaught exceptions in the child process will be printed to standard output, citing current_location as the source. Futhermore, the child process will exit by calling Kernel#exit!, thereby bypassing any at_exit or ensure blocks.
If double_fork is true, then the child process will fork and immediately exit. This technique can be used to avoid zombie processes, at the expense of not being able to waitpid() the second child.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 470 470: def safe_fork(current_location = self.class, double_fork = false) 471: pid = fork 472: if pid.nil? 473: has_exception = false 474: begin 475: if double_fork 476: pid2 = fork 477: if pid2.nil? 478: srand 479: yield 480: end 481: else 482: srand 483: yield 484: end 485: rescue Exception => e 486: has_exception = true 487: print_exception(current_location.to_s, e) 488: ensure 489: exit!(has_exception ? 1 : 0) 490: end 491: else 492: if double_fork 493: Process.waitpid(pid) rescue nil 494: return pid 495: else 496: return pid 497: end 498: end 499: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 807 807: def sanitize_spawn_options(options) 808: defaults = { 809: "app_type" => "rails", 810: "environment" => "production", 811: "spawn_method" => "smart-lv2", 812: "framework_spawner_timeout" => -1, 813: "app_spawner_timeout" => -1, 814: "print_exceptions" => true 815: } 816: options = defaults.merge(options) 817: options["app_group_name"] = options["app_root"] if !options["app_group_name"] 818: options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i 819: options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i 820: if options.has_key?("print_framework_loading_exceptions") 821: options["print_framework_loading_exceptions"] = to_boolean(options["print_framework_loading_exceptions"]) 822: end 823: # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. 824: options["print_exceptions"] = to_boolean(options["print_exceptions"]) 825: 826: options["analytics"] = to_boolean(options["analytics"]) 827: options["show_version_in_header"] = to_boolean(options["show_version_in_header"]) 828: 829: # Smart spawning is not supported when using ruby-debug. 830: options["debugger"] = to_boolean(options["debugger"]) 831: options["spawn_method"] = "conservative" if options["debugger"] 832: 833: return options 834: end
Split the given string into an hash. Keys and values are obtained by splitting the string using the null character as the delimitor.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 839 839: def split_by_null_into_hash(data) 840: return PhusionPassenger::NativeSupport.split_by_null_into_hash(data) 841: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 846 846: def split_by_null_into_hash(data) 847: data << PADDING 848: array = data.split(NULL) 849: array.pop 850: data.slice!(data.size - 1, data.size - 1) 851: return Hash[*array] 852: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 803 803: def to_boolean(value) 804: return !(value.nil? || value == false || value == "false") 805: end
Receive status information that was sent to channel by report_app_init_status. If an error occured according to the received information, then an appropriate exception will be raised.
If print_exception evaluates to true, then the exception message and the backtrace will also be printed. Where it is printed to depends on the type of print_exception:
- If it responds to #puts, then the exception information will be printed using this method.
- If it responds to #to_str, then the exception information will be appended to the file whose filename equals the return value of the #to_str call.
- Otherwise, it will be printed to STDERR.
Raises:
- AppInitError: this class wraps the exception information received through the channel.
- IOError, SystemCallError, SocketError: these errors are raised if an error occurred while receiving the information through the channel.
[ show source ]
# File lib/phusion_passenger/utils.rb, line 609 609: def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") 610: args = channel.read 611: if args.nil? 612: raise EOFError, "Unexpected end-of-file detected." 613: end 614: status = args[0] 615: if status == 'exception' 616: child_exception = unmarshal_exception(channel.read_scalar) 617: stderr = channel.read_scalar 618: exception = AppInitError.new( 619: "Application '#{@app_root}' raised an exception: " << 620: "#{child_exception.class} (#{child_exception.message})", 621: child_exception, 622: app_type, 623: stderr.empty? ? nil : stderr) 624: elsif status == 'exit' 625: child_exception = unmarshal_exception(channel.read_scalar) 626: stderr = channel.read_scalar 627: exception = AppInitError.new("Application '#{@app_root}' exited during startup", 628: child_exception, app_type, stderr.empty? ? nil : stderr) 629: else 630: exception = nil 631: end 632: 633: if print_exception && exception 634: if print_exception.respond_to?(:puts) 635: print_exception(self.class.to_s, child_exception, print_exception) 636: elsif print_exception.respond_to?(:to_str) 637: filename = print_exception.to_str 638: File.open(filename, 'a') do |f| 639: print_exception(self.class.to_s, child_exception, f) 640: end 641: else 642: print_exception(self.class.to_s, child_exception) 643: end 644: end 645: raise exception if exception 646: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 147 147: def unmarshal_exception(data) 148: hash = Marshal.load(data) 149: if hash[:is_initialization_error] 150: if hash[:child_exception] 151: child_exception = unmarshal_exception(hash[:child_exception]) 152: else 153: child_exception = nil 154: end 155: 156: exception = Marshal.load(hash[:exception]) 157: exception.child_exception = child_exception 158: return exception 159: else 160: begin 161: return Marshal.load(hash[:exception]) 162: rescue ArgumentError, TypeError 163: return UnknownError.new(hash[:message], hash[:class], hash[:backtrace]) 164: end 165: end 166: end