mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Make DescendantsTracker thread safe and optimize the #descendants method.
This commit is contained in:
parent
4f106bbb2c
commit
9f84e60ac9
3 changed files with 52 additions and 31 deletions
|
@ -2,35 +2,50 @@ module ActiveSupport
|
|||
# This module provides an internal implementation to track descendants
|
||||
# which is faster than iterating through ObjectSpace.
|
||||
module DescendantsTracker
|
||||
@@direct_descendants = Hash.new { |h, k| h[k] = [] }
|
||||
@@direct_descendants = {}
|
||||
|
||||
def self.direct_descendants(klass)
|
||||
@@direct_descendants[klass]
|
||||
end
|
||||
|
||||
def self.descendants(klass)
|
||||
@@direct_descendants[klass].inject([]) do |descendants, _klass|
|
||||
descendants << _klass
|
||||
descendants.concat _klass.descendants
|
||||
class << self
|
||||
def direct_descendants(klass)
|
||||
@@direct_descendants[klass] || []
|
||||
end
|
||||
end
|
||||
|
||||
def self.clear
|
||||
if defined? ActiveSupport::Dependencies
|
||||
@@direct_descendants.each do |klass, descendants|
|
||||
if ActiveSupport::Dependencies.autoloaded?(klass)
|
||||
@@direct_descendants.delete(klass)
|
||||
else
|
||||
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
|
||||
def descendants(klass)
|
||||
arr = []
|
||||
accumulate_descendants(klass, arr)
|
||||
arr
|
||||
end
|
||||
|
||||
def clear
|
||||
if defined? ActiveSupport::Dependencies
|
||||
@@direct_descendants.each do |klass, descendants|
|
||||
if ActiveSupport::Dependencies.autoloaded?(klass)
|
||||
@@direct_descendants.delete(klass)
|
||||
else
|
||||
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
|
||||
end
|
||||
end
|
||||
else
|
||||
@@direct_descendants.clear
|
||||
end
|
||||
end
|
||||
|
||||
# This is the only method that is not thread safe, but is only ever called
|
||||
# during the eager loading phase.
|
||||
def store_inherited(klass, descendant)
|
||||
(@@direct_descendants[klass] ||= []) << descendant
|
||||
end
|
||||
|
||||
private
|
||||
def accumulate_descendants(klass, acc)
|
||||
if direct_descendants = @@direct_descendants[klass]
|
||||
acc.concat(direct_descendants)
|
||||
direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
|
||||
end
|
||||
else
|
||||
@@direct_descendants.clear
|
||||
end
|
||||
end
|
||||
|
||||
def inherited(base)
|
||||
self.direct_descendants << base
|
||||
DescendantsTracker.store_inherited(self, base)
|
||||
super
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'set'
|
||||
|
||||
module DescendantsTrackerTestCases
|
||||
class Parent
|
||||
extend ActiveSupport::DescendantsTracker
|
||||
|
@ -18,15 +20,15 @@ module DescendantsTrackerTestCases
|
|||
ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
|
||||
|
||||
def test_descendants
|
||||
assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
|
||||
assert_equal [Grandchild1, Grandchild2], Child1.descendants
|
||||
assert_equal [], Child2.descendants
|
||||
assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
|
||||
assert_equal_sets [Grandchild1, Grandchild2], Child1.descendants
|
||||
assert_equal_sets [], Child2.descendants
|
||||
end
|
||||
|
||||
def test_direct_descendants
|
||||
assert_equal [Child1, Child2], Parent.direct_descendants
|
||||
assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
|
||||
assert_equal [], Child2.direct_descendants
|
||||
assert_equal_sets [Child1, Child2], Parent.direct_descendants
|
||||
assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants
|
||||
assert_equal_sets [], Child2.direct_descendants
|
||||
end
|
||||
|
||||
def test_clear
|
||||
|
@ -40,6 +42,10 @@ module DescendantsTrackerTestCases
|
|||
|
||||
protected
|
||||
|
||||
def assert_equal_sets(expected, actual)
|
||||
Set.new(expected) == Set.new(actual)
|
||||
end
|
||||
|
||||
def mark_as_autoloaded(*klasses)
|
||||
# If ActiveSupport::Dependencies is not loaded, forget about autoloading.
|
||||
# This allows using AS::DescendantsTracker without AS::Dependencies.
|
||||
|
|
|
@ -18,17 +18,17 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
|
|||
def test_clear_with_autoloaded_children_and_granchildren
|
||||
mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
|
||||
ActiveSupport::DescendantsTracker.clear
|
||||
assert_equal [Child2], Parent.descendants
|
||||
assert_equal [], Child2.descendants
|
||||
assert_equal_sets [Child2], Parent.descendants
|
||||
assert_equal_sets [], Child2.descendants
|
||||
end
|
||||
end
|
||||
|
||||
def test_clear_with_autoloaded_granchildren
|
||||
mark_as_autoloaded Grandchild1, Grandchild2 do
|
||||
ActiveSupport::DescendantsTracker.clear
|
||||
assert_equal [Child1, Child2], Parent.descendants
|
||||
assert_equal [], Child1.descendants
|
||||
assert_equal [], Child2.descendants
|
||||
assert_equal_sets [Child1, Child2], Parent.descendants
|
||||
assert_equal_sets [], Child1.descendants
|
||||
assert_equal_sets [], Child2.descendants
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue