How to use Ruby's Rinda::Ring

Rinda::Ring allows DRb services and clients to automatically find each other without knowing where they live.

DRb servers register themselves with a RingServer which allows clients to find the servers they need. Many servers may register themselves with the RingServer. The DRb servers don't need to run on the same machine.

To use Rinda::Ring you'll need Ruby 1.8.1 or newer, or drb 2.0.3 or newer.

Starting a RingServer

A RingServer holds a TupleSpace that is used a a naming system for distributed services. Here's a simple ring server:

Download ringserver.rb

#!/usr/bin/env ruby -w
# ringserver.rb
# Rinda RingServer

require 'rinda/ring'
require 'rinda/tuplespace'

# start DRb
DRb.start_service

# Create a TupleSpace to hold named services, and start running
Rinda::RingServer.new Rinda::TupleSpace.new

# Wait until the user explicitly kills the server.
DRb.thread.join

Go ahead and run this script in a shell, and leave it alone. Now services can register themselves with the RingServer for clients to find.

Registering Services

To register a service, a client first looks for the RingServer, then adds a tuple describing the service to it. In this example, we'll provide a TupleSpace that clients can use to communicate.

A RingServer registration is a tuple and a renewer.

The renewer specifies how often the provided service should be checked to see if it is still alive. Rinda provides a SimpleRenewer:

renewer = Rinda::SimpleRenewer.new

The registration tuple is formatted like this:

[:name, class, a DRbObject, note ]

For our TupleSpace example, the tuple we'll use to register the TupleSpace will look like this:

tuple = [:name, :TupleSpace, example_ts, 'TupleSpace']

To register the TupleSpace, we need to know where the RingServer lives. Typically when working with DRb, you must provide a name for the server or use the one which DRb generates when you start it. If you look at the RingServer example above, you'll notice that we didn't do that. RingServers listen on a UDP socket, so they can be found by Rinda::RingFinger.

Here's how you use Rinda::RingFinger to find a RingServer:

require 'rinda/ring'

# start DRb
DRb.start_service

ring_server = Rinda::RingFinger.primary

Now ring_server can be used to register a service, which we like this:

ring_server.write(tuple, renewer)

Fortunately, Rinda::Ring provides a simple way of providing services, Rinda::RingProvider. You simply need to provide the class, object, description, and an optional renewer. Let's put it all together:

Download rinda_ts.rb

#!/usr/bin/env ruby -w
# rinda_ts.rb
# Registering a TupleSpace with Rinda::Ring

require 'rinda/ring'
require 'rinda/tuplespace'

DRb.start_service

ts = Rinda::TupleSpace.new

provider = Rinda::RingProvider.new :TupleSpace, ts, 'Tuple Space'
provider.provide

DRb.thread.join

Go ahead and run this script in a shell and leave it alone.

Finding a Service

So now we should have a TupleSpace that anybody can find via the RingServer. Let's test to see if it got exported correctly by seeing what's registered on the RingServer.

Since the RingServer uses a TupleSpace to hold the the registered Services, we can search it for registered services. Going back to the example of how to find a RingServer, we'll ask the RingServer to find all of the services it knows about.

To search the RingServer's TupleSpace, we use a template that has the same length as the registration tuples, and starts with :name, like this:

ring_server.read_all [:name, nil, nil, nil]

This returns all tuples that start with :name and have anything in the last 3 fields. To put it all together:

Download list_services.rb

#!/usr/bin/env ruby -w
# list_services.rb
# List all of the services the RingServer knows about

require 'rinda/ring'

DRb.start_service

ring_server = Rinda::RingFinger.primary
services = ring_server.read_all [:name, nil, nil, nil]

puts "Services on #{ring_server.__drburi}"
services.each do |service|
  puts "#{service[1]} on #{service[2].__drburi} - #{service[3]}"
end

Running this will give a result similar to the following:

Services on druby://localhost:4572
TupleSpace on druby://localhost:4573 - Tuple Space

Now that we've verified that the service is registered, we can modify some of the DRb examples to find each other via the RingServer. Since we've already got a TupleSpace registered, I'll use rindas.rb and rindac.rb from the Rinda examples.

For rindas.rb, remove the "uri =" and "ts =" lines, then after DRb.start_service add:

ring_server = Rinda::RingFinger.primary

# Fetch the reference to the remote TupleSpace
rts = ring_server.read([:name, :TupleSpace, nil, nil])[2]

ts = Rinda::TupleSpaceProxy.new rts

rindas.rb will end up looking like this:

Download rindas.rb

#!/usr/bin/env ruby -w
# rindas.rb
# rindas modified to find the TupleSpace via a RingServer

require 'rinda/ring'

def do_it(v)
  puts "do_it(#{v})"
  v + v
end

DRb.start_service
ring_server = Rinda::RingFinger.primary

ts = ring_server.read([:name, :TupleSpace, nil, nil])[2]
ts = Rinda::TupleSpaceProxy.new ts

loop do
  r = ts.take(['sum', nil, nil])
  v = do_it(r[2])
  ts.write(['ans', r[1], r[2], v])
end

Do the same for rindac.rb, so you end up with:

Download rindac.rb

#!/usr/bin/env ruby -w
# rindac.rb
# rindac.rb modified to find the TupleSpace via a RingServer

require 'rinda/ring'

def do_it(v)
  puts "do_it(#{v})"
  v + v
end

DRb.start_service
ring_server = Rinda::RingFinger.primary

ts = ring_server.read([:name, :TupleSpace, nil, nil])[2]
ts = Rinda::TupleSpaceProxy.new ts

(1..10).each do |n|
  ts.write(['sum', DRb.uri, n])
end

(1..10).each do |n|
  ans = ts.take(['ans', DRb.uri, n, nil])
  p [ans[2], ans[3]]
end

If you run rindas.rb and rindac.rb in separate shells, you should get the following output:

rindas.rb:

do_it(1)
do_it(2)
do_it(3)
do_it(4)
do_it(5)
do_it(6)
do_it(7)
do_it(8)
do_it(9)
do_it(10)

rindac.rb:

[1, 2]
[2, 4]
[3, 6]
[4, 8]
[5, 10]
[6, 12]
[7, 14]
[8, 16]
[9, 18]
[10, 20]

And that's all there is to Rind::Ring!

This explanation of Rinda::Ring was created from the Masatoshi SEKI's Japanese Rinda::Ring page after translating it to English, Masatoshi's rinda examples in Ruby's sample directories, and reading the Rinda sources.

© Eric Hodel - 20040304