Questions? Office hours, email, newsgroup...
For your first programming assignment of the semester, you will implement a multi-threaded media server using the Pthreads package under the Solaris operating system. You may implement your project using either C or C++. Think of the media server as an electronic version of the local video store. The media server will begin by reading an initialization file (mserv.init) containing an inventory of available titles (title + count) and build a shared data structure (the database) that will be used to respond to client requests. The server has a very simple API and only exports two functions to clients:
int checkoutVideo( int clientID, const char* title )The first allows a client to request a video, the second allows a client to return a previously acquired video. Clients are identified by integer ids and videos by character strings (case-insensitive). An integer return code will indicate success or failure of the request. A return value of 0 will indicate success (UNIX system-call style) and error conditions will be indicated by a variety of positive return codes. To keep things simple, we will not actually move media bits around for this assignment. Subsequent assignments will actually serve media and you may view the acquired media using appropriate viewers.
int returnVideo( int clientID, const char* title )
Server
The server will be multithreaded and the initialization file will specify the number of "worker threads" (nworkers) the server should create. The initialization file will also specify the number of clients (nclients). Clients will be implemented as threads in the same address space as the server. This is a bit unusual but will allow us to perform some non-trivial thread interactions. Since clients are implemented as threads, we can use the thread id (pthread_t) as the client id so the server API is actually as follows:
int checkoutVideo( pthread_t clientID, const char* title )(Remember that pthread_t is an opaque data type and that you should use the function pthread_equal() to compare ids.)
int returnVideo( pthread_t clientID, const char* title )
Errors
There are four possible errors and corresponding error codes:
1 ENoSuchVideo
2 ENotCheckedOut
3 EOverLimit
4 ENotAvailable
If a client requests a video not in the database, return ENoSuchVideo. If a client attempts to return a video not currently checked out by that client return ENotCheckedOut. (Attempts to return an unknown video should return ENotCheckedOut.) The initialization file will specify a maximum number of videos that any client may have checked out at once (nout). If a client requests more videos than permitted, return EOverLimit. If all copies of a video are currently checkedout, return ENotAvailable in response to a checkout request.
Clients
In a sense this program is a simulation of a media server. Clients will execute for a fixed number of "steps" (nsteps) specified in the initialization file. During each step a client will randomly attempt to checkout or return a video, log its action (along with a success or failure indication) to the standard output and then sleep a bit. The pseudo-code for a client is given below:
for nsteps doTo "randomly" checkout a video, the client will need to randomly select a title from the database. Occasionally, clients should attempt to checkout videos not listed in the database. To "randomly" return a video, each client will need to maintain a list of titles currently checked out and randomly select a title from this list. Occasionally, clients should attempt to return a video they do not currently have checked out. Clients should sleep for 0 to 10 seconds, after each step. Use sleep(), usleep() or nanosleep(). Logging in a threaded application is just a bit tricky. You must make each "write" to the standard output "atomic" to avoid unpleasant interleavings of log results. Each call to printf() or sprintf() is guaranteed to be atomic so make sure the logging function contains only a single output statement. You will probably want to flush after printing.
coin = flip()
if ( coin == HEADS )
randomCheckout();
else
randomReturn();
logAction();
sleep( randomDuration() )
return any remaining videos
pthread_exit
Initialization File (mserv.init)
Your program should look for an initialization file named mserv.init in the directory in which it is started. The file will be line-based. The contents are case-insensitive but will adhere to the following format. You may assume the initialization file is in the correct format (no error checking is necessary).
nsteps 100The number preceeding movie titles is the number of available copies of the video. You may assume that there is a single space following the count and that all remaining characters on the line comprise the movie title. In particular, there will be no trailing whitespace (other than the terminating NEWLINE).
nworkers 4
nclients 10
nout 5
ntitles 3
3 Milagro Beanfield War
2 Rashamon
2 Being There
main()
Your completed project should consist of a single executable called mserv. You will probably want to redirect the standard output into a log file for inspection like this:
% mserv >mserv.log
Pseudo-code for your main should look something like this:
int main() {
init();
startWorkers(
nWorkers );
startClients(
nClients );
waitForClients(
nClients );
terminateWorkers(
nWorkers );
dumpDatabase();
return
0;
}
The init() function should read the initialization file, setup the database and any additional data structures (request queue, synchronization variables, etc.) The main thread should wait for all clients to terminate. You can either use the JOIN mechanism or explicit synchronization. Once all clients have terminated, you should terminate the worker threads. You can use CANCELLATION or some other mechanism. Finally, you should dump the final state of the database to the standard output before exiting. This will help you detect any misbehaving threads.
The Database
Conceptually the database is a list of movie titles (strings) and counts (ints). Since the server exports no functions for dynamically updating this list, you may assume it is read-only after initialization. Client and server threads may access this portion of the database without explicit synchronization. (You could add a dynamic update capability for extra credit.) In addition, the server should associate with each title a list of clients (thread ids) that have the title checked out.This portion of the database will be read and written by server threads and must be guarded by synchronization. A simple solution will associate a single mutex with the entire mutex. A more efficient solution will associate a separate mutex with each title. (These individual mutexs can be maintained in the database itself!)
Client-Server Communication
Clients should communicate with the server through a request queue. This is the classic producer/consumer problem. You should use a bounded buffer. The buffer size should be less than the number of clients to produce some contention for the buffer. You can implement the buffer either using linked-lists or an array (circular queue). We have seen several solutions to this problem using mutexes and condition variables or semaphores. Feel free to adapt one of the solutions from the readings. Requests must contain the following information:
client idA client making a request should add the request info to the queue and wait for a reply. The only reply needed is the integer return code. To simplify things, the server thread can simply write the return code in a well-known location (you can declare an array of return codes, one per client). The server thread must "wakeup" the client once the request has been processed and the return code has been written. To implement the client thread/server thread synchronization, you can allocate a per-client condition variable and pass a pointer to this condition variable in the request. After queing the request, the client will wait on the condition variable. You will also need to associate a per-client mutex with the condition variable following the standard pattern. (Why?) When the server thread completes the request it will signal the client using the condition variable provided.
type: checkout or return
title
Packaging
Place your code in a directory along with a README file describing how to compile and run your program and indicating any special capabilities you have implemented. Your directory should contain a sample mserv.init. You can package your code as a single file (mserv.c) or break it up into pieces like (main.c workers.c clients.c). Be sure you properly extern shared data if you break your program into separate source files!
Pthreads Resources
Check the class Web page for a variety of Pthreads-related resources including a reference to debugging multithreaded applications using dbx.
Turnin / Late Policy
To turn in your assignment, create a tarball containing the project directory and email to pwh@cc.gatech.edu with the Subject line: "CS4210 P1 SUBMISSION". The standard late policy applies. Projects can be submitted up to five school days late. A 5% penalty will be applied for each day late. Projects will not be accepted more than 5 days late.
-- Good luck!! Have fun ....