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

Commit

Permalink
Merge 45a115d into d44a576
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasallan committed Mar 23, 2015
2 parents d44a576 + 45a115d commit b10dc83
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 3 deletions.
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ end
group :testing do
gem 'test-unit', '~> 3.0.9'
gem 'rspec', '~> 3.1.0'
#gem 'simplecov', '~> 0.8.2', :require => false
gem 'coveralls', '~> 0.7.3', :require => false
end

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ This library also includes tools for some common uses of weak and soft reference
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.

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

9 changes: 8 additions & 1 deletion lib/ref.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module Ref
$LOAD_PATH.unshift(File.dirname(__FILE__))

require 'ref/abstract_reference_value_map'
require 'ref/abstract_reference_key_map'
require 'ref/reference'
require 'ref/reference_queue'

if defined?(Java)
begin
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'ref_ext'
require 'org/jruby/ext/ref/references'
rescue LoadError
Expand All @@ -19,6 +20,12 @@ module Ref
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
# If using Rubinius set the implementation to use WeakRef since it is very efficient and using finalizers is not.
require 'ref/weak_reference/weak_ref'
elsif defined?(::ObjectSpace::WeakMap)
# Ruby 2.0 has a working implementation of weakref.rb backed by the new ObjectSpace::WeakMap
require 'ref/weak_reference/weak_ref'
elsif defined?(::ObjectSpace._id2ref)
# If ObjectSpace can lookup objects from their object_id, then use the pure ruby implementation.
require 'ref/weak_reference/pure_ruby'
else
# Otherwise, wrap the standard library WeakRef class
require 'ref/weak_reference/weak_ref'
Expand Down
100 changes: 100 additions & 0 deletions lib/ref/weak_reference/pure_ruby.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
module Ref
# This is a pure ruby implementation of a weak reference. It is much more
# efficient than the WeakRef implementation bundled in MRI 1.8 and 1.9
# subclass Delegator which is very heavy to instantiate and utilizes a
# because it does not fair amount of memory under Ruby 1.8.
class WeakReference < Reference

class ReferencePointer
def initialize(object)
@referenced_object_id = object.__id__
add_backreference(object)
end

def cleanup
obj = ObjectSpace._id2ref(@referenced_object_id) rescue nil
remove_backreference(obj) if obj
end

def object
obj = ObjectSpace._id2ref(@referenced_object_id)
obj if verify_backreferences(obj)
rescue RangeError
nil
end

private
# Verify that the object is the same one originally set for the weak reference.
def verify_backreferences(obj) #:nodoc:
return nil unless supports_backreference?(obj)
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
backreferences && backreferences.include?(object_id)
end

# Add a backreference to the object.
def add_backreference(obj) #:nodoc:
return unless supports_backreference?(obj)
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
unless backreferences
backreferences = []
obj.instance_variable_set(:@__weak_backreferences__, backreferences)
end
backreferences << object_id
end

# Remove backreferences from the object.
def remove_backreference(obj) #:nodoc:
return unless supports_backreference?(obj)
backreferences = obj.instance_variable_get(:@__weak_backreferences__) if obj.instance_variable_defined?(:@__weak_backreferences__)
if backreferences
backreferences.dup.delete(object_id)
obj.send(:remove_instance_variable, :@__weak_backreferences__) if backreferences.empty?
end
end

def supports_backreference?(obj)
obj.respond_to?(:instance_variable_get) && obj.respond_to?(:instance_variable_defined?)
rescue NoMethodError
false
end
end

@@weak_references = {}
@@lock = Monitor.new

# Finalizer that cleans up weak references when references are destroyed.
@@reference_finalizer = lambda do |object_id|
@@lock.synchronize do
reference_pointer = @@weak_references.delete(object_id)
reference_pointer.cleanup if reference_pointer
end
end

# Create a new weak reference to an object. The existence of the weak reference
# will not prevent the garbage collector from reclaiming the referenced object.
def initialize(obj) #:nodoc:
@referenced_object_id = obj.__id__
@@lock.synchronize do
@reference_pointer = ReferencePointer.new(obj)
@@weak_references[self.object_id] = @reference_pointer
end
ObjectSpace.define_finalizer(self, @@reference_finalizer)
end

# Get the reference object. If the object has already been garbage collected,
# then this method will return nil.
def object #:nodoc:
if @reference_pointer
obj = @reference_pointer.object
unless obj
@@lock.synchronize do
@@weak_references.delete(object_id)
@reference_pointer.cleanup
@reference_pointer = nil
end
end
obj
end
end
end
end

0 comments on commit b10dc83

Please sign in to comment.