7.5 Finding a Service with Ring
Ring is a name server, and it uses TupleSpace to publish your service on a local area network (LAN). The concept of Ring is close to Java’s Jini. For those familiar with Jini, Ring is similar to Jini’s discovery and lookup functionalities, but there are a few differences in terms of resource management. In Jini, a special service called a lease deletes objects if they haven’t been used for a while, while Ring manages them by simply adding an expiration date on the tuplespace. A server needs to renew the tuplespace on its own. However, the code for the renewal will be minimal by using a callback from the tuplespace.
dRuby didn’t have a default name server in the beginning, and I’ll explain why later. So, why did I write Ring later? The answer is simple: it looked interesting to do. Ring provides you with a way to dynamically look up services. An application can connect to a Ring network, register itself, and search for other available services. Ring can also remove unavailable services periodically or notify when a new service is registered. These functionalities let you change the process architecture of your system dynamically (see Figure 40, How RingService, a service, and an application work together).
Here are the advantages of Ring:
-
There’s no need to know the URI of services in advance.
-
There’s no need to know the URI of a name server in advance.
-
When a service goes down, it will be removed from the name server periodically.
-
The services can receive a notification when a new server joins Ring.
Ring consists of two elements:
-
A structure to search
TupleSpacewithin a LAN -
A name server that utilizes
TupleSpace
Ring doesn’t have a special class; it simply uses TupleSpace as a name server. Once you find a TupleSpace, you can use it for normal usage of TupleSpace (for example, for writing, reading, or deleting tuples).
When you ask Ring for a service, Ring searches in two steps, so it’s not ideal for short-lived processes, such as CGI. It’s more suited for long-running processes between web applications or between web applications and other back-end services.
Locating a Name Server
As mentioned earlier, I haven’t provided a naming service, because you can just use Hash instead. Here is an example.
First, you prepare a name server with a URI such as ’druby://localhost:12345’.
require 'drb/drb' |
|
DRb.start_service('druby://localhost:12345', Hash.new) |
|
DRb.thread.join |
Then, you can add an entry called MyApp as follows:
require 'drb/drb' |
|
require 'myapp' |
|
DRb.start_service(nil, MyApp.new) |
|
ns = DRbObject.new_with_uri('druby://localhost:12345') |
|
ns['MyApp'] = DRbObject.new(DRb.front) |
|
DRb.thread.join |
Once registered, you can use the object, like so:
require 'drb/drb' |
|
DRb.start_service |
|
ns = DRbObject.new_with_uri('druby://localhost:12345') |
|
my_app = ns['MyApp'] |
|
my_app.do_it() |
There’s one thing I don’t like about this approach: you have to know the location of the name server. Both the name server and the client have to know the URI of the name server before starting the server.
In the preceding example, we hard-coded the URI inside the script. Even if you make it configurable, you still need to know the location of the URI.
This isn’t a big problem if your system is relatively small (and the initial scope of dRuby use was for small systems), but I wanted to come up with a way to create a system without needing to know the name server’s URI. This is how I started developing Ring.
Searching TupleSpace
In Ring, the name server functionality is built on top of TupleSpace. To use the TupleSpace, you need to search available services. In this section, we’ll cover publishing and searching.
Publishing TupleSpace with RingServer
Ring uses UDP broadcasting to publish and search TupleSpace.
RingServer is a class to support publishing TupleSpace via the User Datagram Protocol (UDP). RingServer monitors a UDP port and then returns references of TupleSpace requests.
Here’s a basic example of RingServer usage:
| ring00.rb | |
require 'rinda/ring' |
|
require 'rinda/tuplespace' |
|
|
|
DRb.start_service |
|
|
|
ts = Rinda::TupleSpace.new |
|
place = Rinda::RingServer.new(ts) |
|
|
|
DRb.thread.join |
|
To use RingServer, instantiate an object by passing the tuplespace you want to publish as an argument.
RingServer.new(ts, port=7647) generates a RingServer with a tuplespace to publish. RingServer uses the UDP port number specified in the port argument. By default, it is 7647.
The internals of RingServer are a bit tricky.
RingServer keeps waiting until DRbObject arrives at the UDP port of the RingServer. The object that gets sent via UDP is actually an Array tuple, as follows:
[:lookup_ring, DRbObject.new(block)] |
RingServer executes a call method with the published tuple once the tuple arrives.
tuple[1].call(@ts) |
UDP is used only to wait for DRbObject, and it isn’t used to send replies. For replies, you use the remote method invocation of DRbObject. When a client receives the search result, it receives it with dRuby’s RMI rather than writing code to wait for UDP.
Searching TupleSpace with RingFinger
RingFinger is a utility class that’s published via RingServer to search a tuplespace. There are two ways to search; one way is a detailed search using the instance method, and another is a simple search using the class method.
RingFinger searches RingServer by broadcasting a reference (DRbObject) of proc via UDP first and then receiving a callback from RingServer (see Figure 41, Searching by RingFinger using RingFinger#lookup_ring_any).
Use RingFinger.primary to find only one RingServer, and use RingFinger.to_a to find all RingServers in a network. Make sure you start the dRuby service before using RingFinger. Here’s an example of searching one RingServer:
require 'rinda/ring' |
|
DRb.start_service |
|
ts = RingFinger.primary |
Here’s a list of all the RingFinger methods:
-
RingFinger.finger -
Returns an instance of
RingFinger. When first called, it searchesRingServerusinglookup_ring. -
RingFinger.primary -
Returns a reference to the
TupleSpaceofRingServerfound byRingFinger.finger. -
RingFinger.to_a -
Returns all references to the
TupleSpaceofRingServerfound byRingFinger.finger. -
RingFinger.new(broadcast_list=[’<broadcast>’, ’localhost’], port=7647) -
Generates
RingFinger. Can specify broadcast range and port number. -
RingFinger#lookup_ring(timeout=5, &block) -
Awaits callback for the timeout period after a search packet is broadcast via
UDP.RingServeryields the block given atRingFinger#lookup_ringwith a reference toTupleSpace. You should specify an action you want to take once theTupleSpaceis found in the code block. -
RingFinger#lookup_ring_any(timeout=5) -
Simplified version of
lookup_ring. Returns theTupleSpacethat responded to the first callback. RaisesRingNotFoundif nothing is found.
Searching by instance of RingFinger gives you more flexibility, such as being able to specify the range of broadcasting. Searching by class method will cache the result of the first search, so you can’t look up per request. It depends on the use case as to which one to use, but using class methods is enough for simple applications.
To be precise, searching by the class method of RingFinger looks like a singleton, but I didn’t implement it with class methods. This is because you don’t have to limit the number of RingFingers to one, so it doesn’t have to be a strict singleton.
Name Server Using TupleSpace
Ring uses TupleSpace to make a name server. This isn’t a special TupleSpace, and it just uses the TupleSpace published by RingServer or RingFinger.
Here are the responsibilities of Ring’s name server:
-
Make a tuple consisting of name, type, and reference
-
Publish the object via
writetoTupleSpace -
Search via
readorallwith pattern matching
This is the format of the tuple:
[:name, :type, a DRbObject, "comment"] |
- tuple[0]
-
Symbol. Represents that this is a tuple representing a name server entry. The value is always:name. - tuple[1]
-
Symbol. Represents a category (for example,:name_server,:place,:rwiki). This name should be decided per system. - tuple[2]
-
DRbObject. A reference to the object to be published. - tuple[3]
-
String. A comment to explain what this tuple is used for. If there’s nothing to comment, use an empty string ornil.
Let’s look at a few examples:
# Register a name valid for 600sec |
|
ts.write([:name, :rwiki, book.front, "RWiki2 front"], 600) |
|
# Search |
|
tuple = ts.read([:name, :rwiki, DRbObject, nil]) |
|
rwiki = tuple[2] |
There is a utility class to publish these objects. RingProvider supports publication of objects and has these responsibilities (you can still directly control tuples if you need more fine-grained control): it generates tuples and prepares renewer.
Here are the methods of RingProvider:
-
RingProvider.new(klass, front, desc, renewer = nil) -
Prepares
[:name, klass, front, desc]tuple andrenewer. Ifreneweris not specified, generatesSimpleRenewer. -
RingProvider#provide -
Searches tuplespace and writes the tuple and
renewerintoTupleSpace.
To publish a reference of your application to Ring, you need to instantiate RingProvider and then call provide. Here is the code:
| ring01.rb | |
require 'rinda/ring' |
|
class Hello |
|
def greeting |
|
"Hello, World." |
|
end |
|
end |
|
|
|
hello = Hello.new |
|
DRb.start_service(nil, hello) |
|
provider = Rinda::RingProvider.new(:Hello, DRbObject.new(hello), 'Hello') |
|
provider.provide |
|
|
|
DRb.thread.join |
|
The last few lines of this script are key. It first generates Rinda::RingProvider and then calls provide to publish the object to the network. The argument of the new constructor is in the order of the type of the object to publish, the reference to the object, and the explanation of the object.
provider = Rinda::RingProvider.new(:Hello, DRbObject.new(hello), 'Hello') |
|
provider.provide |
provide adds an entry into the name server. When this is called, it writes the following tuple:
[:name, :Hello, DRbObject.new(hello), 'Hello'] |
You’ll find out how to use this service within an application in the next section.

