|
5 | 5 |
|
6 | 6 | module ActiveSupport
|
7 | 7 | # This module provides an internal implementation to track descendants
|
8 |
| - # which is faster than iterating through ObjectSpace. |
| 8 | + # which is faster than iterating through +ObjectSpace+. |
| 9 | + # |
| 10 | + # However Ruby 3.1 provide a fast native +Class#subclasses+ method, |
| 11 | + # so if you know your code won't be executed on older rubies, including |
| 12 | + # +ActiveSupport::DescendantsTracker+ does not provide any benefit. |
9 | 13 | module DescendantsTracker
|
10 |
| - class << self |
11 |
| - def direct_descendants(klass) |
12 |
| - ActiveSupport::Deprecation.warn(<<~MSG) |
13 |
| - ActiveSupport::DescendantsTracker.direct_descendants is deprecated and will be removed in Rails 7.1. |
14 |
| - Use ActiveSupport::DescendantsTracker.subclasses instead. |
15 |
| - MSG |
16 |
| - subclasses(klass) |
17 |
| - end |
18 |
| - end |
19 |
| - |
20 | 14 | @clear_disabled = false
|
21 | 15 |
|
22 |
| - if RubyFeatures::CLASS_SUBCLASSES |
23 |
| - @@excluded_descendants = if RUBY_ENGINE == "ruby" |
24 |
| - # On MRI `ObjectSpace::WeakMap` keys are weak references. |
25 |
| - # So we can simply use WeakMap as a `Set`. |
26 |
| - ObjectSpace::WeakMap.new |
27 |
| - else |
28 |
| - # On TruffleRuby `ObjectSpace::WeakMap` keys are strong references. |
29 |
| - # So we use `object_id` as a key and the actual object as a value. |
30 |
| - # |
31 |
| - # JRuby for now doesn't have Class#descendant, but when it will, it will likely |
32 |
| - # have the same WeakMap semantic than Truffle so we future proof this as much as possible. |
33 |
| - class WeakSet # :nodoc: |
34 |
| - def initialize |
35 |
| - @map = ObjectSpace::WeakMap.new |
36 |
| - end |
| 16 | + if RUBY_ENGINE == "ruby" |
| 17 | + # On MRI `ObjectSpace::WeakMap` keys are weak references. |
| 18 | + # So we can simply use WeakMap as a `Set`. |
| 19 | + class WeakSet < ObjectSpace::WeakMap # :nodoc: |
| 20 | + alias_method :to_a, :keys |
37 | 21 |
|
38 |
| - def [](object) |
39 |
| - @map.key?(object.object_id) |
40 |
| - end |
41 |
| - |
42 |
| - def []=(object, _present) |
43 |
| - @map[object.object_id] = object |
44 |
| - end |
| 22 | + def <<(object) |
| 23 | + self[object] = true |
45 | 24 | end
|
46 |
| - WeakSet.new |
47 | 25 | end
|
48 |
| - |
49 |
| - class << self |
50 |
| - def disable_clear! # :nodoc: |
51 |
| - unless @clear_disabled |
52 |
| - @clear_disabled = true |
53 |
| - remove_method(:subclasses) |
54 |
| - @@excluded_descendants = nil |
55 |
| - end |
| 26 | + else |
| 27 | + # On TruffleRuby `ObjectSpace::WeakMap` keys are strong references. |
| 28 | + # So we use `object_id` as a key and the actual object as a value. |
| 29 | + # |
| 30 | + # JRuby for now doesn't have Class#descendant, but when it will, it will likely |
| 31 | + # have the same WeakMap semantic than Truffle so we future proof this as much as possible. |
| 32 | + class WeakSet # :nodoc: |
| 33 | + def initialize |
| 34 | + @map = ObjectSpace::WeakMap.new |
56 | 35 | end
|
57 | 36 |
|
58 |
| - def subclasses(klass) |
59 |
| - klass.subclasses |
| 37 | + def [](object) |
| 38 | + @map.key?(object.object_id) |
60 | 39 | end
|
| 40 | + alias_method :include?, :[] |
61 | 41 |
|
62 |
| - def descendants(klass) |
63 |
| - klass.descendants |
| 42 | + def []=(object, _present) |
| 43 | + @map[object.object_id] = object |
64 | 44 | end
|
65 | 45 |
|
66 |
| - def clear(classes) # :nodoc: |
67 |
| - raise "DescendantsTracker.clear was disabled because config.enable_reloading is false" if @clear_disabled |
68 |
| - |
69 |
| - classes.each do |klass| |
70 |
| - @@excluded_descendants[klass] = true |
71 |
| - klass.descendants.each do |descendant| |
72 |
| - @@excluded_descendants[descendant] = true |
73 |
| - end |
74 |
| - end |
| 46 | + def to_a |
| 47 | + @map.values |
75 | 48 | end
|
76 | 49 |
|
77 |
| - def native? # :nodoc: |
78 |
| - true |
| 50 | + def <<(object) |
| 51 | + self[object] = true |
79 | 52 | end
|
80 | 53 | end
|
| 54 | + end |
| 55 | + @excluded_descendants = WeakSet.new |
81 | 56 |
|
| 57 | + module ReloadedClassesFiltering # :nodoc: |
82 | 58 | def subclasses
|
83 |
| - subclasses = super |
84 |
| - subclasses.reject! { |d| @@excluded_descendants[d] } |
85 |
| - subclasses |
| 59 | + DescendantsTracker.reject!(super) |
86 | 60 | end
|
87 | 61 |
|
88 | 62 | def descendants
|
89 |
| - subclasses.concat(subclasses.flat_map(&:descendants)) |
| 63 | + DescendantsTracker.reject!(super) |
90 | 64 | end
|
| 65 | + end |
91 | 66 |
|
92 |
| - def direct_descendants |
93 |
| - ActiveSupport::Deprecation.warn(<<~MSG) |
94 |
| - ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1. |
95 |
| - Use #subclasses instead. |
96 |
| - MSG |
97 |
| - subclasses |
98 |
| - end |
99 |
| - else |
100 |
| - @@direct_descendants = {} |
| 67 | + include ReloadedClassesFiltering |
101 | 68 |
|
102 |
| - class << self |
103 |
| - def disable_clear! # :nodoc: |
| 69 | + class << self |
| 70 | + def disable_clear! # :nodoc: |
| 71 | + unless @clear_disabled |
104 | 72 | @clear_disabled = true
|
| 73 | + ReloadedClassesFiltering.remove_method(:subclasses) |
| 74 | + ReloadedClassesFiltering.remove_method(:descendants) |
| 75 | + @excluded_descendants = nil |
105 | 76 | end
|
| 77 | + end |
106 | 78 |
|
107 |
| - def subclasses(klass) |
108 |
| - descendants = @@direct_descendants[klass] |
109 |
| - descendants ? descendants.to_a : [] |
110 |
| - end |
| 79 | + def clear(classes) # :nodoc: |
| 80 | + raise "DescendantsTracker.clear was disabled because config.enable_reloading is false" if @clear_disabled |
111 | 81 |
|
112 |
| - def descendants(klass) |
113 |
| - arr = [] |
114 |
| - accumulate_descendants(klass, arr) |
115 |
| - arr |
116 |
| - end |
117 |
| - |
118 |
| - def clear(classes) # :nodoc: |
119 |
| - raise "DescendantsTracker.clear was disabled because config.enable_reloading is false" if @clear_disabled |
120 |
| - |
121 |
| - @@direct_descendants.each do |klass, direct_descendants_of_klass| |
122 |
| - if classes.member?(klass) |
123 |
| - @@direct_descendants.delete(klass) |
124 |
| - else |
125 |
| - direct_descendants_of_klass.reject! do |direct_descendant_of_class| |
126 |
| - classes.member?(direct_descendant_of_class) |
127 |
| - end |
128 |
| - end |
| 82 | + classes.each do |klass| |
| 83 | + @excluded_descendants << klass |
| 84 | + klass.descendants.each do |descendant| |
| 85 | + @excluded_descendants << descendant |
129 | 86 | end
|
130 | 87 | end
|
131 |
| - |
132 |
| - def native? # :nodoc: |
133 |
| - false |
134 |
| - end |
135 |
| - |
136 |
| - # This is the only method that is not thread safe, but is only ever called |
137 |
| - # during the eager loading phase. |
138 |
| - def store_inherited(klass, descendant) |
139 |
| - (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant |
140 |
| - end |
141 |
| - |
142 |
| - private |
143 |
| - def accumulate_descendants(klass, acc) |
144 |
| - if direct_descendants = @@direct_descendants[klass] |
145 |
| - direct_descendants.each do |direct_descendant| |
146 |
| - acc << direct_descendant |
147 |
| - accumulate_descendants(direct_descendant, acc) |
148 |
| - end |
149 |
| - end |
150 |
| - end |
151 | 88 | end
|
152 | 89 |
|
153 |
| - def inherited(base) |
154 |
| - DescendantsTracker.store_inherited(self, base) |
155 |
| - super |
| 90 | + def reject!(classes) # :nodoc: |
| 91 | + if @excluded_descendants |
| 92 | + classes.reject! { |d| @excluded_descendants.include?(d) } |
| 93 | + end |
| 94 | + classes |
156 | 95 | end
|
| 96 | + end |
157 | 97 |
|
158 |
| - def direct_descendants |
159 |
| - ActiveSupport::Deprecation.warn(<<~MSG) |
160 |
| - ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1. |
161 |
| - Use #subclasses instead. |
162 |
| - MSG |
163 |
| - DescendantsTracker.subclasses(self) |
164 |
| - end |
| 98 | + def descendants |
| 99 | + subclasses = self.subclasses |
| 100 | + subclasses.concat(subclasses.flat_map(&:descendants)) |
| 101 | + end |
165 | 102 |
|
166 |
| - def subclasses |
167 |
| - DescendantsTracker.subclasses(self) |
168 |
| - end |
| 103 | + if RubyFeatures::CLASS_SUBCLASSES |
| 104 | + class << self |
| 105 | + def subclasses(klass) |
| 106 | + klass.subclasses |
| 107 | + end |
169 | 108 |
|
170 |
| - def descendants |
171 |
| - DescendantsTracker.descendants(self) |
| 109 | + def descendants(klass) |
| 110 | + klass.descendants |
| 111 | + end |
172 | 112 | end
|
173 |
| - |
| 113 | + else |
174 | 114 | # DescendantsArray is an array that contains weak references to classes.
|
| 115 | + # Note: DescendantsArray is redundant with WeakSet, however WeakSet when used |
| 116 | + # on Ruby 2.7 or 3.0 can trigger a Ruby crash: https://bugs.ruby-lang.org/issues/18928 |
175 | 117 | class DescendantsArray # :nodoc:
|
176 | 118 | include Enumerable
|
177 | 119 |
|
178 | 120 | def initialize
|
179 | 121 | @refs = []
|
180 | 122 | end
|
181 | 123 |
|
182 |
| - def initialize_copy(orig) |
183 |
| - @refs = @refs.dup |
184 |
| - end |
185 |
| - |
186 | 124 | def <<(klass)
|
187 | 125 | @refs << WeakRef.new(klass)
|
188 | 126 | end
|
@@ -213,6 +151,39 @@ def reject!
|
213 | 151 | end
|
214 | 152 | end
|
215 | 153 | end
|
| 154 | + |
| 155 | + @direct_descendants = {} |
| 156 | + |
| 157 | + def inherited(base) # :nodoc: |
| 158 | + DescendantsTracker.store_inherited(self, base) |
| 159 | + super |
| 160 | + end |
| 161 | + |
| 162 | + class << self |
| 163 | + def subclasses(klass) |
| 164 | + descendants = @direct_descendants[klass] |
| 165 | + descendants ? DescendantsTracker.reject!(descendants.to_a) : [] |
| 166 | + end |
| 167 | + |
| 168 | + def descendants(klass) |
| 169 | + subclasses = self.subclasses(klass) |
| 170 | + subclasses.concat(subclasses.flat_map { |k| descendants(k) }) |
| 171 | + end |
| 172 | + |
| 173 | + # This is the only method that is not thread safe, but is only ever called |
| 174 | + # during the eager loading phase. |
| 175 | + def store_inherited(klass, descendant) # :nodoc: |
| 176 | + (@direct_descendants[klass] ||= DescendantsArray.new) << descendant |
| 177 | + end |
| 178 | + end |
| 179 | + |
| 180 | + def subclasses |
| 181 | + DescendantsTracker.subclasses(self) |
| 182 | + end |
| 183 | + |
| 184 | + def descendants |
| 185 | + DescendantsTracker.descendants(self) |
| 186 | + end |
216 | 187 | end
|
217 | 188 | end
|
218 | 189 | end
|
0 commit comments