trout How To Build A
Distributed Fish Tank.

Design Document For A Java-based Distributed Problem
Solver With The Graphical Interface Of A Fish Tank.


By Daniil Kurbatskiy, Natalya Meltser, Mark Schlowsky, and Adam Trachtenberg.


1.0 Introduction


Our problem is the creation a network consisting of nodes of a computation. These nodes move through the network to and from entities that accept and process these nodes. We have a graphical presentation for this problem. Fish serve as nodes, as they contain a part of the computation problem, and the fish tanks that accept them, allow these nodes to interact with the computers on the network. While these fish compute, some of the processor time will go to doing graphics instead of solving the problem.

The solution is a peer-to-peer network system that follows the principles of encapsulation, data hiding, and code reuse. Naturally, an object-oriented language is best suited for such a design. We chose to implement the design in Java, instead of C++ or SmallTalk, because of Java's cross-platform abilities combined with Remote Machine Instructions (RMI). Using RMI both makes communication across the network easier and preserves an object-oriented paradigm by allowing us to pass messages directly to objects across a network.

The design goals are to create a straight-forward object-oriented design that is reasonable to fully implement in two months. We also placed some criterion upon our design.

*The objects should have, for the most part, an intuitive behavior (a tank holds fish, a fish shipper receives fish, etc.)

*The system should be designed so it can still operate if multiple peers are taken off of the network unexpectedly.

*The design should also be reusable for different computations by just swapping a small number of components.
With the idea of a robust network in mind, the roles of the servers have been minimized. A server is still required to add a tank to the network as well as to keep track of the computation. However, without the server, the currently running and registered tanks will still be able to operate.

2.0 Overview


The networked fish tank can be viewed as three distinct high-level modules: the server, the fish shipper, and the actual fish themselves.

The server is a process running on a machine that serves two purposes. It handles registration of new fish shippers and sends out messages to have fish sent to the new shipper. It also directs the distributed computation carried out by the fish swimming in the network.

Each fish shipper runs on a different user's machine on a network. The shipper is responsible for directing the sending fish from one tank to another. So, there is a 1-1 correspondence between fish shippers and tanks. While a fish is responsible for shipping itself, it uses the fish shipper to do so. In general, a fish tells a fish shipper: "Scoop me out of your local tank and arrange for me to arrive at another fish shipper running on another machine. That new fish shipper should then place me in its personal tank."

The fish are what actually wanders through the network. They know how to draw themselves, have a message attached to them, and carry out a distributed computation. Also, each fish has behavior that governs when the fish will ship itself to another tank.

Many provisions have been made so that the network of shippers will not be brought down by any reasonable number of machines being removed from the network, be it from crashing or some other reason, including the server. The network is organized as a web with edges connecting shippers constantly changing. This is in contrast to a token ring, which could not recover as easily from multiple machines being removed unexpectedly.

2.1 Network Organization


We must ensure that new users can be added to the network and find out about other fish tanks while still maintaining a robust and decentralized network. To do so, we place the server as the entryway to the network. When a new shipper registers with the server, it instructs existing fish shippers to send the new shipper some fish to fill the tank. When the new fish arrive, the shippers will derive information about the location of other shippers from the fish. It will then add these locations to a list containing its most recent shippers. Fish will send themselves out to different shippers on that list. Because of this cross-pollination, the network should resemble a web instead of a token loop. The only times the fish will have to contact the server is to get a piece of the computation and when their section of the computation is done.

2.2 Communication


All communication between objects across the network is carried out by objects using RMI. This reduces the amount of coding required for communications across the network while keeping an object oriented design. While RMI is slow, it is reliable, which is much more important for our purposes.

2.3 Advantages


There are several advantages to this design, the most important is that the system will be able to function with multiple machine or network failures. While no shippers can be able to be added if the tank server crashes, the shippers already running can continue to operate. If the fish server crashes, no fish will be given a part of the computation to do nor will a fish be able to report back the results of its computation; however, the fish will still be able to be created and swim between tanks.

The division between the communication classes, the graphics classes, and the calculation methods make developing the system much easier. Do-nothing code modules can be substituted during development and then, as classes become available, they are substituted in.

2.4 Disadvantages


The major problem is the dependence between the Fish and FishShipper. This was necessary to allow the fish to have more control over its own behavior, while not setting up an extensive callback system. Testing the system without the basic components of at least the FishTank, FishList, FirstFish, NullFish, Fish, and a specific fish is very difficult.

