A tutorial on Tuplespace programming - CiteSeerX

0 downloads 0 Views 131KB Size Report
Nov 30, 2004 - distributed theory and a good background in Java programming will ...... the start of the run, and does not contain earlier problems and solutions.
A tutorial on Tuplespace programming Anders Fongen The Norwegian School of Information Technology

November 30, 2004

Abstract The purpose of this tutorial is to give the reader an overview of the principles of tuplespace programming. The principles of a shared objectspace is discussed and a number of distributed programming patterns are presented. The Java programming language is used throughout the examples. For the example programs and laboratory exercises the TSpaces implementation from IBM is used, and a copy of this software is required in order to work with the examples and exercises. The target audience ofor this tutorial are students of distributed programming as Master’s level, but anyone with some knowledge of distributed theory and a good background in Java programming will be able to understand the discussions and examples.

1

Contents 1 The need for distributed programming abstractions

3

2 Principles of the Tuplespace

3

3 Tuplespace implementations

4

4 TSpaces implementation details 4.1 Tuples and templates . . . . . . . . . . 4.2 Storing tuples in the tuplespace . . . . 4.3 Matching principles . . . . . . . . . . . 4.4 Retrieving tuples from the tuplespace 4.5 Retrieving fields from tuples . . . . . . 4.6 Tuplespace events . . . . . . . . . . . . 4.7 Tuple expiration . . . . . . . . . . . .

. . . . . . .

4 4 5 5 6 7 8 8

. . . . . . .

9 9 10 10 11 11 11 12

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

5 Tuplespace distribution patterns 5.1 Client-server communication (many to one) . . . . . . 5.2 Unicast messaging . . . . . . . . . . . . . . . . . . . . 5.3 Multicast messaging . . . . . . . . . . . . . . . . . . . 5.4 Publish/subscribe messaging . . . . . . . . . . . . . . 5.4.1 Using the class hierarchy for mathing purposes 5.5 Competing servers . . . . . . . . . . . . . . . . . . . . 5.6 Collaborating servers . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

6 Distributed data structures

13

7 Special topics 13 7.1 MarshalledObjects . . . . . . . . . . . . . . . . . . . . . . . . 13 7.2 The equals() - method . . . . . . . . . . . . . . . . . . . . . . 14 7.3 The Codebase system property . . . . . . . . . . . . . . . . . 15 8 Example application: finding prime 8.1 The NumberProblem interface . . 8.2 The NumberCruncher server . . . . 8.3 The Prime class . . . . . . . . . . . 8.4 The client class . . . . . . . . . . . 8.5 The SpaceWrapper class . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

15 15 16 18 20 20

9 Laboratory exercise - the distributed auction 9.1 How to configure and start the TSpaces server 9.2 Programming exercise part 1 . . . . . . . . . . 9.3 Programming exercise part 2 . . . . . . . . . . 9.4 Programming exercise part 3 . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

23 23 24 25 25

2

factors . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . .

1

The need for distributed programming abstractions

Distributed programs are popular because they offer extra advantages over centralized programs, but they also require a programming model that accommodates the particular problems that occur in a distributed environment. The familiar programming models in distributed computing as given in the literature, e.g.[1] are: • Remote Procedure Calls/Remote Method Invocations (RPC/RMI) • Messaging (MOM) • Interacting peers (p2p) A fourth model could be called shared objectspace, which will be presented in this article. For reasons soon to be explained the model is called a Tuplespace. The tuplespace offers an remarkably simple and flexible programming model, and it is possible to solve a wide range of communication and synchronization needs in a straightforward manner.

2

Principles of the Tuplespace

The tuplespace is a “pool” of objects where everyone can put and retrieve objects. The image of a “store” replaces the traditional ideas of “channels”, “messages” and “operations”. Instead, there is a notation of a tuple, which is a container object holding a combination of user data and tags (collectively called Fields). When data objects are stored, they are annotated with a series of tags and passed to the tuplespace. In order to retrieve data from the tuplespace the client presents to the tuplespace a template which becomes the parameter for a “matching” process. The tuples that fit this template will be returned to the client. The retrieval process can be consuming (tuples are removed from the tuplespace) or non-consuming (tuples are copied to the client), and the retrieval operation can be blocking (while waiting for tuples to arrive on the tuplespace) or non-blocking. The properties of a tuplespace can be described with these keywords: shared A tuplespace is a shared “memory” where everyone shares the same information persistent When an object is placed in the tuplespace, it stays there until it is removed or expires its optional lifetime. The existence of the data objects does not rely on the existence of any clients of the tuplespace. 3

