diff options
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG.md | 5 | ||||
-rw-r--r-- | activesupport/activesupport.gemspec | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/descendants_tracker.rb | 54 | ||||
-rw-r--r-- | activesupport/lib/active_support/security_utils.rb | 2 | ||||
-rw-r--r-- | activesupport/test/descendants_tracker_test_cases.rb | 9 |
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 |