Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove delegation from WeakRef. See #6308 in MRI's bug tracker. #406

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 62 additions & 30 deletions lib/weakref.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
# Weak Reference class that allows a referenced object to be # Weak Reference class that allows a referenced object to be
# garbage-collected. # garbage-collected.
# #
# A WeakRef may be used exactly like the object it references.
#
# Usage: # Usage:
# #
# foo = Object.new # create a new object instance # foo = Object.new # create a new object instance
# p foo.to_s # original's class # p foo.to_s # original's class
# foo = WeakRef.new(foo) # reassign foo with WeakRef instance # foo = WeakReference.new(foo) # reassign foo with WeakReference instance
# p foo.to_s # should be same class # p foo.get.to_s # should be same class
# GC.start # start the garbage collector # GC.start # start the garbage collector
# p foo.to_s # should raise exception (recycled) # p foo.get # should be nil (recycled)
# #
# == Example # == Example
# #
# With help from WeakRef, we can implement our own redimentary WeakHash class. # With help from WeakReference, we can implement our own rudimentary WeakHash class.
# #
# We will call it WeakHash, since it's really just a Hash except all of it's # We will call it WeakHash, since it's really just a Hash except all of it's
# keys and values can be garbage collected. # keys and values can be garbage collected.
Expand All @@ -25,42 +23,75 @@
# #
# class WeakHash < Hash # class WeakHash < Hash
# def []= key, obj # def []= key, obj
# super WeakRef.new(key), WeakRef.new(obj) # super WeakReference.new(key), WeakReference.new(obj)
# end
#
# def [] key
# super(key).get
# end # end
# end # end
# #
# This is just a simple implementation, we've opened the Hash class and changed # This is just a simple implementation, we've extend the Hash class and changed
# Hash#store to create a new WeakRef object with +key+ and +obj+ parameters # Hash#store to create a new WeakReference object with +key+ and +obj+ parameters
# before passing them as our key-value pair to the hash. # before passing them as our key-value pair to the hash.
# #
# With this you will have to limit your self to String key's, otherwise you
# will get an ArgumentError because WeakRef cannot create a finalizer for a
# Symbol. Symbols are immutable and cannot be garbage collected.
#
# Let's see it in action: # Let's see it in action:
# #
# omg = "lol" # omg = "lol"
# c = WeakHash.new # c = WeakHash.new
# c['foo'] = "bar" # c['foo'] = "bar"
# c['baz'] = Object.new # c['baz'] = Object.new
# c['qux'] = omg # c[omg] = "rofl"
# puts c.inspect # puts c.inspect
# #=> {"foo"=>"bar", "baz"=>#<Object:0x007f4ddfc6cb48>, "qux"=>"lol"} # #=> {"foo"=>"bar", "baz"=>#<Object:0x007f4ddfc6cb48>, "lol"=>"rofl"}
# #
# # Now run the garbage collector # # Now run the garbage collector
# GC.start # GC.start
# c['foo'] #=> nil # c['foo'] #=> nil
# c['baz'] #=> nil # c['baz'] #=> nil
# c['qux'] #=> nil # c[omg] #=> "rofl"
# omg #=> "lol"
#
# puts c.inspect
# #=> WeakRef::RefError: Invalid Reference - probably recycled
# #
# You can see the local variable +omg+ stayed, although it's reference in our # You can see the key associated with our local variable omg remained available
# hash object was garbage collected, along with the rest of the keys and # while all other keys have been collected.
# values. Also, when we tried to inspect our hash, we got a WeakRef::RefError,
# this is because these objects were also garbage collected. class WeakReference

@@__map = ::ObjectSpace::WeakMap.new

##
# Creates a weak reference to +orig+
#
# Raises an ArgumentError if the given +orig+ is immutable, such as Symbol,
# Fixnum, or Float.

def initialize(orig)
case orig
when true, false, nil
@delegate_sd_obj = orig
else
@@__map[self] = orig
end
end

##
# Retrieve the object referenced by this WeakReference, or nil if the object
# has been collected.

def get # :nodoc:
@@__map[self] or defined?(@delegate_sd_obj) ? @delegate_sd_obj : nil
end
alias __getobj__ get

##
# Returns true if the referenced object is still alive.

def weakref_alive?
!!(@@__map[self] or defined?(@delegate_sd_obj))
end
end

# The old WeakRef class is deprecated since it can lead to hard-to-diagnose
# errors when the referenced object gets collected.


class WeakRef < Delegator class WeakRef < Delegator


Expand All @@ -80,6 +111,7 @@ class RefError < StandardError
# Fixnum, or Float. # Fixnum, or Float.


def initialize(orig) def initialize(orig)
warn "WeakRef is deprecated. Use WeakReference. See https://bugs.ruby-lang.org/issues/6308."
case orig case orig
when true, false, nil when true, false, nil
@delegate_sd_obj = orig @delegate_sd_obj = orig
Expand Down Expand Up @@ -109,9 +141,9 @@ def weakref_alive?
# require 'thread' # require 'thread'
foo = Object.new foo = Object.new
p foo.to_s # original's class p foo.to_s # original's class
foo = WeakRef.new(foo) foo = WeakReference.new(foo)
p foo.to_s # should be same class p foo.get.to_s # should be same class
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
p foo.to_s # should raise exception (recycled) p foo.get.to_s # should raise exception (get returns nil)
end end
29 changes: 11 additions & 18 deletions test/test_weakref.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -3,26 +3,23 @@
require_relative './ruby/envutil' require_relative './ruby/envutil'


class TestWeakRef < Test::Unit::TestCase class TestWeakRef < Test::Unit::TestCase
def make_weakref(level = 10) def make_weakref
obj = Object.new obj = Object.new
str = obj.to_s str = obj.to_s
level.times {obj = WeakRef.new(obj)} return WeakReference.new(obj), str
return WeakRef.new(obj), str
end end


def test_ref def test_ref
weak, str = make_weakref weak, str = make_weakref
assert_equal(str, weak.to_s) assert_equal(str, weak.get.to_s)
end end


def test_recycled def test_recycled
weak, str = make_weakref weak, str = make_weakref
assert_nothing_raised(WeakRef::RefError) {weak.to_s} assert_equal str, weak.get.to_s
assert_predicate(weak, :weakref_alive?)
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
assert_raise(WeakRef::RefError) {weak.to_s} assert_equal nil, weak.get
assert_not_predicate(weak, :weakref_alive?)
end end


def test_not_reference_different_object def test_not_reference_different_object
Expand All @@ -31,17 +28,13 @@ def test_not_reference_different_object
3.times do 3.times do
obj = Object.new obj = Object.new
def obj.foo; end def obj.foo; end
weakrefs << WeakRef.new(obj) weakrefs << WeakReference.new(obj)
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
end end
assert_nothing_raised(NoMethodError, bug7304) { weakrefs.each do |weak|
weakrefs.each do |weak| obj = weak.get
begin assert obj == nil || obj.respond_to?(:foo)
weak.foo end
rescue WeakRef::RefError
end
end
}
end end


def test_weakref_finalize def test_weakref_finalize
Expand All @@ -50,7 +43,7 @@ def test_weakref_finalize
require 'weakref' require 'weakref'
obj = Object.new obj = Object.new
3.times do 3.times do
WeakRef.new(obj) WeakReference.new(obj)
ObjectSpace.garbage_collect ObjectSpace.garbage_collect
end end
}, bug7304 }, bug7304
Expand Down