The dRuby Book

4.3 Handling Unknown Objects with DRbUnknown

In the previous section, we learned that dRuby passes by value if an object can be Marshal.dumped. However, what happens if the receiver of the object doesn’t know about the class definition of the object? In this section, we’ll learn about handling unknown objects. We’ll use the same foo.rb sample. Let’s start terminal 1 as a server and terminal 2 as a client and then pass the Foo object from the client.

  ​# [Terminal 1]​
  ​% irb --prompt simple -r drb/drb​
  ​>> front = {}​
  ​>> DRb.start_service('druby://localhost:12345', front)​
  ​# [Terminal 2]​
  ​% irb --prompt simple -r drb/drb -r ./foo.rb​
  ​>> DRb.start_service​
  ​>> there = DRbObject.new_with_uri('druby://localhost:12345')​
  ​>> foo = Foo.new('Foo1')​
  ​>> there[:foo] = foo​
  ​=> #<Foo:0x ..... ">​

irb at terminal 1 doesn’t know the class definition of Foo. We just set an instance of Foo on front[:foo]. Let’s see what kind of object is passed to terminal 1.

  ​# [Terminal 1]​
  ​>> front[:foo]​
  ​=> #<DRb::DRbUnknown:0x.... @buf="?004?00.....", @name="Foo">​

Hmmm, front[:foo] isn’t an instance of Foo. It says it’s an instance of DRbUnknown.

When an unknown object is loaded via Marshal.load and an exception is raised, dRuby captures it and loads DRbUnknown instead. DRbUnknown knows two things. One is the string buffer that failed to be loaded, and the other is the name of the class or module.

You can use the following methods to find out information about each:

DRbUnknown#buf

Buffer of the serialized string that Marshal.load failed to load

DRbUnknown#name

Name of the unknown class or module names

DRBUnknown#reload

Retries Marshal.load

When dRuby receives an unknown class, it creates the DRbUnknown object automatically. You can’t call the method against DRbUnknown, but you can transfer DRbUnknown.

Let’s pass the DRbUnknown object from terminal 1 to terminal 2.

  ​# [Terminal 2]​
  ​>> bar = there[:foo]​
  ​=> #<Foo:0x .... @name="Foo1">​
  ​>> foo.__id__ == bar.__id__​
  ​=> false​

Terminal 2 received a new Foo instance, instead of DRbUnkown. (We compare by looking at the value of __id__.)

When dRuby does Marshal.load to the DRbUnknown object, it tries to Marshal.load against the buffer, and it returns the real object instead of DRbUnknown when successful.

Let’s transfer the object using the third terminal.

  ​# [Terminal 3]​
  ​% irb --prompt simple -r drb/drb​
  ​>> DRb.start_service​
  ​>> there = DRbObject.new_with_uri('druby://localhost:12345')​
  ​>> unknown = there[:foo]​
  ​=> #<DRb::DRbUnknown:0x.... @buf="?004?00.....", @name="Foo">​
  ​>> unknown.name​
  ​=> "Foo"​

Terminal 3 does receive DRbUnknown because it doesn’t know about the Foo class definition. The result of unknown.name is Foo. Now require foo.rb, and try it again.

  ​# [Terminal 3]​
  ​>> unknown.reload​
  ​=> #<DRb::DRbUnknown:0x.... @buf="?004?00.....", @name="Foo">​
  ​>> require 'foo'​
  ​>> unknown.reload​
  ​=> #<Foo:0x .... @name="Foo1">​

When you first did unknown.reload, you received DRbUnknown. When you tried it again after requiring the class, then it returned Foo. dRuby tried reloading the object.

Let’s send the object again from terminal 1. This time, it should receive Foo, instead of DRbUnknown.

  ​# [Terminal 3]​
  ​>> foo = there[:foo]​
  ​=> #<Foo:0x .... @name="Foo1">​

Good! Since it now knows the definition of Foo, it did receive Foo.

By using DRbUnknown, you can keep unknown objects, even though you can’t call their methods.

Why do we need such functionalities? Consider a Queue service. The Queue is responsible for transferring objects from one process to another. If DRbUnknown did not exist, then the proxying Queue service would require the class definitions of every class that may go through the Queue. DRbUnknown becomes very handy in such cases.