1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Use weak references in descendants tracker

It allows anonymous subclasses to be garbage collected.
This commit is contained in:
Edgars Beigarts 2017-12-14 12:37:51 +02:00
parent 15ca8ad0c1
commit 7432c9226e
No known key found for this signature in database
GPG key ID: E2754EDE79A28038
3 changed files with 64 additions and 4 deletions

View file

@ -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 * 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

View file

@ -1,5 +1,7 @@
# 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 ActiveSupport
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 @@ module ActiveSupport
# 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 @@ module ActiveSupport
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

View file

@ -27,6 +27,15 @@ module DescendantsTrackerTestCases
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