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

Commit 45a115d

Browse files
committed
Adds weak_reference/pure_ruby back in order to keep MRI 1.9.3 compatibility.
1 parent d44a576 commit 45a115d

File tree

4 files changed

+110
-3
lines changed

4 files changed

+110
-3
lines changed

Gemfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ end
1010
group :testing do
1111
gem 'test-unit', '~> 3.0.9'
1212
gem 'rspec', '~> 3.1.0'
13-
#gem 'simplecov', '~> 0.8.2', :require => false
1413
gem 'coveralls', '~> 0.7.3', :require => false
1514
end
1615

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ This library also includes tools for some common uses of weak and soft reference
3636
Ruby does come with the `WeakRef` class in the standard library. However, there are [issues with this class](https://bugs.ruby-lang.org/issues/4168) across several different Ruby runtimes. This gem provides a common interface to weak references that works across MRI, Ruby Enterprise Edition, YARV, JRuby and Rubinius.
3737

3838
1. Rubinius - Rubinius implements `WeakRef` with a lighter weight version of delegation and works very well.
39-
2. MRI Ruby 2.0+ has a good implementation of `WeakRef`.
39+
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.
40+
3. MRI Ruby 2.0+ has a good implementation of `WeakRef`.
4041

lib/ref.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module Ref
2+
$LOAD_PATH.unshift(File.dirname(__FILE__))
3+
24
require 'ref/abstract_reference_value_map'
35
require 'ref/abstract_reference_key_map'
46
require 'ref/reference'
57
require 'ref/reference_queue'
68

79
if defined?(Java)
810
begin
9-
$LOAD_PATH.unshift(File.dirname(__FILE__))
1011
require 'ref_ext'
1112
require 'org/jruby/ext/ref/references'
1213
rescue LoadError
@@ -19,6 +20,12 @@ module Ref
1920
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
2021
# If using Rubinius set the implementation to use WeakRef since it is very efficient and using finalizers is not.
2122
require 'ref/weak_reference/weak_ref'
23+
elsif defined?(::ObjectSpace::WeakMap)
24+
# Ruby 2.0 has a working implementation of weakref.rb backed by the new ObjectSpace::WeakMap
25+
require 'ref/weak_reference/weak_ref'
26+
elsif defined?(::ObjectSpace._id2ref)
27+
# If ObjectSpace can lookup objects from their object_id, then use the pure ruby implementation.
28+
require 'ref/weak_reference/pure_ruby'
2229
else
2330
# Otherwise, wrap the standard library WeakRef class
2431
require 'ref/weak_reference/weak_ref'

lib/ref/weak_reference/pure_ruby.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
module Ref
2+
# This is a pure ruby implementation of a weak reference. It is much more
3+
# efficient than the WeakRef implementation bundled in MRI 1.8 and 1.9
4+
# subclass Delegator which is very heavy to instantiate and utilizes a
5+
# because it does not fair amount of memory under Ruby 1.8.
6+
class WeakReference < Reference
7+
8+
class ReferencePointer
9+
def initialize(object)
10+
@referenced_object_id = object.__id__
11+
add_backreference(object)
12+
end
13+
14+
def cleanup
15+
obj = ObjectSpace._id2ref(@referenced_object_id) rescue nil
16+
remove_backreference(obj) if obj
17+
end
18+
19+
def object
20+
obj = ObjectSpace._id2ref(@referenced_object_id)
21+
obj if verify_backreferences(obj)
22+
rescue RangeError
23+
nil
24+
end
25+
26+
private
27+
# Verify that the object is the same one originally set for the weak reference.
28+
def verify_backreferences(obj) #:nodoc:
29+
return nil unless supports_backreference?(obj)
30+
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
31+
backreferences && backreferences.include?(object_id)
32+
end
33+
34+
# Add a backreference to the object.
35+
def add_backreference(obj) #:nodoc:
36+
return unless supports_backreference?(obj)
37+
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
38+
unless backreferences
39+
backreferences = []
40+
obj.instance_variable_set(:@__weak_backreferences__, backreferences)
41+
end
42+
backreferences << object_id
43+
end
44+
45+
# Remove backreferences from the object.
46+
def remove_backreference(obj) #:nodoc:
47+
return unless supports_backreference?(obj)
48+
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
49+
if backreferences
50+
backreferences.dup.delete(object_id)
51+
obj.send(:remove_instance_variable, :@__weak_backreferences__) if backreferences.empty?
52+
end
53+
end
54+
55+
def supports_backreference?(obj)
56+
obj.respond_to?(:instance_variable_get) && obj.respond_to?(:instance_variable_defined?)
57+
rescue NoMethodError
58+
false
59+
end
60+
end
61+
62+
@@weak_references = {}
63+
@@lock = Monitor.new
64+
65+
# Finalizer that cleans up weak references when references are destroyed.
66+
@@reference_finalizer = lambda do |object_id|
67+
@@lock.synchronize do
68+
reference_pointer = @@weak_references.delete(object_id)
69+
reference_pointer.cleanup if reference_pointer
70+
end
71+
end
72+
73+
# Create a new weak reference to an object. The existence of the weak reference
74+
# will not prevent the garbage collector from reclaiming the referenced object.
75+
def initialize(obj) #:nodoc:
76+
@referenced_object_id = obj.__id__
77+
@@lock.synchronize do
78+
@reference_pointer = ReferencePointer.new(obj)
79+
@@weak_references[self.object_id] = @reference_pointer
80+
end
81+
ObjectSpace.define_finalizer(self, @@reference_finalizer)
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 #:nodoc:
87+
if @reference_pointer
88+
obj = @reference_pointer.object
89+
unless obj
90+
@@lock.synchronize do
91+
@@weak_references.delete(object_id)
92+
@reference_pointer.cleanup
93+
@reference_pointer = nil
94+
end
95+
end
96+
obj
97+
end
98+
end
99+
end
100+
end

0 commit comments

Comments
 (0)