DRb Id Conversion

When calling a method on a remote object via DRb, DRb must look up the object you are refencing before it can call any methods on it. DRb uses id conversion to look up the object from an id given to it from the calling side of the connection.

Changing which id conversion method is used allows you to control how objects get looked up and how remote references to objects get destroyed or revoked.

Changing Id Conversion

You can set the id conversion object on either as a default for the entire DRb service instance, or per DRb server.

Changing The Default Id Conversion

To change the default id conversion, call DRb.install_id_conv with the id converter you wish to use before creating any DRb servers:

require 'drb/timeridconv'
DRb.install_id_conv DRb::TimerIdConv.new

From this point on, all DRbServers created will use the TimerIdConv you created. Installing a new id converter will use that id converter for further DRbServers.

Setting Per-Server Id Conversion

When creating a DRbServer, you can specify an id conversion object to use through the config hash argument. (The first two arguments are the uri to bind and the front object to use, the third is a config hash or an ACL.) The id converter is taken from the :idconv argument of the config hash:

require 'drb/timeridconv'
idconv = DRb::TimerIdConv.new
DRb::DRbServer.new nil, MyServer.new, { :idconv => idconv }

Built-in Id Conversion

DRb has three different ways to perform id conversion built in. DRb::DRbIdConv, DRb::TimerIdConv, and DRb::GWIdConv. The first two allows remote objects to be found on the local server, while the third is used as a gateway between DRb services running on different protocols.

DRb::DRbIdConv

DRb::DRbIdConv is DRb's default id conversion class. It is very simple and uses ObjectSpace to retrieve objects from their ids. You don't need to do anything to use DRbIdConv.

There is one big drawback to DRbIdConv, remote objects may get garbage collected if the only reference to them is a remote reference. (They have no references on the server where they live.) You will have this problem in a situation like this:

class Foo
  include DRbUndumped
end

class Server
  def get_resource
    return Foo.new # no references to this object on the server
                   # it will be GC'd
  end
end

DRb.start_service Server.new

DRb::TimerIdConv

DRb::TimerIdConv keeps objects alive for a certain amount of time after their last access. The default timeout is 600 seconds (10 minutes), and can be changed on initialization of TimerIdConv.

Use TimerIdConv when you want remote objects to expire after they've been out of use for a "safe" amount of time.

DRb::GWIdConv

DRb::GWIdConv is a special use of id conversion in DRb. It forms a gateway between two different DRb protocols or networks to allow references to be looked from places you wouldn't otherwise have access to.

GWIdConv can be used to access resources living across a UNIX socket from a Windows machine with a gateway passing requests between the Windows machine and the UNIX socket via TCP. GWIdConv could also be used to access resources across an SSH tunnel.

Using GWIdConv takes a bit more work to set up than the other IdConv classes. Ruby's samples have an example usage of the GWIdConv written by MASATOSHI Seki, and I have adapted and annotated it here.

First, the gateway service is created:

require 'drb/drb'
require 'drb/unix'
require 'drb/gw'

# Install the GWIdConv
DRb.install_id_conv DRb::GWIdConv.new

# Create the Gateway Services
gw = DRb::GW.new
s1 = DRb::DRbServer.new ARGV.shift, gw
s2 = DRb::DRbServer.new ARGV.shift, gw

# Use these URIs for the endpoints you want to connect
puts "UNIX: #{s1.uri}"
puts "TCP:  #{s2.uri}"

# Wait for the threads to quit before exiting
s1.thread.join
s2.thread.join

Download gw_s.rb

uriel$ ruby gw_s.rb
UNIX: druby://uriel:2069
TCP:  druby://uriel:2070

Then the UNIX service is started, and it registers an object with the gateway:

require 'drb/drb'
require 'drb/unix'

# A dummy object
class Foo
  include DRbUndumped
  
  def foo(n)
    n + n
  end
  
  def bar(n)
    yield(n) + yield(n)
  end
end

# Start a UNIX socket-based service
DRb.start_service 'drbunix:', nil

# Store a Foo object in the gateway
gw = DRbObject.new nil, ARGV.shift
gw[:unix] = Foo.new

DRb.thread.join

Download gw_cu.rb

uriel$ ruby gw_cu.rb druby://uriel:2069

Then you can access the Foo object from a TCP service via the gateway:

require 'drb/drb'

DRb.start_service

# Retrieve the Foo object from the gateway
gw = DRbObject.new nil, ARGV.shift
foo = gw[:unix]

puts "fetched remote object:"
p foo # => #<DRb::DRbObject:0x804c7a8 @ref=[:DRbObject, "druby://uriel:2069", [:DRbObject, "drbunix:/tmp/druby41552.0", 67824596]], @uri="druby://uriel:2070">
puts

# Call foo on the object running on the UNIX service
p foo.foo(1) # => 2
puts

# Call bar on the object running on the UNIX service, passing a
# block
p foo.bar('2') { |n| n * 3 } # => "222222"

Download gw_ct.rb

manzana$ ruby gw_tc.rb druby://uriel:2070
fetched remote object:
#<DRb::DRbObject:0x804c7a8 @ref=[:DRbObject, "druby://uriel:2069", [:DRbObject, "drbunix:/tmp/druby41552.0", 67824596]], @uri="druby://uriel:2070">

2

"222222"

As you can see, the requests are forwarded through the gateway from the TCP side of the service to the UNIX socket side of the service. You can also see the guts of the gatewayed object exposed.

DRb::NamedIdConv

NamedIdConv allows a reference to an object to be retrieved even if the service hosting the object needs to be restarted. Not every object can recreated, of course, but NamedIdConv allows you to add some level of robustness to a system.

I have placed copies of name.rb and namec.rb which are the NamedIdConv implementation and a test client for it, respectively, here for easy access.

In order to 'name' an object for NamedIdConv to work, you have to do two things, you must include DRbNamedObject in the object's Class and you must call self.drb_name= passing in the name you would like the object to have. Note that two objects cannot share the same name.

Writing Your Own Id Converter

The interface for an id conversion class consists of two methods, to_obj(ref) and to_ref(obj) which convert an object into an id (reference) and vice-versa.

Here's DRb::DRbIdConv straight out of drb/drb.rb as an example:

class DRb::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