associative Data objects are associated with tags and retrieved from the tuplespace through an association mechanism between the template and the fields of the data objects in the tuplespace (Several programming languages offers associative data structures like java.util.Hashtable). transactionally secure A series of operations on a tuplespace can be done under transactional control. E.g. a retrieve-modify-store cycle on a tuple can be done without the risk of a race condition. synchronous The retrieval operation can be made synchronous so that the client blocks until there is data available to retrieve.

3

Tuplespace implementations

Tuplespace implementations for Java is found in products like Sun’s JavaSpaces and IBM’s TSpaces. Although JavaSpaces seems to be widely known, the basis for this tutorial is IBM TSpaces. The setup and configuration is easier and more straightforward, and the programming model is simpler. Besides, the call semantics of TSpaces is more intuitive than that of JavaSpaces. IBM’s TSpaces is free for evaluation purposes, and it can be downloaded from http://www.almaden.ibm.com/cs/TSpaces/ (registration required). For the remainder of this tutorial it will be necessary to have access to the documentation files bundled with the downloaded package.

4

TSpaces implementation details

The TSpaces implements tuplespace in a centralized server. Clients operate on the tuplespace by connecting to the server (using TCP). The clients need to know the transport endpoint (IP address or DNS name) of the server in order to connect to it. Also, the client must supply the name of the space, since a server may contains several logically isolated tuplespaces. Once connected, both write and read operations take place over this connection. import com.ibm.tspaces.TupleSpace; ... TupleSpace ts = new TupleSpace("tuples.nith.no","testspace"); There are exceptions thrown during all tuplespace operations. More detailed programming examples will be presented later which will show the necessary try/catch blocks.

4.1

Tuples and templates

Both tuples and templates are represented by objects of the class com.ibm.tspaces.Tuple, which are ordered sets of objects (called Fields). 4

Customer customer = new Customer(...); // Must implement Serializable Tuple t1 = new Tuple("customer",new Integer(2),customer); All members of a tuple (in the constructor parameters) must implement java.io.Serializable. Normally, one would do two things with tuples: Store them in tuplespace and get the value of the different fields.

4.2

Storing tuples in the tuplespace

The tuple is stored in a tuplespace through a write operation on the tuplespace object: ts.write(t1); //alternatively ts.write("customer",new Integer(2),customer); The second form constructs a tuple and store it in the same statement. The consequence of the write operation is that the tuple is serialized and passed to the server over the TCP connection. The tuple is deserialized in the server, which has some implications for the server’s classpath which will be addressed in section 7.1

4.3

Matching principles

Templates are special form of tuples used for retrieval purposes. During retrieval, a template is used for the selection of tuples that are retrieved: all the tuples “matching” the template are candidates for retrieval. Some of the fields in the template can be “wildcards”, in which case they match any value. Wildcards are also known as Formals since they do not contain values, only a type. The tuples and templates are ordered sets of fields. The N th field of a template will only match the N th field in the tuples. Templates will never match a tuple with a different number of fields. The matching process is typesafe in the sense that the matching considers both the type and the value of the fields. Therefore, an Integer field and a Long field will never match, regardless if they have the same number value. A superclass will match any of its subclasses, i.e. a template with a Number field will match fields of type Long and Integer since these are subclasses of Number. The use of wildcards can be typesafe as well, i.e. it will match any value of a field with a specific type. The object new Field(String.class) is a wildcard that match all all fields with a String type. There exists a special form of wildcards which is typeless: new Field(Serializable.class) will match any field regardless its type. Consider these tuples and templates: 5

Tuple tuple1 = new Tuple("invoice",new Integer(2004), new Double(1440.50)); Tuple tuple2 = new Tuple("invoice",new Long(2004), new Double(2344)); Tuple tuple3 = new Tuple("order",new Long(2004), new Double(122),null); ... Tuple templ1 = new Tuple("invoice",new Field(Integer.class), new Field(Double.class)); Tuple templ2 = new Tuple("invoice",new Field(Number.class), new Field(Double.class)); Tuple templ3 = new Tuple(new Field(String.class),new Long(2004), new Field(Double.class),new Field(String.class)); The different templates will match tuples in the following manner: 1. templ1 matches tuple1. The first field matches on value and type, and the two wildcards matches the types of the corresponding tuple fields. 2. templ2 matches tuple1 and tuple2. The first field matches on value and type, and the wilcard matches the type or a sub-type of the tuple field (the class Number is the superclass of Integer and Long, and therefore matches both). 3. templ3 matches tuple3. Field 2 matches on value and type, and the rest (wildcards) matches on type. Note that the value “null” is typeless and matches any wildcard field.

4.4

