Skip to content

Commit 8aad678

Browse files
committed
Sadly, this segv's in 1.8 :(
1 parent 88b5f93 commit 8aad678

File tree

2 files changed

+61
-43
lines changed

2 files changed

+61
-43
lines changed

activesupport/lib/active_support/dependencies.rb

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,58 +63,78 @@ module Dependencies #:nodoc:
6363
mattr_accessor :log_activity
6464
self.log_activity = false
6565

66-
class WatchStack < Array
66+
# The WatchStack keeps a stack of the modules being watched as files are loaded.
67+
# If a file in the process of being loaded (parent.rb) triggers the load of
68+
# another file (child.rb) the stack will ensure that child.rb handles the new
69+
# constants.
70+
#
71+
# If child.rb is being autoloaded, its constants will be added to
72+
# autoloaded_constants. If it was being `require`d, they will be discarded.
73+
#
74+
# This is handled by walking back up the watch stack and adding the constants
75+
# found by child.rb to the list of original constants in parent.rb
76+
class WatchStack < Hash
77+
# @watching is a stack of lists of constants being watched. For instance,
78+
# if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb
79+
# then requires namespace/child.rb, the stack will look like [[Object], [Namespace]].
80+
6781
def initialize
68-
@mutex = Mutex.new
82+
@watching = []
83+
super { |h,k| h[k] = [] }
6984
end
7085

71-
def self.locked(*methods)
72-
methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ }
73-
end
86+
# return a list of new constants found since the last call to watch_modules
87+
def new_constants
88+
constants = []
7489

75-
locked :concat, :each, :delete_if, :<<
90+
# Grab the list of namespaces that we're looking for new constants under
91+
@watching.last.each do |namespace|
92+
# Retrieve the constants that were present under the namespace when watch_modules
93+
# was originally called
94+
original_constants = self[namespace].last
7695

77-
def new_constants_for(frames)
78-
constants = []
79-
frames.each do |mod_name, prior_constants|
80-
mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name)
96+
mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
8197
next unless mod.is_a?(Module)
8298

83-
new_constants = mod.local_constant_names - prior_constants
84-
85-
# If we are checking for constants under, say, :Object, nested under something
86-
# else that is checking for constants also under :Object, make sure the
87-
# parent knows that we have found, and taken care of, the constant.
88-
#
89-
# In particular, this means that since Kernel.require discards the constants
90-
# it finds, parents will be notified that about those constants, and not
91-
# consider them "new". As a result, they will not be added to the
92-
# autoloaded_constants list.
93-
each do |key, value|
94-
value.concat(new_constants) if key == mod_name
99+
# Get a list of the constants that were added
100+
new_constants = mod.local_constant_names - original_constants
101+
102+
# self[namespace] returns an Array of the constants that are being evaluated
103+
# for that namespace. For instance, if parent.rb requires child.rb, the first
104+
# element of self[Object] will be an Array of the constants that were present
105+
# before parent.rb was required. The second element will be an Array of the
106+
# constants that were present before child.rb was required.
107+
self[namespace].each do |constants|
108+
constants.concat(new_constants)
95109
end
96110

111+
# Normalize the list of new constants, and add them to the list we will return
97112
new_constants.each do |suffix|
98-
constants << ([mod_name, suffix] - ["Object"]).join("::")
113+
constants << ([namespace, suffix] - ["Object"]).join("::")
99114
end
100115
end
101116
constants
117+
ensure
118+
# A call to new_constants is always called after a call to watch_modules
119+
pop_modules(@watching.pop)
102120
end
103121

104122
# Add a set of modules to the watch stack, remembering the initial constants
105-
def add_modules(modules)
106-
list = modules.map do |desc|
107-
name = Dependencies.to_constant_name(desc)
108-
consts = Dependencies.qualified_const_defined?(name) ?
109-
Inflector.constantize(name).local_constant_names : []
110-
[name, consts]
123+
def watch_namespaces(namespaces)
124+
watching = []
125+
namespaces.map do |namespace|
126+
module_name = Dependencies.to_constant_name(namespace)
127+
original_constants = Dependencies.qualified_const_defined?(module_name) ?
128+
Inflector.constantize(module_name).local_constant_names : []
129+
130+
watching << module_name
131+
self[module_name] << original_constants
111132
end
112-
concat(list)
113-
list
133+
@watching << watching
114134
end
115135

116-
def lock
117-
@mutex.synchronize { yield self }
136+
def pop_modules(modules)
137+
modules.each { |mod| self[mod].pop }
118138
end
119139
end
120140

@@ -563,14 +583,15 @@ def mark_for_unload(const_desc)
563583
# and will be removed immediately.
564584
def new_constants_in(*descs)
565585
log_call(*descs)
566-
watch_frames = constant_watch_stack.add_modules(descs)
567586

587+
constant_watch_stack.watch_namespaces(descs)
568588
aborting = true
589+
569590
begin
570591
yield # Now yield to the code that is to define new constants.
571592
aborting = false
572593
ensure
573-
new_constants = constant_watch_stack.new_constants_for(watch_frames)
594+
new_constants = constant_watch_stack.new_constants
574595

575596
log "New constants: #{new_constants * ', '}"
576597
return new_constants unless aborting
@@ -580,9 +601,6 @@ def new_constants_in(*descs)
580601
end
581602

582603
return []
583-
ensure
584-
# Remove the stack frames that we added.
585-
watch_frames.each {|f| constant_watch_stack.delete(f) } if watch_frames.present?
586604
end
587605

588606
class LoadingModule #:nodoc:

activesupport/test/dependencies_test.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -576,14 +576,14 @@ def test_unloadable_should_return_change_flag
576576

577577
def test_new_contants_in_without_constants
578578
assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { })
579-
assert ActiveSupport::Dependencies.constant_watch_stack.empty?
579+
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
580580
end
581581

582582
def test_new_constants_in_with_a_single_constant
583583
assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) {
584584
Object.const_set :Hello, 10
585585
}.map(&:to_s)
586-
assert ActiveSupport::Dependencies.constant_watch_stack.empty?
586+
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
587587
ensure
588588
Object.class_eval { remove_const :Hello }
589589
end
@@ -600,7 +600,7 @@ def test_new_constants_in_with_nesting
600600
end
601601

602602
assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
603-
assert ActiveSupport::Dependencies.constant_watch_stack.empty?
603+
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
604604
ensure
605605
%w(OuterBefore Inner OuterAfter).each do |name|
606606
Object.class_eval { remove_const name if const_defined?(name) }
@@ -621,7 +621,7 @@ def test_new_constants_in_module
621621
M.const_set :OuterAfter, 30
622622
end
623623
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
624-
assert ActiveSupport::Dependencies.constant_watch_stack.empty?
624+
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
625625
ensure
626626
Object.class_eval { remove_const :M }
627627
end
@@ -639,7 +639,7 @@ def test_new_constants_in_module_using_name
639639
M.const_set :OuterAfter, 30
640640
end
641641
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
642-
assert ActiveSupport::Dependencies.constant_watch_stack.empty?
642+
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
643643
ensure
644644
Object.class_eval { remove_const :M }
645645
end

0 commit comments

Comments
 (0)