Class Merb::BootLoader::LoadClasses
In: merb-core/lib/merb-core/bootloader.rb
Parent: Merb::BootLoader

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.

Methods

Constants

LOADED_CLASSES = {}
MTIMES = {}
FILES_LOADED = {}

Public Class methods

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

Returns

(Does not return.)

:api: private

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 658
658:     def exit_gracefully
659:       # wait all workers to exit
660:       Process.waitall
661:       # remove master process pid
662:       Merb::Server.remove_pid("main")
663:       # terminate, workers remove their own pids
664:       # in on exit hook
665: 
666:       Merb::BootLoader.before_master_shutdown_callbacks.each do |cb|
667:         begin
668:           cb.call
669:         rescue Exception => e
670:           Merb.logger.fatal "before_master_shutdown callback crashed: #{e.message}"
671:         end
672:       end
673:       exit
674:     end

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

Parameters

*paths<Array>:Array of paths to load classes from - may contain glob pattern

Returns

nil

:api: private

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 894
894:     def load_classes(*paths)
895:       orphaned_classes = []
896:       paths.flatten.each do |path|
897:         Dir[path].each do |file|
898:           begin
899:             load_file file
900:           rescue NameError => ne
901:             orphaned_classes.unshift(file)
902:           end
903:         end
904:       end
905:       load_classes_with_requirements(orphaned_classes)
906:     end

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

Parameters

file<String>:The file to load.

Returns

nil

:api: private

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 840
840:     def load_file(file, reload = false)
841:       Merb.logger.verbose! "#{reload ? "re" : ""}loading #{file}"
842:       
843:       # If we're going to be reloading via constant remove,
844:       # keep track of what constants were loaded and what files
845:       # have been added, so that the constants can be removed
846:       # and the files can be removed from $LOADED_FEAUTRES
847:       if !Merb::Config[:fork_for_class_load]
848:         if FILES_LOADED[file]
849:           FILES_LOADED[file].each {|lf| $LOADED_FEATURES.delete(lf)}
850:         end
851:         
852:         klasses = ObjectSpace.classes.dup
853:         files_loaded = $LOADED_FEATURES.dup
854:       end
855: 
856:       # If we're in the midst of a reload, remove the file
857:       # itself from $LOADED_FEATURES so it will get reloaded
858:       if reload
859:         $LOADED_FEATURES.delete(file) if reload
860:       end
861: 
862:       # Ignore the file for syntax errors. The next time
863:       # the file is changed, it'll be reloaded again
864:       begin
865:         require file
866:       rescue SyntaxError => e
867:         Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}"
868:       ensure
869:         if Merb::Config[:reload_classes]
870:           MTIMES[file] = File.mtime(file)
871:         end
872:       end
873: 
874:       # If we're reloading via constant remove, store off the details
875:       # after the file has been loaded
876:       unless Merb::Config[:fork_for_class_load]
877:         LOADED_CLASSES[file] = ObjectSpace.classes - klasses
878:         FILES_LOADED[file] = $LOADED_FEATURES - files_loaded
879:       end
880: 
881:       nil
882:     end

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<Integer>:The status code to exit with. Defaults to 0.
sig<String>:The signal to send to workers

Returns

(Does not return.)

