module Tennpipes::Reloader

High performance source code reloader middleware

Public Instance Methods

changed?() click to toggle source

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
clear!() click to toggle source

Remove files and classes loaded with stat

# File lib/tennpipes-base/reloader.rb, line 58
def clear!
  MTIMES.clear
  Storage.clear!
end
exclude() click to toggle source

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
exclude_constants() click to toggle source

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
include_constants() click to toggle source

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
lock!() click to toggle source

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!() click to toggle source

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
remove_constant(const) click to toggle source

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_feature(file) click to toggle source

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
safe_load(file, options={}) click to toggle source

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
special_files() click to toggle source

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
special_files=(files) click to toggle source

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
unknown() click to toggle source

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

constant_excluded?(const) click to toggle source

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
external_constant?(const) click to toggle source

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
feature_excluded?(file) click to toggle source

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
figure_path(file) click to toggle source

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
file_changed?(file) click to toggle source

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
file_new?(file) click to toggle source

Returns true if the file is new.

# File lib/tennpipes-base/reloader.rb, line 202
def file_new?(file)
  MTIMES[file].nil?
end
files_for_rotation() click to toggle source

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
mounted_apps_of(file) click to toggle source

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
object_sources(target) click to toggle source

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
reload_regular(file) click to toggle source

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
reload_special(file) click to toggle source

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
reloadable_apps() click to toggle source

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
rotation() { |file| ... } click to toggle source

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
update_modification_time(file) click to toggle source

Macro for mtime update.

# File lib/tennpipes-base/reloader.rb, line 188
def update_modification_time(file)
  MTIMES[file] = File.mtime(file)
end
with_silence() { || ... } click to toggle source

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