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.