Implementing the system involves a thorough understanding of threads and synchronization problems, but it is inherent in this type of system.

3.0 Modules


The modules section describes the projects in lower-level terms than the Overview section. Therefore, it breaks the above-mentioned Server, Fish Shipper, and Fish into smaller pieces.

3.1 Servers


This module is responsible for getting a tank up on the network and for keeping track of the computation. This module is split into two sub-modules, the tank server and the fish server, that are different processes running on possibly two different machines.

The tank server is responsible for telling some tanks already existing on the network that a new tank has been added and that those veteran tanks need to send fish to the new tank. The tank server will periodically write tanks locations to disk, so that if it crashes and is restarted, it does not lose all of its data.

The fish server keeps track of the computation. This server portions out parts of the computation to fish and puts all the parts together when it receives the results. Each fish will contact the server when it is created to get a piece of the computation, then it will contact the server again when the computation is done. The server will need to notify whomever is supervising the computation when a solution is found or when the search space is exhausted.

3.1.1 Tank Server


Responsibilities
  • Registering a tank
  • Having fish sent to the new tank
  • Keeping track of the number of tanks it knows about.
Events
  • Creation: Initialize all necessary data structures
  • Receive a registration: Contact other tanks and have them send fish to the new tank and add the new tank to its bag of known tanks. If it the first tank to register, record the tank location.
3.1.2 Fish Server


Responsibilities
  • Dividing up the calculation
  • Giving out parts of the calculation to the fish
  • Putting the parts of the computation back together
  • Keeping track of what parts have been given out and/or received back
  • Notify someone when the calculation is done
Events
  • Creation: Divide up the computation
  • Receive a request for a part: give out a part of the computation and record it
  • Receive a finished part: mark that part as finished and process it accordingly
  • Computation is completed: Output to the screen or send email to indicate that it is done. All subsequent requests for parts will be respond with an indicator that the computation is done.

3.2 TankLocationBag


This module is an abstraction of having an unordered bag containing the locations of other tanks. The location itself, in reality an URL, is represented as a string.

The bag module has certain properties. It can only hold a specific number of locations. When it is full, the oldest tank locations are thrown away first. There is a method to get a random location from the bag, as well as to remove a specific location from the bag. There is also method to add a location to the bag if it is not already in there and there is a method to compare two bags and add the locations that are not already in the bag "foo" to bag "bar."

This set of modules abstracts the idea of a bag of locations that can easily be transferred to tanks and fish.

Responsibilities

  • Keeping a length bounded list of tank locations (strings)
  • Adding new location
  • Removing old location to make room for new ones.
  • Returning a random location
Events
  • Creation: is told the number of tank locations it can hold and initialize necessary data structures
  • Add a location
  • Add all unknown locations from another bag
  • Get a random location

3.3 Tank


The tank is responsible for drawing itself on the screen and managing the list of fish in its tank. It has a list head and forwards messages from the fish shipper (such as, sending a fish to a specific tank) to the list. The tank will be responsible for clearing the tank of fish before it is destroyed. It will also have a list of all known tanks.

Responsibilities

  • Drawing a tank
  • User interface
    • Adding a fish including setting the message
    • Displaying the message from a fish
    • Displaying where the fish has been recently
  • Creating new fish and specifying if it should be sent to a specific tank or if it should be in this tank.
  • Maintaining a list of fish
  • Keeping a tank location bag
Events
  • Creation: Set up all necessary data structures and register with the tank server
  • Receive a fish: Get its tank location bag, add any new tanks to the tank's tank location bag, add the current tank location to the fish's tank location bag, start the fish's thread running, and add it to the list
  • Receive a force send: Tell the fish most likely to leave to ship itself to the new tank by passing a message to the list
  • User creates a fish: the fish is chosen with pull down menus and the message is set. Start the fish's thread and add it to the list.
  • Destruction of tank: Force all fish out before the tank is destroyed (user quit.)

3.4 Fish


The fish module and all of its sub-components tries to abstract a real fish as much as possible. Each kind of fish has some common functionality, such as shipping themselves. On the other hand, each specific fish has different behavior, such as visual appearance and swimming speed. This is accomplished through inheritance and method overriding. The rest of the modules are a framework that allow the fish to swim through the network while performing their computation.