Retrieving tuples from the tuplespace

When retrieving data from the tuplespace, the client presents a template to the tuplespace and get tuples in return. Several different methods for retrieving tuples are available and their semantics differs along three aspects of operations: 1. Will the retrieval operation block until there is at least one tuple to be returned (synchronous) or will it return a null value when no tuples are available (asynchronous)? 2. Can more than one matching tuple be returned in one operation (multituples)? 3. Will the returned tuples be removed from the tuplespace as well (consuming)?

6

The table below show the semantic properties of the different retrieval operations: asynchronous no yes no yes no yes no yes

multi-tuples no no yes yes no no yes yes

consuming no no no no yes yes yes yes

method name waitToRead read not available scan waitToTake take not available consumingScan

Given the tuple instances shown above, and that tuple1-tuple3 are stored in tuplespace, consider the following code: while (true) Tuple t = ts.take(templ2); The first two calls to ts.take would complete immediately and return tuple1 og tuple2 in an unknown order 1 . The third call will block until a matching tuple becomes available. Consider then this piece of code: while (true) Tuple t = ts.read(templ2); Since the ts.read operation is non-consuming, the loop will go on indefinitely, reading the same tuples over and over again. If we need to find all matching tuples in a non-consuming operation, the ts.scan operation should be used: Tuple tupleset = ts.scan(templ2); The returned tuple is merely a container for the tuples matching the template, which is found in tupleset as fields.

4.5

Retrieving fields from tuples

A tuple returned from tuplespace will normally be inspected for values and types of its fields. The fields of a tuple can be extracted into variables of type com.ibm.tspaces.Field, A tuple offers the method numberOfFields which can be combined with the method getField(int) in order to iterate over all fields in the tuple or to extract one particular field (remember that fields are ordered). A Field object offer the methods getValue and getType which can be used for extracting field values. 1

The TSpaces server can be configured to assure FIFO ordering of tuples

7

So in order to extract the Double value from the third field in the tuple (numbering starts at 0, so the index addressing the third field i 2) this piece of code will do: Double d = (Double)tuple.getField(2).getValue(); double dx = d.doubleValue(); The casting operator may cause a ClassCastException if the field is not of Double type.

4.6

Tuplespace events

An important property of the tuplespace is the ability to “notify” the client about new or deleted tuples. This notification uses a “callback” mechanism which will be briefly described here. In order to register for event notification the client calls the eventRegister method on the tuplespace with the following parameters: 1. Operation: Is there a TupleSpace.WRITE or a TupleSpace.DELETE that is interesting? 2. Template: Which tuples are we interesting in? 3. Handler: Where is the object that should be called for event notification? This object must implement the com.ibm.tspaces.Callback interface. The eventRegister method returns a sequence number which is also passed as a parameter to the callback method, so that the same callback method can handle several event registrations. The Callback interface requires only one method, call, which passes as parameters the sequence number and the tuple causing the event. For more details see the TSpaces documentation.

4.7

Tuple expiration

It is possible to “tag” a tuple so that it is automatically removed from the tuplespace after an expiration period. The expiration period is given in milliseconds: Tuple tuple = new Tuple(...); long expiration = 60*5*1000; // 5 minutes tuple.setExpire(expiration);

8

5

Tuplespace distribution patterns

Considering the simple semantics for storing and retrieving tuples from a tuplespace, a remarkably wide range of distributed applications is possible. In this section the most common distribution patterns will be presented together with a short outline of how they may be implemented using a tuplespace.

5.1

Client-server communication (many to one)

This most widely used distribution pattern imply that the client initiates a server operation (and passes parameters) and then blocks while waiting for the response (with a return value). Any number of clients can simultaneously call the same client. This pattern can be implemented in a tuplespace by designing tuples that contain fields for sender, recipient, operation name and operation parameters. The clients know the name of the server and the parameter requirements of the available operations in advance. Each client is required to have a unique name. The client creates a tuple with its own name in the sender field and the server name in the recipient field and the other fields set up according to the “service contract”. The client also creates a template that matches any tuple with its own name in the recipient field. The client code for calling a server operation now look like this: Tuple call = new Tuple(servername, myname, operationName, parameterObject); Tuple template = new Tuple(myname, servername, new Field(String.class), new Field(Serializable.class)); ts.write(call); // Now block client while waiting for response Tuple response = ts.waitTotake(template); responseObject = (responseClass)response.getField(3).getValue(); And the server code for receiving and processing requests may look like this: Tuple template = new Tuple(serverName,new Field(String.class), new Field(String.class), new Field(Serializable.class)); Tuple request = ts.waitToTake(template); String clientName = (String)request.getField(1).getValue(); String operationName = (String)request.getField(2).getValue(); // Process request and make a responseObject Tuple response = new Tuple(clientName,serverName, 9

