module Tennpipes::Reloader
High performance source code reloader middleware
Public Instance Methods
Returns true if any file changes are detected.
# File lib/tennpipes-base/reloader.rb, line 66 def changed? rotation do |file| break true if file_changed?(file) end end
Remove files and classes loaded with stat
# File lib/tennpipes-base/reloader.rb, line 58 def clear! MTIMES.clear Storage.clear! end
Specified folders can be excluded from the code reload detection process. Default excluded directories at Tennpipes.root
are: test, spec, features, tmp, config, db and public
# File lib/tennpipes-base/reloader.rb, line 26 def exclude @_exclude ||= Set.new %w(test spec tmp features config public db).map{ |path| Tennpipes.root(path) } end
Specified constants can be excluded from the code unloading process.
# File lib/tennpipes-base/reloader.rb, line 33 def exclude_constants @_exclude_constants ||= Set.new end
Specified constants can be configured to be reloaded on every request. Default included constants are: [none]
# File lib/tennpipes-base/reloader.rb, line 41 def include_constants @_include_constants ||= Set.new end
We lock dependencies sets to prevent reloading of protected constants
# File lib/tennpipes-base/reloader.rb, line 75 def lock! klasses = ObjectSpace.classes do |klass| klass._orig_klass_name.split('::').first end klasses |= Tennpipes.mounted_apps.map(&:app_class) exclude_constants.merge(klasses) end
Reload apps and files with changes detected.
# File lib/tennpipes-base/reloader.rb, line 48 def reload! rotation do |file| next unless file_changed?(file) reload_special(file) || reload_regular(file) end end
Removes the specified class and constant.
# File lib/tennpipes-base/reloader.rb, line 111 def remove_constant(const) return if constant_excluded?(const) base, _, object = const.to_s.rpartition('::') base = base.empty? ? Object : base.constantize base.send :remove_const, object logger.devel "Removed constant #{const} from #{base}" rescue NameError end
Remove a feature from $LOADED_FEATURES so it can be required again.
# File lib/tennpipes-base/reloader.rb, line 123 def remove_feature(file) $LOADED_FEATURES.delete(file) unless feature_excluded?(file) end
A safe Kernel::require which issues the necessary hooks depending on results
# File lib/tennpipes-base/reloader.rb, line 86 def safe_load(file, options={}) began_at = Time.now file = figure_path(file) return unless options[:force] || file_changed?(file) return require(file) if feature_excluded?(file) Storage.prepare(file) # might call #safe_load recursively logger.devel(file_new?(file) ? :loading : :reload, began_at, file) begin with_silence{ require(file) } Storage.commit(file) update_modification_time(file) rescue Exception => exception unless options[:cyclic] logger.exception exception, :short logger.error "Failed to load #{file}; removing partially defined constants" end Storage.rollback(file) raise end end
Returns the list of special tracked files for Reloader
.
# File lib/tennpipes-base/reloader.rb, line 130 def special_files @special_files ||= Set.new end
Sets the list of special tracked files for Reloader
.
# File lib/tennpipes-base/reloader.rb, line 137 def special_files=(files) @special_files = Set.new(files) end
This reloader is suited for use in a many environments because each file will only be checked once and only one system call to stat(2) is made.
Please note that this will not reload files in the background, and does so only when explicitly invoked.
# File lib/tennpipes-base/reloader.rb, line 17 extend self
Private Instance Methods
Tells if a constant should be excluded from Reloader
routines.
# File lib/tennpipes-base/reloader.rb, line 243 def constant_excluded?(const) external_constant?(const) || (exclude_constants - include_constants).any?{ |excluded_constant| const._orig_klass_name.start_with?(excluded_constant) } end
Tells if a constant is defined only outside of Tennpipes
project path. If a constant has any methods defined inside of the project path it's considered internal and will be included in further testing.
# File lib/tennpipes-base/reloader.rb, line 252 def external_constant?(const) sources = object_sources(const) begin if sample = ObjectSpace.each_object(const).first sources += object_sources(sample) end rescue RuntimeError => error # JRuby 1.7.12 fails to ObjectSpace.each_object raise unless RUBY_PLATFORM =='java' && error.message.start_with?("ObjectSpace is disabled") end !sources.any?{ |source| source.start_with?(Tennpipes.root) } end
Tells if a feature should be excluded from Reloader
tracking.
# File lib/tennpipes-base/reloader.rb, line 236 def feature_excluded?(file) !file.start_with?(Tennpipes.root) || exclude.any?{ |excluded_path| file.start_with?(excluded_path) } end
Returns absolute path of the file.
# File lib/tennpipes-base/reloader.rb, line 146 def figure_path(file) return file if Pathname.new(file).absolute? $LOAD_PATH.each do |path| found = File.join(path, file) return File.expand_path(found) if File.file?(found) end file end
Returns true if the file is new or it's modification time changed.
# File lib/tennpipes-base/reloader.rb, line 195 def file_changed?(file) file_new?(file) || File.mtime(file) > MTIMES[file] end
Returns true if the file is new.
# File lib/tennpipes-base/reloader.rb, line 202 def file_new?(file) MTIMES[file].nil? end
Creates an array of paths for use in rotation
.
# File lib/tennpipes-base/reloader.rb, line 222 def files_for_rotation files = Set.new files += Dir.glob("#{Tennpipes.root}/{lib,models,shared}/**/*.rb") reloadable_apps.each do |app| files << app.app_file files += Dir.glob(app.app_obj.prerequisites) files += app.app_obj.dependencies end files + special_files end
Return the mounted_apps providing the app location. Can be an array because in one app.rb we can define multiple Tennpipes::Application
.
# File lib/tennpipes-base/reloader.rb, line 285 def mounted_apps_of(file) Tennpipes.mounted_apps.select { |app| File.identical?(file, app.app_file) } end
Gets all the sources in which target's class or instance methods are defined.
Note: Method#source_location is for Ruby 1.9.3+ only.
# File lib/tennpipes-base/reloader.rb, line 269 def object_sources(target) sources = Set.new target.methods.each do |method_name| next unless method_name.kind_of?(Symbol) method_object = target.method(method_name) if method_object.owner == (target.class == Class ? target.singleton_class : target.class) sources << method_object.source_location.first end end sources end
Reloads ruby file and applications dependent on it.
# File lib/tennpipes-base/reloader.rb, line 172 def reload_regular(file) apps = mounted_apps_of(file) if apps.empty? reloadable_apps.each do |app| app.app_obj.reload! if app.app_obj.dependencies.include?(file) end safe_load(file) else apps.each { |app| app.app_obj.reload! } update_modification_time(file) end end
Reloads the file if it's special. For now it's only I18n locale files.
# File lib/tennpipes-base/reloader.rb, line 158 def reload_special(file) return unless special_files.any?{ |special_file| File.identical?(special_file, file) } if defined?(I18n) began_at = Time.now I18n.reload! update_modification_time(file) logger.devel :reload, began_at, file end true end
Return the apps that allow reloading.
# File lib/tennpipes-base/reloader.rb, line 292 def reloadable_apps Tennpipes.mounted_apps.select do |app| next unless app.app_file.start_with?(Tennpipes.root) app.app_obj.respond_to?(:reload) && app.app_obj.reload? end end
Searches Ruby files in your Tennpipes.load_paths
, Tennpipes::Application.load_paths and monitors them for any changes.
# File lib/tennpipes-base/reloader.rb, line 210 def rotation files_for_rotation.each do |file| file = File.expand_path(file) next if Reloader.exclude.any? { |base| file.start_with?(base) } || !File.file?(file) yield file end nil end
Macro for mtime update.
# File lib/tennpipes-base/reloader.rb, line 188 def update_modification_time(file) MTIMES[file] = File.mtime(file) end
Disables output, yields block, switches output back.
# File lib/tennpipes-base/reloader.rb, line 302 def with_silence verbosity_level, $-v = $-v, nil yield ensure $-v = verbosity_level end