Class Index [+]

Quicksearch

Merb::BootLoader::LoadClasses

Load all classes inside the load paths.

This is used in conjunction with Merb::BootLoader::ReloadClasses to track files that need to be reloaded, and which constants need to be removed in order to reload a file.

This also adds the model, controller, and lib directories to the load path, so they can be required in order to avoid load-order issues.

Constants

LOADED_CLASSES
MTIMES
FILES_LOADED

Public Class Methods

exit_gracefully() click to toggle source

Wait for any children to exit, remove the “main” PID, and exit.

Returns

(Does not return.)

:api: private

     # File lib/merb-core/bootloader.rb, line 685
685:     def exit_gracefully
686:       # wait all workers to exit
687:       Process.waitall
688:       # remove master process pid
689:       Merb::Server.remove_pid("main")
690:       # terminate, workers remove their own pids
691:       # in on exit hook
692: 
693:       Merb::BootLoader.before_master_shutdown_callbacks.each do |cb|
694:         begin
695:           cb.call
696:         rescue Exception => e
697:           Merb.logger.fatal "before_master_shutdown callback crashed: #{e.message}"
698:         end
699:       end
700:       exit
701:     end
load_classes(*paths) click to toggle source

Load classes from given paths - using path/glob pattern.

Parameters

*paths

Array of paths to load classes from - may contain glob pattern

Returns

nil

:api: private

     # File lib/merb-core/bootloader.rb, line 926
926:     def load_classes(*paths)
927:       orphaned_classes = []
928:       paths.flatten.each do |path|
929:         Dir[path].sort.each do |file|
930:           begin
931:             load_file file
932:           rescue NameError => ne
933:             Merb.logger.verbose! "Stashed file with missing requirements for later reloading: #{file}"
934:             ne.backtrace.each_with_index { |line, idx| Merb.logger.verbose! "[#{idx}]: #{line}" }
935:             orphaned_classes.unshift(file)
936:           end
937:         end
938:       end
939:       load_classes_with_requirements(orphaned_classes)
940:     end
load_file(file, reload = false) click to toggle source

Loads a file, tracking its modified time and, if necessary, the classes it declared.

Parameters

file

The file to load.

Returns

nil

:api: private

     # File lib/merb-core/bootloader.rb, line 872
872:     def load_file(file, reload = false)
873:       Merb.logger.verbose! "#{reload ? "re" : ""}loading #{file}"
874:       
875:       # If we're going to be reloading via constant remove,
876:       # keep track of what constants were loaded and what files
877:       # have been added, so that the constants can be removed
878:       # and the files can be removed from $LOADED_FEAUTRES
879:       if !Merb::Config[:fork_for_class_load]
880:         if FILES_LOADED[file]
881:           FILES_LOADED[file].each {|lf| $LOADED_FEATURES.delete(lf)}
882:         end
883:         
884:         klasses = ObjectSpace.classes.dup
885:         files_loaded = $LOADED_FEATURES.dup
886:       end
887: 
888:       # If we're in the midst of a reload, remove the file
889:       # itself from $LOADED_FEATURES so it will get reloaded
890:       if reload
891:         $LOADED_FEATURES.delete(file) if reload
892:       end
893: 
894:       # Ignore the file for syntax errors. The next time
895:       # the file is changed, it'll be reloaded again
896:       begin
897:         require file
898:       rescue SyntaxError => e
899:         Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}"
900:       ensure
901:         if Merb::Config[:reload_classes]
902:           MTIMES[file] = File.mtime(file)
903:         end
904:       end
905: 
906:       # If we're reloading via constant remove, store off the details
907:       # after the file has been loaded
908:       unless Merb::Config[:fork_for_class_load]
909:         LOADED_CLASSES[file] = ObjectSpace.classes - klasses
910:         FILES_LOADED[file] = $LOADED_FEATURES - files_loaded
911:       end
912: 
913:       nil
914:     end
reap_workers(status = 0, sig = reap_workers_signal) click to toggle source

Reap any workers of the spawner process and exit with an appropriate status code.

Note that exiting the spawner process with a status code of 128 when a master process exists will cause the spawner process to be recreated, and the app code reloaded.

Parameters

status

The status code to exit with. Defaults to 0.

sig

The signal to send to workers

Returns

(Does not return.)

:api: private

     # File lib/merb-core/bootloader.rb, line 826
826:     def reap_workers(status = 0, sig = reap_workers_signal)
827:       
828:       Merb.logger.info "Executed all before worker shutdown callbacks..."
829:       Merb::BootLoader.before_worker_shutdown_callbacks.each do |cb|
830:         begin
831:           cb.call
832:         rescue Exception => e
833:           Merb.logger.fatal "before worker shutdown callback crashed: #{e.message}"
834:         end
835: 
836:       end
837: 
838:       Merb.exiting = true unless status == 128
839: 
840:       begin
841:         if @writer
842:           @writer.puts(status.to_s)
843:           @writer.close
844:         end
845:       rescue SystemCallError
846:       end
847: 
848:       threads = []
849: 
850:       ($WORKERS || []).each do |p|
851:         threads << Thread.new do
852:           begin
853:             Process.kill(sig, p)
854:             Process.wait2(p)
855:           rescue SystemCallError
856:           end
857:         end
858:       end
859:       threads.each {|t| t.join }
860:       exit(status)
861:     end
reap_workers_signal() click to toggle source
     # File lib/merb-core/bootloader.rb, line 807
