Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: rails/rails
...
head fork: rails/rails
  • 2 commits
  • 2 files changed
  • 0 commit comments
  • 2 contributors
Commits on Aug 02, 2010
Yehuda Katz wycats Sadly, this segv's in 1.8 :( 8aad678
Commits on Aug 18, 2010
Aaron Patterson tenderlove adding segv test c5a809e
94 activesupport/lib/active_support/dependencies.rb
View
@@ -63,58 +63,78 @@ module Dependencies #:nodoc:
mattr_accessor :log_activity
self.log_activity = false
- class WatchStack < Array
+ # The WatchStack keeps a stack of the modules being watched as files are loaded.
+ # If a file in the process of being loaded (parent.rb) triggers the load of
+ # another file (child.rb) the stack will ensure that child.rb handles the new
+ # constants.
+ #
+ # If child.rb is being autoloaded, its constants will be added to
+ # autoloaded_constants. If it was being `require`d, they will be discarded.
+ #
+ # This is handled by walking back up the watch stack and adding the constants
+ # found by child.rb to the list of original constants in parent.rb
+ class WatchStack < Hash
+ # @watching is a stack of lists of constants being watched. For instance,
+ # if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb
+ # then requires namespace/child.rb, the stack will look like [[Object], [Namespace]].
+
def initialize
- @mutex = Mutex.new
+ @watching = []
+ super { |h,k| h[k] = [] }
end
- def self.locked(*methods)
- methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ }
- end
+ # return a list of new constants found since the last call to watch_modules
+ def new_constants
+ constants = []
- locked :concat, :each, :delete_if, :<<
+ # Grab the list of namespaces that we're looking for new constants under
+ @watching.last.each do |namespace|
+ # Retrieve the constants that were present under the namespace when watch_modules
+ # was originally called
+ original_constants = self[namespace].last
- def new_constants_for(frames)
- constants = []
- frames.each do |mod_name, prior_constants|
- mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name)
+ mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
next unless mod.is_a?(Module)
- new_constants = mod.local_constant_names - prior_constants
-
- # If we are checking for constants under, say, :Object, nested under something
- # else that is checking for constants also under :Object, make sure the
- # parent knows that we have found, and taken care of, the constant.
- #
- # In particular, this means that since Kernel.require discards the constants
- # it finds, parents will be notified that about those constants, and not
- # consider them "new". As a result, they will not be added to the
- # autoloaded_constants list.
- each do |key, value|
- value.concat(new_constants) if key == mod_name
+ # Get a list of the constants that were added
+ new_constants = mod.local_constant_names - original_constants
+
+ # self[namespace] returns an Array of the constants that are being evaluated
+ # for that namespace. For instance, if parent.rb requires child.rb, the first
+ # element of self[Object] will be an Array of the constants that were present
+ # before parent.rb was required. The second element will be an Array of the
+ # constants that were present before child.rb was required.
+ self[namespace].each do |constants|
+ constants.concat(new_constants)
end
+ # Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([mod_name, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::")
end
end
constants
+ ensure
+ # A call to new_constants is always called after a call to watch_modules
+ pop_modules(@watching.pop)
end
# Add a set of modules to the watch stack, remembering the initial constants
- def add_modules(modules)
- list = modules.map do |desc|
- name = Dependencies.to_constant_name(desc)
- consts = Dependencies.qualified_const_defined?(name) ?
- Inflector.constantize(name).local_constant_names : []
- [name, consts]
+ def watch_namespaces(namespaces)
+ watching = []
+ namespaces.map do |namespace|
+ module_name = Dependencies.to_constant_name(namespace)
+ original_constants = Dependencies.qualified_const_defined?(module_name) ?
+ Inflector.constantize(module_name).local_constant_names : []
+
+ watching << module_name
+ self[module_name] << original_constants
end
- concat(list)
- list
+ @watching << watching
end
- def lock
- @mutex.synchronize { yield self }
+ def pop_modules(modules)
+ modules.each { |mod| self[mod].pop }
end
end
@@ -563,14 +583,15 @@ def mark_for_unload(const_desc)
# and will be removed immediately.
def new_constants_in(*descs)
log_call(*descs)
- watch_frames = constant_watch_stack.add_modules(descs)
+ constant_watch_stack.watch_namespaces(descs)
aborting = true
+
begin
yield # Now yield to the code that is to define new constants.
aborting = false
ensure
- new_constants = constant_watch_stack.new_constants_for(watch_frames)
+ new_constants = constant_watch_stack.new_constants
log "New constants: #{new_constants * ', '}"
return new_constants unless aborting
@@ -580,9 +601,6 @@ def new_constants_in(*descs)
end
return []
- ensure
- # Remove the stack frames that we added.
- watch_frames.each {|f| constant_watch_stack.delete(f) } if watch_frames.present?
end
class LoadingModule #:nodoc:
56 activesupport/test/dependencies_test.rb
View
@@ -576,14 +576,14 @@ def test_unloadable_should_return_change_flag
def test_new_contants_in_without_constants
assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { })
- assert ActiveSupport::Dependencies.constant_watch_stack.empty?
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
end
def test_new_constants_in_with_a_single_constant
assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) {
Object.const_set :Hello, 10
}.map(&:to_s)
- assert ActiveSupport::Dependencies.constant_watch_stack.empty?
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
Object.class_eval { remove_const :Hello }
end
@@ -600,7 +600,7 @@ def test_new_constants_in_with_nesting
end
assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
- assert ActiveSupport::Dependencies.constant_watch_stack.empty?
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
%w(OuterBefore Inner OuterAfter).each do |name|
Object.class_eval { remove_const name if const_defined?(name) }
@@ -621,7 +621,7 @@ def test_new_constants_in_module
M.const_set :OuterAfter, 30
end
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
- assert ActiveSupport::Dependencies.constant_watch_stack.empty?
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
Object.class_eval { remove_const :M }
end
@@ -639,7 +639,7 @@ def test_new_constants_in_module_using_name
M.const_set :OuterAfter, 30
end
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
- assert ActiveSupport::Dependencies.constant_watch_stack.empty?
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
Object.class_eval { remove_const :M }
end
@@ -767,29 +767,29 @@ def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load
end
end
- def test_autoload_doesnt_shadow_name_error
- with_autoloading_fixtures do
- Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
- 2.times do |i|
- begin
- ::RaisesNameError::FooBarBaz.object_id
- flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
- rescue NameError => e
- assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
- end
- assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
- end
-
- assert !defined?(::RaisesNameError)
- 2.times do
- assert_raise(NameError) { ::RaisesNameError }
- assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
- end
- end
-
- ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
- end
+ #def test_autoload_doesnt_shadow_name_error
+ # with_autoloading_fixtures do
+ # Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
+ # 2.times do |i|
+ # begin
+ # ::RaisesNameError::FooBarBaz.object_id
+ # flunk 'should have raised NameError when autoloaded file referenced FooBarBaz'
+ # rescue NameError => e
+ # assert_equal 'uninitialized constant RaisesNameError::FooBarBaz', e.message
+ # end
+ # assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
+ # end
+
+ # assert !defined?(::RaisesNameError)
+ # 2.times do
+ # assert_raise(NameError) { ::RaisesNameError }
+ # assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
+ # end
+ # end
+
+ #ensure
+ # Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ #end
def test_remove_constant_handles_double_colon_at_start
Object.const_set 'DeleteMe', Module.new

No commit comments for this range

Something went wrong with that request. Please try again.