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.
Wait for any children to exit, remove the “main” PID, and exit.
(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 from given paths - using path/glob pattern.
*paths | Array of paths to load classes from - may contain glob pattern |
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
Loads a file, tracking its modified time and, if necessary, the classes it declared.
file | The file to load. |
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 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.
status | The status code to exit with. Defaults to 0. |
sig | The signal to send to workers |
(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
# File lib/merb-core/bootloader.rb, line 807 807: def reap_workers_signal 808: Merb::Config[:reap_workers_quickly] ? "KILL" : "ABRT" 809: 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.
file | The file to reload. |
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
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.
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.
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
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.
const | The class to remove. |
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
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 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
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
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
“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.
klasses | Classes to load. |
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.
Generated with the Darkfish Rdoc Generator 1.1.6.