I’ve been getting into Ruby last week whilst playing with Selenium at work. We’re trying to get around quite a big and well-known limitation of Selenium, that you cannot reuse a driver already created within another process.
Since we’re executing our in steps, each step running in its own script host, this gives us a fair headache. Somehow we need to keep the driver instance alive and controllable between script instance. Enter: DRb, Distributed Ruby
The DRb framework is ridiculously easy to work with and compared to my previous encounters with network-based RPC quite reliant even when doing complex stuff and using real objects. However, it’s not flawless and you can shoot yourself in the foot. Basically, everything was going fine up until I started using blocks. The issue is that when using a Ruby block on a DRb proxy object the scope of what’s being used becomes a bit complicated. The block will have to execute on the DRb server to yield the element to the block but then all of a sudden your local variables need to be on the server so you might get a lot of calls back and forth.
This started giving me errors such as:
DRb::DRbConnError: An existing connection was forcibly closed by the remote host.
and
RangeError: 0x179a7ec is recycled object
Since I was looking for the DRbConnError rather than the RangeError it took me quite a while before I came across this post on stackoverflow. This was quickly proved by adding the following line to my DRb server script:
GC.disable
The DRb docs explains this:
A dRuby reference to an object is not sufficient to prevent it being garbage collected!
And my objects got out of scope quite a lot since I was using wrappers etc all the time. Thankfully the DRb documentation and standard implementation comes with a solution, using the TimerIdConverter class which will keep your objects alive for a certain amount of time before letting the GC have it’s fun with them. How to use this is even better explained in this book, Distributed Programming in Ruby.
My short excerpt:
require 'drb' require 'drb/timeridconv' # ... code ... DRb.install_id_conv DRb::TimerIdConv.new 600 DRb.start_service(SERVER, self)
Now, on to getting everything else working…