weakref.rb

Replacement for weakref.rb - Brian Durand, 12/18/2010 01:57 AM

Download (5.2 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 = ActiveSupport::WeakReference.new(foo)
15
#   ref.alive?                        # should be true
16
#   ref.object                        # should be foo
17
#   ObjectSpace.garbage_collect
18
#   ref.alive?                        # should be false
19
#   ref.object                        # should be nil
20
class WeakReference
21
  attr_reader :referenced_object_id
22
  
23
  # Map of references to the object_id's they refer to.
24
  @@referenced_object_ids = {}
25

    
26
  # Map of object_ids to references to them.
27
  @@object_id_references = {}
28

    
29
  @@monitor = Monitor.new
30

    
31
  # Finalizer that cleans up weak references when an object is destroyed.
32
  @@object_finalizer = lambda do |object_id|
33
    @@monitor.synchronize do
34
      reference_ids = @@object_id_references[object_id]
35
      if reference_ids
36
              reference_ids.each do |reference_object_id|
37
                @@referenced_object_ids.delete(reference_object_id)
38
              end
39
              @@object_id_references.delete(object_id)
40
            end
41
    end
42
  end
43

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

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

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

    
99
  private
100

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

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

    
116
require "delegate"
117

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

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