:api: private

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 797
797:     def reap_workers(status = 0, sig = reap_workers_signal)
798:       
799:       Merb.logger.info "Executed all before worker shutdown callbacks..."
800:       Merb::BootLoader.before_worker_shutdown_callbacks.each do |cb|
801:         begin
802:           cb.call
803:         rescue Exception => e
804:           Merb.logger.fatal "before worker shutdown callback crashed: #{e.message}"
805:         end
806: 
807:       end
808: 
809:       Merb.exiting = true unless status == 128
810: 
811:       begin
812:         @writer.puts(status.to_s) if @writer
813:       rescue SystemCallError
814:       end
815: 
816:       threads = []
817: 
818:       ($WORKERS || []).each do |p|
819:         threads << Thread.new do
820:           begin
821:             Process.kill(sig, p)
822:             Process.wait2(p)
823:           rescue SystemCallError
824:           end
825:         end
826:       end
827:       threads.each {|t| t.join }
828:       exit(status)
829:     end

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 778
778:     def reap_workers_signal
779:       Merb::Config[:reap_workers_quickly] ? "KILL" : "ABRT"
780:     end

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<String>: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

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 923
923:     def reload(file)
924:       if Merb::Config[:fork_for_class_load]
925:         reap_workers(128)
926:       else
927:         remove_classes_in_file(file) { |f| load_file(f, true) }
928:       end
929:     end

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<String>: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

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 945
945:     def remove_classes_in_file(file, &block)
946:       Merb.klass_hashes.each { |x| x.protect_keys! }
947:       if klasses = LOADED_CLASSES.delete(file)
948:         klasses.each { |klass| remove_constant(klass) unless klass.to_s =~ /Router/ }
949:       end
950:       yield file if block_given?
951:       Merb.klass_hashes.each {|x| x.unprotect_keys!}
952:       nil
953:     end

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<Class>:The class to remove.

Returns

nil

:api: private

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 969
969:     def remove_constant(const)
970:       # This is to support superclasses (like AbstractController) that track
971:       # their subclasses in a class variable.
972:       superklass = const
973:       until (superklass = superklass.superclass).nil?
974:         if superklass.respond_to?(:_subclasses_list)
975:           superklass.send(:_subclasses_list).delete(klass)
976:           superklass.send(:_subclasses_list).delete(klass.to_s)
977:         end
978:       end
979: 
980:       parts = const.to_s.split("::")
981:       base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
982:       object = parts[-1].to_s
983:       begin
984:         base.send(:remove_const, object)
985:         Merb.logger.debug("Removed constant #{object} from #{base}")
986:       rescue NameError
987:         Merb.logger.debug("Failed to remove constant #{object} from #{base}")
988:       end
989:       nil
990:     end

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

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 618
618:     def run
619:       # process name you see in ps output
620:       $0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master"
621: 
622:       # Log the process configuration user defined signal 1 (SIGUSR1) is received.
623:       Merb.trap("USR1") do
624:         require "yaml"
625:         Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n"
626:       end
627: 
628:       if Merb::Config[:fork_for_class_load] && !Merb.testing?
629:         start_transaction
630:       else
631:         Merb.trap('INT') do
632:           Merb.logger.warn! "Reaping Workers"
633:           reap_workers
634:         end
635:       end
636: 
637:       # Load application file if it exists - for flat applications
638:       load_file Merb.dir_for(:application) if File.file?(Merb.dir_for(:application))
639: 
640:       # Load classes and their requirements
641:       Merb.load_paths.each do |component, path|
642:         next if path.last.blank? || component == :application || component == :router
643:         load_classes(path.first / path.last)
644:       end
645: 
646:       Merb::Controller.send :include, Merb::GlobalHelpers
647: 
648:       nil
649:     end

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

[Source]

     # File merb-core/lib/merb-core/bootloader.rb, line 689
