Skip to content

Commit

Permalink
Sadly, this segv's in 1.8 :(
Browse files Browse the repository at this point in the history
  • Loading branch information
wycats committed Aug 2, 2010
1 parent 88b5f93 commit 8aad678
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 43 deletions.
94 changes: 56 additions & 38 deletions activesupport/lib/active_support/dependencies.rb
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
10 changes: 5 additions & 5 deletions activesupport/test/dependencies_test.rb
Expand Up @@ -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
Expand All @@ -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) }
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 8aad678

Please sign in to comment.