807:     def reap_workers_signal
808:       Merb::Config[:reap_workers_quickly] ? "KILL" : "ABRT"
809:     end
reload(file) click to toggle source

Reloads the classes in the specified file. If fork-based loading is used, this causes the current processes to be killed and and all classes to be reloaded. If class-based loading is not in use, the classes declared in that file are removed and the file is reloaded.

Parameters

file

The file to reload.

Returns

When fork-based loading is used:

  (Does not return.)

When fork-based loading is not in use:

  nil

:api: private

     # File lib/merb-core/bootloader.rb, line 957
957:     def reload(file)
958:       if Merb::Config[:fork_for_class_load]
959:         reap_workers(128)
960:       else
961:         remove_classes_in_file(file) { |f| load_file(f, true) }
962:       end
963:     end
remove_classes_in_file(file, &block) click to toggle source

Removes all classes declared in the specified file. Any hashes which use classes as keys will be protected provided they have been added to Merb.klass_hashes. These hashes have their keys substituted with placeholders before the file’s classes are unloaded. If a block is provided, it is called before the substituted keys are reconstituted.

Parameters

file

The file to remove classes for.

&block

A block to call with the file that has been removed before klass_hashes are updated

to use the current values of the constants they used as keys.

Returns

nil

:api: private

     # File lib/merb-core/bootloader.rb, line 979
979:     def remove_classes_in_file(file, &block)
980:       Merb.klass_hashes.each { |x| x.protect_keys! }
981:       if klasses = LOADED_CLASSES.delete(file)
982:         klasses.each { |klass| remove_constant(klass) unless klass.to_s =~ /Router/ }
983:       end
984:       yield file if block_given?
985:       Merb.klass_hashes.each {|x| x.unprotect_keys!}
986:       nil
987:     end
remove_constant(const) click to toggle source

Removes the specified class.

Additionally, removes the specified class from the subclass list of every superclass that tracks it’s subclasses in an array returned by _subclasses_list. Classes that wish to use this functionality are required to alias the reader for their list of subclasses to _subclasses_list. Plugins for ORMs and other libraries should keep this in mind.

Parameters

const

The class to remove.

Returns

nil

:api: private

      # File lib/merb-core/bootloader.rb, line 1003
1003:     def remove_constant(const)
1004:       # This is to support superclasses (like AbstractController) that track
1005:       # their subclasses in a class variable.
1006:       superklass = const
1007:       until (superklass = superklass.superclass).nil?
1008:         if superklass.respond_to?(:_subclasses_list)
1009:           superklass.send(:_subclasses_list).delete(klass)
1010:           superklass.send(:_subclasses_list).delete(klass.to_s)
1011:         end
1012:       end
1013: 
1014:       parts = const.to_s.split("::")
1015:       base = parts.size == 1 ? Object : Object.full_const_get(parts[0..2].join("::"))
1016:       object = parts[1].to_s
1017:       begin
1018:         base.send(:remove_const, object)
1019:         Merb.logger.debug("Removed constant #{object} from #{base}")
1020:       rescue NameError
1021:         Merb.logger.debug("Failed to remove constant #{object} from #{base}")
1022:       end
1023:       nil
1024:     end
run() click to toggle source

Load all classes from Merb’s native load paths.

If fork-based loading is used, every time classes are loaded this will return in a new spawner process and boot loading will continue from this point in the boot loading process.

If fork-based loading is not in use, this only returns once and does not fork a new process.

Returns

Returns at least once:

  nil

:api: plugin

     # File lib/merb-core/bootloader.rb, line 645
645:     def run
646:       # process name you see in ps output
647:       $0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master"
648: 
649:       # Log the process configuration user defined signal 1 (SIGUSR1) is received.
650:       Merb.trap("USR1") do
651:         require "yaml"
652:         Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n"
653:       end
654: 
655:       if Merb::Config[:fork_for_class_load] && !Merb.testing?
656:         start_transaction
657:       else
658:         Merb.trap('INT') do
659:           Merb.logger.warn! "Reaping Workers"
660:           reap_workers
661:         end
662:       end
663: 
664:       # Load application file if it exists - for flat applications
665:       load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application))
666: 
667:       # Load classes and their requirements
668:       Merb.load_paths.each do |component, path|
669:         next if path.last.blank? || component == :application || component == :router
670:         load_classes(path.first / path.last)
671:       end
672: 
673:       Merb::Controller.send :include, Merb::GlobalHelpers
674: 
675:       nil
676:     end
start_transaction() click to toggle source

Set up the BEGIN point for fork-based loading and sets up any signals in the parent and child. This is done by forking the app. The child process continues on to run the app. The parent process waits for the child process to finish and either forks again

