weakref.rb

Fixed rdoc comments - Brian Durand, 12/18/2010 02:43 AM

Download (5.12 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
    @@monitor.synchronize do
32
      reference_ids = @@object_id_references[object_id]
33
      if reference_ids
34
              reference_ids.each do |reference_object_id|
35
                @@referenced_object_ids.delete(reference_object_id)
36
              end
37
              @@object_id_references.delete(object_id)
38
            end
39
    end
40
  end
41

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

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

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

    
97
  private
98

    
99
    def add_backreference(obj)
100
      backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
101
      unless backreferences
102
        backreferences = []
103
        obj.instance_variable_set(:@__weak_backreferences__, backreferences)
104
      end
105
      backreferences << object_id
106
    end
107

    
108
    def verify_backreferences(obj)
109
      backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
110
      backreferences && backreferences.include?(object_id)
111
    end
112
end
113

    
114
require "delegate"
115

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

    
154
if __FILE__ == $0
155
  foo = Object.new
156
  p foo.to_s                        # original's class
157
  foo = WeakRef.new(foo)
158
  p foo.to_s                        # should be same class
159
  ObjectSpace.garbage_collect
160
  ObjectSpace.garbage_collect
161
  p foo.to_s                        # should raise exception (recycled)
162
end