689:     def start_transaction
690:       Merb.logger.warn! "Parent pid: #{Process.pid}"
691:       reader, writer = nil, nil
692: 
693:       if GC.respond_to?(:copy_on_write_friendly=)
694:         GC.copy_on_write_friendly = true
695:       end
696: 
697:       loop do
698:         # create two connected endpoints
699:         # we use them for master/workers communication
700:         reader, @writer = IO.pipe
701:         pid = Kernel.fork
702: 
703:         # pid means we're in the parent; only stay in the loop if that is case
704:         break unless pid
705:         # writer must be closed so reader can generate EOF condition
706:         @writer.close
707: 
708:         # master process stores pid to merb.main.pid
709:         Merb::Server.store_pid("main") if Merb::Config[:daemonize] || Merb::Config[:cluster]
710: 
711:         if Merb::Config[:console_trap]
712:           Merb.trap("INT") {}
713:         else
714:           # send ABRT to worker on INT
715:           Merb.trap("INT") do
716:             Merb.logger.warn! "Reaping Workers"
717:             begin
718:               Process.kill(reap_workers_signal, pid)
719:             rescue SystemCallError
720:             end
721:             exit_gracefully
722:           end
723:         end
724: 
725:         Merb.trap("HUP") do
726:           Merb.logger.warn! "Doing a fast deploy\n"
727:           Process.kill("HUP", pid)
728:         end
729: 
730:         reader_ary = [reader]
731:         loop do
732:           # wait for worker to exit and capture exit status
733:           #
734:           #
735:           # WNOHANG specifies that wait2 exists without waiting
736:           # if no worker processes are ready to be noticed.
737:           if exit_status = Process.wait2(pid, Process::WNOHANG)
738:             # wait2 returns a 2-tuple of process id and exit
739:             # status.
740:             #
741:             # We do not care about specific pid here.
742:             exit_status[1] && exit_status[1].exitstatus == 128 ? break : exit
743:           end
744:           # wait for data to become available, timeout in 0.25 of a second
745:           if select(reader_ary, nil, nil, 0.25)
746:             begin
747:               # no open writers
748:               next if reader.eof?
749:               msg = reader.readline
750:               if msg =~ /128/
751:                 Process.detach(pid)
752:                 break
753:               else
754:                 exit_gracefully
755:               end
756:             rescue SystemCallError
757:               exit_gracefully
758:             end
759:           end
760:         end
761:       end
762: 
763:       reader.close
764: 
765:       # add traps to the worker
766:       if Merb::Config[:console_trap]
767:         Merb::Server.add_irb_trap
768:         at_exit { reap_workers }
769:       else
770:         Merb.trap('INT') do
771:           Merb::BootLoader.before_worker_shutdown_callbacks.each { |cb| cb.call }
772:         end
773:         Merb.trap('ABRT') { reap_workers }
774:         Merb.trap('HUP') { reap_workers(128, "ABRT") }
775:       end
776:     end

Private Class methods

"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<Array[Class]>:Classes to load.

Returns

nil

:api: private

[Source]

      # File merb-core/lib/merb-core/bootloader.rb, line 1005
1005:     def load_classes_with_requirements(klasses)
1006:       klasses.uniq!
1007: 
1008:       while klasses.size > 0
1009:         # Note size to make sure things are loading
1010:         size_at_start = klasses.size
1011: 
1012:         # List of failed classes
1013:         failed_classes = []
1014:         # Map classes to exceptions
1015:         error_map = {}
1016: 
1017:         klasses.each do |klass|
1018:           klasses.delete(klass)
1019:           begin
1020:             load_file klass
1021:           rescue NameError => ne
1022:             error_map[klass] = ne
1023:             failed_classes.push(klass)
1024:           end
1025:         end
1026: 
1027:         # Keep list of classes unique
1028:         failed_classes.each { |k| klasses.push(k) unless klasses.include?(k) }
1029: 
1030:         # Stop processing if nothing loads or if everything has loaded
1031:         if klasses.size == size_at_start && klasses.size != 0
1032:           # Write all remaining failed classes and their exceptions to the log
1033:           messages = error_map.only(*failed_classes).map do |klass, e|
1034:             ["Could not load #{klass}:\n\n#{e.message} - (#{e.class})",
1035:               "#{(e.backtrace || []).join("\n")}"]
1036:           end
1037:           messages.each { |msg, trace| Merb.logger.fatal!("#{msg}\n\n#{trace}") }
1038:           Merb.fatal! "#{failed_classes.join(", ")} failed to load."
1039:         end
1040:         break if(klasses.size == size_at_start || klasses.size == 0)
1041:       end
1042: 
1043:       nil
1044:     end

[Validate]