Due Date: March 4, 2008. 100 points
Single person project. Do your own work!
In this project, you will learn about the POSIX Threads Library. You will implement a multi-threaded server that executes requests on behalf of a client system (provided).
The system is a simple client-server where the client sends the server requests (numbers) and the server emulates processing of these requests by waiting (sleeping) for a specified amount of time. The server function emulates off-load of the task to a database, other web server, or disk, so other requests must be handled during these delays.
NOTE: The client and server are separate processes that communicate using socket IPC. The socket IPC mechanism is provided for you, but will need to be aware of sockets in two ways: (1) the client program invokes the server on a particular system by its IP address or the localhost address (127.0.0.1), if the client and server are on the same machine, and (2) you may have to change the value of your server port (PROTOCOL_PORT, defined in cse473-network.h), as multiple people using the same machine may conflict.
The client-server system provides a similar mechanism to that between a GUI and a processing system. The GUI (client) provides requests that the processing system (server) must handle. Besides simple request handling (task #2, below), the GUI may also request system shutdown (task #3) and request cancellation (task #4).
Do not be fooled by the deceptive simplicity of the client-server system. The processing of commands using a multi-threaded system is tricky to program, tricky to debug, and tricky to get functioning correctly. Task #4 is fairly complex to get right. Fortunately, gdb does support multiple threads now, so you should learn it and how to examine multi-threaded programs.
The project will consist of the following tasks:
Download the following tarball Project 2 Code to your CSE account file space. You should have one file p2-threads.tgz.
The first step is to build a basic multi-threaded server that responds to client requests. The server will create a new POSIX thread (pthread_create) for each request that is received. You will need to implement the function serverAddWork to prepare and create a thread for each request that will start at the function worker (provided) to service the request. NOTE: The function serverInitThreads is provided for any server initialization that you may require (e.g., mutexes, we'll discuss in Chapter 6).
serverAddWork is defined in the file cse473-pthread.c and has supporting definitions in cse473-pthread.h. serverAddWork needs to support the following functions:
The socket (sock) and argument (arg) must be delivered to a new thread that starts from worker.
A new thread must be created for each request. This thread starts at worker which performs that necessary request processing for you. To simulate some offline work, the request processing simply sleeps the thread for the specified number of seconds.
Build the client and server (using make) on a Linux system (could run it on Solaris too, but we want to compare it to a Linux threads implementation, below).
Run the server first (./cse473-pt-server 0 &) in the background, then run the client (./cse473-p2 127.0.0.1 1). Once the client is started, the server will process the specified requests. Collect the output of each by redirecting their output to a file.
For the server call: ./cse473-pt-server 0 > server.out
&. You will need to kill the server (kill -9
For the client call: ./cse473-p2 127.0.0.1 1 > client.out
example server and client output. Yours should be similar but may not be the same.
Measure the time of the client to process all its requests using the UNIX utility time. Read the man page to determine how to use it. Run the client at least 5 times to see if the timing is consistent before selecting a value (someone else may be using your machine).
The second step requires that you add a mechanism that terminates the server when the client passes the argument '0' (i.e., '0' is passed as the value of arg in serverAddWork. You will implement this by extending the serverAddWork function to distinguish between normal requests and termination requests. The server will take no new requests after the termination, but you must allow it to finish the previous requests.
serverAddWork needs to support the following functions:
It must receive a termination request that prevents any further requests from being accepted by the server (ends the main server thread that accepts new requests in serverReq in cse473-server.c.
All requests that have already been started must complete before the server stops. That is, serverProcessReq must run to completion -- including sending a response to the client.
To keep the client from hanging, your server must send a reply to the termination request to the client. This must be sent after all the other requests have been processed and before the server is terminated.
Build the system, run the system, and collect output and timings as for task #2 above.
example server and client output. Yours should be similar but may not be the same. You shouldn't have to stop the server via a kill request (since you programmed it to stop).
The third step is to process client termination requests for previous client requests that are not yet completed. For this, you must track the assignment of client requests to threads, so that the proper requests can be terminated. You must implement the following function:
You must extend serverAddWork to identify and execute client termination requests (task 4A in cse473-pthread.c).
A client termination request occurs when the value of arg to serverAddWork is negative. If arg is negative, we must identify the requests to terminate (see below), and cancel those threads.
Extend Implement the function terminateProcessReq to find
the threads to terminate (task 4B in cse473-pthread.c).
This function will also send the termination request to those
threads. We first discuss the requirements for terminating a
thread. When a client sends a termination request, the tasks that
will be terminated are those outstanding requests whose duration
(the value of arg to serverAddWork) is the
additive inverse (negative) of the original request. For
example, if a request of 10 is submitted, a request for -10
means "terminate all outstanding requests of duration 10." terminateProcessReq will identify these requests from
the set of outstanding requests (using a representation of such
requests, see below) and cancel the threads according to
specific requirements (see below).
cse473-pthread.h defines a structure
thread_ongoing_t which associates a thread with a request
(i.e., thread_work_t, also defined in
cse473-pthread.h). This must be updated as follows (task
4C in cse473-pthread.c).
When a thread is assigned to a request in
serverAddWork, then it should be added to the
tp->ongoing list in the thread pool (tpool_t, defined
in cse473-pthread.h also).
When its work is completed by the thread in
serverProcessReq, it should be removed from the
tp->ongoing list.
Finally, when the thread is selected for termination, it must
be removed from the tp->ongoing list too. The function
removeFromOngoing must be defined for this and the
previous case (code reuse).
NOTE: Since the main thread may add or delete entries and the
worker threads remove themselves from the queue, we need a mutex
to protect tp->ongoing. One is defined in the tpool_t
structure (tp->ongoing_lock).
You must extend serverProcessReq so the request
thread can receive cancellations (task 4D in
cse473-pthread.c)..
Specifically, the goal is to terminate any thread either
immediately before it does its processing (i.e., which we
emulate via a sleep in
serverProcessReq) or immediately after. If the thread is
ready to send a response, then let it proceed. By default, POSIX threads may be terminated, but only
synchronously. POSIX threads provide a function called
pthread_testcancel that a thread uses to check whether it
is to be cancelled. Please have your threads call that function
to test for their cancellation at the point prescribed above. After your threads have passed their last
pthread_testcancel, they can no longer be cancelled, so
you can use the function
Build the system, run the system, and collect output and timings as for task #2 above.
example server and client output. Yours should be similar but may not be the same. You shouldn't have to stop the server via a kill request (since you programmed it to stop).
Your submission will consist of three things: (1) a copy of your code in cse473-pthread.c; (2) the traces of your client and server (include client average runtimes); and (3) a writeup describing the mechanism you used (e.g., pthread functions and mutexes) to ensure that the main thread (a) terminates future requests while letting prior requests complete and (b) terminates specified requests synchronously while allowing others to proceed.
Grading:
Correct solution for #2: 25 points
Correct solution for #3: 25 points
Correct solution for #4: 30 points
Correct submission (per #5), including writeup for 3a and 3b: 20 points
Extra credit:
Use ongoing queue for #3 -- collect tasks into ongoing queue and check for completion of ongoing threads (not just any thread that you started): 5 points
Solve the readers and writers problem for ongoing, by permitting multiple readers, but limiting access to one writer if anyone wants to write: 10 points