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.
You can set the id conversion object on either as a default for the entire DRb service instance, or per DRb server.
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.
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 }
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 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 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 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.
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.
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