Returns

Parent Process:

  (Does not return.)

Child Process returns at least once:

  nil

:api: private

     # File lib/merb-core/bootloader.rb, line 716
716:     def start_transaction
717:       Merb.logger.warn! "Parent pid: #{Process.pid}"
718:       reader, writer = nil, nil
719: 
720:       # Enable REE garbage collection
721:       if GC.respond_to?(:copy_on_write_friendly=)
722:         GC.copy_on_write_friendly = true
723:       end
724: 
725:       loop do
726:         # create two connected endpoints
727:         # we use them for master/workers communication
728:         reader, @writer = IO.pipe
729:         pid = Kernel.fork
730: 
731:         # pid means we're in the parent; only stay in the loop if that is case
732:         break unless pid
733:         # writer must be closed so reader can generate EOF condition
734:         @writer.close
735: 
736:         # master process stores pid to merb.main.pid
737:         Merb::Server.store_pid("main") if Merb::Config[:daemonize] || Merb::Config[:cluster]
738: 
739:         if Merb::Config[:console_trap]
740:           Merb.trap("INT") {}
741:         else
742:           # send ABRT to worker on INT
743:           Merb.trap("INT") do
744:             Merb.logger.warn! "Reaping Workers"
745:             begin
746:               Process.kill(reap_workers_signal, pid)
747:             rescue SystemCallError
748:             end
749:             exit_gracefully
750:           end
751:         end
752: 
753:         Merb.trap("HUP") do
754:           Merb.logger.warn! "Doing a fast deploy\n"
755:           Process.kill("HUP", pid)
756:         end
757: 
758:         reader_ary = [reader]
759:         loop do
760:           # wait for worker to exit and capture exit status
761:           #
762:           #
763:           # WNOHANG specifies that wait2 exists without waiting
764:           # if no worker processes are ready to be noticed.
765:           if exit_status = Process.wait2(pid, Process::WNOHANG)
766:             # wait2 returns a 2-tuple of process id and exit
767:             # status.
768:             #
769:             # We do not care about specific pid here.
770:             exit_status[1] && exit_status[1].exitstatus == 128 ? break : exit
771:           end
772:           # wait for data to become available, timeout in 0.5 of a second
773:           if select(reader_ary, nil, nil, 0.5)
774:             begin
775:               # no open writers
776:               next if reader.eof?
777:               msg = reader.readline
778:               reader.close
779:               if msg.to_i == 128
780:                 Process.waitpid(pid, Process::WNOHANG)
781:                 break
782:               else
783:                 exit_gracefully
784:               end
785:             rescue SystemCallError
786:               exit_gracefully
787:             end
788:           end
789:         end
790:       end
791: 
792:       reader.close
793: 
794:       # add traps to the worker
795:       if Merb::Config[:console_trap]
796:         Merb::Server.add_irb_trap
797:         at_exit { reap_workers }
798:       else
799:         Merb.trap('INT') do
800:           Merb::BootLoader.before_worker_shutdown_callbacks.each { |cb| cb.call }
801:         end
802:         Merb.trap('ABRT') { reap_workers }
803:         Merb.trap('HUP') { reap_workers(128, "ABRT") }
804:       end
805:     end

Private Class Methods

load_classes_with_requirements(klasses) click to toggle source

“Better loading” of classes. If a file fails to load due to a NameError it will be added to the failed_classes and load cycle will be repeated unless no classes load.

Parameters

klasses

Classes to load.

Returns

nil

:api: private

      # File lib/merb-core/bootloader.rb, line 1039
1039:     def load_classes_with_requirements(klasses)
1040:       klasses.uniq!
1041: 
1042:       while klasses.size > 0
1043:         # Note size to make sure things are loading
1044:         size_at_start = klasses.size
1045: 
1046:         # List of failed classes
1047:         failed_classes = []
1048:         # Map classes to exceptions
1049:         error_map = {}
1050: 
1051:         klasses.each do |klass|
1052:           begin
1053:             load_file klass
1054:           rescue NameError => ne
1055:             error_map[klass] = ne
1056:             failed_classes.push(klass)
1057:           end
1058:         end
1059:         klasses.clear
1060: 
1061:         # Keep list of classes unique
1062:         failed_classes.each { |k| klasses.push(k) unless klasses.include?(k) }
1063: 
1064:         # Stop processing if nothing loads or if everything has loaded
1065:         if klasses.size == size_at_start && klasses.size != 0
1066:           # Write all remaining failed classes and their exceptions to the log
1067:           messages = error_map.only(*failed_classes).map do |klass, e|
1068:             ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})",
1069:               "#{(e.backtrace || []).join("\n")}"]
1070:           end
1071:           messages.each { |msg, trace| Merb.logger.fatal!("#{msg}\n\n#{trace}") }
1072:           Merb.fatal! "#{failed_classes.join(", ")} failed to load."
1073:         end
1074:         break if(klasses.size == size_at_start || klasses.size == 0)
1075:       end
1076: 
1077:       nil
1078:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.