Project 3

CMSC 433
Programming Language Technologies and Paradigms
Fall 2002

Due Friday, November 15, 2002 at 6pm

Introduction

In project 2 you constructed a generic server. That server accepted connections one at a time and then invoked handlers one at a time. This approach works perfectly well in many situations. For example, it works well when there are a moderate numbers of client requests and all handlers execute quickly. That is because in this situation clients will tend to receive service quickly and predictably.

However, this kind of server may perform poorly in other situations. For instance, imagine a server that has at least one unresponsive handler. When that handler slows down or blocks, all other clients will also be blocked -- even if the blocked clients don't use the unresponsive handler. This is like when you get in line to order just a soda at McDonald's and it turns out that the guy in front of you is ordering for his entire office!

Another issue arises when there are many client requests. Here, clients may have to wait a long time even if the individual jobs are small. This is something like driving on a tollway with only one toll booth during a holiday weekend. Even though it doesn't take too long for any single car to pay the toll, the aggregate time accumulates dramatically when there are a lot of cars on the road.

These issues arise in project 2 because BasicServer is designed to handle client requests sequentially. In this project we will introduce the idea of concurrent programs (via Java Threads) as a way to improve program performance. In particular, rather than having the server process each request one at a time, we will have each request processed in its own thread, in parallel. In terms of our informal examples above, this is like having many cashiers at McDonald's or many toll booths on the tollway; clients can still make progress when there is one slow client or many clients in aggregate.

This project has two main goals: In this assignment you will create 3 applications: SeqPageProxy, ThreadedPageProxy, and BoundedThreadedPageProxy. We will test your applications with several experiments. You can also get this assignment in PDF and postscript. You will want to reuse some or all of your code from Project 2. If you wish, you can use our implementation of project 2 rather than your own; we will send out our implementation to all students via e-mail.

1  SeqPageProxy

SeqPageProxy will be an application composed of a BasicServer and some handlers; we describe each part in turn.

1.1  Handlers

Some of these handlers you have already implemented in project 2, while the remainder you will have to implement at this time. Note that you will need to consider the possible error conditions that could arise with the new handlers; if something erroneous occurs, send back a suitable message to the client via HttpResponse.errorResponse.

1.2  The Application

To build the application, you will create the handlers as follows; unless otherwise noted, they should have order constraint NO_PREFERENCE:
  1. Create two empty Maps, one for the log map, and one for the session map. In the log map, store a LocalLog of size 500, associated with key ``mylog''. The session map will be empty. Create a StartSessionHandler and an EndSessionHandler, passing the log map and session map to each as necessary.
  2. Create a StartTimerHandler and an EndTimerHandler with a shared Timer, having pattern remoteGetFile/.*. Pass to the EndTimerHandler the same LocalLog as was stored in the log map above. The StartTimerHandler should have order constraint EARLY and the EndTimerHandler should have order constraint LATE.
  3. Create a SummaryHandler, passing it the log map created above.
  4. Create a RemoteGetFileHandler.
All applications should accept the -Dport=XXX flag to set the port to use to listen for connections.

1.3  Experiment 1

Using your code, we will run two experiments. The first experiment will start a session, then make a number of requests of your server, one at a time. When the requests complete, it will end the session and invoke the summary handler to see the results.

The script to run this experiment is called exp1. To run it, you first start a slow server to which your RemoteGetFileHandler requests will be directed. The slow server is implemented in the file SlowServer.java. Then, assuming you have started the slow server on port p and you have started your SeqPageProxy on port q, from the same machine you run the script by doing


exp1 q p


The script performs all of requests to your server using wget, requesting remote files from the slow server. The final results will be printed to the screen.

1.4  Experiment 2

The second experiment is like the first, except that it makes all of its requests at once, rather than one at a time. However, because your server is sequential, this will not necessarily help the waiting times of requests, except that some shorter requests might (randomly) sneak in before the larger ones.