operationName,responseObject); ts.write(response);

5.2

Unicast messaging

Another common method for communicating is through messaging, where a sender asynchronously sends messages to a receiver without waiting for a response. It may be necessary to keep messages in the “pipeline” until the receiver is ready to receive it. The messages are normally expected to be FIFO ordered. A tuplespace implementation of messaging has a lot in common with client-server communication: A tuple needs to hold sender name, receiver name and necessary message objects. The sender creates the tuple and writes in to the tuplespace: Tuple message = new Tuple(receiverName, myName, messageObject); ts.write(message); The receiver must make a template that matches tuples sent from anyone or one specific sender, depending on the application. The receiver can also choose between blocking (waitToTake) or nonblocking (take) operations. The example below uses a nonblocking operation. Tuple fromAnyone = new Tuple(myName,new Field(String.class), new Field(Serializable.class)); Tuple fromOne = new Tuple(myName,senderName, new Field(Serializable.class)); Tuple message = ts.take(fromAnyone); // alt. fromOne // Extract messageObject and use it

5.3

Multicast messaging

A message that is received by several receivers is referred to as a multicast message. It is possible to implement multicast messaging in a tuplespace if the intended receivers read the tuplespace with the same template. The receivers cannot use the take operation however, since the first receiver will remove the tuple using this operation. All receivers must use the nonconsuming read or waitToRead operations. The use of nonconsuming read raises two problems: (1) How are the tuples (after being read by everyone) removed from the tuplespace? (2) How can we avoid that the receivers read the same tuple over and over again? If the number of receivers are known in advance, then the problem can be solved by a counter (a field) in the tuple which represents the remaining number of receivers that have not yet received the message. Each member 10

take the tuple, decrement the counter and write it back if the counter is greater than zero. This algorithm is correct, but generates a lot of traffic and unnecessary synchronization. Besides, the number of recievers is likely to be unknown (and varying). In the particular variant of multicast messaging called publish/subscribe the number of receivers is definitely unknown. The solution lies in the combination of tuple expiration and event registration: A tuple is set to expire after every receiver has had a chance to receive it (with a reasonable margin). To avoid that the same tuple is read several times, it is not read with a waitToRead operation, but passed to the receivers as a parameter to the event registration callback method.

5.4

Publish/subscribe messaging

A particular variant of multicast messaging involves the use of message topics. A message does not have a receiver address, but a topic. Receivers subscribe to messages on specified topics. The use of topics becomes an indirect addressing scheme where the number and identity of the receivers is unknown (and uninteresting). A publish/subscribe messaging system can be made with the tuplespace using the scheme described in the previous section, i.e. the combination of tuple expiration and event registration. The receivers indicate which topics they wish to subscribe to through event registrations, and the sender annotates the tuple with expiration times so that they do not fill up the tuplespace forever. 5.4.1

Using the class hierarchy for mathing purposes

Rather than using string values for topic representation, Java classes may be used for this purpose. In that case, different topics may be represented by different Java classes (these classes would not contain methods or variables, they are purely a declarative thing), and a common super-topic can be represented by a common superclass. The implications of this scheme is that a topic hierarchy can be represented by a class hierarchy with a natural matching semantics. The matching semantics will allow a template to match a “branch” of the topic hierarchy. If “Local news” and “National news” are subcategories of “News”, a template using News.class as a template will also match local and national news. The ability to subscribe to broad or specific topics is useful in many applications.

5.5

Competing servers

In large scale systems it is often useful to have an array of competing servers. The workload may be balanced between several servers so that their process11

ing capacity is aggregated. They also represent a fault-tolerant resource since some servers may fail without disrupting the service. The tuplespace handles this configuration very well and offers a natural solution to the resource management problem: the clients create job requests in tuples which are written to the tuplespace. Each server takes tuples (all servers use identical templates) from the space, processes the request, writes the result back to the client (through unicast messaging) and then takes another request tuple. This model distributes the requests in a round-robin fashion between the available servers, and queues up requests in the tuplespace when there are no available servers. The model works well with a dynamic number of servers, so new servers can be added to increase the processing capacity (transparent both to clients and servers). The model even offers to restart the request on another server in case of server crash. The take and the subsequent write operation may be done in a transactional context. This implies that the request tuple is not removed from the tuplespace until the reply is written back and the transaction is committed. In case the server crashes the transaction is aborted and another server will eventually read and serve the request tuple. The model of competing servers assume that there is no communication between the servers: they are all self-contained and process requests without any collaboration with other servers. Web servers, printing servers etc. are well suited for this distribution pattern.

