-
Notifications
You must be signed in to change notification settings - Fork 21.4k
/
descendants_tracker.rb
218 lines (183 loc) · 5.8 KB
/
descendants_tracker.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# frozen_string_literal: true
require "weakref"
require "active_support/ruby_features"
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
module DescendantsTracker
class << self
def direct_descendants(klass)
ActiveSupport::Deprecation.warn(<<~MSG)
ActiveSupport::DescendantsTracker.direct_descendants is deprecated and will be removed in Rails 7.1.
Use ActiveSupport::DescendantsTracker.subclasses instead.
MSG
subclasses(klass)
end
end
@clear_disabled = false
if RubyFeatures::CLASS_SUBCLASSES
@@excluded_descendants = if RUBY_ENGINE == "ruby"
# On MRI `ObjectSpace::WeakMap` keys are weak references.
# So we can simply use WeakMap as a `Set`.
ObjectSpace::WeakMap.new
else
# On TruffleRuby `ObjectSpace::WeakMap` keys are strong references.
# So we use `object_id` as a key and the actual object as a value.
#
# JRuby for now doesn't have Class#descendant, but when it will, it will likely
# have the same WeakMap semantic than Truffle so we future proof this as much as possible.
class WeakSet # :nodoc:
def initialize
@map = ObjectSpace::WeakMap.new
end
def [](object)
@map.key?(object.object_id)
end
def []=(object, _present)
@map[object.object_id] = object
end
end
WeakSet.new
end
class << self
def disable_clear! # :nodoc:
unless @clear_disabled
@clear_disabled = true
remove_method(:subclasses)
@@excluded_descendants = nil
end
end
def subclasses(klass)
klass.subclasses
end
def descendants(klass)
klass.descendants
end
def clear(classes) # :nodoc:
raise "DescendantsTracker.clear was disabled because config.cache_classes = true" if @clear_disabled
classes.each do |klass|
@@excluded_descendants[klass] = true
klass.descendants.each do |descendant|
@@excluded_descendants[descendant] = true
end
end
end
def native? # :nodoc:
true
end
end
def subclasses
subclasses = super
subclasses.reject! { |d| @@excluded_descendants[d] }
subclasses
end
def descendants
subclasses.concat(subclasses.flat_map(&:descendants))
end
def direct_descendants
ActiveSupport::Deprecation.warn(<<~MSG)
ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1.
Use #subclasses instead.
MSG
subclasses
end
else
@@direct_descendants = {}
class << self
def disable_clear! # :nodoc:
@clear_disabled = true
end
def subclasses(klass)
descendants = @@direct_descendants[klass]
descendants ? descendants.to_a : []
end
def descendants(klass)
arr = []
accumulate_descendants(klass, arr)
arr
end
def clear(classes) # :nodoc:
raise "DescendantsTracker.clear was disabled because config.cache_classes = true" if @clear_disabled
@@direct_descendants.each do |klass, direct_descendants_of_klass|
if classes.member?(klass)
@@direct_descendants.delete(klass)
else
direct_descendants_of_klass.reject! do |direct_descendant_of_class|
classes.member?(direct_descendant_of_class)
end
end
end
end
def native? # :nodoc:
false
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] ||= DescendantsArray.new) << descendant
end
private
def accumulate_descendants(klass, acc)
if direct_descendants = @@direct_descendants[klass]
direct_descendants.each do |direct_descendant|
acc << direct_descendant
accumulate_descendants(direct_descendant, acc)
end
end
end
end
def inherited(base)
DescendantsTracker.store_inherited(self, base)
super
end
def direct_descendants
ActiveSupport::Deprecation.warn(<<~MSG)
ActiveSupport::DescendantsTracker#direct_descendants is deprecated and will be removed in Rails 7.1.
Use #subclasses instead.
MSG
DescendantsTracker.subclasses(self)
end
def subclasses
DescendantsTracker.subclasses(self)
end
def descendants
DescendantsTracker.descendants(self)
end
# DescendantsArray is an array that contains weak references to classes.
class DescendantsArray # :nodoc:
include Enumerable
def initialize
@refs = []
end
def initialize_copy(orig)
@refs = @refs.dup
end
def <<(klass)
@refs << WeakRef.new(klass)
end
def each
@refs.reject! do |ref|
yield ref.__getobj__
false
rescue WeakRef::RefError
true
end
self
end
def refs_size
@refs.size
end
def cleanup!
@refs.delete_if { |ref| !ref.weakref_alive? }
end
def reject!
@refs.reject! do |ref|
yield ref.__getobj__
rescue WeakRef::RefError
true
end
end
end
end
end
end