1 | require "delegate"
|
---|
2 |
|
---|
3 | # WeakRef 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. Because
|
---|
6 | # WeakRef delegates method calls to the referenced object, it may be used in
|
---|
7 | # place of that object, i.e. it is of the same duck type.
|
---|
8 | #
|
---|
9 | # Usage:
|
---|
10 | #
|
---|
11 | # foo = Object.new
|
---|
12 | # foo = Object.new
|
---|
13 | # p foo.to_s # original's class
|
---|
14 | # foo = WeakRef.new(foo)
|
---|
15 | # p foo.to_s # should be same class
|
---|
16 | # ObjectSpace.garbage_collect
|
---|
17 | # p foo.to_s # should raise exception (recycled)
|
---|
18 | class WeakRef<Delegator
|
---|
19 |
|
---|
20 | # RefError is raised if an object cannot be referenced by a WeakRef.
|
---|
21 | class RefError<StandardError
|
---|
22 | end
|
---|
23 |
|
---|
24 | @@id_map = {} # obj -> [ref,...]
|
---|
25 | @@id_rev_map = {} # ref -> obj
|
---|
26 | @@final = lambda{|id|
|
---|
27 | __old_status = Thread.critical
|
---|
28 | Thread.critical = true
|
---|
29 | begin
|
---|
30 | rids = @@id_map[id]
|
---|
31 | if rids
|
---|
32 | for rid in rids
|
---|
33 | @@id_rev_map.delete(rid)
|
---|
34 | end
|
---|
35 | @@id_map.delete(id)
|
---|
36 | end
|
---|
37 | rid = @@id_rev_map[id]
|
---|
38 | if rid
|
---|
39 | @@id_rev_map.delete(id)
|
---|
40 | @@id_map[rid].delete(id)
|
---|
41 | @@id_map.delete(rid) if @@id_map[rid].empty?
|
---|
42 | end
|
---|
43 | ensure
|
---|
44 | Thread.critical = __old_status
|
---|
45 | end
|
---|
46 | }
|
---|
47 |
|
---|
48 | # Create a new WeakRef from +orig+.
|
---|
49 | def initialize(orig)
|
---|
50 | super
|
---|
51 | __setobj__(orig)
|
---|
52 | end
|
---|
53 |
|
---|
54 | # Return the object this WeakRef references. Raises RefError if the object
|
---|
55 | # has been garbage collected. The object returned is the object to which
|
---|
56 | # method calls are delegated (see Delegator).
|
---|
57 | def __getobj__
|
---|
58 | unless @@id_rev_map[self.__id__] == @__id
|
---|
59 | raise RefError, "Illegal Reference - probably recycled", caller(2)
|
---|
60 | end
|
---|
61 | begin
|
---|
62 | ObjectSpace._id2ref(@__id)
|
---|
63 | rescue RangeError
|
---|
64 | raise RefError, "Illegal Reference - probably recycled", caller(2)
|
---|
65 | end
|
---|
66 | end
|
---|
67 |
|
---|
68 | def __setobj__(obj)
|
---|
69 | @__id = obj.__id__
|
---|
70 | __old_status = Thread.critical
|
---|
71 | begin
|
---|
72 | Thread.critical = true
|
---|
73 | unless @@id_rev_map.key?(self)
|
---|
74 | ObjectSpace.define_finalizer obj, @@final
|
---|
75 | ObjectSpace.define_finalizer self, @@final
|
---|
76 | end
|
---|
77 | @@id_map[@__id] = [] unless @@id_map[@__id]
|
---|
78 | ensure
|
---|
79 | Thread.critical = __old_status
|
---|
80 | end
|
---|
81 | @@id_map[@__id].push self.__id__
|
---|
82 | @@id_rev_map[self.__id__] = @__id
|
---|
83 | end
|
---|
84 |
|
---|
85 | # Returns true if the referenced object still exists, and false if it has
|
---|
86 | # been garbage collected.
|
---|
87 | def weakref_alive?
|
---|
88 | @@id_rev_map[self.__id__] == @__id
|
---|
89 | end
|
---|
90 | end
|
---|
91 |
|
---|
92 | if __FILE__ == $0
|
---|
93 | require 'thread'
|
---|
94 | foo = Object.new
|
---|
95 | p foo.to_s # original's class
|
---|
96 | foo = WeakRef.new(foo)
|
---|
97 | p foo.to_s # should be same class
|
---|
98 | ObjectSpace.garbage_collect
|
---|
99 | p foo.to_s # should raise exception (recycled)
|
---|
100 | end
|
---|