Skip to content
This repository was archived by the owner on Mar 15, 2022. It is now read-only.

Commit 41788eb

Browse files
author
Brian Durand
committed
Rearchitect weak references
1 parent 070c6a8 commit 41788eb

32 files changed

+680
-555
lines changed

README.rdoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
This library provides implementations of various types of object references for Ruby as well as some common utilities for working with references. Reference objects are pretty much pointers, but they come in three distinct flavors that interact differently with the garbage collector.
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.
22

33
* References::StrongReference - This is a plain old pointer to another object.
44
* References::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.
55
* References::SoftReference - This is similar to a weak reference, but the garbage collector is not as eager to reclaim the referenced object.
66

7-
All of these classes extend from a common References::Reference class.
7+
All of these classes extend from a common References::Reference class and have a common interface.
88

99
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.
1010

Rakefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,17 @@ namespace :java do
4242
base_dir = File.dirname(__FILE__)
4343
tmp_dir = File.join(base_dir, "tmp")
4444
classes_dir = File.join(tmp_dir, "classes")
45+
jar_dir = File.join(base_dir, "lib", "org", "jruby", "ext", "references")
4546
FileUtils.rm_rf(classes_dir)
4647
ext_dir = File.join(base_dir, "ext", "java")
4748
source_files = FileList["#{base_dir}/**/*.java"]
4849
FileUtils.mkdir_p(classes_dir)
4950
`#{ENV['JAVA_HOME']}/bin/javac -classpath '#{"#{ENV['JRUBY_HOME']}/lib/jruby.jar"}' -d '#{classes_dir}' -sourcepath '#{ext_dir}' '#{source_files.join("' '")}'`
50-
`#{ENV['JAVA_HOME']}/bin/jar cf '#{base_dir}/lib/references/java_support.jar' -C '#{classes_dir}' references`
51+
if $? == 0
52+
FileUtils.rm_rf(jar_dir) if File.exist?(jar_dir)
53+
FileUtils.mkdir_p(jar_dir)
54+
`#{ENV['JAVA_HOME']}/bin/jar cf '#{File.join(jar_dir, 'reference.jar')}' -C '#{classes_dir}' org`
55+
end
5156
FileUtils.rm_rf(classes_dir)
5257
end
5358
end

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.1
1+
1.0.0

ext/java/references/JavaSupportService.java renamed to ext/java/references/ReferenceService.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
package references;
2-
3-
import references.weak_reference.JavaImpl;
1+
package org.jruby.ext.references;
42