5.6

Collaborating servers

A variation of the competing servers pattern can be found in certain computational problems, where the servers may need to communicate between themselves during the processing phase. The communication may be required to: • Pass service requests from one server to another. During the processing phase, the server may identify new tasks that any server can process. A tuple is therefore created and written to the tuplespace and eventually piced up by one of the servers for processing. • Notify all servers of job completion. In e.g. a “brute force” cryptographic attack a large number of servers may divide a key space in small portions and exhaust the key space in search of a valid key (aka a “known plaintext” attack). If one server finds the key, the job is completed and all servers should be told to stop. The first requirement is easily met by allowing any server to act as a client and write request tuples to the space. The other requirement is met by event 12

registration with the same template in every server. The callback handler method in the server program must modify a variable which is checked regularily during the computing process so that the program stops. The example presented later in this tutorial will demonstrate the details of activity coordination in a system of collaborating servers.

6

Distributed data structures

The discussion so far has mainly dealt with the tuplespace as a communication paradigm. Is is easily seen that the tuplespace can offer distributed data structures as well as communication mechanisms. The concept of distributed data structures builds on the idea of data elements (tuples) that are individually accessible by many clients, and that the infrastructure of these elements (e.g. a tree or a graph) can be used and modified by several clients simultaneously. A relational database can be used for shared access to structured data, but the limitations of the relational model requires a lot of “mapping” in order to operate on general structures. The tuple lends itself well to the design of general data structures: Varying number of references (as in graphs and trees) are easily handled as List fields etc. More important though is the concept of transactions which enable a client to “lock” a tuple without removing it (using a take operation). E.g. the deletion or insertion of an element in a doubly linked list requries that two other tuples are “locked” while the operation is in progress. Transactions also offers crash resilience: if the client crashes during this composite operation, the transaction will abort and the structure will be seen as if the operation never took place. There will be no further discussion of distributed data structures in this tutorial, but the reader is encouraged to consult books on this subject, e.g. Bishop [2] or Freeman [3].

7

Special topics

In this section a number of more subtle detailes of tuplespace programming will be presented.

7.1

MarshalledObjects

When tuples are received by the TSpaces server, the tuple fields are deserialized. When the JVM2 deserializes an object, the class loader will load the 2

Java Virtual Machine

13

corresponding class. If this class is not available, a ClassNotFoundException will be thrown. Under these circumstances it appears that the tuplespace server will need to have the classes in its CLASSPATH for all objects passing through it, which is clearly an inflexible solution: It would need to have classes for all different request and response objects installed before it can operate an application. Java offers a “wrapper class”, java.rmi.MarshalledObject, that comes to rescue in this situation. A MarshalledObject can contain another object in a serialized form so that it can be passed to the tuplespace server without the implications just mentioned. A field containing a MarshalledObject cannot be used for value matching, since there is no “true” representations of the object available, only a serialized one. The SpaceWrapper class shown in the example in Section 8.5 is using this approach for making the application objects invisible to the tuplespace server. Please consult the source code for details.

7.2

The equals() - method

When the tuplespace server tries to match two fields, it checks the types and the values of both objects: • The type of the template field must be “assignable” to the data field, i.e. it must be the same class as the data field, a superclass or an interface that the data field is implementing (thus the use of Serializable.class for matching to any type Object.class would have the same effect). If the types are not matching, no value checking take place. • If the template field has no value, only type (called a Formal), then no value matching take place. • Matching of values of the two fields uses the equals(Object) method present in any class (it is inherited from Object). Therefore, the use of application objects as template fields requires that the tuplespace server has the corresponding classes installed and that the application classes has a sensible implementation of the equals method. An overloaded equals method may alter the matching semantics in interesting ways, by returning true if the objects are “nearly equal”, “equivalent, but not equal”, “in close proximity” etc. Two geometric points could be considered equal if they are sufficiently close to each other, or two documents could be regarded as equal based on the angles between their highdimensional vector representations.

14

7.3

The Codebase system property

The MarshalledObject class can be annotated with a URL that tells where the class of the contained object can be loaded. The receiver of the MarshalledObject will do a get operation on it in order to retrieve the contained object, and the class loader will load the class from the given URL. The annotation of MarshalledObjects is used the based on the system property java.rmi.server.codebase which can be set during JVM startup (as a command line parameter) or in the code using a System.setProperty(..) method call. See Pitt [4] for more details. The combination of MarshalledObjects and use of Codebase properties allow anyone to receive objects of unknown classes, and to operate on these classes through interface declarations. A framework for deployment and operation of mobile agents can be built on these mechanism. Tuplespace programming fits well into this picture as an infrastructure for deployment of mobile agent objects and for communication between the agent and its home base or other mobile agents.

