[ruby-core:26414] DRbIdConv without _id2ref

As mentioned in a previous thread on ruby-talk, we are looking to
remove ObjectSpace_id2ref from JRuby. Matz explained that it was added
to support WeakRef, for which we have our own (Java-based)
implementation. Supporting it on the JVM (or any VM where objects move
in memory) is rather expensive.

The only place in stdlib that uses _id2ref_ is this class in drb/drb.rb:

  class DRbIdConv

    # Convert an object reference id to an object.

···

#
    # This implementation looks up the reference id in the local object
    # space and returns the object it refers to.
    def to_obj(ref)
      ObjectSpace._id2ref(ref)
    end

    # Convert an object into a reference id.
    #
    # This implementation returns the object's __id__ in the local
    # object space.
    def to_id(obj)
      obj.nil? ? nil : obj.__id__
    end
  end

I have come up with a possible replacement for this that uses WeakRef.
I'd like a few more eyes on it. It's not concurrency-safe, which might
be a good addition. The 'clean' method is also not very efficient, but
Ruby's WeakRef doesn't have the notion of a reference queue you can
poll cheaply. Adding finalizers would be a heavy-weight way to add
support for a reference queue.

Here's the code I've come up with (plus a "require 'weakref'" near the
top of drb/drb.rb):

  class DRbIdConv
    def initialize
      @id2ref = {}
    end

    # Convert an object reference id to an object.
    #
    # This implementation looks up the reference id in the local object
    # space and returns the object it refers to.
    def to_obj(ref)
      _get(ref)
    end

    # Convert an object into a reference id.
    #
    # This implementation returns the object's __id__ in the local
    # object space.
    def to_id(obj)
      obj.nil? ? nil : _put(obj)
    end

    def _clean
      dead = []
      @id2ref.each {|id,weakref| dead << id unless weakref.weakref_alive?}
      dead.each {|id| @id2ref.delete(id)}
    end

    def _put(obj)
      _clean
      @id2ref[obj.__id__] = WeakRef.new(obj)
      obj.__id__
    end

    def _get(id)
      weakref = @id2ref[id]
      if weakref
        result = weakref.__getobj__ rescue nil
        if result
          return result
        else
          @id2ref.delete id
        end
      end
      nil
    end
    private :_clean, :_put, :_get
  end

And here is a test for it:

require 'test/unit'
require 'drb'
require 'java'

class TestWeakDrbIdConv < Test::Unit::TestCase
  def test_weak_drb_id_conv
    conv = DRb::DRbIdConv.new
    obj_ary = []
    id_ary = []

    # populate
    100.times do
      obj = Object.new
      obj_ary << obj
      id_ary << conv.to_id(obj)
    end

    # confirm they're there
    id_ary.each do |id|
      assert conv.to_obj(id)
    end

    # dereference objects and force GC
    obj_ary = nil
    2.times {java.lang.System.gc}

    # confirm they're gone
    id_ary.each do |id|
      assert !conv.to_obj(id)
    end
  end
end

- Charlie