The fish module can have many sub modules, some of them corresponding to specific kinds of fish. The fish class itself has the code to ship itself. It also has a generic method to draw itself on the screen, which allows the tank to treat each fish the same, while having all of the different types of fish act differently. Also, each fish is responsible for how it draws itself on the screen and for deciding when to ship itself. When a fish will ship is based on two factors: how many fish are in the tank and if there is a specific type of fish (say a shark) in the tank. When a fish ships itself, it will notify the list that it has left, which will allow all of the remaining fish to re-figure their own probabilities of leaving.

Each fish should run its drawing in its own thread with the appropriate yield call so that it will not hog processor time on the Solaris platform (until the implementation of the Virtual Machine changes).

Our fish act in an objective sense similarly to the queens of the 8-Queens problem. Each fish will know about its neighbor and will forward the appropriate messages to them. There is a NullFish that acts as a sentinel at the end of the list. Now, no fish has to check to see if it is the last fish in the list, and the NullFish can act appropriately when it receives messages (it will return 0 for the number of fish including it to its left, etc.) There is also be a FirstFish that will be the first fish in the list. Since the list is doubly linked, it will serve the same purpose as the NullFish; however, it will also add any new fish to the list right below it. The NullFish and the First fish are always in the tank and can never leave. There are also a fish list head for both the tank module and each individual fish, so when a fish leaves the list, that fish can tell the rest of the fish that it has left and code can be executed accordingly.

Responsibilities

  • Draw itself on screen
  • Do calculation
  • Forward appropriate messages down the list
    • number of fish in tank
    • How many fish are in the tank
    • force send to a tank
    • Checking for a specific fish
    • All fish must leave
  • Ship itself to a new tank
Events
  • Creation: set message
  • Receive list message: act on it and forward it
    • How many fish are in the list: 1 + the number further down the line
    • Searching for a specific type: if the fish matches its type return TRUE otherwise return if the specific type is further down the list
    • All fish must leave: All fish further down the list much leave first before this one.
    • Force send to another tank: if this fish is the last send it to the tank (query the next fish.)
    • Are you the last fish
  • Ship itself: contact new tank, send itself, remove itself from the list, and tell the list it has left, kill the thread its running in.

3.4.1 NullFish


Responsibilities
  • Handle all messages that reach the end of the list
Events
  • Creation
  • Receive a message: act appropriately as the end of the list
    • How many fish are down list from this: 0
    • Is a fish of a certain type in the tank: FALSE
    • Force leave: return immediately, the NullFish never leaves the tank
    • Are you the NullFish: return TRUE

3.4.2 First Fish


Responsibilities
  • Be the other end of a doubly linked list.
  • Add fish to the list
  • Forward all messages from the list head to the rest of the list
    • How many fish are in the list
    • Is a certain type of fish present
    • Force send to a tank
    • Force all fish to leave
  • Forward all messages returning up the list to the list head
    • The number of fish in the list is: X
    • All of the fish have left the list
Events
  • Creation: create a null fish and set all pointers for the list appropriately (it is a doubly linked list)
  • Receive a message from the list head: forward it to the rest of the list
    • How may fish are in the list
    • Is a certain type of fish present
    • Force send to a specific tank
    • Force all fish to leave
  • Receive a message from the first fish: forward it up to the list head
    • The number of fish in the list are: X
    • All of the fish have left the tank
  • Receive a fish: add the fish to the list as the next fish in the line (i.e. fish will be in chronological order to when they were added to the list)

3.5 FishList


This module is responsible for holding the top of a list of fish. The list is doubly linked and all fish are passed to the FirstFish to be added to the list. All fish added to the list will have a pointer to the FishList so that they can tell the list when they are leaving or to check if a certain fish is in the tank. The list lets the fish manage themselves.

Responsibilities

  • Maintain a pointer to the first in the list
  • Add Fish to the list
  • Receive messages from fish to be asked to the entire list and results returned
Events
  • Creation: Create the first fish in the list. It will in turn create the last fish and the list will be ready to accept fish.
  • Add a fish: pass the fish to the FirstFish
  • Find the Length: Ask the list how long it is and then tell the list the new length
  • Is a certain fish in the tank: Ask the list if a certain fish is present and return True or False accordingly.

3.6 FishShipper


This module is responsible for receiving a fish and sending it to its own tank, as well as, receiving a message to send a fish to a specific tank. This message will be forwarded to the tank as well. The fish will be received using RMI. A fish will call a FishShipper's method to ship itself to the new tank to be reinstanciated. The tank must compare the fish's tank location bag with its own to get any new tank locations as well as having the fish add its location to its own bag. The fish will then be passed to the tank to be put in it to swim.