The script implementing this experiment is exp2. It is invoked exactly as exp1, described above.

2  ThreadedServer

The second application will be similar to the SeqPageServer except that the Server will be threaded. To do this you should create a ThreadedServer class that has the same interface as BasicServer:
public class ThreadedServer {
  public ThreadedServer(int port);
  public void attach (HttpHandler handler);
  public void detach (HttpHandler handler);
  public void run ();
The difference will be that it will process each client request in a newly spawned thread, so that the request handlers and the main loop can operate in parallel.

The handlers you use in this application will be the same as those in SeqPageServer (from the client's point of view.) However, you must re-code each handler to be thread-safe. A handler is thread-safe if it properly deals with the fact that its state could be shared, either by a different handler in another concurrently running thread, or another copy of the same handler in another thread. You must synchronize on data that could be shared between threads, as well as account for state that must be duplicated to allow the same handler to run multiple times concurrently. For example, there is now the possibility that multiple copies of the same StartTimerHandler and EndTimerHandler will run in different threads simultaneously; what effect will this have on the shared Timer? You will also have to be concerned with Log and Map objects that are shared between threads. There may be other concerns as well.

To simplify the grading process, we require that rather than change the code for the handlers you have, make the thread-safe versions as separate classes: You may have to create thread-safe versions of other classes as well; you may change any class that you need to, as long as the constructors to the handlers are as specified above. Make these new versions separate classes as well, rather than modifying the original ones. To keep things simple, you may wish to keep your code from problem 1 separate from the code in this problem, in a different subdirectory (but be sure to copy it all to the same directory before you submit it). Finally, depending on how you implement your synchronization, you may not need to change some or even all of your handler classes; nonetheless, make a separate copy having the new name, as specified above.

Finally, create a version of SeqPageProxy, called ThreadedPageProxy, that uses the ThreadedServer and the thread-safe versions of the handlers (and possibly other classes).

2.1  Experiment 3

We will re-run experiment 2, using the script exp2. However, you will use ThreadedPageProxy as your server rather than SeqPageProxy. Now that the server is multi-threaded, the shorter jobs should complete sooner, despite the presence of longer ones. The differences will be shown by the summary handler.

3  BoundedThreadedServer

This application will be the same as the ThreadedServer except that the Server will only allow k client request Threads to run at any one time (make k settable by a command-line argument if you wish, but have it default to 10). The general idea is sketched out below in pseudocode (this is not Java code!):
while (true) {
    if (max # of threads running) {
      wait until one completes
    }
    else {
       accept connection
       process request
       ....
    }
}
The number of threads k should be settable by a user of BoundedThreadedServer:
public class BoundedThreadedServer {
  public BoundedThreadedServer(int port, int maxThreads);
  public void attach (HttpHandler handler);
  public void detach (HttpHandler handler);
  public void run ();

3.1  Experiment 4

Create a version of ThreadedPageProxy, called BoundedThreadedPageProxy, that uses the BoundedThreadedServer rather than the ThreadedServer. You should create a command-line option for setting the number of threads, as follows:
java -Dnumthreads=10 -Dport=13304 BoundedThreadedPageProxy
Here, numThreads indicates the number of threads to use (so BoundedThreadedPageProxy will use this value to set k in BoundedThreadedServer) and we use the port argument as usual to specify the port on which to accept connections.

We will re-run experiment 3, again with script exp2, using this version of the server and observe how the performance differs, as shown by the summary handler.

4  Amendments

The project is now due on November 15, rather than November 13.

4.1  Testing

Here is what you must test: You can submit others for a little bit of extra credit if you wish.

Comments on the tests themselves: In general, don't worry about checking exact output; since there will be timing variability, just check things that you know are *invariant* such as the fact that a record should or should not be written to a Log, that an entry with a certain key should be in a Map, etc.

4.2  Cautions and Tips

4.3  Bugs


This document was translated from LATEX by HEVEA.