8

Example application: finding prime factors

This section will present a tuplespace application for a number crunching problem. The chosen problem is to find prime factors for a number. We have a large number which is a product of two primes, and the problem is to find these two primes. The mathematical approach to this problem can have different levels of sophistication, but in this program every possible combination of factors are multiplied (they are not even checked for primality). The purpose of the program is to demonstrate how an unknown number of servers can collaborate to divide a problem into manageable pieces, and to broadcast a solution so the other servers can stop their work. The tuplespace is used as a coordination mechanism. “Problems” and “Solutions” are represented as tuples and passed between the client and the servers (also between the servers). The actions of the servers and the client will be shown as the programming code is presented.

8.1

The NumberProblem interface

The example uses an interface to describe a wide range of combinational problems. These problems have in common that (1) They require a repeated computation over a range of parameter values, and (2) that each set of parameter values produce either a success (solution found, report and stop computing) or a failure (try again on different parameter values), and (3) that the problem may be divided into smaller problems by partitioning the parameter range. package no.nith.andfon.cs5023.tspc3; 15

// An interface for a generic numeric problem. The mathematical // expression, the parameters and the sequence of parameter iterations // are hidden in the implementation. public interface NumericProblem extends java.io.Serializable { // Return true of the problem is solved with the // current set of parameters boolean solved(); // Return the number of necessary iterations on the solved()-method // in order to exhaust the parameter space long iterations(); // Change the parameters so that a new iteration of solce() // can be called boolean nextIteration(); // Split the "iteration space" into two objects. This object // retains one half, and the other half into the returned object NumericProblem split(); // Present a solution to the problem in string format String solution(); } The idea behind the split function is that it should be possible for a server to split the problem in two halves, return one half and keep the other. Since the client (who defines the problem) does not know the number of servers or their processing capacities, it is felt as a good idea to let the servers consider the workload of one problem description (through the iterations-method). If the server does not have sufficient capacity for exhausting the parameter space in a reasonable time, it may choose to partition it.

8.2

The NumberCruncher server

The server class that operates on instances of the NumericProblem interface does not know anything about what kind of problem it is solving. The class does the following: 1. Picks a problem from the space 2. Successively partitions it (and puts one half back to the space) until the problem is within maximum size 16

3. Iterates over the parameter range. If a solution has been found, it is posted to the space so that everyone can read it. package no.nith.andfon.cs5023.tspc3; // This is the "slave" program, which receives number crunching tasks // from the tuple space. If the task is too big, it is split in two halfs, // and one half is returned to the tuplespace public class NumberCruncher implements NumberCruncherInterface { // stop iterating when someone has found a solution private boolean iterating = true; public static void main(String[] args) { long maxNumberOfIterations = Long.parseLong(args[0]); new NumberCruncher(maxNumberOfIterations); } public NumberCruncher(long maxIterations) { SpaceWrapper sw = new SpaceWrapper(this); while (true) { // pick a task from the space NumericProblem np = sw.getTask(); // calculate the necessary number of iterations for this task long it = np.iterations(); // if it is too many, split the task in two while (it > maxIterations) { NumericProblem np2 = np.split(); // keep np, and return np2 back to the space sw.putTask(np2); it = np.iterations(); } // now iterate over this task and look for a solution while (iterating) { if (np.solved()) { // announce solution sw.putSolution(np); iterating = false; } if (!np.nextIteration()) break; } // do not fetch more tasks if a solution // has been found (by anyone) if (!iterating) return; 17

} } // this is the listener method which is called when // a solution tuple is written to the space public void stopIterating() { iterating = false; } } The class implements the NumberCruncherInerface which only requires the stopIterating method. This method is supplied so that the class can be asynchronously notified of solutions found by other servers (so it can stop working).

8.3

The Prime class

The Class implementing the NumericProblem interface takes a very naive approach to the factoring problem. The code is shown below. package no.nith.andfon.cs5023.tspc3; // A specific mathematical problem: Find a product’s // two prime factors public class Prime implements NumericProblem { public long lo1,up1,lo2,up2; // Defines the iteration room private long x1,x2; // Working variables private long primeProduct; // Right side of the equation public Prime(long l1,long u1,long l2, long u2, long prod) { lo1 = l1; up1 = u1; lo2 = l2; up2 = u2; x1 = lo1; x2 = lo2; primeProduct = prod; } // Calculate (or predict) the number of iterations necessary // to exhaust the parameter room public long iterations() { return (up1-lo1)*(up2-lo2); } // Make the instance variables ready for the next call 18

