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.
LOADED_CLASSES | = | {} |
MTIMES | = | {} |
FILES_LOADED | = | {} |
Wait for any children to exit, remove the "main" PID, and exit.
(Does not return.)
:api: private
# 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.
*paths<Array>: | Array of paths to load classes from - may contain glob pattern |
nil
:api: private
# 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.
file<String>: | The file to load. |
nil
:api: private
# 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.
status<Integer>: | The status code to exit with. Defaults to 0. |
sig<String>: | The signal to send to workers |
(Does not return.)
:api: private
# 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
# 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.
file<String>: | 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 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.
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.
nil
:api: private
# 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.
const<Class>: | The class to remove. |
nil
:api: private
# 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 at least once:
nil
:api: plugin
# 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
Parent Process:
(Does not return.)
Child Process returns at least once:
nil
:api: private
# 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
"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<Array[Class]>: | Classes to load. |
nil
:api: private
# 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