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
NULL | = | "\0".freeze |
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 657 657: def self.lower_privilege_called 658: 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 InvalidPath, 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 757 757: def check_directory_tree_permissions(dir) 758: components = dir.split("/") 759: components.shift 760: i = 0 761: # We can't use File.readable() and friends here because they 762: # don't always work right with ACLs. Instead of we use 'real' 763: # checks. 764: while i < components.size 765: path = "/" + components[0..i].join("/") 766: begin 767: File.stat(path) 768: rescue Errno::EACCES 769: return [File.dirname(path), true] 770: end 771: i += 1 772: end 773: begin 774: Dir.chdir(dir) do 775: return nil 776: end 777: rescue Errno::EACCES 778: return [dir, false] 779: end 780: 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 784 784: def global_backtrace_report 785: if Kernel.respond_to?(:caller_for_all_threads) 786: output = "========== Process #{Process.pid}: backtrace dump ==========\n" 787: caller_for_all_threads.each_pair do |thread, stack| 788: output << ("-" * 60) << "\n" 789: output << "# Thread: #{thread.inspect}, " 790: if thread == Thread.main 791: output << "[main thread], " 792: end 793: if thread == Thread.current 794: output << "[current thread], " 795: end 796: output << "alive = #{thread.alive?}\n" 797: output << ("-" * 60) << "\n" 798: output << " " << stack.join("\n ") 799: output << "\n\n" 800: end 801: else 802: output = "========== Process #{Process.pid}: backtrace dump ==========\n" 803: output << ("-" * 60) << "\n" 804: output << "# Current thread: #{Thread.current.inspect}\n" 805: output << ("-" * 60) << "\n" 806: output << " " << caller.join("\n ") 807: end 808: return output 809: 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 662 662: def lower_privilege(startup_file, options) 663: Utils.lower_privilege_called 664: return if Process.euid != 0 665: 666: if options["default_user"] && !options["default_user"].empty? 667: default_user = options["default_user"] 668: else 669: default_user = "nobody" 670: end 671: if options["default_group"] && !options["default_group"].empty? 672: default_group = options["default_group"] 673: else 674: default_group = Etc.getgrgid(Etc.getpwnam(default_user).gid).name 675: end 676: 677: if options["user"] && !options["user"].empty? 678: begin 679: user_info = Etc.getpwnam(options["user"]) 680: rescue ArgumentError 681: user_info = nil 682: end 683: else 684: uid = File.lstat(startup_file).uid 685: begin 686: user_info = Etc.getpwuid(uid) 687: rescue ArgumentError 688: user_info = nil 689: end 690: end 691: if !user_info || user_info.uid == 0 692: begin 693: user_info = Etc.getpwnam(default_user) 694: rescue ArgumentError 695: user_info = nil 696: end 697: end 698: 699: if options["group"] && !options["group"].empty? 700: if options["group"] == "!STARTUP_FILE!" 701: gid = File.lstat(startup_file).gid 702: begin 703: group_info = Etc.getgrgid(gid) 704: rescue ArgumentError 705: group_info = nil 706: end 707: else 708: begin 709: group_info = Etc.getgrnam(options["group"]) 710: rescue ArgumentError 711: group_info = nil 712: end 713: end 714: elsif user_info 715: begin 716: group_info = Etc.getgrgid(user_info.gid) 717: rescue ArgumentError 718: group_info = nil 719: end 720: else 721: group_info = nil 722: end 723: if !group_info || group_info.gid == 0 724: begin 725: group_info = Etc.getgrnam(default_group) 726: rescue ArgumentError 727: group_info = nil 728: end 729: end 730: 731: if !user_info 732: raise SecurityError, "Cannot determine a user to lower privilege to" 733: end 734: if !group_info 735: raise SecurityError, "Cannot determine a group to lower privilege to" 736: end 737: 738: NativeSupport.switch_user(user_info.name, user_info.uid, group_info.gid) 739: ENV['USER'] = user_info.name 740: ENV['HOME'] = user_info.dir 741: 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 560 560: def report_app_init_status(channel, sink = STDERR) 561: begin 562: old_global_stderr = $stderr 563: old_stderr = STDERR 564: stderr_output = "" 565: 566: pseudo_stderr = PseudoIO.new(sink) 567: Object.send(:remove_const, 'STDERR') rescue nil 568: Object.const_set('STDERR', pseudo_stderr) 569: $stderr = pseudo_stderr 570: 571: begin 572: yield 573: ensure 574: Object.send(:remove_const, 'STDERR') rescue nil 575: Object.const_set('STDERR', old_stderr) 576: $stderr = old_global_stderr 577: stderr_output = pseudo_stderr.done! 578: end 579: 580: channel.write('success') 581: return true 582: rescue StandardError, ScriptError, NoMemoryError => e 583: channel.write('exception') 584: channel.write_scalar(marshal_exception(e)) 585: channel.write_scalar(stderr_output) 586: return false 587: rescue SystemExit => e 588: channel.write('exit') 589: channel.write_scalar(marshal_exception(e)) 590: channel.write_scalar(stderr_output) 591: raise 592: end 593: 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 815 815: def sanitize_spawn_options(options) 816: defaults = { 817: "app_type" => "rails", 818: "environment" => "production", 819: "spawn_method" => "smart-lv2", 820: "framework_spawner_timeout" => -1, 821: "app_spawner_timeout" => -1, 822: "print_exceptions" => true 823: } 824: options = defaults.merge(options) 825: options["app_group_name"] = options["app_root"] if !options["app_group_name"] 826: options["framework_spawner_timeout"] = options["framework_spawner_timeout"].to_i 827: options["app_spawner_timeout"] = options["app_spawner_timeout"].to_i 828: if options.has_key?("print_framework_loading_exceptions") 829: options["print_framework_loading_exceptions"] = to_boolean(options["print_framework_loading_exceptions"]) 830: end 831: # Force this to be a boolean for easy use with Utils#unmarshal_and_raise_errors. 832: options["print_exceptions"] = to_boolean(options["print_exceptions"]) 833: 834: options["analytics"] = to_boolean(options["analytics"]) 835: options["show_version_in_header"] = to_boolean(options["show_version_in_header"]) 836: 837: # Smart spawning is not supported when using ruby-debug. 838: options["debugger"] = to_boolean(options["debugger"]) 839: options["spawn_method"] = "conservative" if options["debugger"] 840: 841: return options 842: 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 847 847: def split_by_null_into_hash(data) 848: return PhusionPassenger::NativeSupport.split_by_null_into_hash(data) 849: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 853 853: def split_by_null_into_hash(data) 854: args = data.split(NULL, -1) 855: args.pop 856: return Hash[*args] 857: end
[ show source ]
# File lib/phusion_passenger/utils.rb, line 811 811: def to_boolean(value) 812: return !(value.nil? || value == false || value == "false") 813: 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 617 617: def unmarshal_and_raise_errors(channel, print_exception = nil, app_type = "rails") 618: args = channel.read 619: if args.nil? 620: raise EOFError, "Unexpected end-of-file detected." 621: end 622: status = args[0] 623: if status == 'exception' 624: child_exception = unmarshal_exception(channel.read_scalar) 625: stderr = channel.read_scalar 626: exception = AppInitError.new( 627: "Application '#{@app_root}' raised an exception: " << 628: "#{child_exception.class} (#{child_exception.message})", 629: child_exception, 630: app_type, 631: stderr.empty? ? nil : stderr) 632: elsif status == 'exit' 633: child_exception = unmarshal_exception(channel.read_scalar) 634: stderr = channel.read_scalar 635: exception = AppInitError.new("Application '#{@app_root}' exited during startup", 636: child_exception, app_type, stderr.empty? ? nil : stderr) 637: else 638: exception = nil 639: end 640: 641: if print_exception && exception 642: if print_exception.respond_to?(:puts) 643: print_exception(self.class.to_s, child_exception, print_exception) 644: elsif print_exception.respond_to?(:to_str) 645: filename = print_exception.to_str 646: File.open(filename, 'a') do |f| 647: print_exception(self.class.to_s, child_exception, f) 648: end 649: else 650: print_exception(self.class.to_s, child_exception) 651: end 652: end 653: raise exception if exception 654: 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