Responsibilities

  • Receive a fish
  • Manage the fish tank
  • Register itself with the tank server
Events
  • Creation: initialize the fish tank
  • Receive a fish: pass it to the fish tank

3.7 Calculation


This module does the calculation on the fish. It is specific to the problem being solved and what needs to be swapped out when a different problem is being tackled. It will run in its own thread and let the fish know when it is done. If it is possible, it should be able to swap these module on the fly.

Responsibilities

  • Get a part of the calculation to do
  • Do the calculation
  • Return the result
Events
  • Creation: contact the fish server to get a part of the calculation
  • Completion of the calculation: contact the fish server to return the result
  • The Calculation is finished: use a do-nothing loop

3.8 Inheritance


Inheritance Picture 1

Inheritance Picture 2

Inheritance Picture 3

All other objects are descendant from the base class of Object.

3.9 Dependencies


Dependencies Picture 1

Dependencies Picture 2

Reasons for the circular dependencies:

In almost all designs circular dependencies are extremely bad. It makes it very hard to test a class using regression testing (though in Java, not impossible) and to use a specific class you need to grab all of the objects in the circular dependency. However, the choice to have a circular dependency was a conscious one with its advantages far outweighing its disadvantages.

First, the important classes are not in the circular dependence. By important we mean: the classes that we envision of being some real use to someone else and the classes that would have to be swapped out on a regular basis if the system is up and running for a long time. A TankLocationBag would be a useful thing to someone else, while a fish without the associated tank and fish list, etc. would not be. The calculation class and the fish server have a very simple and well defined dependency. It will be easy to rewrite those based on the problem to solve, test them in isolation and then plug them into the system.

Also, the dependency of Fish on the Fish Shipper allows the fish to act more naturally like a fish. It can decide when to ship itself, and to a certain extent, where to ship itself. A design that would not have a circular dependency would have the fish shipper shipping the fish based on either when the fish wants to leave or when the shipper thinks it should leave. While either of these models makes logical sense, the implementation would require a fish shipper to periodically check to see if a fish wants to leave. This is a slow design, as it would have a lot of unnecessary method invocations. Also even with this design, one can not remove the dependency of the shipping object to the receiving object, which is inherent in RMI.

4.0 Scenarios


Here we provide a walk-through tour of the major object and module-level interactions.

4.1 Adding a new Fish Tank to the Network


Adding a new Fish Tank To the Network

1. User starts up Networked Fish Tank Java Application
2. Fish Tank contacts Tank Server. Sends its location.
3. Server contacts some Fish Tanks already on the network. Instructs them to send Fish to the new Tank.
4. They send Fish to the new Tank.
5. Fish swim around in Tank.

4.2 Shipping a Fish from One Tank to Another


Shipping a Fish From One Tank to Another

1. Fish tells Fish Shipper: "I want to leave."
2. Fish Shipper talks to another Fish Shipper on the Network and Ships the Fish.
3. New Fish Shipper Receives Fish.
4. Fish places itself in Tank. Swims around.

4.3 Creating a Fish


Creating a Fish

1. Fish is created. The user picks the fish type and the fish's message.
2. Fish's thread is started and then it is passed to the list.
3. Fish is passed to First Fish and is added to the list.

4.4 Calculation


Calculation

1. Fish contacts Fish Server and requests a calculation. The Fish Server returns a calculation.
2. Fish swims from tank to tank to tank to tank.
3. Fish continues to swim and finally finishes its calculation.
4. Fish contacts Fish and returns its result. Repeat from Step 1.

4.5 Shipping A Fish


Shipping A Fish

Left Column
1. Fish passes itself to FishShipper using RMI and kills its own thread.
2. Passes the fish to the Tank.
3. Get the TankLocationBag from the new fish, adds its own location to the fish before starting the Fish's thread and passing it to the FishList.
4. Passes the fish to the First Fish.
5. Adds the fish to the list.

Right Column
1. Tells the list the fish has left by telling it to redo the number of fish in the list.
2. Asks the list of fish: "How many fish are in the list?"
3. Forwards the message to the next fish.
4. Returns 1 + the number of fish after it in the list.
5. Returns the number of fish in the list.
6. Tells the list how many fish are in the list.
7. Forwards to the next fish how many fish are in the list.