Use weak references in descendants tracker

It allows anonymous subclasses to be garbage collected.
上级 15ca8ad0
* Use weak references in descendants tracker to allow anonymous subclasses to
be garbage collected.
*Edgars Beigarts*
* Update `ActiveSupport::Notifications::Instrumenter#instrument` to make * Update `ActiveSupport::Notifications::Instrumenter#instrument` to make
passing a block optional. This will let users use passing a block optional. This will let users use
`ActiveSupport::Notifications` messaging features outside of `ActiveSupport::Notifications` messaging features outside of
......
# frozen_string_literal: true # frozen_string_literal: true
require "weakref"
module ActiveSupport module ActiveSupport
# This module provides an internal implementation to track descendants # This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace. # which is faster than iterating through ObjectSpace.
...@@ -8,7 +10,8 @@ module DescendantsTracker ...@@ -8,7 +10,8 @@ module DescendantsTracker
class << self class << self
def direct_descendants(klass) def direct_descendants(klass)
@@direct_descendants[klass] || [] descendants = @@direct_descendants[klass]
descendants ? descendants.to_a : []
end end
def descendants(klass) def descendants(klass)
...@@ -34,15 +37,17 @@ def clear ...@@ -34,15 +37,17 @@ def clear
# This is the only method that is not thread safe, but is only ever called # This is the only method that is not thread safe, but is only ever called
# during the eager loading phase. # during the eager loading phase.
def store_inherited(klass, descendant) def store_inherited(klass, descendant)
(@@direct_descendants[klass] ||= []) << descendant (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant
end end
private private
def accumulate_descendants(klass, acc) def accumulate_descendants(klass, acc)
if direct_descendants = @@direct_descendants[klass] if direct_descendants = @@direct_descendants[klass]
acc.concat(direct_descendants) direct_descendants.each do |direct_descendant|
direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } acc << direct_descendant
accumulate_descendants(direct_descendant, acc)
end
end end
end end
end end
...@@ -59,5 +64,46 @@ def direct_descendants ...@@ -59,5 +64,46 @@ def direct_descendants
def descendants def descendants
DescendantsTracker.descendants(self) DescendantsTracker.descendants(self)
end 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
end end
...@@ -27,6 +27,15 @@ def test_descendants ...@@ -27,6 +27,15 @@ def test_descendants
assert_equal_sets [], Child2.descendants assert_equal_sets [], Child2.descendants
end 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 def test_direct_descendants
assert_equal_sets [Child1, Child2], Parent.direct_descendants assert_equal_sets [Child1, Child2], Parent.direct_descendants
assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册