aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/dependencies.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/dependencies.rb')
-rw-r--r--activesupport/lib/active_support/dependencies.rb156
1 files changed, 102 insertions, 54 deletions
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 93a11d4586..16b726bcba 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,6 +1,6 @@
require 'set'
require 'thread'
-require 'thread_safe'
+require 'concurrent'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
+require "active_support/dependencies/interlock"
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ mattr_accessor :interlock
+ self.interlock = Interlock.new
+
+ # :doc:
+
+ # Execute the supplied block without interference from any
+ # concurrent loads
+ def self.run_interlock
+ Dependencies.interlock.running { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.load_interlock
+ Dependencies.interlock.loading { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.unload_interlock
+ Dependencies.interlock.unloading { yield }
+ end
+
+ # :nodoc:
+
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
@@ -30,6 +58,10 @@ module ActiveSupport #:nodoc:
mattr_accessor :loaded
self.loaded = Set.new
+ # Stack of files being loaded.
+ mattr_accessor :loading
+ self.loading = []
+
# Should we load files or require them?
mattr_accessor :mechanism
self.mechanism = ENV['NO_RELOAD'] ? :require : :load
@@ -124,7 +156,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -201,7 +233,10 @@ module ActiveSupport #:nodoc:
# Object includes this module.
module Loadable #:nodoc:
def self.exclude_from(base)
- base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
+ base.class_eval do
+ define_method(:load, Kernel.instance_method(:load))
+ private :load
+ end
end
def require_or_load(file_name)
@@ -237,18 +272,6 @@ module ActiveSupport #:nodoc:
raise
end
- def load(file, wrap = false)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
- def require(file)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
# Mark the given constant as unloadable. Unloadable constants are removed
# each time dependencies are cleared.
#
@@ -265,6 +288,20 @@ module ActiveSupport #:nodoc:
def unloadable(const_desc)
Dependencies.mark_for_unload const_desc
end
+
+ private
+
+ def load(file, wrap = false)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
+
+ def require(file)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
end
# Exception file-blaming.
@@ -316,8 +353,11 @@ module ActiveSupport #:nodoc:
def clear
log_call
- loaded.clear
- remove_unloadable_constants!
+ Dependencies.unload_interlock do
+ loaded.clear
+ loading.clear
+ remove_unloadable_constants!
+ end
end
def require_or_load(file_name, const_path = nil)
@@ -326,41 +366,49 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
- # Record that we've seen this file *before* loading it to avoid an
- # infinite loop with mutual dependencies.
- loaded << expanded
-
- begin
- if load?
- log "loading #{file_name}"
+ Dependencies.load_interlock do
+ # Maybe it got loaded while we were waiting for our lock:
+ return if loaded.include?(expanded)
- # Enable warnings if this file has not been loaded before and
- # warnings_on_first_load is set.
- load_args = ["#{file_name}.rb"]
- load_args << const_path unless const_path.nil?
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << expanded
+ loading << expanded
- if !warnings_on_first_load or history.include?(expanded)
- result = load_file(*load_args)
+ begin
+ if load?
+ log "loading #{file_name}"
+
+ # Enable warnings if this file has not been loaded before and
+ # warnings_on_first_load is set.
+ load_args = ["#{file_name}.rb"]
+ load_args << const_path unless const_path.nil?
+
+ if !warnings_on_first_load or history.include?(expanded)
+ result = load_file(*load_args)
+ else
+ enable_warnings { result = load_file(*load_args) }
+ end
else
- enable_warnings { result = load_file(*load_args) }
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
+ ensure
+ loading.pop
end
- rescue Exception
- loaded.delete expanded
- raise
- end
- # Record history *after* loading so first load gets warnings.
- history << expanded
- result
+ # Record history *after* loading so first load gets warnings.
+ history << expanded
+ result
+ end
end
# Is the provided constant path defined?
def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''), false)
+ Object.const_defined?(path, false)
end
# Given +path+, a filesystem path to a ruby file, return an array of
@@ -373,13 +421,13 @@ module ActiveSupport #:nodoc:
bases.each do |root|
expanded_root = File.expand_path(root)
- next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
+ next unless expanded_path.start_with?(expanded_root)
- nesting = expanded_path[(expanded_root.size)..-1]
- nesting = nesting[1..-1] if nesting && nesting[0] == ?/
- next if nesting.blank?
+ root_size = expanded_root.size
+ next if expanded_path[root_size] != ?/.freeze
- paths << nesting.camelize
+ nesting = expanded_path[(root_size + 1)..-1]
+ paths << nesting.camelize unless nesting.blank?
end
paths.uniq!
@@ -388,7 +436,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -408,7 +456,7 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where String#starts_with?(Pathname)
+ # to_s works around a ruby issue where String#starts_with?(Pathname)
# will raise a TypeError: no implicit conversion of Pathname into String
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
@@ -473,9 +521,9 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
- if loaded.include?(expanded)
+ if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
else
require_or_load(expanded, qualified_name)
@@ -537,7 +585,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = ThreadSafe::Cache.new
+ @store = Concurrent::Map.new
end
def empty?
@@ -594,7 +642,7 @@ module ActiveSupport #:nodoc:
def autoloaded?(desc)
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
- return false unless qualified_const_defined? name
+ return false unless qualified_const_defined?(name)
return autoloaded_constants.include?(name)
end
@@ -729,7 +777,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if log_activity?
- arg_str = args.collect { |arg| arg.inspect } * ', '
+ arg_str = args.collect(&:inspect) * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"