Module PhusionPassenger::Utils
In: lib/phusion_passenger/utils/tmpdir.rb
lib/phusion_passenger/utils/file_system_watcher.rb
lib/phusion_passenger/utils/rewindable_input.rb
lib/phusion_passenger/utils/unseekable_socket.rb
lib/phusion_passenger/utils/hosts_file_parser.rb
lib/phusion_passenger/utils.rb

Utility functions.

Methods

Classes and Modules

Class PhusionPassenger::Utils::FileSystemWatcher
Class PhusionPassenger::Utils::HostsFileParser
Class PhusionPassenger::Utils::PseudoIO
Class PhusionPassenger::Utils::RewindableInput
Class PhusionPassenger::Utils::UnseekableSocket

Constants

FileSystemWatcher = NativeSupport::FileSystemWatcher
NULL = "\0".freeze

Public Class methods

[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

[Source]

    # File lib/phusion_passenger/utils/file_system_watcher.rb, line 69
69:                 def self.opens_files?
70:                         return true
71:                 end

Protected Class methods

No-op, hook for unit tests.

[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.

[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

[Source]

    # File lib/phusion_passenger/utils/tmpdir.rb, line 65
65:         def self.passenger_tmpdir=(dir)
66:                 @@passenger_tmpdir = dir
67:         end

Protected Instance methods

To be called after the request handler main loop is exited. This function will fire off necessary events perform necessary cleanup tasks.

[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.

[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.

[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.

[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.

[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.

[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

[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.

[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.

[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.

[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

[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

[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.

[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

[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.

[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

[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.

[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

[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

[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.

[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.

[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

[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.

[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.

[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.

[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

[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.

[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

[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

[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.

[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

[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

[Validate]