diff --git a/README.md b/README.md index c36afe1..5f114bb 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,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. 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. -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. 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. -4. Rubinius - Rubinius implements `WeakRef` with a lighter weight version of delegation and works very well. -5. MRI Ruby 2.0 has a good implementation of `WeakRef`. +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`. -## BasicObject - -Note that weak references will not work with MRI 1.9 or earlier. References will be created, but the objects will never be stored so the reference object will always treat the object as if it is always garbage collected. BasicObject does not implement the necessary methods to maintain the reference. diff --git a/lib/ref.rb b/lib/ref.rb index a1b6113..ada5076 100644 --- a/lib/ref.rb +++ b/lib/ref.rb @@ -19,12 +19,6 @@ 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' diff --git a/lib/ref/weak_reference/pure_ruby.rb b/lib/ref/weak_reference/pure_ruby.rb deleted file mode 100644 index 3197817..0000000 --- a/lib/ref/weak_reference/pure_ruby.rb +++ /dev/null @@ -1,100 +0,0 @@ -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 diff --git a/ref.gemspec b/ref.gemspec index 126aead..9c7515b 100644 --- a/ref.gemspec +++ b/ref.gemspec @@ -5,11 +5,11 @@ require 'ref/version' Gem::Specification.new do |s| s.name = 'ref' s.version = Ref::VERSION - s.authors = ['Brian Durand'] - s.email = ['bbdurand@gmail.com'] + s.authors = ['Brian Durand', 'The Ruby Concurrency Team'] + s.email = ['bbdurand@gmail.com', 'concurrent-ruby@googlegroups.com'] s.homepage = "http://github.com/ruby-concurrency/ref" s.summary = "Library that implements weak, soft, and strong references in Ruby." - s.description = "Library that implements weak, soft, and strong references in Ruby that work across multiple runtimes (MRI, REE, YARV, Jruby and Rubinius). Also includes implementation of maps/hashes that use references and a reference queue." + s.description = "Library that implements weak, soft, and strong references in Ruby that work across multiple runtimes (MRI,Jruby and Rubinius). Also includes implementation of maps/hashes that use references and a reference queue." s.license = "MIT" s.date = Time.now.strftime('%Y-%m-%d')