// to solved(). (Note: we do not ensure prime factors in this code) public boolean nextIteration() { if (x1 < up1) x1++; else if (x2 < up2) { x1 = lo1; x2++; } else { return false; } return true; } // Split the problem in two smaller halves by dividing // the iteration room in two public NumericProblem split() { long x; NumericProblem np; // Pick one of the two parameters at random, and divide if (Math.random() > 0.5) { x = (up1-lo1)/2 + lo1; np = new Prime(lo1,x,lo2,up2,primeProduct); lo1 = x+1; } else { x = (up2-lo2)/2 + lo2; np = new Prime(lo1,up1,lo2,x,primeProduct); lo2 = x+1; } x1 = lo1; x2 = lo2; return np; } // Check if we have found a solution with these parameters public boolean solved() { return x1 * x2 == primeProduct; } // Present the solution in string format public String solution() { return "The factors are " + x1 + " and " + x2; } public String toString() { return "NP: " + lo1 + " " + up1 + " " + lo2 + " " + up2 + " " + x1 + " " + x2; } }

19

Please note that the implementation is a completely self-contained instance of the current state of the problem, i.e: • it contains working variables that indicates the portion of the parameter space already checked, so that the server may call nextIteration without knowing anything about how the parameter space is traversed. • it also serves as a description of a solved problem, so that it may be sent to the tuplespace back to the client as soon as the solved method returns true.

8.4

The client class

The code of the client is very simple: Formulate the problem, put it in the tuplespace and wait for the answer The code is shown below: package no.nith.andfon.cs5023.tspc3; // This is the master and the conductor for the solution of the // numeric problem. It generates the first task and puts it on the // space, and then waits for the solution public class PrimeBallerina { public static void main(String[] args) { SpaceWrapper sw = new SpaceWrapper(); sw.eraseAllTuples(); long primeprod = Long.parseLong(args[0]); NumericProblem np = new Prime(1,10000,1,10000,primeprod); sw.putTask(np); NumericProblem np2 = sw.getSolution(); System.out.println(np2.solution()); } } The method eraseAllTuples is made to ensure that the tuplespace is empty at the start of the run, and does not contain earlier problems and solutions that may disturb the program.

8.5

The SpaceWrapper class

From the code shown so far there are no references to the tuplespace object or to the the take or read operations. These methods are encapsulated in a “wrapper” class together with the details of tuple design. The reason for doing this is mainly of portability. Other tuplespace implementations like Sun JavaSpaces are well suited for the numbercrunching application, but the JavaSpaces API is wastly different from TSPaces’ API. 20

The wrapper class shown below improve the portability through isloation of the API details. The wrapper class is not generic, since it is tightly coupled to the other classes of the application and the tuple design used in the application. package no.nith.andfon.cs5023.tspc3; import com.ibm.tspaces.*; import java.rmi.MarshalledObject; // This helper class wraps the TSpace server into application // specific methods, and hides the Tuple design. public class SpaceWrapper implements Callback { NumberCruncherInterface ncListener; TupleSpace ts; public SpaceWrapper() { // locate space server try { ts = new TupleSpace("NumberCruncher","192.168.2.29"); } catch (TupleSpaceException e1) { e1.printStackTrace(); } } public SpaceWrapper(NumberCruncherInterface nci) { this(); ncListener = nci; // subscribe to events from "problem solved"-message try { Tuple template = new Tuple("Solution",new Field(MarshalledObject.class)); // Set up callback service int subscription = ts.eventRegister(TupleSpace.WRITE,template,this,true); } catch (TupleSpaceException e1) { e1.printStackTrace(); } } public void eraseAllTuples() { Tuple t=null; try { t = ts.consumingScan(new Field(String.class), new Field(MarshalledObject.class)); } catch (Exception e3) { e3.printStackTrace(); } } 21

public void putTask(NumericProblem np) { // send np to space try { ts.write("Task",new MarshalledObject(np)); } catch (Exception e2) { e2.printStackTrace(); } } public NumericProblem getTask() { // take problem from space Tuple t=null; try { t = ts.waitToTake("Task",new Field(MarshalledObject.class)); MarshalledObject mo = (MarshalledObject)t.getField(1).getValue(); return (NumericProblem)mo.get(); } catch (Exception e3) { e3.printStackTrace(); } return null; //Just to keep the compiler happy } public void putSolution(NumericProblem np) { // send solution to space try { ts.write("Solution",new MarshalledObject(np)); } catch (Exception e4) { e4.printStackTrace(); } } public NumericProblem getSolution() { // read (non-consuming) solution from space Tuple t=null; try { t = ts.waitToTake("Solution",new Field(MarshalledObject.class)); MarshalledObject mo = (MarshalledObject)t.getField(1).getValue(); return (NumericProblem)mo.get(); } catch (Exception e5) { e5.printStackTrace(); } return null; //Just to keep the compiler happy } public boolean call(String eventName,String tsName, int seqNum,SuperTuple tuple,boolean isException) { if ((!isException) && (ncListener!=null)) { ncListener.stopIterating(); } return false; } // call() 22

}