53
import java.io.IOException;
64
import org.jruby.Ruby;
@@ -14,15 +12,14 @@
1412
*
1513
* @author Brian Durand
1614
*/
17-
public class JavaSupportService implements BasicLibraryService {
15+
public class ReferenceService implements BasicLibraryService {
1816
public boolean basicLoad(Ruby runtime) throws IOException {
1917
RubyModule referencesModule = runtime.getModule("References");
18+
RubyClass referenceClass = referencesModule.getClass("Reference");
2019

21-
RubyClass weakReferenceClass = referencesModule.getClass("WeakReference");
22-
RubyClass javaWeakReferenceClass = runtime.defineClassUnder("JavaImpl", runtime.getObject(), JavaImpl.ALLOCATOR, weakReferenceClass);
23-
javaWeakReferenceClass.defineAnnotatedMethods(JavaImpl.class);
20+
RubyClass rubyWeakReferenceClass = runtime.defineClassUnder("WeakReference", referenceClass, RubyWeakReference.ALLOCATOR, referencesModule);
21+
rubyWeakReferenceClass.defineAnnotatedMethods(RubyWeakReference.class);
2422

25-
RubyClass referenceClass = referencesModule.getClass("Reference");
2623
RubyClass rubySoftReferenceClass = runtime.defineClassUnder("SoftReference", referenceClass, RubySoftReference.ALLOCATOR, referencesModule);
2724
rubySoftReferenceClass.defineAnnotatedMethods(RubySoftReference.class);
2825

ext/java/references/RubySoftReference.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package references;
1+
package org.jruby.ext.references;
22

33
import java.lang.ref.SoftReference;
44
import org.jruby.Ruby;

ext/java/references/weak_reference/JavaImpl.java renamed to ext/java/references/RubyWeakReference.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package references.weak_reference;
1+
package org.jruby.ext.references;
22

33
import java.lang.ref.WeakReference;
44
import org.jruby.Ruby;
@@ -10,22 +10,24 @@
1010
import org.jruby.runtime.ThreadContext;
1111
import org.jruby.runtime.Visibility;
1212

13-
public class JavaImpl extends RubyObject {
13+
public class RubyWeakReference extends RubyObject {
1414
private WeakReference _ref;
15+
private static final String REFERENCED_OBJECT_ID_VARIABLE = "@referenced_object_id".intern();
1516

16-
public JavaImpl(Ruby runtime, RubyClass klass) {
17+
public RubyWeakReference(Ruby runtime, RubyClass klass) {
1718
super(runtime, klass);
1819
}
1920

2021
public static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
2122
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
22-
return new JavaImpl(runtime, klass);
23+
return new RubyWeakReference(runtime, klass);
2324
}
2425
};
2526

2627
@JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
2728
public IRubyObject initialize(ThreadContext context, IRubyObject obj) {
2829
_ref = new WeakReference<IRubyObject>(obj);
30+
fastSetInstanceVariable(REFERENCED_OBJECT_ID_VARIABLE, obj.id());
2931
return context.getRuntime().getNil();
3032
}
3133

4.59 KB
Binary file not shown.

lib/references.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,38 @@
33
module References
44
autoload :AbstractReferenceValueMap, File.join(File.dirname(__FILE__), "references", "abstract_reference_value_map.rb")
55
autoload :AbstractReferenceKeyMap, File.join(File.dirname(__FILE__), "references", "abstract_reference_key_map.rb")
6+
autoload :Mock, File.join(File.dirname(__FILE__), "references", "mock.rb")
67
autoload :Reference, File.join(File.dirname(__FILE__), "references", "reference.rb")
78
autoload :ReferenceQueue, File.join(File.dirname(__FILE__), "references", "reference_queue.rb")
89
autoload :SoftKeyMap, File.join(File.dirname(__FILE__), "references", "soft_key_map.rb")
910
autoload :SoftValueMap, File.join(File.dirname(__FILE__), "references", "soft_value_map.rb")
1011
autoload :StrongReference, File.join(File.dirname(__FILE__), "references", "strong_reference.rb")
11-
autoload :WeakReference, File.join(File.dirname(__FILE__), "references", "weak_reference.rb")
1212
autoload :WeakKeyMap, File.join(File.dirname(__FILE__), "references", "weak_key_map.rb")
1313
autoload :WeakValueMap, File.join(File.dirname(__FILE__), "references", "weak_value_map.rb")
1414

1515
# Set the best implementation for weak references based on the runtime.
1616
if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
17+
# Use native Java references
1718
begin
1819
$LOAD_PATH.unshift(File.dirname(__FILE__))
19-
require 'references/java_support'
20-
WeakReference.implementation = WeakReference::JavaImpl
20+
require 'org/jruby/ext/references/reference'
2121
ensure
2222
$LOAD_PATH.shift if $LOAD_PATH.first == File.dirname(__FILE__)
2323
end
2424
else
2525
autoload :SoftReference, File.join(File.dirname(__FILE__), "references", "soft_reference.rb")
2626
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
27-
WeakReference.implementation = WeakReference::IronRubyImpl
27+
# IronRuby has it's own implementation of weak references.
28+
autoload :WeakReference, File.join(File.dirname(__FILE__), "references", "weak_reference", "iron_ruby.rb")
2829
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
2930
# If using Rubinius set the implementation to use WeakRef since it is very efficient.
30-
require 'weakref'
31-
WeakReference.implementation = WeakReference::WeakRefImpl
31+
autoload :WeakReference, File.join(File.dirname(__FILE__), "references", "weak_reference", "weak_ref.rb")
32+
elsif defined?(ObjectSpace._id2ref)
33+
# If ObjectSpace can lookup objects from their object_id, then use the pure ruby implementation.
34+
autoload :WeakReference, File.join(File.dirname(__FILE__), "references", "weak_reference", "pure_ruby.rb")
3235
else
33-
WeakReference.implementation = WeakReference::RubyImpl
36+
# Otherwise, wrap the standard library WeakRef class
37+
autoload :WeakReference, File.join(File.dirname(__FILE__), "references", "weak_reference", "weak_ref.rb")
3438
end
3539
end
3640
end

lib/references/java_support.jar

-4.18 KB
Binary file not shown.

lib/references/mock.rb

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
module References
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 "References::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 Mock.object_space.include?(obj.__id__)
42+
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 == MockWeakReference
57+
new_without_mock_reference(obj)
58+
else
59+
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 == MockSoftReference
72+
new_without_mock_reference(obj)
73+
else
74+
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

0 commit comments

Comments
 (0)