7.1 Adding a Timeout in a Tuple
We often make mistakes. Scripts contain bugs. While writing a script, we tend to rerun the same process again and again as we modify it. What happens if we leave a tuple in tuplespace that shouldn’t be duplicated while we’re developing? When both the tuplespace and the application run in one process, we won’t leave obsolete tuples because both the tuplespace and the applications are restarted. However, the tuplespace process can contain old tuples only when the tuplespace and the application live in different processes and only the application process is restarted. In an ideal world, an application should clear the tuplespace before it stops, but this doesn’t always happen.
Timeout and Renewer Class
You can set a timeout on a tuple (see Figure 37, A tuple times out after thirty seconds and gets deleted automatically). When a tuple expires, it is automatically removed from the tuplespace. This avoids obsolete tuples left in tuplespace even when an application quits under unexpected circumstances. You can set the timeout in the second argument of the write
method.
@ts.write(tuple, sec) |
After it reaches the set period, the tuple is removed from tuplespace automatically. When nil
is given, the timeout period becomes indefinite, and it doesn’t get deleted automatically. The default is set to nil
.
% irb -r rinda/tuplespace --prompt simple |
|
>> ts = DRbObject.new_with_uri('druby://localhost:12345') |
|
=> #<Rinda::TupleSpace:0x007ff99b03ba00> |
|
>> ts.read_all(['test']) |
|
=> [] |
|
>> ts.write(['test'], 30) |
|
=> #<Rinda::TupleEntry:0x007ff99b889f90> |
|
>> ts.read_all(['test']) |
|
=> [["test"]] |
|
>> sleep(30) |
|
=> 30 |
|
>> ts.read_all(['test']) |
|
=> [] |
We can specify not only Integer
and nil
but also an object called Renewer
. Renewer
has a renew
instance method. The Renewer
object returns how long it wants to extend once renew
is called. Once the renew
method returns true
, the tuple is expired and removed from tuplespace immediately. An application can adjust how long it wants to extend a tuple’s life span depending on its state by using Renewer
.
Rinda has a class called Rinda::SimpleRenewer
. You can define SimpleRenewer
easily.
class SimpleRenewer |
|
include DRbUndumped |
|
def initialize(sec=180) |
|
@sec = sec |
|
end |
|
def renew |
|
@sec |
|
end |
|
end |
All this renew
method does is return 180. The point is that this is being passed by reference because we included DRbUndumped
. If you pass this SimpleRenewer
as a second argument of the tuple’s write
method, the renew
method will be called every time a tuple reaches the timeout period.
If an application finishes without leaving a tuple, the tuplespace calls the renew
method in the next timeout period. However, it will fail to call the method because the application that puts SimpleRenewer
has already finished. The tuplespace catches that it failed to call the renew
method and deletes the tuple. By doing this, the tuplespace deletes the tuple for the next timeout period even when an application fails to clear up the tuple (see Figure 38, When a process that holds SimpleRenewer finishes, it fails to update and the tuple is deleted).
Figure 38. When a process that holds
SimpleRenewer
finishes, it fails to update and the tuple is deleted.
The tuple expiration timing interval depends on the number that the renew
method returns. In other words, it’s possible for a tuple to stay on the tuplespace for a while after the application finishes. You can make this interval shorter, but making the interval too short may impact performance because Renewer
requires dRuby method invocation for the periodic update.
Invalidation Using TupleEntry
In addition to the Renewer
method, there is a way to invalidate a tuple. However, this method is a little difficult to use when TupleSpace
and an application are in different processes, so I recommend using Renewer
.
When you call the write
method of TupleSpace
, the method actually returns a value called TupleEntry
. You can operate the tuple you put into tuplespace via TupleEntry
. TupleEntry
has the following methods:
-
cancel
-
Set a tuple’s timeout period to a past value and invalidate the tuple. The tuple is deleted from tuplespace.
-
renew(sec_or_renewer)
-
Renew a tuple’s timeout period. You can provide either a new time period value in seconds or a
Renewer
object. If you specifynil
, it sets the timeout to indefinite.
Here are some examples:
@entry = @ts.write(['foo', 'bar]) |
|
@entry.cancel |
The cancel
operation doesn’t fail even when the tuple is taken by some other process.
TupleEntry
is created inside TupleSpace
during a write
operation. If its associated tuple is taken, then TupleEntry
is left and forgotten. One thing you have to be careful about is the life span of TupleEntry
. This isn’t an issue when the process that allocates TupleSpace
and the process that writes the tuple are the same, but it will cause a GC problem if they are different. If another process has a returning value of a write
operation, it holds only DRObject
—which is referenced by TupleEntry
—and it doesn’t refer to TupleEntry
itself. If take
happens, then the object TupleEntry
refers to doesn’t exist anymore, and the object is wiped by GC.
There are two workarounds to avoid this.
-
Use
TimerIdConv
on theTupleSpace
server to protect an object that dRuby refers to from GC (we’ll discuss this in more detail in Chapter 11, Handling Garbage Collection). -
Don’t use
take
or don’t count onTupleEntry
for tuples that may not take.
You can accomplish the first workaround by putting the logic to control GC into a process that provides TupleSpace
and avoids TupleEntry
being garbage collected.
You can accomplish the second at the application level by controlling TupleEntry
only for the tuples that are less likely to be taken. However, both workarounds seem a bit cumbersome. If you know that multiple processes are to be involved, it’s better to use Renewer
.