Skip to content
This repository
Browse code

Make DescendantsTracker thread safe and optimize the #descendants met…

…hod.
  • Loading branch information...
commit 9f84e60ac9d7bf07d6ae1bc94f3941f5b8f1a228 1 parent 4f106bb
authored June 30, 2012 tenderlove committed October 18, 2012
53  activesupport/lib/active_support/descendants_tracker.rb
@@ -2,35 +2,50 @@ module ActiveSupport
2 2
   # This module provides an internal implementation to track descendants
3 3
   # which is faster than iterating through ObjectSpace.
4 4
   module DescendantsTracker
5  
-    @@direct_descendants = Hash.new { |h, k| h[k] = [] }
  5
+    @@direct_descendants = {}
6 6
 
7  
-    def self.direct_descendants(klass)
8  
-      @@direct_descendants[klass]
9  
-    end
  7
+    class << self
  8
+      def direct_descendants(klass)
  9
+        @@direct_descendants[klass] || []
  10
+      end
10 11
 
11  
-    def self.descendants(klass)
12  
-      @@direct_descendants[klass].inject([]) do |descendants, _klass|
13  
-        descendants << _klass
14  
-        descendants.concat _klass.descendants
  12
+      def descendants(klass)
  13
+        arr = []
  14
+        accumulate_descendants(klass, arr)
  15
+        arr
15 16
       end
16  
-    end
17 17
 
18  
-    def self.clear
19  
-      if defined? ActiveSupport::Dependencies
20  
-        @@direct_descendants.each do |klass, descendants|
21  
-          if ActiveSupport::Dependencies.autoloaded?(klass)
22  
-            @@direct_descendants.delete(klass)
23  
-          else
24  
-            descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
  18
+      def clear
  19
+        if defined? ActiveSupport::Dependencies
  20
+          @@direct_descendants.each do |klass, descendants|
  21
+            if ActiveSupport::Dependencies.autoloaded?(klass)
  22
+              @@direct_descendants.delete(klass)
  23
+            else
  24
+              descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
  25
+            end
25 26
           end
  27
+        else
  28
+          @@direct_descendants.clear
  29
+        end
  30
+      end
  31
+
  32
+      # This is the only method that is not thread safe, but is only ever called
  33
+      # during the eager loading phase.
  34
+      def store_inherited(klass, descendant)
  35
+        (@@direct_descendants[klass] ||= []) << descendant
  36
+      end
  37
+
  38
+      private
  39
+      def accumulate_descendants(klass, acc)
  40
+        if direct_descendants = @@direct_descendants[klass]
  41
+          acc.concat(direct_descendants)
  42
+          direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
26 43
         end
27  
-      else
28  
-        @@direct_descendants.clear
29 44
       end
30 45
     end
31 46
 
32 47
     def inherited(base)
33  
-      self.direct_descendants << base
  48
+      DescendantsTracker.store_inherited(self, base)
34 49
       super
35 50
     end
36 51
 
18  activesupport/test/descendants_tracker_test_cases.rb
... ...
@@ -1,3 +1,5 @@
  1
+require 'set'
  2
+
1 3
 module DescendantsTrackerTestCases
2 4
   class Parent
3 5
     extend ActiveSupport::DescendantsTracker
@@ -18,15 +20,15 @@ class Grandchild2 < Child1
18 20
   ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
19 21
 
20 22
   def test_descendants
21  
-    assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
22  
-    assert_equal [Grandchild1, Grandchild2], Child1.descendants
23  
-    assert_equal [], Child2.descendants
  23
+    assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
  24
+    assert_equal_sets [Grandchild1, Grandchild2], Child1.descendants
  25
+    assert_equal_sets [], Child2.descendants
24 26
   end
25 27
 
26 28
   def test_direct_descendants
27  
-    assert_equal [Child1, Child2], Parent.direct_descendants
28  
-    assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
29  
-    assert_equal [], Child2.direct_descendants
  29
+    assert_equal_sets [Child1, Child2], Parent.direct_descendants
  30
+    assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants
  31
+    assert_equal_sets [], Child2.direct_descendants
30 32
   end
31 33
 
32 34
   def test_clear
@@ -40,6 +42,10 @@ def test_clear
40 42
 
41 43
   protected
42 44
 
  45
+  def assert_equal_sets(expected, actual)
  46
+    Set.new(expected) == Set.new(actual)
  47
+  end
  48
+
43 49
   def mark_as_autoloaded(*klasses)
44 50
     # If ActiveSupport::Dependencies is not loaded, forget about autoloading.
45 51
     # This allows using AS::DescendantsTracker without AS::Dependencies.
10  activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -18,17 +18,17 @@ def test_clear_with_autoloaded_parent_children_and_granchildren
18 18
   def test_clear_with_autoloaded_children_and_granchildren
19 19
     mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
20 20
       ActiveSupport::DescendantsTracker.clear
21  
-      assert_equal [Child2], Parent.descendants
22  
-      assert_equal [], Child2.descendants
  21
+      assert_equal_sets [Child2], Parent.descendants
  22
+      assert_equal_sets [], Child2.descendants
23 23
     end
24 24
   end
25 25
 
26 26
   def test_clear_with_autoloaded_granchildren
27 27
     mark_as_autoloaded Grandchild1, Grandchild2 do
28 28
       ActiveSupport::DescendantsTracker.clear
29  
-      assert_equal [Child1, Child2], Parent.descendants
30  
-      assert_equal [], Child1.descendants
31  
-      assert_equal [], Child2.descendants
  29
+      assert_equal_sets [Child1, Child2], Parent.descendants
  30
+      assert_equal_sets [], Child1.descendants
  31
+      assert_equal_sets [], Child2.descendants
32 32
     end
33 33
   end
34 34
 end

0 notes on commit 9f84e60

Please sign in to comment.
Something went wrong with that request. Please try again.