weakref.rb

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

Download (4.97 KB)

 
1
require 'monitor'
2

    
3
# WeakReference is a class to represent a reference to an object that is not seen by
4
# the tracing phase of the garbage collector.  This allows the referenced
5
# object to be garbage collected as if nothing is referring to it.
6
#
7
# The difference between this class and WeakRef is that this class does not
8
# use the delegator pattern and so has an interface more suited for detecting
9
# if the referenced object has been reclaimed.
10
#
11
# Usage:
12
#
13
#   foo = Object.new
14
#   ref = WeakReference.new(foo)
15
#   ref.object                        # should be foo
16
#   ObjectSpace.garbage_collect
17
#   ref.object                        # should be nil
18
class WeakReference
19
  attr_reader :referenced_object_id
20
  
21
  # Map of references to the object_id's they refer to.
22
  @@referenced_object_ids = {}
23

    
24
  # Map of object_ids to references to them.
25
  @@object_id_references = {}
26

    
27
  @@monitor = Monitor.new
28

    
29
  # Finalizer that cleans up weak references when an object is destroyed.
30
  @@object_finalizer = lambda do |object_id|
31
    reference_ids = @@object_id_references.delete(object_id)
32
    if reference_ids
33
            reference_ids.each do |reference_object_id|
34
              @@referenced_object_ids.delete(reference_object_id)
35
            end
36
          end
37
  end
38

    
39
  # Finalizer that cleans up weak references when references are destroyed.
40
  @@reference_finalizer = lambda do |object_id|
41
    referenced_id = @@referenced_object_ids.delete(object_id)
42
    if referenced_id
43
      obj = ObjectSpace._id2ref(referenced_object_id) rescue nil
44
      if obj
45
        backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
46
        if backreferences
47
          backreferences.delete(object_id)
48
          obj.send(:remove_instance_variable, :@__weak_backreferences__) if backreferences.empty?
49
        end
50
      end
51
      references = @@object_id_references[referenced_id]
52
      if references
53
        references.delete(object_id)
54
              @@object_id_references.delete(referenced_id) if references.empty?
55
            end
56
          end
57
  end
58

    
59
  # Create a new weak reference to an object. The existence of the weak reference
60
  # will not prevent the garbage collector from reclaiming the referenced object.
61
  def initialize(obj)
62
    @referenced_object_id = obj.__id__
63
    ObjectSpace.define_finalizer(obj, @@object_finalizer)
64
    ObjectSpace.define_finalizer(self, @@reference_finalizer)
65
    @@monitor.synchronize do
66
      @@referenced_object_ids[self.__id__] = obj.__id__
67
      add_backreference(obj)
68
      references = @@object_id_references[obj.__id__]
69
      unless references
70
        references = []
71
        @@object_id_references[obj.__id__] = references
72
      end
73
      references.push(self.__id__)
74
    end
75
  end
76

    
77
  # Get the reference object. If the object has already been garbage collected,
78
  # then this method will return nil.
79
  def object
80
    obj = nil
81
    begin
82
      if referenced_object_id == @@referenced_object_ids[self.object_id]
83
        obj = ObjectSpace._id2ref(referenced_object_id)
84
        obj = nil unless verify_backreferences(obj)
85
      end
86
    rescue RangeError
87
      # Object has been garbage collected.
88
    end
89
    obj
90
  end
91

    
92
  private
93

    
94
    def add_backreference(obj)
95
      backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
96
      unless backreferences
97
        backreferences = []
98
        obj.instance_variable_set(:@__weak_backreferences__, backreferences)
99
      end
100
      backreferences << object_id
101
    end
102

    
103
    def verify_backreferences(obj)
104
      backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
105
      backreferences && backreferences.include?(object_id)
106
    end
107
end
108

    
109
require "delegate"
110

    
111
# WeakRef is a class to represent a reference to an object that is not seen by the tracing
112
# phase of the garbage collector. This allows the referenced object to be garbage collected
113
# as if nothing is referring to it. Because WeakRef delegates method calls to the referenced
114
# object, it may be used in place of that object, i.e. it is of the same duck type.
115
#
116
# If you don't need to use the delegator pattern, you can use WeakReference instead.
117
#
118
# Usage:
119
#   foo = Object.new
120
#   foo = Object.new
121
#   p foo.to_s                        # original's class
122
#   foo = WeakRef.new(foo)
123
#   p foo.to_s                        # should be same class
124
#   ObjectSpace.garbage_collect
125
#   p foo.to_s                        # should raise exception (recycled)
126
class WeakRef < Delegator
127
  class RefError < StandardError
128
  end
129
  
130
  def initialize(obj)
131
    super
132
  end
133
  
134
  def __getobj__
135
    obj = @reference.object
136
    Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(1)) unless obj
137
    obj
138
  end
139
  
140
  def __setobj__(obj)
141
    @reference = WeakReference.new(obj)
142
  end
143
  
144
  def weakref_alive?
145
    !!@reference.object
146
  end
147
end
148

    
149
if __FILE__ == $0
150
  foo = Object.new
151
  p foo.to_s                        # original's class
152
  foo = WeakRef.new(foo)
153
  p foo.to_s                        # should be same class
154
  ObjectSpace.garbage_collect
155
  ObjectSpace.garbage_collect
156
  p foo.to_s                        # should raise exception (recycled)
157
end