weakref.rb

Reverted replacement for weakref.rb with WeakReference - Brian Durand, 01/11/2011 12:43 AM

Download (5.09 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.delete(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
            end
38
          end
39
  end
40

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

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

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

    
96
  private
97

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

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

    
113
require "delegate"
114

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

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