|
require 'monitor'
|
|
|
|
# WeakReference is a class to represent a reference to an object that is not seen by
|
|
# the tracing phase of the garbage collector. This allows the referenced
|
|
# object to be garbage collected as if nothing is referring to it.
|
|
#
|
|
# The difference between this class and WeakRef is that this class does not
|
|
# use the delegator pattern and so has an interface more suited for detecting
|
|
# if the referenced object has been reclaimed.
|
|
#
|
|
# Usage:
|
|
#
|
|
# foo = Object.new
|
|
# ref = WeakReference.new(foo)
|
|
# ref.object # should be foo
|
|
# ObjectSpace.garbage_collect
|
|
# ref.object # should be nil
|
|
class WeakReference
|
|
attr_reader :referenced_object_id
|
|
|
|
# Map of references to the object_id's they refer to.
|
|
@@referenced_object_ids = {}
|
|
|
|
# Map of object_ids to references to them.
|
|
@@object_id_references = {}
|
|
|
|
@@monitor = Monitor.new
|
|
|
|
# Finalizer that cleans up weak references when an object is destroyed.
|
|
@@object_finalizer = lambda do |object_id|
|
|
@@monitor.synchronize do
|
|
reference_ids = @@object_id_references.delete(object_id)
|
|
if reference_ids
|
|
reference_ids.each do |reference_object_id|
|
|
@@referenced_object_ids.delete(reference_object_id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Finalizer that cleans up weak references when references are destroyed.
|
|
@@reference_finalizer = lambda do |object_id|
|
|
@@monitor.synchronize do
|
|
referenced_id = @@referenced_object_ids.delete(object_id)
|
|
if referenced_id
|
|
obj = ObjectSpace._id2ref(referenced_object_id) rescue nil
|
|
if obj
|
|
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
|
|
if backreferences
|
|
backreferences.delete(object_id)
|
|
obj.send(:remove_instance_variable, :@__weak_backreferences__) if backreferences.empty?
|
|
end
|
|
end
|
|
references = @@object_id_references[referenced_id]
|
|
if references
|
|
references.delete(object_id)
|
|
@@object_id_references.delete(referenced_id) if references.empty?
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# Create a new weak reference to an object. The existence of the weak reference
|
|
# will not prevent the garbage collector from reclaiming the referenced object.
|
|
def initialize(obj)
|
|
@referenced_object_id = obj.__id__
|
|
ObjectSpace.define_finalizer(obj, @@object_finalizer)
|
|
ObjectSpace.define_finalizer(self, @@reference_finalizer)
|
|
@@monitor.synchronize do
|
|
@@referenced_object_ids[self.__id__] = obj.__id__
|
|
add_backreference(obj)
|
|
references = @@object_id_references[obj.__id__]
|
|
unless references
|
|
references = []
|
|
@@object_id_references[obj.__id__] = references
|
|
end
|
|
references.push(self.__id__)
|
|
end
|
|
end
|
|
|
|
# Get the reference object. If the object has already been garbage collected,
|
|
# then this method will return nil.
|
|
def object
|
|
obj = nil
|
|
begin
|
|
if referenced_object_id == @@referenced_object_ids[self.object_id]
|
|
obj = ObjectSpace._id2ref(referenced_object_id)
|
|
obj = nil unless verify_backreferences(obj)
|
|
end
|
|
rescue RangeError
|
|
# Object has been garbage collected.
|
|
end
|
|
obj
|
|
end
|
|
|
|
private
|
|
|
|
def add_backreference(obj)
|
|
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
|
|
unless backreferences
|
|
backreferences = []
|
|
obj.instance_variable_set(:@__weak_backreferences__, backreferences)
|
|
end
|
|
backreferences << object_id
|
|
end
|
|
|
|
def verify_backreferences(obj)
|
|
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
|
|
backreferences && backreferences.include?(object_id)
|
|
end
|
|
end
|
|
|
|
require "delegate"
|
|
|
|
# WeakRef is a class to represent a reference to an object that is not seen by the tracing
|
|
# phase of the garbage collector. This allows the referenced object to be garbage collected
|
|
# as if nothing is referring to it. Because WeakRef delegates method calls to the referenced
|
|
# object, it may be used in place of that object, i.e. it is of the same duck type.
|
|
#
|
|
# If you don't need to use the delegator pattern, you can use WeakReference instead.
|
|
#
|
|
# Usage:
|
|
# foo = Object.new
|
|
# foo = Object.new
|
|
# p foo.to_s # original's class
|
|
# foo = WeakRef.new(foo)
|
|
# p foo.to_s # should be same class
|
|
# ObjectSpace.garbage_collect
|
|
# p foo.to_s # should raise exception (recycled)
|
|
class WeakRef < Delegator
|
|
class RefError < StandardError
|
|
end
|
|
|
|
def initialize(obj)
|
|
super
|
|
end
|
|
|
|
def __getobj__
|
|
obj = @reference.object
|
|
Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(1)) unless obj
|
|
obj
|
|
end
|
|
|
|
def __setobj__(obj)
|
|
@reference = WeakReference.new(obj)
|
|
end
|
|
|
|
def weakref_alive?
|
|
!!@reference.object
|
|
end
|
|
end
|
|
|
|
if __FILE__ == $0
|
|
foo = Object.new
|
|
p foo.to_s # original's class
|
|
foo = WeakRef.new(foo)
|
|
p foo.to_s # should be same class
|
|
ObjectSpace.garbage_collect
|
|
ObjectSpace.garbage_collect
|
|
p foo.to_s # should raise exception (recycled)
|
|
end
|