ref 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +35 -0
- data/VERSION +1 -0
- data/ext/java/org/jruby/ext/ref/ReferencesService.java +28 -0
- data/ext/java/org/jruby/ext/ref/RubySoftReference.java +43 -0
- data/ext/java/org/jruby/ext/ref/RubyWeakReference.java +43 -0
- data/lib/org/jruby/ext/ref/references.jar +0 -0
- data/lib/ref.rb +39 -0
- data/lib/ref/abstract_reference_key_map.rb +117 -0
- data/lib/ref/abstract_reference_value_map.rb +127 -0
- data/lib/ref/mock.rb +145 -0
- data/lib/ref/reference.rb +24 -0
- data/lib/ref/reference_queue.rb +89 -0
- data/lib/ref/safe_monitor.rb +50 -0
- data/lib/ref/soft_key_map.rb +26 -0
- data/lib/ref/soft_reference.rb +67 -0
- data/lib/ref/soft_value_map.rb +28 -0
- data/lib/ref/strong_reference.rb +17 -0
- data/lib/ref/weak_key_map.rb +26 -0
- data/lib/ref/weak_reference.rb +26 -0
- data/lib/ref/weak_reference/iron_ruby.rb +14 -0
- data/lib/ref/weak_reference/pure_ruby.rb +91 -0
- data/lib/ref/weak_reference/weak_ref.rb +23 -0
- data/lib/ref/weak_value_map.rb +27 -0
- data/test/reference_key_map_behavior.rb +157 -0
- data/test/reference_key_map_behavior.rbc +4296 -0
- data/test/reference_queue_test.rb +60 -0
- data/test/reference_queue_test.rbc +1954 -0
- data/test/reference_value_map_behavior.rb +137 -0
- data/test/reference_value_map_behavior.rbc +3615 -0
- data/test/soft_key_map_test.rb +13 -0
- data/test/soft_key_map_test.rbc +374 -0
- data/test/soft_reference_test.rb +49 -0
- data/test/soft_reference_test.rbc +1481 -0
- data/test/soft_value_map_test.rb +13 -0
- data/test/soft_value_map_test.rbc +374 -0
- data/test/strong_reference_test.rb +15 -0
- data/test/strong_reference_test.rbc +562 -0
- data/test/test_helper.rb +4 -0
- data/test/weak_key_map_test.rb +13 -0
- data/test/weak_key_map_test.rbc +374 -0
- data/test/weak_reference_test.rb +46 -0
- data/test/weak_reference_test.rbc +1254 -0
- data/test/weak_value_map_test.rb +13 -0
- data/test/weak_value_map_test.rbc +374 -0
- metadata +113 -0
data/README.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
This library provides object references for Ruby as well as some common utilities for working with references. Object references are used to point to other objects and come in three distinct flavors that interact differently with the garbage collector.
|
2
|
+
|
3
|
+
* Ref::StrongReference - This is a plain old pointer to another object.
|
4
|
+
* Ref::WeakReference - This is a pointer to another object, but it is not seen by the garbage collector and the memory used by the object can be reclaimed at any time.
|
5
|
+
* Ref::SoftReference - This is similar to a weak reference, but the garbage collector is not as eager to reclaim the referenced object.
|
6
|
+
|
7
|
+
All of these classes extend from a common Ref::Reference class and have a common interface.
|
8
|
+
|
9
|
+
Weak and soft references are useful when you have instantiated objects that you may want to use again but can recreate if necessary. Since the garbage collector determines when to reclaim the memory used by the objects, you don't need to worry about bloating the Ruby heap.
|
10
|
+
|
11
|
+
= Example Usage
|
12
|
+
|
13
|
+
ref = Ref::WeakReference.new("hello")
|
14
|
+
ref.object # should be "hello"
|
15
|
+
ObjectSpace.garbage_collect
|
16
|
+
ref.object # should be nil (assuming the garbage collector reclaimed the reference)
|
17
|
+
|
18
|
+
= Goodies
|
19
|
+
|
20
|
+
This library also includes tools for some common uses of weak and soft references.
|
21
|
+
|
22
|
+
* Ref::WeakKeyMap - A map of keys to values where the keys are weak references
|
23
|
+
* Ref::WeakValueMap - A map of keys to values where the values are weak references
|
24
|
+
* Ref::SoftKeyMap - A map of keys to values where the keys are soft references
|
25
|
+
* Ref::SoftValueMap - A map of keys to values where the values are soft references
|
26
|
+
* Ref::ReferenceQueue - A thread safe implementation of a queue that will add references to itself as their objects are garbage collected.
|
27
|
+
|
28
|
+
= Problems with WeakRef
|
29
|
+
|
30
|
+
Ruby does come with the WeakRef class in the standard library. However, there are issues with this class across several different Ruby runtimes. This gem provides a common interface to weak references that works across MRI, Ruby Enterprise Edition, YARV, Jruby, Rubinius, and IronRuby.
|
31
|
+
|
32
|
+
1. MRI and REE 1.8 - WeakRef extends from Delegator which is a very heavy weight class under Ruby 1.8. Creating a WeakRef object will allocate thousands of other objects and use up hundreds of kilobytes of memory. This makes WeakRef all but unusable even if you only need several hundred of them.
|
33
|
+
2. YARV 1.9 - WeakRef is unsafe to use because the garbage collector can run in a different system thread than a thread allocating memory. This exposes a bug where a WeakRef may end up pointing to a completely different object than it originally referenced.
|
34
|
+
3. Jruby and IronRuby - Jruby and IronRuby using the Ruby 1.8 libraries suffers from the same performance issue with the Delegator class. Furthermore, these VM's don't implement the method used to load an object from the heap using an object id and so cannot use a pure Ruby method to implement weak references.
|
35
|
+
4. Rubinius - Rubinius implements WeakRef with a lighter weight version of delegation and works very well.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
package org.jruby.ext.ref;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
import org.jruby.Ruby;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyModule;
|
7
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
8
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
9
|
+
|
10
|
+
/**
|
11
|
+
* This library adds native Java support for weak and soft references.
|
12
|
+
*
|
13
|
+
* @author Brian Durand
|
14
|
+
*/
|
15
|
+
public class ReferencesService implements BasicLibraryService {
|
16
|
+
public boolean basicLoad(Ruby runtime) throws IOException {
|
17
|
+
RubyModule refModule = runtime.getModule("Ref");
|
18
|
+
RubyClass referenceClass = refModule.getClass("Reference");
|
19
|
+
|
20
|
+
RubyClass rubyWeakReferenceClass = runtime.defineClassUnder("WeakReference", referenceClass, RubyWeakReference.ALLOCATOR, refModule);
|
21
|
+
rubyWeakReferenceClass.defineAnnotatedMethods(RubyWeakReference.class);
|
22
|
+
|
23
|
+
RubyClass rubySoftReferenceClass = runtime.defineClassUnder("SoftReference", referenceClass, RubySoftReference.ALLOCATOR, refModule);
|
24
|
+
rubySoftReferenceClass.defineAnnotatedMethods(RubySoftReference.class);
|
25
|
+
|
26
|
+
return true;
|
27
|
+
}
|
28
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
package org.jruby.ext.ref;
|
2
|
+
|
3
|
+
import java.lang.ref.SoftReference;
|
4
|
+
import org.jruby.Ruby;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.anno.JRubyMethod;
|
8
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
9
|
+
import org.jruby.runtime.ObjectAllocator;
|
10
|
+
import org.jruby.runtime.ThreadContext;
|
11
|
+
import org.jruby.runtime.Visibility;
|
12
|
+
|
13
|
+
public class RubySoftReference extends RubyObject {
|
14
|
+
private SoftReference _ref;
|
15
|
+
private static final String REFERENCED_OBJECT_ID_VARIABLE = "@referenced_object_id".intern();
|
16
|
+
|
17
|
+
public RubySoftReference(Ruby runtime, RubyClass klass) {
|
18
|
+
super(runtime, klass);
|
19
|
+
}
|
20
|
+
|
21
|
+
public static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
22
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
23
|
+
return new RubySoftReference(runtime, klass);
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
@JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
|
28
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject obj) {
|
29
|
+
_ref = new SoftReference<IRubyObject>(obj);
|
30
|
+
fastSetInstanceVariable(REFERENCED_OBJECT_ID_VARIABLE, obj.id());
|
31
|
+
return context.getRuntime().getNil();
|
32
|
+
}
|
33
|
+
|
34
|
+
@JRubyMethod(name = "object")
|
35
|
+
public IRubyObject object() {
|
36
|
+
IRubyObject obj = (IRubyObject)_ref.get();
|
37
|
+
if (obj != null) {
|
38
|
+
return obj;
|
39
|
+
} else {
|
40
|
+
return getRuntime().getNil();
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
package org.jruby.ext.ref;
|
2
|
+
|
3
|
+
import java.lang.ref.WeakReference;
|
4
|
+
import org.jruby.Ruby;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.anno.JRubyMethod;
|
8
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
9
|
+
import org.jruby.runtime.ObjectAllocator;
|
10
|
+
import org.jruby.runtime.ThreadContext;
|
11
|
+
import org.jruby.runtime.Visibility;
|
12
|
+
|
13
|
+
public class RubyWeakReference extends RubyObject {
|
14
|
+
private WeakReference _ref;
|
15
|
+
private static final String REFERENCED_OBJECT_ID_VARIABLE = "@referenced_object_id".intern();
|
16
|
+
|
17
|
+
public RubyWeakReference(Ruby runtime, RubyClass klass) {
|
18
|
+
super(runtime, klass);
|
19
|
+
}
|
20
|
+
|
21
|
+
public static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
22
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
23
|
+
return new RubyWeakReference(runtime, klass);
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
@JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
|
28
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject obj) {
|
29
|
+
_ref = new WeakReference<IRubyObject>(obj);
|
30
|
+
fastSetInstanceVariable(REFERENCED_OBJECT_ID_VARIABLE, obj.id());
|
31
|
+
return context.getRuntime().getNil();
|
32
|
+
}
|
33
|
+
|
34
|
+
@JRubyMethod(name = "object")
|
35
|
+
public IRubyObject object() {
|
36
|
+
IRubyObject obj = (IRubyObject)_ref.get();
|
37
|
+
if (obj != null) {
|
38
|
+
return obj;
|
39
|
+
} else {
|
40
|
+
return getRuntime().getNil();
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
Binary file
|
data/lib/ref.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Ref
|
2
|
+
autoload :AbstractReferenceValueMap, File.join(File.dirname(__FILE__), "ref", "abstract_reference_value_map.rb")
|
3
|
+
autoload :AbstractReferenceKeyMap, File.join(File.dirname(__FILE__), "ref", "abstract_reference_key_map.rb")
|
4
|
+
autoload :Mock, File.join(File.dirname(__FILE__), "ref", "mock.rb")
|
5
|
+
autoload :Reference, File.join(File.dirname(__FILE__), "ref", "reference.rb")
|
6
|
+
autoload :ReferenceQueue, File.join(File.dirname(__FILE__), "ref", "reference_queue.rb")
|
7
|
+
autoload :SafeMonitor, File.join(File.dirname(__FILE__), "ref", "safe_monitor.rb")
|
8
|
+
autoload :SoftKeyMap, File.join(File.dirname(__FILE__), "ref", "soft_key_map.rb")
|
9
|
+
autoload :SoftValueMap, File.join(File.dirname(__FILE__), "ref", "soft_value_map.rb")
|
10
|
+
autoload :StrongReference, File.join(File.dirname(__FILE__), "ref", "strong_reference.rb")
|
11
|
+
autoload :WeakKeyMap, File.join(File.dirname(__FILE__), "ref", "weak_key_map.rb")
|
12
|
+
autoload :WeakValueMap, File.join(File.dirname(__FILE__), "ref", "weak_value_map.rb")
|
13
|
+
|
14
|
+
# Set the best implementation for weak references based on the runtime.
|
15
|
+
if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
|
16
|
+
# Use native Java references
|
17
|
+
begin
|
18
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
19
|
+
require 'org/jruby/ext/ref/references'
|
20
|
+
ensure
|
21
|
+
$LOAD_PATH.shift if $LOAD_PATH.first == File.dirname(__FILE__)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
autoload :SoftReference, File.join(File.dirname(__FILE__), "ref", "soft_reference.rb")
|
25
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
|
26
|
+
# IronRuby has it's own implementation of weak references.
|
27
|
+
autoload :WeakReference, File.join(File.dirname(__FILE__), "ref", "weak_reference", "iron_ruby.rb")
|
28
|
+
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
29
|
+
# If using Rubinius set the implementation to use WeakRef since it is very efficient and using finalizers is not.
|
30
|
+
autoload :WeakReference, File.join(File.dirname(__FILE__), "ref", "weak_reference", "weak_ref.rb")
|
31
|
+
elsif defined?(ObjectSpace._id2ref)
|
32
|
+
# If ObjectSpace can lookup objects from their object_id, then use the pure ruby implementation.
|
33
|
+
autoload :WeakReference, File.join(File.dirname(__FILE__), "ref", "weak_reference", "pure_ruby.rb")
|
34
|
+
else
|
35
|
+
# Otherwise, wrap the standard library WeakRef class
|
36
|
+
autoload :WeakReference, File.join(File.dirname(__FILE__), "ref", "weak_reference", "weak_ref.rb")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Ref
|
2
|
+
# Abstract base class for WeakKeyMap and SoftKeyMap.
|
3
|
+
#
|
4
|
+
# The classes behave similar to Hashes, but the keys in the map are not strong references
|
5
|
+
# and can be reclaimed by the garbage collector at any time. When a key is reclaimed, the
|
6
|
+
# map entry will be removed.
|
7
|
+
class AbstractReferenceKeyMap
|
8
|
+
class << self
|
9
|
+
def reference_class=(klass) #:nodoc:
|
10
|
+
@reference_class = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def reference_class #:nodoc:
|
14
|
+
raise NotImplementedError.new("#{name} is an abstract class and cannot be instantiated") unless @reference_class
|
15
|
+
@reference_class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a new map. Values added to the hash will be cleaned up by the garbage
|
20
|
+
# collector if there are no other reference except in the map.
|
21
|
+
def initialize
|
22
|
+
@values = {}
|
23
|
+
@references_to_keys_map = {}
|
24
|
+
@lock = SafeMonitor.new
|
25
|
+
@reference_cleanup = lambda{|object_id| remove_reference_to(object_id)}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get a value from the map by key. If the value has been reclaimed by the garbage
|
29
|
+
# collector, this will return nil.
|
30
|
+
def [](key)
|
31
|
+
rkey = ref_key(key)
|
32
|
+
@values[rkey] if rkey
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add a key/value to the map.
|
36
|
+
def []=(key, value)
|
37
|
+
ObjectSpace.define_finalizer(key, @reference_cleanup)
|
38
|
+
@lock.synchronize do
|
39
|
+
@references_to_keys_map[key.__id__] = self.class.reference_class.new(key)
|
40
|
+
@values[key.__id__] = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove the value associated with the key from the map.
|
45
|
+
def delete(key)
|
46
|
+
rkey = ref_key(key)
|
47
|
+
if rkey
|
48
|
+
@references_to_keys_map.delete(rkey)
|
49
|
+
@values.delete(rkey)
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get an array of keys that have not yet been garbage collected.
|
56
|
+
def keys
|
57
|
+
@values.keys.collect{|rkey| @references_to_keys_map[rkey].object}.compact
|
58
|
+
end
|
59
|
+
|
60
|
+
# Turn the map into an arry of [key, value] entries.
|
61
|
+
def to_a
|
62
|
+
array = []
|
63
|
+
each{|k,v| array << [k, v]}
|
64
|
+
array
|
65
|
+
end
|
66
|
+
|
67
|
+
# Iterate through all the key/value pairs in the map that have not been reclaimed
|
68
|
+
# by the garbage collector.
|
69
|
+
def each
|
70
|
+
@references_to_keys_map.each do |rkey, ref|
|
71
|
+
key = ref.object
|
72
|
+
yield(key, @values[rkey]) if key
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Clear the map of all key/value pairs.
|
77
|
+
def clear
|
78
|
+
@lock.synchronize do
|
79
|
+
@values.clear
|
80
|
+
@references_to_keys_map.clear
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Merge the values from another hash into this map.
|
85
|
+
def merge!(other_hash)
|
86
|
+
other_hash.each do |key, value|
|
87
|
+
self[key] = value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect
|
92
|
+
live_entries = {}
|
93
|
+
each do |key, value|
|
94
|
+
live_entries[key] = value
|
95
|
+
end
|
96
|
+
live_entries.inspect
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def ref_key (key)
|
102
|
+
ref = @references_to_keys_map[key.__id__]
|
103
|
+
if ref && ref.object
|
104
|
+
ref.referenced_object_id
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def remove_reference_to(object_id)
|
111
|
+
@lock.synchronize do
|
112
|
+
@references_to_keys_map.delete(object_id)
|
113
|
+
@values.delete(object_id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Ref
|
2
|
+
# Abstract base class for WeakValueMap and SoftValueMap.
|
3
|
+
#
|
4
|
+
# The classes behave similar to Hashes, but the values in the map are not strong references
|
5
|
+
# and can be reclaimed by the garbage collector at any time. When a value is reclaimed, the
|
6
|
+
# map entry will be removed.
|
7
|
+
class AbstractReferenceValueMap
|
8
|
+
class << self
|
9
|
+
def reference_class=(klass) #:nodoc:
|
10
|
+
@reference_class = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def reference_class #:nodoc:
|
14
|
+
raise NotImplementedError.new("#{name} is an abstract class and cannot be instantiated") unless @reference_class
|
15
|
+
@reference_class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a new map. Values added to the map will be cleaned up by the garbage
|
20
|
+
# collector if there are no other reference except in the map.
|
21
|
+
def initialize
|
22
|
+
@references = {}
|
23
|
+
@references_to_keys_map = {}
|
24
|
+
@lock = SafeMonitor.new
|
25
|
+
@reference_cleanup = lambda{|object_id| remove_reference_to(object_id)}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get a value from the map by key. If the value has been reclaimed by the garbage
|
29
|
+
# collector, this will return nil.
|
30
|
+
def [](key)
|
31
|
+
ref = @references[key]
|
32
|
+
value = ref.object if ref
|
33
|
+
value
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add a key/value to the map.
|
37
|
+
def []=(key, value)
|
38
|
+
ObjectSpace.define_finalizer(value, @reference_cleanup)
|
39
|
+
key = key.dup if key.is_a?(String)
|
40
|
+
@lock.synchronize do
|
41
|
+
@references[key] = self.class.reference_class.new(value)
|
42
|
+
keys_for_id = @references_to_keys_map[value.__id__]
|
43
|
+
unless keys_for_id
|
44
|
+
keys_for_id = []
|
45
|
+
@references_to_keys_map[value.__id__] = keys_for_id
|
46
|
+
end
|
47
|
+
keys_for_id << key
|
48
|
+
end
|
49
|
+
value
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove the entry associated with the key from the map.
|
53
|
+
def delete(key)
|
54
|
+
ref = @references.delete(key)
|
55
|
+
if ref
|
56
|
+
keys_to_id = @references_to_keys_map[ref.referenced_object_id]
|
57
|
+
if keys_to_id
|
58
|
+
keys_to_id.delete(key)
|
59
|
+
@references_to_keys_map.delete(ref.referenced_object_id) if keys_to_id.empty?
|
60
|
+
end
|
61
|
+
ref.object
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the list of all values that have not yet been garbage collected.
|
68
|
+
def values
|
69
|
+
vals = []
|
70
|
+
each{|k,v| vals << v}
|
71
|
+
vals
|
72
|
+
end
|
73
|
+
|
74
|
+
# Turn the map into an arry of [key, value] entries
|
75
|
+
def to_a
|
76
|
+
array = []
|
77
|
+
each{|k,v| array << [k, v]}
|
78
|
+
array
|
79
|
+
end
|
80
|
+
|
81
|
+
# Iterate through all the key/value pairs in the map that have not been reclaimed
|
82
|
+
# by the garbage collector.
|
83
|
+
def each
|
84
|
+
@references.each do |key, ref|
|
85
|
+
value = ref.object
|
86
|
+
yield(key, value) if value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Clear the map of all key/value pairs.
|
91
|
+
def clear
|
92
|
+
@lock.synchronize do
|
93
|
+
@references.clear
|
94
|
+
@references_to_keys_map.clear
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Merge the values from another hash into this map.
|
99
|
+
def merge!(other_hash)
|
100
|
+
other_hash.each do |key, value|
|
101
|
+
self[key] = value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
live_entries = {}
|
107
|
+
each do |key, value|
|
108
|
+
live_entries[key] = value
|
109
|
+
end
|
110
|
+
live_entries.inspect
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def remove_reference_to(object_id)
|
116
|
+
@lock.synchronize do
|
117
|
+
keys = @references_to_keys_map[object_id]
|
118
|
+
if keys
|
119
|
+
keys.each do |key|
|
120
|
+
@references.delete(key)
|
121
|
+
end
|
122
|
+
@references_to_keys_map.delete(object_id)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/ref/mock.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
module Ref
|
2
|
+
# This module provides mock weak and strong references that are designed to be
|
3
|
+
# used in tests. You can define a block where all weak and soft references created
|
4
|
+
# will be mock references. You can then mimic running the garbage collector on
|
5
|
+
# the objects pointed to by the references.
|
6
|
+
#
|
7
|
+
# Example usage:
|
8
|
+
#
|
9
|
+
# References::Mock.use do
|
10
|
+
# obj = Object.new
|
11
|
+
# ref = References::WeakReference.new(obj)
|
12
|
+
# ref.object # obj
|
13
|
+
# References::Mock.gc(obj) # mimics the garbage collector reclaiming the referenced object
|
14
|
+
# ref.object # nil
|
15
|
+
# end
|
16
|
+
module Mock
|
17
|
+
class << self
|
18
|
+
# Use the mock implementation inside a block and then restore the original implementation.
|
19
|
+
def use
|
20
|
+
if object_space
|
21
|
+
yield
|
22
|
+
else
|
23
|
+
setup
|
24
|
+
begin
|
25
|
+
yield
|
26
|
+
ensure
|
27
|
+
cleanup
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Start using mock references.
|
33
|
+
def setup
|
34
|
+
raise "Ref::Mock already setup" if object_space
|
35
|
+
|
36
|
+
@object_space = {}
|
37
|
+
|
38
|
+
class << ObjectSpace
|
39
|
+
unless method_defined?(:define_finalizer_with_mock_reference)
|
40
|
+
def define_finalizer_with_mock_reference(obj, finalizer)
|
41
|
+
if ::Ref::Mock.object_space.include?(obj.__id__)
|
42
|
+
::Ref::Mock.object_space[obj.__id__] << finalizer
|
43
|
+
else
|
44
|
+
define_finalizer_without_mock_reference(obj, finalizer)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :define_finalizer_without_mock_reference, :define_finalizer
|
50
|
+
alias_method :define_finalizer, :define_finalizer_with_mock_reference
|
51
|
+
end
|
52
|
+
|
53
|
+
class << WeakReference
|
54
|
+
unless method_defined?(:new_with_mock_reference)
|
55
|
+
def new_with_mock_reference(obj)
|
56
|
+
if self == Mock::MockWeakReference
|
57
|
+
new_without_mock_reference(obj)
|
58
|
+
else
|
59
|
+
Mock::MockWeakReference.new(obj)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_method :new_without_mock_reference, :new
|
65
|
+
alias_method :new, :new_with_mock_reference
|
66
|
+
end
|
67
|
+
|
68
|
+
class << SoftReference
|
69
|
+
unless method_defined?(:new_with_mock_reference)
|
70
|
+
def new_with_mock_reference(obj)
|
71
|
+
if self == Mock::MockSoftReference
|
72
|
+
new_without_mock_reference(obj)
|
73
|
+
else
|
74
|
+
Mock::MockSoftReference.new(obj)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :new_without_mock_reference, :new
|
80
|
+
alias_method :new, :new_with_mock_reference
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Stop using mock references.
|
85
|
+
def cleanup
|
86
|
+
@object_space = nil
|
87
|
+
class << ObjectSpace
|
88
|
+
alias_method :define_finalizer_with_mock_reference, :define_finalizer
|
89
|
+
alias_method :define_finalizer, :define_finalizer_without_mock_reference
|
90
|
+
end
|
91
|
+
|
92
|
+
class << WeakReference
|
93
|
+
alias_method :new_with_mock_reference, :new
|
94
|
+
alias_method :new, :new_without_mock_reference
|
95
|
+
end
|
96
|
+
|
97
|
+
class << SoftReference
|
98
|
+
alias_method :new_with_mock_reference, :new
|
99
|
+
alias_method :new, :new_without_mock_reference
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def object_space # :nodoc:
|
104
|
+
@object_space if instance_variable_defined?(:@object_space)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Simulate garbage collection of the objects passed in as arguments. If no objects
|
108
|
+
# are specified, all objects will be reclaimed.
|
109
|
+
def gc(*objects)
|
110
|
+
objects = object_space.keys if objects.empty?
|
111
|
+
objects.each do |obj|
|
112
|
+
finalizers = object_space.delete(obj.__id__)
|
113
|
+
if finalizers
|
114
|
+
finalizers.each{|finalizer| finalizer.call(obj.__id__)}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module MockReference #:nodoc:
|
121
|
+
def initialize(obj)
|
122
|
+
@object = obj
|
123
|
+
@referenced_object_id = obj.__id__
|
124
|
+
raise "Reference::Mock not setup yet" unless Mock.object_space
|
125
|
+
Mock.object_space[obj.__id__] ||= []
|
126
|
+
end
|
127
|
+
|
128
|
+
def object
|
129
|
+
if @object && Mock.object_space.include?(@object.__id__)
|
130
|
+
@object
|
131
|
+
else
|
132
|
+
@object = nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class MockWeakReference < WeakReference #:nodoc:
|
138
|
+
include MockReference
|
139
|
+
end
|
140
|
+
|
141
|
+
class MockSoftReference < SoftReference #:nodoc:
|
142
|
+
include MockReference
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|