aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md5
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb54
-rw-r--r--activesupport/lib/active_support/security_utils.rb2
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb9
5 files changed, 66 insertions, 6 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 03c9dfc546..0551f0781f 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Use weak references in descendants tracker to allow anonymous subclasses to
+ be garbage collected.
+
+ *Edgars Beigarts*
+
* Update `ActiveSupport::Notifications::Instrumenter#instrument` to make
passing a block optional. This will let users use
`ActiveSupport::Notifications` messaging features outside of
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index be041944f6..bf0fe0f76d 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -34,5 +34,5 @@ Gem::Specification.new do |s|
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
- s.add_dependency "zeitwerk", "~> 1.4", ">= 1.4.2"
+ s.add_dependency "zeitwerk", "~> 1.4", ">= 1.4.3"
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 05236d3162..2dca990712 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "weakref"
+
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
@@ -8,7 +10,8 @@ module ActiveSupport
class << self
def direct_descendants(klass)
- @@direct_descendants[klass] || []
+ descendants = @@direct_descendants[klass]
+ descendants ? descendants.to_a : []
end
def descendants(klass)
@@ -34,15 +37,17 @@ module ActiveSupport
# 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
+ (@@direct_descendants[klass] ||= DescendantsArray.new) << 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) }
+ direct_descendants.each do |direct_descendant|
+ acc << direct_descendant
+ accumulate_descendants(direct_descendant, acc)
+ end
end
end
end
@@ -59,5 +64,46 @@ module ActiveSupport
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)
+ cleanup!
+ @refs << WeakRef.new(klass)
+ end
+
+ def each
+ @refs.each do |ref|
+ yield ref.__getobj__
+ rescue WeakRef::RefError
+ end
+ 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
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 20b6b9cd3f..5e455fca57 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -24,7 +24,7 @@ module ActiveSupport
# The values are first processed by SHA256, so that we don't leak length info
# via timing attacks.
def secure_compare(a, b)
- fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
+ fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
end
module_function :secure_compare
end
diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 2c94c3c56c..f8752688d2 100644
--- a/activesupport/test/descendants_tracker_test_cases.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -27,6 +27,15 @@ module DescendantsTrackerTestCases
assert_equal_sets [], Child2.descendants
end
+ def test_descendants_with_garbage_collected_classes
+ 1.times do
+ child_klass = Class.new(Parent)
+ assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2, child_klass], Parent.descendants
+ end
+ GC.start
+ assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ end
+
def test_direct_descendants
assert_equal_sets [Child1, Child2], Parent.direct_descendants
assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants