Project

General

Profile

Feature #4168 » weakref.rb

Replacement for weakref.rb without synchronization in the finalizers - bdurand (Brian Durand), 01/06/2011 03:00 AM

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

# Finalizer that cleans up weak references when references are destroyed.
@@reference_finalizer = lambda do |object_id|
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

# 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
(6-6/9)