The dRuby Book

11.2 Using DRbIdConv to Prevent GC

DRbObject consists of two parts. The URI is used to identify the remote server location, and the reference is used to identify the referencing object inside the remote server. DRbIdConv is used to exchange objects and their reference information. By default, DRbIdConv uses Object#__id__ as reference information. By exchanging these, you can protect objects against GC.

Let’s first find out how to customize DRbIdConv and then talk about how to use it to protect against GC.

Customizing DRbIdConv

Let’s look into the definition of DRbIdConv, which we use to exchange information.

  ​class DRbIdConv​
  ​ def to_obj(ref)​
  ​ ObjectSpace._id2ref(ref)​
  ​ end​
  ​​
  ​ def to_id(obj)​
  ​ obj.nil? ? nil : obj.__id__​
  ​ end​
  ​end​

DRbIdConv has two methods: to_obj and to_id. The to_obj method is used to identify an object from its reference information, and to_id is used to identify a reference ID from the object.

By overwriting to_id, you can hook into each reference exchange. Let’s write an example DRbIdConv class to convert everything into a Hash while converting objects into references.

dumbidconv.rb
  ​require 'drb/drb'
  class DumbIdConv < DRb::DRbIdConv​
  def initialize​
  ​ @table = Hash.new​
  end
  ​ attr_reader :table​
  def to_id(obj)​
  ​ ref = super(obj)​
  ​ @table[ref] = obj​
  ​ ref​
  end
  end

We made DumbIdConv a subclass of the DRbIdConv class. In the initialize method, we assign the @table instance variable, and the to_id method retains a reference as a key to the object.

To use DumbIdConv instead of DRbIdConv as the default, you need to configure it before DRb.start_service is called.

Once configured, this is how to use it:

  ​DRb.install_id_conv(DumbIdConv.new)​

You can also assign it as an argument in DRbServer.start_service.

  ​DRb.start_service(uri, front, {:idconv => DumbIdConv.new})​

Let’s try to use DumbIdConv via irb.

First, start a service to publish Hash using DumbIdConv. Then add a Thread object into the Hash as follows:

  ​% irb -r './dumbidconv' --prompt simple​
  ​>> $dumb = DumbIdConv.new​
  ​>> DRb.start_service('druby://localhost:12345', {}, {:idconv => $dumb})​
  ​>> DRb.front['main-thread'] = Thread.current​

Let’s open irb from another terminal and access the service you just started. Let’s obtain a reference to the Thread object and call the method.

  ​% irb -r drb --prompt simple​
  ​>> DRb.start_service​
  ​>> ro = DRbObject.new_with_uri('druby://localhost:12345')​
  ​>> ro.keys​
  ​=> ["main-thread"]​
  ​>> ro['main-thread']​
  ​=> #<DRb::DRbObject:0x2ad89598 @ref=358724596,​
  ​@uri="druby://localhost:12345">​
  ​>> ro['main-thread'].status​
  ​=> "sleep"​

So far, Thread is the only reference published on the first terminal. Let’s check table inside DumbIdConv there.

  ​>> $dumb.table​
  ​=> {358724596=>#<Thread:0x2ac367e8 run>}​

You should see that the Thread object is registered with an integer key. Any object passed by reference will be preserved on this table because any objects referenced in this table will be protected against GC. These objects won’t be garbage collected while other processes are using them.

So far, we tried customizing DRbIdConv and preserving the reference of objects passed by reference.

DumbIdConv solves one problem by not garbage collecting objects, but it becomes a problem if all objects are preserved forever. DumbIdConv even preserves temporal objects that are actually not in use anymore.

To solve this problem, we have a class called TimerIdConv that sets a timer on objects to clear up. Though initially all objects are preserved, if methods on the registered objects have not been called for a certain period set by the timer, the objects will be removed from the table.

To use TimerIdConv, you need to require ’drb/timeridconv’.

  ​require 'drb/timeridconv'​
  ​DRb.install_id_conv(DRb::TimerIdConv.new)​

With TimerIdConv, you can keep all referenced objects while releasing some temporal objects and reusing its memory space.