9

Laboratory exercise - the distributed auction

After this discussions of tuplespace principles and usage patterns there is time for some programming exercises. In order to do these exercises the following software is required: • a Java SDK and an editor (or an IDE like Eclipse or NetBeans) • the TSpaces library file tspaces.jar • a computer with the TSpaces server running (can be the local PC)

9.1

How to configure and start the TSpaces server

During a lab experiment you may use a TSpaces server that is set up for you by the laboratory supervisor, or you can start your own server. In the former case you must be careful to create spaces with a unique name, or your use will probably interfere with the others. In the latter case (where you will start your own server) the following command should be issued (make sure that the PATH includes the java executables): java -cp tspaces.jar com.ibm.tspaces.server.TSServer -c tspaces.cfg The command refers to the file spaces.cfg which must be found on the current directory. Ensure that the following lines are present in this file: [HTTPServer] HTTPServerSupport = true HttpPort = 8201 HTTPAdminSupport = true When the TSpaces server has started, use a web browser to issue a request to it on port number 8201: http://aa.bb.cc.dd:8201/. The response is a nice administration menu which lets you inspect and clear the spaces in the server. If you choose to use a common TSpaces server, you must be very careful not to destroy data belonging to other users. The on-line documentation should be available on the common TSpaces server using the web port.

23

9.2

Programming exercise part 1

The first programming assignment of this tutorial is to write a distributed auction application using a tuplespace. Here are some guidelines for the design: • The client presents a GUI and receives bid from the user. It sends the bids to the server and asks for the highest bid in return. • The server receives bids and records the amount of the highest bid and the name of the bidder. Use the GUI interface below as a starting point to create the client program. The server program should be written without a GUI. Start your work with a tuple design decision and check that your protocol works before you start programming. Put in a lot of System.out.println-sentences to help you with the debugging. // make your own package declaration import java.awt.*; import java.awt.event.*; import javax.swing.*; public class AuctionClient extends JFrame implements ActionListener { String me; JTextField bid,highBid; public AuctionClient_org() { me = JOptionPane.showInputDialog(this,"What is your name?"); if (me.equals("")) return; // and Exit Container c = getContentPane(); setTitle("Auction by: " + me); c.setLayout(new GridLayout(2,2)); c.add(new JLabel("Your bid:")); c.add(bid = new JTextField(8)); c.add(new JLabel("Highest bid:")); c.add(highBid = new JTextField(12)); bid.addActionListener(this); highBid.setEditable(false); addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { System.exit(0); } } 24

); setSize(200,80); setVisible(true); } public void actionPerformed(ActionEvent e) { // Add code here } public static void main(String[] args) { new AuctionClient_org(); } }

9.3

Programming exercise part 2

The second programming assignment of this exercise is to extend the functionality of the work in part 1: Now there is a requirement that higher bids from other clients should be distributed to every client so that the client program may notify the user asynchronously about the new bid. The required program extension will be solved by event registration and callback methods. Please consult the on-line documentation and the example application in this tutorial. How can new clients joining in be notified of the highest bid immediately?

9.4

Programming exercise part 3

The third programming assignments will be to change the distribtued application into a serverless, peer-to-peer architecture. Create a program that distributes bids to every other client and let the client themselves find out if this bid is highest or not. The implementation of this program will also require the use of event registration. How can new clients joining in be notified of the highest bid immediately? Will the application require a definitive ordering of bids (i.e. must all clients receive bids in the same order, or can client X receive BidA then BidB, while client Y receives BidB then BidA)?

References [1] G. R. Andrews. Foundations of multithreaded, parallel, and distributed programming. Addison-Wesley, 2000. [2] P. Bishop and N. Warren. JavaSpaces in practice. Addison Wesley Longman Inc., 2003. 25

[3] E. Freeman, S. Hupfer, and K. Arnold. JavaSpaces Principles, Patterns and Practice. Addison Wesley Longman Inc., 1999. [4] E. Pitt and K. McNiff. Java.rmi. The remove invocation guide. Addison Wesley Longman Inc., 2001.

26